From e3cd433d9db8f7e7b12bf3fe4ce4e0ea04839c25 Mon Sep 17 00:00:00 2001 From: yanghaoran Date: Sat, 18 Jul 2020 14:40:37 +0800 Subject: [PATCH] synchronize Ascend software suite 18 Jul 2020 --- inc/common/opskernel/ge_task_info.h | 11 + inc/common/util/compress/compress.h | 1 + inc/common/util/compress/compress_weight.h | 33 + inc/common/util/platform_info.h | 4 +- inc/external/ge/ge_api_types.h | 8 + inc/external/graph/types.h | 3 +- inc/external/register/register.h | 2 + inc/framework/common/debug/ge_log.h | 24 - inc/framework/common/debug/log.h | 55 +- inc/framework/common/ge_types.h | 8 +- inc/framework/common/helper/model_helper.h | 2 - inc/framework/common/types.h | 7 + inc/framework/executor/ge_executor.h | 4 +- inc/framework/ge_runtime/model_runner.h | 7 +- inc/framework/ge_runtime/task_info.h | 135 +- inc/framework/generator/ge_generator.h | 1 + inc/framework/omg/omg.h | 7 +- inc/framework/omg/omg_inner_types.h | 2 + inc/graph/compute_graph.h | 10 +- inc/graph/debug/ge_attr_define.h | 23 +- inc/graph/detail/attributes_holder.h | 1 - inc/graph/ge_context.h | 1 + inc/graph/ge_tensor.h | 7 +- inc/graph/model_serialize.h | 1 - inc/graph/op_desc.h | 4 + inc/graph/utils/graph_utils.h | 15 +- inc/graph/utils/node_utils.h | 25 + inc/graph/utils/tensor_adapter.h | 1 + inc/graph/utils/tensor_utils.h | 1 + src/common/graph/CMakeLists.txt | 1 + src/common/graph/compute_graph.cc | 13 + src/common/graph/debug/ge_op_types.h | 4 + src/common/graph/format_refiner.cc | 90 +- src/common/graph/format_refiner.h | 8 +- src/common/graph/ge_attr_define.cc | 23 +- src/common/graph/ge_tensor.cc | 11 + src/common/graph/graph.cc | 2 +- src/common/graph/graph.mk | 109 +- src/common/graph/model_serialize.cc | 25 +- src/common/graph/node.cc | 47 +- src/common/graph/op_desc.cc | 53 +- src/common/graph/operator.cc | 73 +- src/common/graph/option/ge_context.cc | 2 + src/common/graph/ref_relation.cc | 4 + src/common/graph/shape_refiner.cc | 172 ++- src/common/graph/utils/ge_ir_utils.h | 10 +- src/common/graph/utils/graph_utils.cc | 79 +- src/common/graph/utils/node_utils.cc | 171 ++- src/common/graph/utils/op_desc_utils.cc | 66 +- src/common/graph/utils/tensor_utils.cc | 8 +- src/common/graph/utils/type_utils.cc | 3 +- src/ge/CMakeLists.txt | 7 +- src/ge/client/ge_api.cc | 57 +- src/ge/common/convert/pb2json.cc | 11 +- .../format_transfers/datatype_transfer.cc | 1 - .../format_transfers/datatype_transfer.h | 1 - .../format_transfer_dhwcn_fracz3D.cc | 1 - ...format_transfer_dhwnc_fracz3D_transpose.cc | 1 - .../format_transfer_fractal_nz.cc | 2 +- .../format_transfer_fractal_z.cc | 95 +- .../format_transfer_nchw_fz_c04.cc | 4 +- .../formats/utils/formats_definitions.h | 2 - .../formats/utils/formats_trans_utils.h | 1 - src/ge/common/fp16_t.h | 2 +- src/ge/common/ge/op_tiling_manager.cc | 81 ++ .../ge/op_tiling_manager.h} | 21 +- src/ge/common/helper/model_helper.cc | 103 +- src/ge/common/math/fp16_math.h | 2 +- src/ge/common/math_util.h | 2 - src/ge/common/model_saver.cc | 7 +- src/ge/common/profiling/profiling_manager.cc | 20 +- src/ge/common/properties_manager.cc | 320 +++-- src/ge/common/properties_manager.h | 68 +- src/ge/common/tbe_kernel_store.h | 1 - src/ge/common/types.cc | 195 +-- src/ge/common/util.cc | 66 +- src/ge/engine_manager/dnnengine_manager.cc | 22 +- src/ge/engine_manager/dnnengine_manager.h | 3 + src/ge/executor/CMakeLists.txt | 2 +- src/ge/executor/ge_executor.cc | 7 +- src/ge/executor/module.mk | 3 +- src/ge/ge_inference.mk | 34 +- src/ge/ge_runner.mk | 39 +- src/ge/ge_runtime/model_runner.cc | 31 + src/ge/ge_runtime/output.cc | 2 +- src/ge/ge_runtime/runtime_model.cc | 81 +- src/ge/ge_runtime/runtime_model.h | 9 +- src/ge/ge_runtime/task/aicpu_task.cc | 14 +- src/ge/ge_runtime/task/aicpu_task.h | 6 + src/ge/ge_runtime/task/hccl_task.cc | 1 - src/ge/ge_runtime/task/label_goto_task.cc | 70 + src/ge/ge_runtime/task/label_goto_task.h | 41 + src/ge/ge_runtime/task/label_set_task.cc | 70 + src/ge/ge_runtime/task/label_set_task.h | 41 + src/ge/ge_runtime/task/label_switch_task.cc | 131 ++ src/ge/ge_runtime/task/label_switch_task.h | 44 + src/ge/ge_runtime/task/stream_switch_task.cc | 2 +- src/ge/ge_runtime/task/task.h | 6 + src/ge/ge_runtime/task/tbe_task.cc | 7 +- src/ge/ge_runtime/task/tbe_task.h | 4 + src/ge/ge_train.mk | 333 ----- src/ge/generator/ge_generator.cc | 135 +- src/ge/graph/build/graph_builder.cc | 122 +- src/ge/graph/build/graph_builder.h | 1 + src/ge/graph/build/label_allocator.cc | 2 - .../graph/build/logical_stream_allocator.cc | 62 +- src/ge/graph/build/logical_stream_allocator.h | 4 - .../graph/build/memory/block_mem_assigner.cc | 293 ++-- .../graph/build/memory/block_mem_assigner.h | 58 +- .../graph/build/memory/graph_mem_assigner.cc | 15 +- .../graph/build/memory/var_mem_assign_util.cc | 40 +- .../graph/build/memory/var_mem_assign_util.h | 4 +- src/ge/graph/build/model_builder.cc | 50 +- src/ge/graph/build/model_builder.h | 4 +- src/ge/graph/build/run_context.cc | 1 - src/ge/graph/build/stream_allocator.cc | 231 ++-- src/ge/graph/build/stream_allocator.h | 10 +- src/ge/graph/build/task_generator.cc | 229 +-- src/ge/graph/build/task_generator.h | 18 +- src/ge/graph/common/ge_call_wrapper.h | 48 + src/ge/graph/execute/graph_execute.cc | 20 +- src/ge/graph/execute/graph_execute.h | 6 +- src/ge/graph/load/graph_loader.cc | 3 +- .../new_model_manager/cpu_queue_schedule.cc | 2 +- .../load/new_model_manager/data_dumper.cc | 366 +++-- .../load/new_model_manager/data_dumper.h | 34 +- .../load/new_model_manager/davinci_model.cc | 973 +++++++------ .../load/new_model_manager/davinci_model.h | 120 +- .../load/new_model_manager/model_manager.cc | 87 +- .../load/new_model_manager/model_manager.h | 10 +- .../load/new_model_manager/model_utils.cc | 237 ++-- .../load/new_model_manager/model_utils.h | 37 +- .../task_info/end_graph_task_info.cc | 3 +- .../task_info/end_graph_task_info.h | 10 +- .../task_info/hccl_task_info.cc | 125 +- .../task_info/hccl_task_info.h | 15 +- .../task_info/kernel_ex_task_info.cc | 74 +- .../task_info/kernel_ex_task_info.h | 2 + .../task_info/kernel_task_info.cc | 137 +- .../task_info/kernel_task_info.h | 13 + .../label_switch_by_index_task_info.cc | 33 +- .../label_switch_by_index_task_info.h | 7 +- .../task_info/memcpy_addr_async_task_info.cc | 86 +- .../task_info/memcpy_addr_async_task_info.h | 10 +- .../task_info/memcpy_async_task_info.cc | 106 +- .../task_info/memcpy_async_task_info.h | 14 +- .../task_info/stream_switch_task_info.cc | 43 +- .../task_info/stream_switch_task_info.h | 5 + .../task_info/stream_switchn_task_info.cc | 46 +- .../task_info/stream_switchn_task_info.h | 6 +- .../task_info/super_kernel/super_kernel.h | 15 +- .../super_kernel/super_kernel_factory.cc | 106 +- .../super_kernel/super_kernel_factory.h | 3 +- .../new_model_manager/task_info/task_info.h | 2 + .../task_info/task_info_factory.h | 2 +- .../load/new_model_manager/zero_copy_task.cc | 6 - src/ge/graph/load/output/output.cc | 175 --- src/ge/graph/load/output/output.h | 94 -- .../graph/manager/graph_caching_allocator.cc | 10 +- .../graph/manager/graph_caching_allocator.h | 12 +- src/ge/graph/manager/graph_manager.cc | 93 +- src/ge/graph/manager/graph_manager.h | 2 +- src/ge/graph/manager/graph_mem_allocator.h | 2 +- src/ge/graph/manager/graph_var_manager.cc | 7 +- src/ge/graph/manager/graph_var_manager.h | 2 +- .../manager/model_manager/event_manager.h | 3 +- src/ge/graph/manager/trans_var_data_utils.cc | 18 +- src/ge/graph/manager/util/hcom_util.cc | 9 +- src/ge/graph/manager/util/hcom_util.h | 4 +- src/ge/graph/manager/util/rt_context_util.cc | 27 +- src/ge/graph/manager/util/rt_context_util.h | 13 +- src/ge/graph/optimize/graph_optimize.cc | 32 + src/ge/graph/optimize/graph_optimize.h | 5 +- src/ge/graph/optimize/summary_optimize.cc | 3 +- .../partition/dynamic_shape_partition.cc | 107 +- src/ge/graph/partition/engine_place.cc | 6 +- src/ge/graph/partition/graph_partition.cc | 67 +- src/ge/graph/passes/atomic_addr_clean_pass.cc | 26 +- src/ge/graph/passes/atomic_addr_clean_pass.h | 1 + .../graph/passes/attach_stream_label_pass.cc | 319 +++++ .../graph/passes/attach_stream_label_pass.h | 97 ++ src/ge/graph/passes/cast_remove_pass.cc | 1 - .../common_subexpression_elimination_pass.cc | 1 + src/ge/graph/passes/compile_nodes_pass.cc | 4 +- src/ge/graph/passes/cond_pass.cc | 4 +- src/ge/graph/passes/cond_remove_pass.cc | 19 +- src/ge/graph/passes/constant_folding_pass.cc | 35 + src/ge/graph/passes/constant_folding_pass.h | 6 + src/ge/graph/passes/control_trigger_pass.cc | 9 +- src/ge/graph/passes/hccl_memcpy_pass.cc | 26 +- src/ge/graph/passes/hccl_memcpy_pass.h | 2 - .../graph/passes/identify_reference_pass.cc | 52 - src/ge/graph/passes/infershape_pass.cc | 4 +- src/ge/graph/passes/iterator_op_pass.cc | 12 +- src/ge/graph/passes/iterator_op_pass.h | 2 +- .../graph/passes/link_gen_mask_nodes_pass.cc | 7 + src/ge/graph/passes/mark_same_addr_pass.cc | 81 ++ src/ge/graph/passes/mark_same_addr_pass.h | 32 + .../passes/merge_to_stream_merge_pass.cc | 234 ++++ .../graph/passes/merge_to_stream_merge_pass.h | 75 + src/ge/graph/passes/multi_batch_pass.cc | 116 +- src/ge/graph/passes/multi_batch_pass.h | 9 +- src/ge/graph/passes/next_iteration_pass.cc | 102 +- src/ge/graph/passes/next_iteration_pass.h | 57 +- src/ge/graph/passes/pass_manager.cc | 1 + src/ge/graph/passes/permute_pass.cc | 3 - src/ge/graph/passes/print_op_pass.h | 2 +- .../passes/ref_identity_delete_op_pass.cc | 225 +++ .../passes/ref_identity_delete_op_pass.h | 40 + src/ge/graph/passes/reshape_recovery_pass.cc | 9 +- .../same_transdata_breadth_fusion_pass.cc | 33 +- .../same_transdata_breadth_fusion_pass.h | 2 +- src/ge/graph/passes/subgraph_pass.cc | 357 ++--- src/ge/graph/passes/subgraph_pass.h | 78 +- src/ge/graph/passes/switch_op_pass.cc | 1227 ----------------- .../passes/switch_to_stream_switch_pass.cc | 755 ++++++++++ ..._pass.h => switch_to_stream_switch_pass.h} | 200 ++- .../passes/transop_breadth_fusion_pass.cc | 3 - .../graph/passes/transop_depth_fusion_pass.cc | 3 - .../transop_symmetry_elimination_pass.cc | 1 - .../transop_without_reshape_fusion_pass.cc | 3 - src/ge/graph/passes/variable_op_pass.cc | 34 +- src/ge/graph/passes/variable_op_pass.h | 1 + .../graph/passes/variable_prepare_op_pass.cc | 319 +++-- .../graph/passes/variable_prepare_op_pass.h | 13 +- .../passes/variable_ref_delete_op_pass.cc | 37 +- ...ble_ref_useless_control_out_delete_pass.cc | 1 - src/ge/graph/preprocess/graph_preprocess.cc | 249 +++- src/ge/graph/preprocess/graph_preprocess.h | 7 +- .../graph/preprocess/insert_op/ge_aipp_op.cc | 4 +- .../insert_op/util_insert_aipp_op.cc | 50 +- .../insert_op/util_insert_aipp_op.h | 1 + .../preprocess/multi_batch_copy_graph.cc | 50 +- .../graph/preprocess/multi_batch_copy_graph.h | 1 + src/ge/host_kernels/add_kernel.cc | 11 +- src/ge/host_kernels/broadcast_args_kernel.cc | 1 - src/ge/host_kernels/concat_offset_kernel.cc | 11 +- src/ge/host_kernels/dynamic_stitch_kernel.cc | 10 +- src/ge/host_kernels/empty_kernel.cc | 11 +- src/ge/host_kernels/expanddims_kernel.cc | 2 +- src/ge/host_kernels/floordiv_kernel.cc | 2 +- src/ge/host_kernels/floormod_kernel.cc | 2 +- src/ge/host_kernels/gather_v2_kernel.cc | 28 +- src/ge/host_kernels/identity_kernel.cc | 63 + src/ge/host_kernels/identity_kernel.h | 31 + src/ge/host_kernels/pack_kernel.cc | 9 +- src/ge/host_kernels/permute_kernel.cc | 4 +- src/ge/host_kernels/rank_kernel.cc | 2 +- src/ge/host_kernels/reduce_prod_kernel.cc | 24 +- src/ge/host_kernels/reformat_kernel.cc | 9 +- src/ge/host_kernels/reshape_kernel.cc | 2 +- src/ge/host_kernels/rsqrt_kernel.cc | 6 +- src/ge/host_kernels/slice_d_kernel.cc | 23 +- src/ge/host_kernels/slice_d_kernel.h | 1 + src/ge/host_kernels/slice_kernel.cc | 2 +- src/ge/host_kernels/ssd_prior_box_kernel.cc | 2 +- src/ge/host_kernels/strided_slice_kernel.cc | 39 +- src/ge/host_kernels/strided_slice_kernel.h | 1 + src/ge/host_kernels/sub_kernel.cc | 2 +- src/ge/host_kernels/transdata_kernel.cc | 2 +- src/ge/host_kernels/transpose_kernel.cc | 4 +- src/ge/hybrid/common/npu_memory_allocator.cc | 26 +- src/ge/hybrid/common/npu_memory_allocator.h | 21 +- src/ge/hybrid/common/tensor_value.cc | 4 +- src/ge/hybrid/common/tensor_value.h | 4 +- .../executor/hybrid_execution_context.cc | 31 +- .../executor/hybrid_execution_context.h | 26 +- .../executor/hybrid_model_async_executor.cc | 107 +- .../executor/hybrid_model_async_executor.h | 8 +- .../hybrid/executor/hybrid_model_executor.cc | 103 +- .../hybrid/executor/hybrid_model_executor.h | 17 +- src/ge/hybrid/executor/hybrid_profiler.cc | 3 +- src/ge/hybrid/executor/node_done_manager.cc | 41 +- src/ge/hybrid/executor/node_done_manager.h | 5 +- src/ge/hybrid/executor/node_state.cc | 126 +- src/ge/hybrid/executor/node_state.h | 77 +- src/ge/hybrid/executor/rt_callback_manager.cc | 1 - src/ge/hybrid/executor/subgraph_context.cc | 112 ++ src/ge/hybrid/executor/subgraph_context.h | 61 + src/ge/hybrid/executor/subgraph_executor.cc | 373 +++++ src/ge/hybrid/executor/subgraph_executor.h | 101 ++ .../executor/worker/execution_engine.cc | 194 ++- .../hybrid/executor/worker/execution_engine.h | 21 +- .../executor/worker/shape_inference_engine.cc | 225 +-- .../executor/worker/shape_inference_engine.h | 66 +- .../executor/worker/task_compile_engine.cc | 170 +-- .../executor/worker/task_compile_engine.h | 33 +- src/ge/hybrid/hybrid_davinci_model.cc | 13 +- src/ge/hybrid/hybrid_davinci_model.h | 2 + src/ge/hybrid/hybrid_davinci_model_stub.cc | 2 + src/ge/hybrid/model/graph_item.cc | 62 + src/ge/hybrid/model/graph_item.h | 64 + src/ge/hybrid/model/hybrid_model.cc | 61 +- src/ge/hybrid/model/hybrid_model.h | 48 +- src/ge/hybrid/model/hybrid_model_builder.cc | 576 +++++--- src/ge/hybrid/model/hybrid_model_builder.h | 22 +- src/ge/hybrid/model/node_item.cc | 43 +- src/ge/hybrid/model/node_item.h | 17 +- .../aicore/aicore_node_executor.cc | 291 ++-- .../aicore/aicore_node_executor.h | 16 +- .../node_executor/aicore/aicore_op_task.cc | 296 +++- .../node_executor/aicore/aicore_op_task.h | 50 +- .../aicore/aicore_task_builder.cc | 110 +- .../aicore/aicore_task_builder.h | 21 +- .../aicore/aicore_task_compiler.cc | 11 +- .../node_executor/aicpu/aicpu_ext_info.cc | 1 - .../node_executor/aicpu/aicpu_ext_info.h | 1 - .../aicpu/aicpu_node_executor.cc | 79 +- .../node_executor/aicpu/aicpu_node_executor.h | 4 +- .../compiledsubgraph/known_node_executor.cc | 9 +- .../controlop/control_op_executor.cc | 318 +++++ .../controlop/control_op_executor.h | 100 ++ .../node_executor/hccl/hccl_node_executor.cc | 207 +++ .../node_executor/hccl/hccl_node_executor.h | 59 + .../hostcpu/ge_local_node_executor.cc | 31 +- .../hostcpu/ge_local_node_executor.h | 13 +- src/ge/hybrid/node_executor/node_executor.cc | 120 +- src/ge/hybrid/node_executor/node_executor.h | 130 +- .../partitioned_call_node_executor.cc | 81 ++ .../partitioned_call_node_executor.h | 54 + src/ge/hybrid/node_executor/task_context.cc | 144 +- src/ge/hybrid/node_executor/task_context.h | 43 +- src/ge/inc/kernel_factory.h | 2 +- src/ge/init/gelib.cc | 47 +- src/ge/init/gelib.h | 6 +- src/ge/ir_build/atc_ir_common.cc | 118 +- src/ge/ir_build/atc_ir_common.h | 4 +- src/ge/ir_build/ge_ir_build.cc | 2 - src/ge/model/ge_model.h | 2 +- src/ge/offline/main.cc | 188 ++- src/ge/offline/single_op_parser.cc | 20 +- .../opskernel_manager/ops_kernel_manager.cc | 4 +- src/ge/opskernel_manager/ops_kernel_manager.h | 2 + src/ge/session/inner_session.cc | 58 +- src/ge/session/omg.cc | 155 ++- src/ge/session/session_manager.cc | 10 +- src/ge/session/session_manager.h | 5 +- src/ge/single_op/single_op.cc | 52 +- src/ge/single_op/single_op_model.cc | 70 +- src/ge/single_op/single_op_model.h | 1 + .../task/aicpu_kernel_task_builder.cc | 56 + .../task/aicpu_kernel_task_builder.h | 40 + src/ge/single_op/task/aicpu_task_builder.cc | 3 +- src/ge/single_op/task/build_task_utils.cc | 45 +- src/ge/single_op/task/build_task_utils.h | 16 + src/ge/single_op/task/op_task.cc | 61 +- src/ge/single_op/task/op_task.h | 29 + src/ge/single_op/task/tbe_task_builder.cc | 2 + tests/depends/cce/src/op_kernel_registry.cc | 16 - third_party/fwkacllib/inc/hccl/base.h | 11 + third_party/fwkacllib/inc/hccl/hcom.h | 6 +- .../fwkacllib/inc/mmpa/sub_inc/mmpa_linux.h | 2 + .../fwkacllib/inc/mmpa/sub_inc/mmpa_win.h | 1 + third_party/fwkacllib/inc/ops/all_ops.h | 2 + third_party/fwkacllib/inc/ops/array_ops.h | 37 + third_party/fwkacllib/inc/ops/ctc_ops.h | 72 +- .../inc/ops/elewise_calculation_ops.h | 118 +- third_party/fwkacllib/inc/ops/hvd_ops.h | 77 ++ third_party/fwkacllib/inc/ops/internal_ops.h | 48 + third_party/fwkacllib/inc/ops/math_ops.h | 6 +- .../inc/ops/matrix_calculation_ops.h | 39 + .../fwkacllib/inc/ops/nn_batch_norm_ops.h | 30 +- .../fwkacllib/inc/ops/nn_calculation_ops.h | 106 +- third_party/fwkacllib/inc/ops/nn_detect_ops.h | 68 +- third_party/fwkacllib/inc/ops/nn_norm_ops.h | 60 +- .../fwkacllib/inc/ops/nn_pooling_ops.h | 322 ++++- .../fwkacllib/inc/ops/nn_training_ops.h | 2 +- .../fwkacllib/inc/ops/nonlinear_fuc_ops.h | 8 +- third_party/fwkacllib/inc/ops/reduce_ops.h | 187 ++- third_party/fwkacllib/inc/ops/rnn.h | 9 +- third_party/fwkacllib/inc/ops/selection_ops.h | 11 +- .../fwkacllib/inc/ops/split_combination_ops.h | 9 +- .../fwkacllib/inc/ops/transformation_ops.h | 24 +- .../fwkacllib/inc/register/op_registry.h | 15 +- .../fwkacllib/inc/register/op_tiling.h | 130 ++ third_party/fwkacllib/inc/runtime/base.h | 10 +- third_party/fwkacllib/inc/runtime/config.h | 3 +- third_party/fwkacllib/inc/runtime/context.h | 10 - third_party/fwkacllib/inc/runtime/dev.h | 78 +- third_party/fwkacllib/inc/runtime/rt_model.h | 27 + third_party/fwkacllib/inc/toolchain/slog.h | 85 +- .../prebuild/aarch64/liberror_manager.so | Bin 0 -> 888880 bytes third_party/prebuild/aarch64/libslog.so | Bin 0 -> 150360 bytes .../prebuild/x86_64/liberror_manager.so | Bin 0 -> 852544 bytes third_party/prebuild/x86_64/libslog.so | Bin 89440 -> 103704 bytes 385 files changed, 13754 insertions(+), 7507 deletions(-) create mode 100644 inc/common/util/compress/compress_weight.h create mode 100644 src/ge/common/ge/op_tiling_manager.cc rename src/ge/{graph/passes/identify_reference_pass.h => common/ge/op_tiling_manager.h} (62%) create mode 100644 src/ge/ge_runtime/task/label_goto_task.cc create mode 100644 src/ge/ge_runtime/task/label_goto_task.h create mode 100644 src/ge/ge_runtime/task/label_set_task.cc create mode 100644 src/ge/ge_runtime/task/label_set_task.h create mode 100644 src/ge/ge_runtime/task/label_switch_task.cc create mode 100644 src/ge/ge_runtime/task/label_switch_task.h delete mode 100644 src/ge/ge_train.mk delete mode 100644 src/ge/graph/load/output/output.cc delete mode 100644 src/ge/graph/load/output/output.h create mode 100644 src/ge/graph/passes/attach_stream_label_pass.cc create mode 100644 src/ge/graph/passes/attach_stream_label_pass.h delete mode 100644 src/ge/graph/passes/identify_reference_pass.cc create mode 100644 src/ge/graph/passes/mark_same_addr_pass.cc create mode 100644 src/ge/graph/passes/mark_same_addr_pass.h create mode 100644 src/ge/graph/passes/merge_to_stream_merge_pass.cc create mode 100644 src/ge/graph/passes/merge_to_stream_merge_pass.h create mode 100644 src/ge/graph/passes/ref_identity_delete_op_pass.cc create mode 100644 src/ge/graph/passes/ref_identity_delete_op_pass.h delete mode 100644 src/ge/graph/passes/switch_op_pass.cc create mode 100644 src/ge/graph/passes/switch_to_stream_switch_pass.cc rename src/ge/graph/passes/{switch_op_pass.h => switch_to_stream_switch_pass.h} (61%) create mode 100644 src/ge/host_kernels/identity_kernel.cc create mode 100644 src/ge/host_kernels/identity_kernel.h create mode 100644 src/ge/hybrid/executor/subgraph_context.cc create mode 100644 src/ge/hybrid/executor/subgraph_context.h create mode 100644 src/ge/hybrid/executor/subgraph_executor.cc create mode 100644 src/ge/hybrid/executor/subgraph_executor.h create mode 100644 src/ge/hybrid/model/graph_item.cc create mode 100644 src/ge/hybrid/model/graph_item.h create mode 100644 src/ge/hybrid/node_executor/controlop/control_op_executor.cc create mode 100644 src/ge/hybrid/node_executor/controlop/control_op_executor.h create mode 100644 src/ge/hybrid/node_executor/hccl/hccl_node_executor.cc create mode 100644 src/ge/hybrid/node_executor/hccl/hccl_node_executor.h create mode 100644 src/ge/hybrid/node_executor/partitioned_call/partitioned_call_node_executor.cc create mode 100644 src/ge/hybrid/node_executor/partitioned_call/partitioned_call_node_executor.h create mode 100644 src/ge/single_op/task/aicpu_kernel_task_builder.cc create mode 100644 src/ge/single_op/task/aicpu_kernel_task_builder.h create mode 100644 third_party/fwkacllib/inc/ops/hvd_ops.h create mode 100644 third_party/fwkacllib/inc/ops/internal_ops.h create mode 100644 third_party/fwkacllib/inc/register/op_tiling.h create mode 100755 third_party/prebuild/aarch64/liberror_manager.so create mode 100755 third_party/prebuild/aarch64/libslog.so create mode 100755 third_party/prebuild/x86_64/liberror_manager.so diff --git a/inc/common/opskernel/ge_task_info.h b/inc/common/opskernel/ge_task_info.h index 360f8a5d..8a55b7de 100644 --- a/inc/common/opskernel/ge_task_info.h +++ b/inc/common/opskernel/ge_task_info.h @@ -52,5 +52,16 @@ struct GETaskInfo { std::vector kernelHcclInfo; }; + +struct HcomOpertion { + std::string hcclType; + void *inputPtr; + void *outputPtr; + uint64_t count; + int32_t dataType; + int32_t opType; + int32_t root; +}; + } // namespace ge #endif // INC_COMMON_OPSKERNEL_GE_TASK_INFO_H_ diff --git a/inc/common/util/compress/compress.h b/inc/common/util/compress/compress.h index 6908fb75..e350f9e5 100644 --- a/inc/common/util/compress/compress.h +++ b/inc/common/util/compress/compress.h @@ -28,6 +28,7 @@ struct CompressConfig { size_t channel; // channels of L2 or DDR. For load balance size_t fractalSize; // size of compressing block bool isTight; // whether compose compressed data tightly + size_t init_offset; }; CmpStatus CompressWeights(char* input, const CompressConfig& compressConfig, char* indexs, char* output, diff --git a/inc/common/util/compress/compress_weight.h b/inc/common/util/compress/compress_weight.h new file mode 100644 index 00000000..34ea47d1 --- /dev/null +++ b/inc/common/util/compress/compress_weight.h @@ -0,0 +1,33 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COMPRESS_WEIGHT_H +#define COMPRESS_WEIGHT_H + +#include "compress.h" + +const int SHAPE_SIZE_WEIGHT = 4; + +struct CompressOpConfig { + int64_t wShape[SHAPE_SIZE_WEIGHT]; + size_t compressTilingK; + size_t compressTilingN; + struct CompressConfig compressConfig; +}; + +extern "C" CmpStatus CompressWeightsConv2D(const char *const input, char *const zipBuffer, char *const infoBuffer, + CompressOpConfig *const param); +#endif // COMPRESS_WEIGHT_H diff --git a/inc/common/util/platform_info.h b/inc/common/util/platform_info.h index cd143fcc..2a145d68 100644 --- a/inc/common/util/platform_info.h +++ b/inc/common/util/platform_info.h @@ -27,7 +27,6 @@ using std::string; using std::vector; namespace fe { - class PlatformInfoManager { public: PlatformInfoManager(const PlatformInfoManager &) = delete; @@ -39,6 +38,8 @@ class PlatformInfoManager { uint32_t GetPlatformInfo(const string SoCVersion, PlatformInfo &platformInfo, OptionalInfo &optiCompilationInfo); + uint32_t GetPlatformInfoWithOutSocVersion(PlatformInfo &platformInfo, OptionalInfo &optiCompilationInfo); + void SetOptionalCompilationInfo(OptionalInfo &optiCompilationInfo); private: @@ -94,6 +95,5 @@ class PlatformInfoManager { map platformInfoMap_; OptionalInfo optiCompilationInfo_; }; - } // namespace fe #endif diff --git a/inc/external/ge/ge_api_types.h b/inc/external/ge/ge_api_types.h index 1632f11c..cffb28bd 100644 --- a/inc/external/ge/ge_api_types.h +++ b/inc/external/ge/ge_api_types.h @@ -44,8 +44,12 @@ const char *const OPTION_EXEC_ENABLE_DUMP = "ge.exec.enableDump"; const char *const OPTION_EXEC_DUMP_PATH = "ge.exec.dumpPath"; const char *const OPTION_EXEC_DUMP_STEP = "ge.exec.dumpStep"; const char *const OPTION_EXEC_DUMP_MODE = "ge.exec.dumpMode"; +const char *const OPTION_EXEC_ENABLE_DUMP_DEBUG = "ge.exec.enableDumpDebug"; +const char *const OPTION_EXEC_DUMP_DEBUG_MODE = "ge.exec.dumpDebugMode"; +const char *const OPTION_EXEC_OP_DEBUG_LEVEL = "ge.exec.opDebugLevel"; const char *const OPTION_EXEC_ENABLE_INCRE_BUILD = "ge.exec.enableIncreBuild"; const char *const OPTION_EXEC_INCRE_BUILD_CACHE_PATH = "ge.exec.increBuildCachePath"; +const char *const OPTION_EXEC_ENABLE_SCOPE_FUSION_PASSES = "ge.exec.enableScopeFusionPasses"; // profiling flag const char *const OPTION_EXEC_PROFILING_MODE = "ge.exec.profilingMode"; const char *const OPTION_EXEC_PROFILING_OPTIONS = "ge.exec.profilingOptions"; @@ -219,6 +223,10 @@ const char *const ENABLE_SINGLE_STREAM = "ge.enableSingleStream"; // Configure input fp16 nodes const std::string INPUT_FP16_NODES = "ge.INPUT_NODES_SET_FP16"; +// Configure debug level, its value should be 0(default), 1 or 2. +// 0: close debug; 1: open TBE compiler; 2: open ccec compiler +const std::string OP_DEBUG_LEVEL = "ge.opDebugLevel"; + // Graph run mode enum GraphRunMode { PREDICTION = 0, TRAIN }; diff --git a/inc/external/graph/types.h b/inc/external/graph/types.h index 4cd9ba91..a1245c9d 100644 --- a/inc/external/graph/types.h +++ b/inc/external/graph/types.h @@ -145,7 +145,8 @@ enum Format { FORMAT_FRACTAL_ZN_LSTM, FORMAT_FRACTAL_Z_G, FORMAT_RESERVED, - FORMAT_ALL + FORMAT_ALL, + FORMAT_NULL }; // for unknown shape op type diff --git a/inc/external/register/register.h b/inc/external/register/register.h index a8421511..9834d8a8 100644 --- a/inc/external/register/register.h +++ b/inc/external/register/register.h @@ -98,6 +98,8 @@ class FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY OpRegistrationData { OpRegistrationData &DelInputWithOriginalType(int input_idx, const std::string &ori_type); + OpRegistrationData &InputReorderVector(const vector &input_order); + domi::ImplyType GetImplyType() const; std::string GetOmOptype() const; std::set GetOriginOpTypeSet() const; diff --git a/inc/framework/common/debug/ge_log.h b/inc/framework/common/debug/ge_log.h index e2023cb8..6ac00037 100644 --- a/inc/framework/common/debug/ge_log.h +++ b/inc/framework/common/debug/ge_log.h @@ -51,30 +51,6 @@ inline pid_t GetTid() { return tid; } -#define GE_TIMESTAMP_START(stage) uint64_t startUsec_##stage = ge::GetCurrentTimestap() - -#define GE_TIMESTAMP_END(stage, stage_name) \ - do { \ - uint64_t endUsec_##stage = ge::GetCurrentTimestap(); \ - GEEVENT("[GEPERFTRACE] The time cost of %s is [%lu] micro second.", (stage_name), \ - (endUsec_##stage - startUsec_##stage)); \ - } while (0); - -#define GE_TIMESTAMP_CALLNUM_START(stage) \ - uint64_t startUsec_##stage = ge::GetCurrentTimestap(); \ - uint64_t call_num_of##stage = 0; \ - uint64_t time_of##stage = 0 - -#define GE_TIMESTAMP_RESTART(stage) (startUsec_##stage = ge::GetCurrentTimestap()) - -#define GE_TIMESTAMP_ADD(stage) \ - time_of##stage += ge::GetCurrentTimestap() - startUsec_##stage; \ - call_num_of##stage++ - -#define GE_TIMESTAMP_CALLNUM_END(stage, stage_name) \ - GEEVENT("[GEPERFTRACE] The time cost of %s is [%lu] micro second, call num is %lu", (stage_name), time_of##stage, \ - call_num_of##stage) - #define GE_LOG_ERROR(MOD_NAME, ERROR_CODE, fmt, ...) \ dlog_error(MOD_NAME, "%lu %s: ErrorNo: %d(%s) " fmt, GetTid(), __FUNCTION__, ERROR_CODE, \ ((GE_GET_ERRORNO_STR(ERROR_CODE)).c_str()), ##__VA_ARGS__) diff --git a/inc/framework/common/debug/log.h b/inc/framework/common/debug/log.h index 28c6585e..f07a8fa0 100644 --- a/inc/framework/common/debug/log.h +++ b/inc/framework/common/debug/log.h @@ -19,15 +19,12 @@ #include -#include "cce/cce_def.hpp" +#include "runtime/rt.h" #include "common/string_util.h" #include "common/util.h" #include "framework/common/debug/ge_log.h" #include "ge/ge_api_error_codes.h" -using cce::CC_STATUS_SUCCESS; -using cce::ccStatus_t; - #if !defined(__ANDROID__) && !defined(ANDROID) #define DOMI_LOGE(...) GE_LOG_ERROR(GE_MODULE_NAME, ge::FAILED, __VA_ARGS__) #else @@ -102,17 +99,13 @@ using cce::ccStatus_t; } while (0); // If expr is not true, print the log and return the specified status -#define GE_CHK_BOOL_RET_STATUS(expr, _status, ...) \ - do { \ - bool b = (expr); \ - if (!b) { \ - std::string msg; \ - (void)msg.append(ge::StringUtils::FormatString(__VA_ARGS__)); \ - (void)msg.append( \ - ge::StringUtils::FormatString(" Error Code:0x%X(%s)", _status, GET_ERRORNO_STR(_status).c_str())); \ - DOMI_LOGE("%s", msg.c_str()); \ - return _status; \ - } \ +#define GE_CHK_BOOL_RET_STATUS(expr, _status, ...) \ + do { \ + bool b = (expr); \ + if (!b) { \ + GELOGE(_status, __VA_ARGS__); \ + return _status; \ + } \ } while (0); // If expr is not true, print the log and return the specified status @@ -132,7 +125,7 @@ using cce::ccStatus_t; DOMI_LOGE(__VA_ARGS__); \ exec_expr; \ } \ - }; + } // If expr is not true, print the log and execute a custom statement #define GE_CHK_BOOL_EXEC_WARN(expr, exec_expr, ...) \ @@ -142,7 +135,7 @@ using cce::ccStatus_t; GELOGW(__VA_ARGS__); \ exec_expr; \ } \ - }; + } // If expr is not true, print the log and execute a custom statement #define GE_CHK_BOOL_EXEC_INFO(expr, exec_expr, ...) \ { \ @@ -151,7 +144,7 @@ using cce::ccStatus_t; GELOGI(__VA_ARGS__); \ exec_expr; \ } \ - }; + } // If expr is not true, print the log and execute a custom statement #define GE_CHK_BOOL_TRUE_EXEC_INFO(expr, exec_expr, ...) \ @@ -161,7 +154,7 @@ using cce::ccStatus_t; GELOGI(__VA_ARGS__); \ exec_expr; \ } \ - }; + } // If expr is true, print logs and execute custom statements #define GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(expr, exec_expr, ...) \ @@ -171,7 +164,7 @@ using cce::ccStatus_t; DOMI_LOGE(__VA_ARGS__); \ exec_expr; \ } \ - }; + } // If expr is true, print the Information log and execute a custom statement #define GE_CHK_TRUE_EXEC_INFO(expr, exec_expr, ...) \ { \ @@ -180,7 +173,7 @@ using cce::ccStatus_t; GELOGI(__VA_ARGS__); \ exec_expr; \ } \ - }; + } // If expr is not SUCCESS, print the log and execute the expression + return #define GE_CHK_BOOL_TRUE_RET_VOID(expr, exec_expr, ...) \ @@ -191,7 +184,7 @@ using cce::ccStatus_t; exec_expr; \ return; \ } \ - }; + } // If expr is not SUCCESS, print the log and execute the expression + return _status #define GE_CHK_BOOL_TRUE_EXEC_RET_STATUS(expr, _status, exec_expr, ...) \ @@ -202,7 +195,7 @@ using cce::ccStatus_t; exec_expr; \ return _status; \ } \ - }; + } // If expr is not true, execute a custom statement #define GE_CHK_BOOL_EXEC_NOLOG(expr, exec_expr) \ @@ -211,7 +204,7 @@ using cce::ccStatus_t; if (!b) { \ exec_expr; \ } \ - }; + } // -----------------runtime related macro definitions------------------------------- // If expr is not RT_ERROR_NONE, print the log @@ -231,7 +224,7 @@ using cce::ccStatus_t; DOMI_LOGE("Call rt api failed, ret: 0x%X", _rt_ret); \ exec_expr; \ } \ - }; + } // If expr is not RT_ERROR_NONE, print the log and return #define GE_CHK_RT_RET(expr) \ @@ -243,23 +236,13 @@ using cce::ccStatus_t; } \ } while (0); -// ------------------------cce related macro definitions---------------------------- -// If expr is not CC_STATUS_SUCCESS, print the log -#define GE_CHK_CCE(expr) \ - do { \ - ccStatus_t _cc_ret = (expr); \ - if (_cc_ret != CC_STATUS_SUCCESS) { \ - DOMI_LOGE("Call cce api failed, ret: 0x%X", _cc_ret); \ - } \ - } while (0); - // If expr is true, execute exec_expr without printing logs #define GE_IF_BOOL_EXEC(expr, exec_expr) \ { \ if (expr) { \ exec_expr; \ } \ - }; + } // If make_shared is abnormal, print the log and execute the statement #define GE_MAKE_SHARED(exec_expr0, exec_expr1) \ diff --git a/inc/framework/common/ge_types.h b/inc/framework/common/ge_types.h index 27ae28ee..00bfa301 100644 --- a/inc/framework/common/ge_types.h +++ b/inc/framework/common/ge_types.h @@ -54,9 +54,9 @@ const char *const GE_ENGINE_ATTR_MEM_TYPE_HBM = "HBM"; struct DataBuffer { public: void *data; // Data address - uint32_t length; // Data length + uint64_t length; // Data length bool isDataSupportMemShare = false; - DataBuffer(void *dataIn, uint32_t len, bool isSupportMemShare) + DataBuffer(void *dataIn, uint64_t len, bool isSupportMemShare) : data(dataIn), length(len), isDataSupportMemShare(isSupportMemShare) {} DataBuffer() : data(nullptr), length(0), isDataSupportMemShare(false) {} @@ -106,7 +106,7 @@ struct ShapeDescription { // Definition of input and output description information struct InputOutputDescInfo { std::string name; - uint32_t size; + uint64_t size; uint32_t data_type; ShapeDescription shape_info; }; @@ -231,6 +231,7 @@ struct Options { // Profiling info of task struct TaskDescInfo { + std::string model_name; std::string op_name; uint32_t block_dim; uint32_t task_id; @@ -239,6 +240,7 @@ struct TaskDescInfo { // Profiling info of graph struct ComputeGraphDescInfo { + std::string model_name; std::string op_name; std::string op_type; std::vector input_format; diff --git a/inc/framework/common/helper/model_helper.h b/inc/framework/common/helper/model_helper.h index 3c9de891..3671f970 100644 --- a/inc/framework/common/helper/model_helper.h +++ b/inc/framework/common/helper/model_helper.h @@ -44,8 +44,6 @@ class ModelHelper { void SetSaveMode(bool val) { is_offline_ = val; } bool GetSaveMode(void) const { return is_offline_; } - static Status TransModelToGeModel(const ModelPtr& model, GeModelPtr& ge_model); - static Status TransGeModelToModel(const GeModelPtr& geModelPtr, ModelPtr& modelPtr); Status GetBaseNameFromFileName(const std::string& file_name, std::string& base_name); Status GetModelNameFromMergedGraphName(const std::string& graph_name, std::string& model_name); diff --git a/inc/framework/common/types.h b/inc/framework/common/types.h index e3844a61..50e41755 100644 --- a/inc/framework/common/types.h +++ b/inc/framework/common/types.h @@ -48,6 +48,9 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY extern const std::string DUMP_S FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY extern const std::string DUMP_LAYER; FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY extern const std::string DUMP_FILE_PATH; FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY extern const std::string DUMP_MODE; +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY extern const std::string OP_DEBUG_AICORE; +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY extern const std::string OP_DEBUG_ATOMIC; +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY extern const std::string OP_DEBUG_ALL; // Supported public properties name FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY extern const std::string PROP_OME_START_TIME; // Start time @@ -335,6 +338,7 @@ REGISTER_OPTYPE_DECLARE(BASICLSTMCELL, "BasicLSTMCell"); REGISTER_OPTYPE_DECLARE(GETNEXT, "GetNext"); REGISTER_OPTYPE_DECLARE(INITDATA, "InitData"); REGISTER_OPTYPE_DECLARE(TRANSSHAPE, "TransShape") +REGISTER_OPTYPE_DECLARE(REFIDENTITY, "RefIdentity"); // ANN dedicated operator REGISTER_OPTYPE_DECLARE(ANN_MEAN, "AnnMean"); @@ -631,6 +635,9 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY extern const std::string NODE_N FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY extern const std::string NODE_NAME_END_GRAPH; +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY extern const std::string NODE_NAME_OP_DEBUG; +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY extern const std::string OP_TYPE_OP_DEBUG; + // convolution node type FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY extern const std::string OP_TYPE_CONVOLUTION; // adds a convolutional node name for the hard AIPP diff --git a/inc/framework/executor/ge_executor.h b/inc/framework/executor/ge_executor.h index 87e30805..2b7335ef 100644 --- a/inc/framework/executor/ge_executor.h +++ b/inc/framework/executor/ge_executor.h @@ -21,12 +21,12 @@ #include #include +#include "common/dynamic_aipp.h" #include "common/ge_inner_error_codes.h" #include "common/ge_types.h" #include "common/types.h" #include "graph/tensor.h" #include "runtime/base.h" -#include "common/dynamic_aipp.h" namespace ge { class ModelListenerAdapter; @@ -62,7 +62,7 @@ class GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY GeExecutor { // Get input and output descriptor ge::Status GetModelDescInfo(uint32_t model_id, std::vector &input_desc, - std::vector &output_desc); + std::vector &output_desc, bool new_model_desc = false); /// /// @ingroup ge diff --git a/inc/framework/ge_runtime/model_runner.h b/inc/framework/ge_runtime/model_runner.h index 6e7abcb9..8e312b09 100644 --- a/inc/framework/ge_runtime/model_runner.h +++ b/inc/framework/ge_runtime/model_runner.h @@ -28,16 +28,21 @@ namespace ge { namespace model_runner { class RuntimeModel; - +using RuntimeInfo = std::tuple; class ModelRunner { public: static ModelRunner &Instance(); bool LoadDavinciModel(uint32_t device_id, uint64_t session_id, uint32_t model_id, std::shared_ptr davinci_model, std::shared_ptr listener); + bool LoadModelComplete(uint32_t model_id); const std::vector &GetTaskIdList(uint32_t model_id) const; + const std::vector &GetStreamIdList(uint32_t model_id) const; + + const std::map> &GetRuntimeInfoMap(uint32_t model_id) const; + bool UnloadModel(uint32_t model_id); bool RunModel(uint32_t model_id, const InputData &input_data, OutputData *output_data); diff --git a/inc/framework/ge_runtime/task_info.h b/inc/framework/ge_runtime/task_info.h index a48ed68b..68d71870 100644 --- a/inc/framework/ge_runtime/task_info.h +++ b/inc/framework/ge_runtime/task_info.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "cce/taskdown_api.h" @@ -52,21 +53,27 @@ class TaskInfo { virtual ~TaskInfo() {} uint32_t stream_id() const { return stream_id_; } TaskInfoType type() const { return type_; } + std::string op_name() const { return op_name_; } + bool dump_flag() const { return dump_flag_; } protected: - TaskInfo(uint32_t stream_id, TaskInfoType type) : stream_id_(stream_id), type_(type) {} + TaskInfo(const std::string &op_name, uint32_t stream_id, TaskInfoType type, bool dump_flag) + : op_name_(op_name), stream_id_(stream_id), type_(type), dump_flag_(dump_flag) {} private: + std::string op_name_; uint32_t stream_id_; TaskInfoType type_; + bool dump_flag_; }; class CceTaskInfo : public TaskInfo { public: - CceTaskInfo(uint32_t stream_id, const cce::ccOpContext &ctx, const std::string &stub_func, uint32_t block_dim, - const std::vector &args, uint32_t args_size, const std::vector &sm_desc, - const std::vector &flow_table, const std::vector &args_offset, bool is_flowtable) - : TaskInfo(stream_id, TaskInfoType::CCE), + CceTaskInfo(const std::string &op_name, uint32_t stream_id, const cce::ccOpContext &ctx, const std::string &stub_func, + uint32_t block_dim, const std::vector &args, uint32_t args_size, + const std::vector &sm_desc, const std::vector &flow_table, + const std::vector &args_offset, bool is_flowtable) + : TaskInfo(op_name, stream_id, TaskInfoType::CCE, false), ctx_(ctx), stub_func_(stub_func), block_dim_(block_dim), @@ -102,11 +109,11 @@ class CceTaskInfo : public TaskInfo { class TbeTaskInfo : public TaskInfo { public: - TbeTaskInfo(uint32_t stream_id, const std::string &stub_func, uint32_t block_dim, const std::vector &args, - uint32_t args_size, const std::vector &sm_desc, void *binary, uint32_t binary_size, - const std::vector &meta_data, const std::vector &input_data_addrs, - const std::vector &output_data_addrs, const std::vector &workspace_addrs) - : TaskInfo(stream_id, TaskInfoType::TBE), + TbeTaskInfo(const std::string &op_name, uint32_t stream_id, const std::string &stub_func, uint32_t block_dim, + const std::vector &args, uint32_t args_size, const std::vector &sm_desc, void *binary, + uint32_t binary_size, const std::vector &meta_data, const std::vector &input_data_addrs, + const std::vector &output_data_addrs, const std::vector &workspace_addrs, bool dump_flag) + : TaskInfo(op_name, stream_id, TaskInfoType::TBE, dump_flag), stub_func_(stub_func), block_dim_(block_dim), args_(args), @@ -153,9 +160,10 @@ class TbeTaskInfo : public TaskInfo { class AicpuTaskInfo : public TaskInfo { public: - AicpuTaskInfo(uint32_t stream_id, const string &so_name, const std::string &kernel_name, const std::string &node_def, - const std::vector &input_data_addrs, const std::vector &output_data_addrs) - : TaskInfo(stream_id, TaskInfoType::AICPU), + AicpuTaskInfo(const std::string &op_name, uint32_t stream_id, const string &so_name, const std::string &kernel_name, + const std::string &node_def, const std::vector &input_data_addrs, + const std::vector &output_data_addrs, bool dump_flag) + : TaskInfo(op_name, stream_id, TaskInfoType::AICPU, dump_flag), so_name_(so_name), kernel_name_(kernel_name), node_def_(node_def), @@ -177,37 +185,45 @@ class AicpuTaskInfo : public TaskInfo { std::vector output_data_addrs_; }; -class LabelTaskInfo : public TaskInfo { +class LabelSetTaskInfo : public TaskInfo { public: + LabelSetTaskInfo(const std::string &op_name, uint32_t stream_id, uint32_t label_id) + : TaskInfo(op_name, stream_id, TaskInfoType::LABEL_SET, false), label_id_(label_id) {} + ~LabelSetTaskInfo() override {} uint32_t label_id() const { return label_id_; } - protected: - LabelTaskInfo(uint32_t stream_id, TaskInfoType type, uint32_t label_id) - : TaskInfo(stream_id, type), label_id_(label_id) {} - virtual ~LabelTaskInfo() override {} - + private: uint32_t label_id_; }; -class LabelSetTaskInfo : public LabelTaskInfo { +class LabelGotoTaskInfo : public TaskInfo { public: - LabelSetTaskInfo(uint32_t stream_id, uint32_t label_id) - : LabelTaskInfo(stream_id, TaskInfoType::LABEL_SET, label_id) {} - ~LabelSetTaskInfo() override {} + LabelGotoTaskInfo(const std::string &op_name, uint32_t stream_id, uint32_t label_id) + : TaskInfo(op_name, stream_id, TaskInfoType::LABEL_GOTO, false), label_id_(label_id) {} + ~LabelGotoTaskInfo() override {} + uint32_t label_id() const { return label_id_; } + + private: + uint32_t label_id_; }; -class LabelSwitchTaskInfo : public LabelTaskInfo { +class LabelSwitchTaskInfo : public TaskInfo { public: - LabelSwitchTaskInfo(uint32_t stream_id, uint32_t label_id) - : LabelTaskInfo(stream_id, TaskInfoType::LABEL_SWITCH, label_id) {} + LabelSwitchTaskInfo(const std::string &op_name, uint32_t stream_id, uint32_t label_size, + const std::vector &label_list, void *cond) + : TaskInfo(op_name, stream_id, TaskInfoType::LABEL_SWITCH, false), + label_size_(label_size), + label_list_(label_list), + cond_(cond) {} ~LabelSwitchTaskInfo() override {} -}; + uint32_t label_size() { return label_size_; }; + const std::vector &label_list() { return label_list_; }; + void *cond() { return cond_; }; -class LabelGotoTaskInfo : public LabelTaskInfo { - public: - LabelGotoTaskInfo(uint32_t stream_id, uint32_t label_id) - : LabelTaskInfo(stream_id, TaskInfoType::LABEL_GOTO, label_id) {} - ~LabelGotoTaskInfo() override {} + private: + uint32_t label_size_; + std::vector label_list_; + void *cond_; }; class EventTaskInfo : public TaskInfo { @@ -215,8 +231,8 @@ class EventTaskInfo : public TaskInfo { uint32_t event_id() const { return event_id_; } protected: - EventTaskInfo(uint32_t stream_id, TaskInfoType type, uint32_t event_id) - : TaskInfo(stream_id, type), event_id_(event_id) {} + EventTaskInfo(const std::string &op_name, uint32_t stream_id, TaskInfoType type, uint32_t event_id) + : TaskInfo(op_name, stream_id, type, false), event_id_(event_id) {} virtual ~EventTaskInfo() override {} uint32_t event_id_; @@ -224,39 +240,41 @@ class EventTaskInfo : public TaskInfo { class EventRecordTaskInfo : public EventTaskInfo { public: - EventRecordTaskInfo(uint32_t stream_id, uint32_t event_id) - : EventTaskInfo(stream_id, TaskInfoType::EVENT_RECORD, event_id) {} + EventRecordTaskInfo(const std::string &op_name, uint32_t stream_id, uint32_t event_id) + : EventTaskInfo(op_name, stream_id, TaskInfoType::EVENT_RECORD, event_id) {} ~EventRecordTaskInfo() override {} }; class EventWaitTaskInfo : public EventTaskInfo { public: - EventWaitTaskInfo(uint32_t stream_id, uint32_t event_id) - : EventTaskInfo(stream_id, TaskInfoType::EVENT_WAIT, event_id) {} + EventWaitTaskInfo(const std::string &op_name, uint32_t stream_id, uint32_t event_id) + : EventTaskInfo(op_name, stream_id, TaskInfoType::EVENT_WAIT, event_id) {} ~EventWaitTaskInfo() override {} }; class FusionStartTaskInfo : public TaskInfo { public: - explicit FusionStartTaskInfo(uint32_t stream_id) : TaskInfo(stream_id, TaskInfoType::FUSION_START) {} + explicit FusionStartTaskInfo(const std::string &op_name, uint32_t stream_id) + : TaskInfo(op_name, stream_id, TaskInfoType::FUSION_START, false) {} ~FusionStartTaskInfo() override {} }; class FusionEndTaskInfo : public TaskInfo { public: - explicit FusionEndTaskInfo(uint32_t stream_id) : TaskInfo(stream_id, TaskInfoType::FUSION_END) {} + explicit FusionEndTaskInfo(const std::string &op_name, uint32_t stream_id) + : TaskInfo(op_name, stream_id, TaskInfoType::FUSION_END, false) {} ~FusionEndTaskInfo() override {} }; class HcclTaskInfo : public TaskInfo { public: - HcclTaskInfo(uint32_t stream_id, const std::string hccl_type, void *input_data_addr, void *output_data_addr, - void *workspace_addr, int64_t workspace_size, int64_t hccl_stream_num, + HcclTaskInfo(const std::string &op_name, uint32_t stream_id, const std::string hccl_type, void *input_data_addr, + void *output_data_addr, void *workspace_addr, int64_t workspace_size, int64_t hccl_stream_num, const std::vector &private_def, void *ops_kernel_store, int32_t count, int64_t root_id, - int64_t op_type, int64_t data_type, std::function hcom_bind_model, - std::function hcom_unbind_model, - std::function, void *)> hcom_distribute_task) - : TaskInfo(stream_id, TaskInfoType::HCCL), + int64_t op_type, int64_t data_type, const std::string &group, + std::function hcom_bind_model, std::function hcom_unbind_model, + std::function, void *)> hcom_distribute_task, bool dump_flag) + : TaskInfo(op_name, stream_id, TaskInfoType::HCCL, dump_flag), hccl_type_(hccl_type), input_data_addr_(input_data_addr), output_data_addr_(output_data_addr), @@ -269,6 +287,7 @@ class HcclTaskInfo : public TaskInfo { root_id_(root_id), op_type_(op_type), data_type_(data_type), + group_(group), hcom_bind_model_(hcom_bind_model), hcom_unbind_model_(hcom_unbind_model), hcom_distribute_task_(hcom_distribute_task) {} @@ -286,6 +305,7 @@ class HcclTaskInfo : public TaskInfo { int64_t root_id() const { return root_id_; } int64_t op_type() const { return op_type_; } int64_t data_type() const { return data_type_; } + const std::string &group() const { return group_; } std::function hcom_bind_model() const { return hcom_bind_model_; } std::function hcom_unbind_model() const { return hcom_unbind_model_; } std::function, void *)> hcom_distribute_task() const { @@ -305,6 +325,7 @@ class HcclTaskInfo : public TaskInfo { int64_t root_id_; int64_t op_type_; int64_t data_type_; + std::string group_; std::function hcom_bind_model_; std::function hcom_unbind_model_; std::function, void *)> hcom_distribute_task_; @@ -312,8 +333,11 @@ class HcclTaskInfo : public TaskInfo { class ProfilerTraceTaskInfo : public TaskInfo { public: - ProfilerTraceTaskInfo(uint32_t stream_id, uint64_t log_id, bool notify, uint32_t flat) - : TaskInfo(stream_id, TaskInfoType::PROFILER_TRACE), log_id_(log_id), notify_(notify), flat_(flat) {} + ProfilerTraceTaskInfo(const std::string &op_name, uint32_t stream_id, uint64_t log_id, bool notify, uint32_t flat) + : TaskInfo(op_name, stream_id, TaskInfoType::PROFILER_TRACE, false), + log_id_(log_id), + notify_(notify), + flat_(flat) {} ~ProfilerTraceTaskInfo() override {} uint64_t log_id() const { return log_id_; } @@ -328,8 +352,9 @@ class ProfilerTraceTaskInfo : public TaskInfo { class MemcpyAsyncTaskInfo : public TaskInfo { public: - MemcpyAsyncTaskInfo(uint32_t stream_id, void *dst, uint64_t dst_max, void *src, uint64_t count, uint32_t kind) - : TaskInfo(stream_id, TaskInfoType::MEMCPY_ASYNC), + MemcpyAsyncTaskInfo(const std::string &op_name, uint32_t stream_id, void *dst, uint64_t dst_max, void *src, + uint64_t count, uint32_t kind, bool dump_flag) + : TaskInfo(op_name, stream_id, TaskInfoType::MEMCPY_ASYNC, dump_flag), dst_(dst), dst_max_(dst_max), src_(src), @@ -353,9 +378,9 @@ class MemcpyAsyncTaskInfo : public TaskInfo { class StreamSwitchTaskInfo : public TaskInfo { public: - StreamSwitchTaskInfo(uint32_t stream_id, int64_t true_stream_id, void *input_addr, void *value_addr, int64_t cond, - int64_t data_type) - : TaskInfo(stream_id, TaskInfoType::STREAM_SWITCH), + StreamSwitchTaskInfo(const std::string &op_name, uint32_t stream_id, int64_t true_stream_id, void *input_addr, + void *value_addr, int64_t cond, int64_t data_type) + : TaskInfo(op_name, stream_id, TaskInfoType::STREAM_SWITCH, false), true_stream_id_(true_stream_id), input_addr_(input_addr), value_addr_(value_addr), @@ -379,8 +404,8 @@ class StreamSwitchTaskInfo : public TaskInfo { class StreamActiveTaskInfo : public TaskInfo { public: - StreamActiveTaskInfo(uint32_t stream_id, uint32_t active_stream_id) - : TaskInfo(stream_id, TaskInfoType::STREAM_ACTIVE), active_stream_id_(active_stream_id) {} + StreamActiveTaskInfo(const std::string &op_name, uint32_t stream_id, uint32_t active_stream_id) + : TaskInfo(op_name, stream_id, TaskInfoType::STREAM_ACTIVE, false), active_stream_id_(active_stream_id) {} ~StreamActiveTaskInfo() override {} uint32_t active_stream_id() const { return active_stream_id_; } diff --git a/inc/framework/generator/ge_generator.h b/inc/framework/generator/ge_generator.h index f0707c67..d3f472e9 100644 --- a/inc/framework/generator/ge_generator.h +++ b/inc/framework/generator/ge_generator.h @@ -27,6 +27,7 @@ #include "graph/ge_tensor.h" #include "graph/graph.h" #include "graph/op_desc.h" +#include "graph/detail/attributes_holder.h" namespace ge { class GeGenerator { diff --git a/inc/framework/omg/omg.h b/inc/framework/omg/omg.h index 07d78490..45a8896d 100644 --- a/inc/framework/omg/omg.h +++ b/inc/framework/omg/omg.h @@ -98,13 +98,14 @@ Status DumpInfershapeJson(const ge::Graph &graph, const char *json_file); Status SetOutputNodeInfo(ge::Graph &graph, const std::string &output_type, const std::string &output_format); -Status GetOutputLeaf(ge::NodePtr node, std::vector> &output_nodes_info, - std::vector &output_nodes_name); +Status GetOutputLeaf(ge::NodePtr node, std::vector> &output_nodes_info); + +void GetOutputNodesNameAndIndex(std::vector> &output_nodes_info, + std::vector &output_nodes_name); void UpdateOmgCtxWithParserCtx(); void UpdateParserCtxWithOmgCtx(); - } // namespace ge namespace domi { diff --git a/inc/framework/omg/omg_inner_types.h b/inc/framework/omg/omg_inner_types.h index 8e5bc484..70d59c2f 100644 --- a/inc/framework/omg/omg_inner_types.h +++ b/inc/framework/omg/omg_inner_types.h @@ -94,6 +94,8 @@ struct OmgContext { std::vector> user_out_nodes; // net out nodes (where user_out_nodes or leaf nodes) std::vector net_out_nodes; + // net out nodes top names(only caffe has top) + std::vector out_top_names; // path for the aicpu custom operator so_file std::vector aicpu_op_run_paths; // ddk version diff --git a/inc/graph/compute_graph.h b/inc/graph/compute_graph.h index 4f865f12..1cb65a6c 100644 --- a/inc/graph/compute_graph.h +++ b/inc/graph/compute_graph.h @@ -74,6 +74,9 @@ class ComputeGraph : public std::enable_shared_from_this, public A size_t GetAllNodesSize() const; Vistor GetAllNodes() const; + // is_unknown_shape: false, same with GetAllNodes func + // is_unknown_shape: true, same with GetDirectNodes func + Vistor GetNodes(bool is_unknown_shape) const; size_t GetDirectNodesSize() const; Vistor GetDirectNode() const; Vistor GetInputNodes() const; @@ -174,6 +177,10 @@ class ComputeGraph : public std::enable_shared_from_this, public A void SetInputSize(uint32_t size) { input_size_ = size; } uint32_t GetInputSize() const { return input_size_; } + // false: known shape true: unknow shape + bool GetGraphUnknownFlag() const { return is_unknown_shape_graph_; } + void SetGraphUnknownFlag(bool flag) { is_unknown_shape_graph_ = flag; } + /// /// Set is need train iteration. /// If set true, it means this graph need to be run iteration some @@ -282,7 +289,8 @@ class ComputeGraph : public std::enable_shared_from_this, public A std::map op_name_map_; uint64_t session_id_ = 0; ge::Format data_format_ = ge::FORMAT_ND; + // unknown graph indicator, default is false, mean known shape + bool is_unknown_shape_graph_ = false; }; } // namespace ge - #endif // INC_GRAPH_COMPUTE_GRAPH_H_ diff --git a/inc/graph/debug/ge_attr_define.h b/inc/graph/debug/ge_attr_define.h index ea5544d1..ff015be1 100644 --- a/inc/graph/debug/ge_attr_define.h +++ b/inc/graph/debug/ge_attr_define.h @@ -139,6 +139,8 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string NEW_AIPP GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_AIPP_INPUTS; GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_AIPP_OUTPUTS; +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_INPUT_DIMS; + GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_SESSION_GRAPH_ID; GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_PARENT_GRAPH_NAME; @@ -776,6 +778,10 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_MOD GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_MODEL_CORE_TYPE; +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_MODEL_ATC_VERSION; + +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_MODEL_OPP_VERSION; + GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string QUANTIZE_SCALE_MODE; GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string QUANTIZE_SCALE_VALUE; GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string QUANTIZE_SCALE_OFFSET; @@ -994,7 +1000,7 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAM GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_DATA_DUMP_ORIGIN_FORMAT; GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_DATA_DUMP_ORIGIN_DATA_TYPE; -// used for l1 fusion and other fusion in future +// used for lX fusion GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_L1_FUSION_GROUP_ID; GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_L1_FUSION_GROUP_KEY; GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_FUSION_GROUP_KEY; @@ -1008,9 +1014,17 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAM GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_SWITCH_FOR_L1_FUSION; GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_N_BATCH_SPILT; GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NO_TASK_AND_DUMP_NEEDED; +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_DATA_DUMP_REF; GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_OUTPUT_OFFSET_FOR_BUFFER_FUSION; GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_L2_FUSION_GROUP_ID; GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_SWITCH_FOR_L2_FUSION; +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_OP_INPUT_L1_FLAG; +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_OP_INPUT_L1_ADDR; +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_OP_INPUT_L1_VALID_SIZE; + +// op overflow dump +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_OP_DEBUG_FLAG; +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_OP_DEBUG_MODE; // functional ops attr GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_IF_THEN_BRANCH; @@ -1056,6 +1070,13 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_HOR // for gradient group GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_HCCL_FUSED_GROUP; GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_HCCL_FUSED_FLAG; + +// dynamic shape attrs +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_DYNAMIC_SHAPE_FIXED_ADDR; +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_DYNAMIC_SHAPE_FIXED_ADDR_INDEX; + +// for fusion op plugin +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_NAME_FUSIONOP_ORIGINAL_TYPE; } // namespace ge #endif // INC_GRAPH_DEBUG_GE_ATTR_DEFINE_H_ diff --git a/inc/graph/detail/attributes_holder.h b/inc/graph/detail/attributes_holder.h index bb26dec5..a82ecca8 100644 --- a/inc/graph/detail/attributes_holder.h +++ b/inc/graph/detail/attributes_holder.h @@ -149,5 +149,4 @@ class GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY AttrHolder { AnyMap extAttrs_; }; } // namespace ge - #endif // INC_GRAPH_DETAIL_ATTRIBUTES_HOLDER_H_ diff --git a/inc/graph/ge_context.h b/inc/graph/ge_context.h index b1ccd5b9..af6b35bc 100644 --- a/inc/graph/ge_context.h +++ b/inc/graph/ge_context.h @@ -28,6 +28,7 @@ class GEContext { uint32_t DeviceId(); uint64_t TraceId(); void Init(); + void SetSessionId(uint64_t session_id); void SetCtxDeviceId(uint32_t device_id); private: diff --git a/inc/graph/ge_tensor.h b/inc/graph/ge_tensor.h index 29a315d6..834dca0b 100644 --- a/inc/graph/ge_tensor.h +++ b/inc/graph/ge_tensor.h @@ -25,6 +25,7 @@ #include "graph/buffer.h" #include "graph/ge_error_codes.h" #include "graph/types.h" + namespace ge { class GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY GeShape { public: @@ -108,8 +109,11 @@ class GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY GeTensorDesc : public AttrH DataType GetDataType() const; void SetDataType(DataType dt); - void SetOriginDataType(DataType originDataType); DataType GetOriginDataType() const; + void SetOriginDataType(DataType originDataType); + + std::vector GetRefPortIndex() const; + void SetRefPortByIndex(const std::vector &index); GeTensorDesc Clone() const; GeTensorDesc &operator=(const GeTensorDesc &desc); @@ -186,5 +190,4 @@ class GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY GeTensor { GeTensorDesc &DescReference() const; }; } // namespace ge - #endif // INC_GRAPH_GE_TENSOR_H_ diff --git a/inc/graph/model_serialize.h b/inc/graph/model_serialize.h index 3f7d65a9..16529512 100644 --- a/inc/graph/model_serialize.h +++ b/inc/graph/model_serialize.h @@ -49,5 +49,4 @@ class ModelSerialize { friend class GraphDebugImp; }; } // namespace ge - #endif // INC_GRAPH_MODEL_SERIALIZE_H_ diff --git a/inc/graph/op_desc.h b/inc/graph/op_desc.h index faca2d99..1bba7340 100644 --- a/inc/graph/op_desc.h +++ b/inc/graph/op_desc.h @@ -105,6 +105,8 @@ class OpDesc : public std::enable_shared_from_this, public AttrHolder { GeTensorDescPtr MutableInputDesc(uint32_t index) const; + GeTensorDescPtr MutableInputDesc(const string &name) const; + Vistor GetAllInputsDesc() const; Vistor GetAllInputsDescPtr() const; @@ -127,6 +129,8 @@ class OpDesc : public std::enable_shared_from_this, public AttrHolder { GeTensorDescPtr MutableOutputDesc(uint32_t index) const; + GeTensorDescPtr MutableOutputDesc(const string &name) const; + uint32_t GetAllOutputsDescSize() const; Vistor GetAllOutputsDesc() const; diff --git a/inc/graph/utils/graph_utils.h b/inc/graph/utils/graph_utils.h index 6c344435..61c713c1 100644 --- a/inc/graph/utils/graph_utils.h +++ b/inc/graph/utils/graph_utils.h @@ -130,7 +130,7 @@ struct NodeIndexIO { IOType io_type_ = kOut; std::string value_; - std::string ToString() const { return value_; } + const std::string &ToString() const { return value_; } }; class GraphUtils { @@ -188,8 +188,8 @@ class GraphUtils { /// @param [in] output_index /// @return graphStatus /// - static graphStatus InsertNodeBefore(const OutDataAnchorPtr &src, const std::vector &dsts, - const NodePtr &insert_node, uint32_t input_index = 0, uint32_t output_index = 0); + static graphStatus InsertNodeAfter(const OutDataAnchorPtr &src, const std::vector &dsts, + const NodePtr &insert_node, uint32_t input_index = 0, uint32_t output_index = 0); static graphStatus RemoveJustNode(ComputeGraphPtr compute_graph, const NodePtr &node); @@ -303,6 +303,14 @@ class GraphUtils { /// static graphStatus MoveOutCtrlEdges(NodePtr &src_node, NodePtr &dst_node); + /// + /// Copy all in-data edges from `src_node` to `dst_node` + /// @param src_node + /// @param dst_node + /// @return + /// + static graphStatus CopyInDataEdges(const NodePtr &src_node, NodePtr &dst_node); + static ComputeGraphPtr FindRootGraph(ComputeGraphPtr graph); static graphStatus TopologicalSortingByName(const ge::ComputeGraphPtr &compute_graph, vector &node_vec); @@ -728,5 +736,4 @@ class PartialGraphBuilder : public ComputeGraphBuilder { std::vector exist_nodes_; }; } // namespace ge - #endif // INC_GRAPH_UTILS_GRAPH_UTILS_H_ diff --git a/inc/graph/utils/node_utils.h b/inc/graph/utils/node_utils.h index 6e0e655d..4307e008 100644 --- a/inc/graph/utils/node_utils.h +++ b/inc/graph/utils/node_utils.h @@ -99,6 +99,13 @@ class NodeUtils { /// static NodePtr GetParentInput(const NodePtr &node); + /// + /// @brief Check is varying_input for while node + /// @param [in] node: Data node for subgraph + /// @return bool + /// + static bool IsWhileVaryingInput(const ge::NodePtr &node); + /// /// @brief Get subgraph input is constant. /// @param [in] node @@ -114,6 +121,24 @@ class NodeUtils { /// static graphStatus RemoveSubgraphsOnNode(const NodePtr &node); + /// + /// @brief Get subgraph input data node by index. + /// @param [in] node + /// @return Node + /// + static vector GetSubgraphDataNodesByIndex(const Node &node, int index); + + /// + /// @brief Get subgraph input data node by index. + /// @param [in] node + /// @return Node + /// + static vector GetSubgraphOutputNodes(const Node &node); + + static NodePtr GetInDataNodeByIndex(const Node &node, int index); + + static vector GetOutDataNodesByIndex(const Node &node, int index); + private: static std::map> map_send_info_; static std::map> map_recv_info_; diff --git a/inc/graph/utils/tensor_adapter.h b/inc/graph/utils/tensor_adapter.h index f9993606..a7355553 100644 --- a/inc/graph/utils/tensor_adapter.h +++ b/inc/graph/utils/tensor_adapter.h @@ -20,6 +20,7 @@ #include #include "graph/ge_tensor.h" #include "graph/tensor.h" + namespace ge { using GeTensorPtr = std::shared_ptr; using ConstGeTensorPtr = std::shared_ptr; diff --git a/inc/graph/utils/tensor_utils.h b/inc/graph/utils/tensor_utils.h index 2fa398db..caa80dcf 100644 --- a/inc/graph/utils/tensor_utils.h +++ b/inc/graph/utils/tensor_utils.h @@ -21,6 +21,7 @@ #include "graph/def_types.h" #include "graph/ge_error_codes.h" #include "graph/ge_tensor.h" + namespace ge { class TensorUtils { public: diff --git a/src/common/graph/CMakeLists.txt b/src/common/graph/CMakeLists.txt index 43f5b597..f041e4b6 100755 --- a/src/common/graph/CMakeLists.txt +++ b/src/common/graph/CMakeLists.txt @@ -71,5 +71,6 @@ target_link_libraries(graph PRIVATE ${PROTOBUF_LIBRARY} ${c_sec} ${slog} + ${error_manager} rt dl) diff --git a/src/common/graph/compute_graph.cc b/src/common/graph/compute_graph.cc index b73cf939..8a0c9f06 100644 --- a/src/common/graph/compute_graph.cc +++ b/src/common/graph/compute_graph.cc @@ -106,6 +106,15 @@ ComputeGraph::Vistor ComputeGraph::AllGraphNodes(std::vector(shared_from_this(), all_nodes); } +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY ComputeGraph::Vistor ComputeGraph::GetNodes( + bool is_unknown_shape) const { + if (is_unknown_shape) { + return GetDirectNode(); + } else { + return GetAllNodes(); + } +} + size_t ComputeGraph::GetDirectNodesSize() const { return nodes_.size(); } GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY ComputeGraph::Vistor ComputeGraph::GetDirectNode() const { @@ -497,6 +506,10 @@ ComputeGraph::AddSubgraph(const std::string &name, const std::shared_ptrGetName()) { GELOGW("The subgraph name %s is different with input %s", subgraph->GetName().c_str(), name.c_str()); } + if (names_to_subgraph_.find(name) != names_to_subgraph_.end()) { + GE_LOGE("The subgraph %s existed", name.c_str()); + return GRAPH_PARAM_INVALID; + } sub_graph_.push_back(subgraph); names_to_subgraph_[name] = subgraph; return GRAPH_SUCCESS; diff --git a/src/common/graph/debug/ge_op_types.h b/src/common/graph/debug/ge_op_types.h index da36f72c..f11ef31e 100644 --- a/src/common/graph/debug/ge_op_types.h +++ b/src/common/graph/debug/ge_op_types.h @@ -34,12 +34,16 @@ GE_REGISTER_OPTYPE(EXPANDDIMS, "ExpandDims"); GE_REGISTER_OPTYPE(SWITCH, "Switch"); GE_REGISTER_OPTYPE(MERGE, "Merge"); GE_REGISTER_OPTYPE(STREAMMERGE, "StreamMerge"); +GE_REGISTER_OPTYPE(ENTER, "Enter"); +GE_REGISTER_OPTYPE(REFENTER, "RefEnter"); GE_REGISTER_OPTYPE(NEXTITERATION, "NextIteration"); GE_REGISTER_OPTYPE(REFNEXTITERATION, "RefNextIteration"); GE_REGISTER_OPTYPE(CONSTANT, "Const"); +GE_REGISTER_OPTYPE(PLACEHOLDER, "PlaceHolder"); GE_REGISTER_OPTYPE(FRAMEWORKOP, "FrameworkOp"); GE_REGISTER_OPTYPE(GETNEXT, "GetNext"); GE_REGISTER_OPTYPE(INITDATA, "InitData"); +GE_REGISTER_OPTYPE(REFIDENTITY, "RefIdentity"); GE_REGISTER_OPTYPE(ANN_DATA, "AnnData"); GE_REGISTER_OPTYPE(CONSTANTOP, "Constant"); diff --git a/src/common/graph/format_refiner.cc b/src/common/graph/format_refiner.cc index 11a610ce..9cb76539 100644 --- a/src/common/graph/format_refiner.cc +++ b/src/common/graph/format_refiner.cc @@ -41,11 +41,9 @@ using namespace ge; using namespace std; namespace ge { namespace { -static const std::unordered_set kChangeDimNodes = {PERMUTE, EXPANDDIMS, SQUEEZE}; -static bool net_format_is_nd = true; -static Format g_user_set_format = FORMAT_ND; -static bool is_first_infer = true; -static RefRelations reflection_builder; +const std::unordered_set kChangeDimNodes = {PERMUTE, EXPANDDIMS, SQUEEZE}; +const string kIsGraphInferred = "_is_graph_inferred"; +RefRelations reflection_builder; } // namespace graphStatus ReflectionProcess(const std::unordered_set &reflection, @@ -72,9 +70,49 @@ graphStatus ReflectionProcess(const std::unordered_set &re return GRAPH_SUCCESS; } -graphStatus FormatRefiner::RefreshConstantOutProcess(const OpDescPtr &op_desc) { +graphStatus BiasAddFormatFixProcess(ge::NodePtr &node_ptr) { + // 5 meas dim num + if (node_ptr->GetType() != "BiasAdd") { + return GRAPH_SUCCESS; + } + std::unordered_map kTfFormatFix = {{"NHWC", FORMAT_NDHWC}, {"NCHW", FORMAT_NCDHW}}; + for (size_t i = 0; i < node_ptr->GetOpDesc()->GetInputsSize(); i++) { + auto in_desc = node_ptr->GetOpDesc()->MutableInputDesc(i); + GE_CHECK_NOTNULL(in_desc); + if (in_desc->MutableShape().GetDimNum() != 5) { // 5 means dim num + continue; + } + auto format = in_desc->GetOriginFormat(); + auto key = TypeUtils::FormatToSerialString(format); + auto fixed_format = (kTfFormatFix.count(key) == 0) ? format : kTfFormatFix[key]; + in_desc->SetOriginFormat(fixed_format); + in_desc->SetFormat(fixed_format); + GELOGD("fix the %zu'th input of node[%s]. Origin format is %s , after fixed it is %s", i, + node_ptr->GetName().c_str(), TypeUtils::FormatToSerialString(format).c_str(), + TypeUtils::FormatToSerialString(fixed_format).c_str()); + } + for (size_t i = 0; i < node_ptr->GetOpDesc()->GetOutputsSize(); i++) { + auto out_desc = node_ptr->GetOpDesc()->MutableOutputDesc(i); + GE_CHECK_NOTNULL(out_desc); + if (out_desc->MutableShape().GetDimNum() != 5) { // 5 means dim num + continue; + } + auto format = out_desc->GetOriginFormat(); + auto key = TypeUtils::FormatToSerialString(format); + auto fixed_format = (kTfFormatFix.count(key) == 0) ? format : kTfFormatFix[key]; + out_desc->SetOriginFormat(fixed_format); + out_desc->SetFormat(fixed_format); + GELOGD("fix the %zu'th output of node[%s]. Origin format is %s , after fixed it is %s", i, + node_ptr->GetName().c_str(), TypeUtils::FormatToSerialString(format).c_str(), + TypeUtils::FormatToSerialString(fixed_format).c_str()); + } + return GRAPH_SUCCESS; +} + +graphStatus FormatRefiner::RefreshConstantOutProcess(const ComputeGraphPtr &graph, const OpDescPtr &op_desc) { + GE_CHECK_NOTNULL(graph); GE_CHECK_NOTNULL(op_desc); - if (op_desc->GetType() == CONSTANTOP && is_first_infer == true) { + if (op_desc->GetType() == CONSTANTOP && !IsGraphInferred(graph)) { ConstGeTensorPtr tensor_value; if (!AttrUtils::GetTensor(op_desc, "value", tensor_value)) { GELOGE(GRAPH_FAILED, "Get value failed, node name:%s.", op_desc->GetName().c_str()); @@ -95,7 +133,7 @@ graphStatus FormatRefiner::GetAnchorPoints(const ge::ComputeGraphPtr &graph, std } anchor_points.clear(); // Get all anchor point nodes and switch nodes - for (const auto &node_ptr : graph->GetAllNodes()) { + for (auto &node_ptr : graph->GetAllNodes()) { if (node_ptr == nullptr) { return GRAPH_FAILED; } @@ -103,7 +141,7 @@ graphStatus FormatRefiner::GetAnchorPoints(const ge::ComputeGraphPtr &graph, std if (op_desc == nullptr) { return GRAPH_FAILED; } - graphStatus status = RefreshConstantOutProcess(op_desc); + graphStatus status = RefreshConstantOutProcess(graph, op_desc); if (status != GRAPH_SUCCESS) { GELOGE(GRAPH_FAILED, "refresh constant out process failed!"); return GRAPH_FAILED; @@ -135,6 +173,16 @@ graphStatus FormatRefiner::GetAnchorPoints(const ge::ComputeGraphPtr &graph, std if (!node_is_all_nd) { continue; } + // special process for biasAdd op + // In tensorflow, biasAdd's format is alwayse NHWC even though set the arg + // "data_format" to NDHWC or NCDHW.It will destroy our format-infer mechanism + // so here do special process + status = BiasAddFormatFixProcess(node_ptr); + if (status != GRAPH_SUCCESS) { + GELOGE(GRAPH_FAILED, "fix biasAdd process failed!"); + return GRAPH_FAILED; + } + GELOGD("Node[%s] is anchor point!", node_ptr->GetName().c_str()); anchor_points.push_back(node_ptr); } @@ -344,14 +392,11 @@ void FormatRefiner::RefreshOriginFormatOfAnchor(std::vector &anchor } } -void FormatRefiner::SetInferOrigineFormatFlag(bool is_first) { is_first_infer = is_first; } - -graphStatus FormatRefiner::DataNodeFormatProcess(std::vector &data_nodes, ge::Format data_format, +graphStatus FormatRefiner::DataNodeFormatProcess(const ComputeGraphPtr &graph, std::vector &data_nodes, + ge::Format data_format, std::unordered_map &node_status) { - bool is_internal_format = TypeUtils::IsInternalFormat(data_format); - bool need_process = (!is_first_infer) && (!is_internal_format) && (data_format != FORMAT_ND); - if (!need_process) { - GELOGI("no necessary to do DataNodeFormatProcess.is_first_infer:%d, data_format:%s", is_first_infer, + if (!(IsGraphInferred(graph) && (!TypeUtils::IsInternalFormat(data_format)) && (data_format != FORMAT_ND))) { + GELOGI("no necessary to do DataNodeFormatProcess. is_graph_inferred:%d, data_format:%s", IsGraphInferred(graph), TypeUtils::FormatToSerialString(data_format).c_str()); return GRAPH_SUCCESS; } @@ -410,8 +455,6 @@ graphStatus FormatRefiner::InferOrigineFormat(const ge::ComputeGraphPtr &graph) std::vector anchor_points; std::vector data_nodes; // global net format - net_format_is_nd = true; - g_user_set_format = FORMAT_ND; if (graph == nullptr) { GELOGE(GRAPH_FAILED, "input graph is null"); @@ -448,10 +491,15 @@ graphStatus FormatRefiner::InferOrigineFormat(const ge::ComputeGraphPtr &graph) /// format for these data nodes. /// Notice: ignore 5D formats auto data_format = graph->GetDataFormat(); - status = DataNodeFormatProcess(data_nodes, data_format, node_status); - // Set infer flag to false - SetInferOrigineFormatFlag(false); + status = DataNodeFormatProcess(graph, data_nodes, data_format, node_status); + + (void)AttrUtils::SetBool(graph, kIsGraphInferred, true); return status; } + +bool FormatRefiner::IsGraphInferred(const ComputeGraphPtr &graph) { + bool is_graph_inferred = false; + return (AttrUtils::GetBool(graph, kIsGraphInferred, is_graph_inferred) && is_graph_inferred); +} } // namespace ge diff --git a/src/common/graph/format_refiner.h b/src/common/graph/format_refiner.h index fa40a034..eca93bae 100644 --- a/src/common/graph/format_refiner.h +++ b/src/common/graph/format_refiner.h @@ -30,10 +30,9 @@ namespace ge { class FormatRefiner { public: static graphStatus InferOrigineFormat(const ge::ComputeGraphPtr &graph); - static void SetInferOrigineFormatFlag(bool is_first = true); private: - static graphStatus RefreshConstantOutProcess(const OpDescPtr &op_desc); + static graphStatus RefreshConstantOutProcess(const ComputeGraphPtr &graph, const OpDescPtr &op_desc); static graphStatus GetAnchorPoints(const ge::ComputeGraphPtr &graph, std::vector &anchor_points, std::vector &data_nodes, std::unordered_map &node_status); @@ -43,8 +42,9 @@ class FormatRefiner { std::unordered_map &node_status); static graphStatus ForwardInferProcess(std::deque &nodes, ge::NodePtr &node, std::unordered_map &node_status); - static graphStatus DataNodeFormatProcess(std::vector &data_nodes, ge::Format data_format, - std::unordered_map &node_status); + static graphStatus DataNodeFormatProcess(const ComputeGraphPtr &graph, std::vector &data_nodes, + ge::Format data_format, std::unordered_map &node_status); + static bool IsGraphInferred(const ComputeGraphPtr &graph); }; } // namespace ge #endif // COMMON_GRAPH_FORMAT_REFINER_H_ diff --git a/src/common/graph/ge_attr_define.cc b/src/common/graph/ge_attr_define.cc index f780d525..90f1bc6a 100644 --- a/src/common/graph/ge_attr_define.cc +++ b/src/common/graph/ge_attr_define.cc @@ -121,6 +121,8 @@ const std::string NEW_AIPP_CONV_OP = "new_conv_op_for_aipp"; const std::string ATTR_NAME_AIPP_INPUTS = "_aipp_inputs"; const std::string ATTR_NAME_AIPP_OUTPUTS = "_aipp_outputs"; +const std::string ATTR_NAME_INPUT_DIMS = "input_dims"; + const std::string ATTR_NAME_SESSION_GRAPH_ID = "_session_graph_id"; const std::string ATTR_NAME_PARENT_GRAPH_NAME = "_parent_graph_name"; @@ -723,6 +725,10 @@ const std::string ATTR_MODEL_TASK_INDEX_OP_NAME = "task_index_op_name"; const std::string ATTR_MODEL_CORE_TYPE = "core_type"; +const std::string ATTR_MODEL_ATC_VERSION = "atc_version"; + +const std::string ATTR_MODEL_OPP_VERSION = "opp_version"; + // Public attribute const std::string ATTR_NAME_IMPLY_TYPE = "imply_type"; @@ -932,7 +938,7 @@ const std::string ATTR_NAME_MEMORY_TYPE_WORKSPACE = "memory_type_workspace"; const std::string MODEL_ATTR_SESSION_ID = "session_id"; -// l1 fusion and other fusion in future +// lx fusion const std::string ATTR_NAME_L1_FUSION_GROUP_ID = "_l1_fusion_group_id"; const std::string ATTR_NAME_FUSION_GROUP_KEY = "_fusion_group_key"; const std::string ATTR_NAME_L1_FUSION_GROUP_KEY = "_l1_fusion_group_key"; @@ -946,9 +952,17 @@ const std::string ATTR_NAME_OUTPUT_OFFSET_FOR_L1_FUSION = "_output_offset_for_l1 const std::string ATTR_NAME_SWITCH_FOR_L1_FUSION = "_enable_l1_fusion"; const std::string ATTR_N_BATCH_SPILT = "_is_n_batch_split"; const std::string ATTR_NO_TASK_AND_DUMP_NEEDED = "_no_task_and_dump_needed"; +const std::string ATTR_DATA_DUMP_REF = "_datadump_ref"; const std::string ATTR_NAME_OUTPUT_OFFSET_FOR_BUFFER_FUSION = "_output_offset_for_buffer_fusion"; const std::string ATTR_NAME_L2_FUSION_GROUP_ID = "_l2_fusion_group_id"; const std::string ATTR_NAME_SWITCH_FOR_L2_FUSION = "_enable_l2_fusion"; +const std::string ATTR_NAME_OP_INPUT_L1_FLAG = "_op_input_l1_flag"; +const std::string ATTR_NAME_OP_INPUT_L1_ADDR = "_op_input_l1_addr"; +const std::string ATTR_NAME_OP_INPUT_L1_VALID_SIZE = "_op_input_l1_valid_size"; + +// Op debug attrs +const std::string ATTR_OP_DEBUG_FLAG = "_op_debug_flag"; +const std::string ATTR_OP_DEBUG_MODE = "_op_debug_mode"; // Atomic addr clean attrs const std::string ATOMIC_ATTR_INPUT_INDEX = "atomic_input_index"; @@ -1013,4 +1027,11 @@ const std::string ATTR_HOROVOD_ATTR_REDUCE_TYPE = "reduce_op"; // used for allreduce tailing optimization const std::string ATTR_NAME_HCCL_FUSED_GROUP = "_hccl_fused_group"; const std::string ATTR_NAME_HCCL_FUSED_FLAG = "_hccl_fused_node"; + +// dynamic shape attr +const std::string ATTR_DYNAMIC_SHAPE_FIXED_ADDR = "_alloc_fixed_addr"; +const std::string ATTR_DYNAMIC_SHAPE_FIXED_ADDR_INDEX = "_alloc_fixed_addr_index"; + +// for fusion op plugin +const std::string ATTR_NAME_FUSIONOP_ORIGINAL_TYPE = "_fusionop_original_type"; } // namespace ge diff --git a/src/common/graph/ge_tensor.cc b/src/common/graph/ge_tensor.cc index 8ffbba91..196b8569 100644 --- a/src/common/graph/ge_tensor.cc +++ b/src/common/graph/ge_tensor.cc @@ -220,6 +220,7 @@ const string TENSOR_UTILS_ORIGIN_SHAPE = "origin_shape"; const string TENSOR_UTILS_ORIGIN_FORMAT = "origin_format"; const string TENSOR_UTILS_ORIGIN_DATA_TYPE = "origin_data_type"; const string TENSOR_UTILS_SHAPE_RANGE = "shape_range"; +const string TENSOR_UTILS_REF_PORT_INDEX = "ref_port_index"; GeShape::GeShape(const ProtoMsgOwner &proto_owner, proto::ShapeDef *proto_msg) : shape_def_(proto_owner, proto_msg) {} @@ -567,6 +568,16 @@ DataType GeTensorDesc::GetOriginDataType() const { return TypeUtils::SerialStringToDataType(origin_data_type_str); } +std::vector GeTensorDesc::GetRefPortIndex() const { + vector ref_port_index; + (void)AttrUtils::GetListInt(this, TENSOR_UTILS_REF_PORT_INDEX, ref_port_index); + return ref_port_index; +} + +void GeTensorDesc::SetRefPortByIndex(const std::vector &index) { + (void)AttrUtils::SetListInt(this, TENSOR_UTILS_REF_PORT_INDEX, index); +} + graphStatus GeTensorDesc::IsValid() const { auto dtype = this->GetDataType(); auto format = this->GetFormat(); diff --git a/src/common/graph/graph.cc b/src/common/graph/graph.cc index 09d4fd56..fc30e9d6 100644 --- a/src/common/graph/graph.cc +++ b/src/common/graph/graph.cc @@ -210,7 +210,7 @@ class GraphImpl { graphStatus FindOpByName(const string &name, ge::Operator &op) const { auto it = op_list_.find(name); - GE_CHK_BOOL_EXEC(it != op_list_.end(), return GRAPH_FAILED, "Error: there is no op: %s.", name.c_str()); + GE_CHK_BOOL_EXEC(it != op_list_.end(), return GRAPH_FAILED, "there is no op: %s.", name.c_str()); op = it->second; return GRAPH_SUCCESS; } diff --git a/src/common/graph/graph.mk b/src/common/graph/graph.mk index 744d1725..14e8b4b1 100644 --- a/src/common/graph/graph.mk +++ b/src/common/graph/graph.mk @@ -1,5 +1,5 @@ LOCAL_PATH := $(call my-dir) - +include $(LOCAL_PATH)/stub/Makefile COMMON_LOCAL_SRC_FILES := \ ./proto/om.proto \ ./proto/ge_ir.proto \ @@ -77,6 +77,7 @@ LOCAL_SHARED_LIBRARIES := \ libc_sec \ libprotobuf \ libslog \ + liberror_manager \ LOCAL_LDFLAGS := -lrt -ldl @@ -85,6 +86,54 @@ LOCAL_PROPRIETARY_MODULE := true include $(BUILD_HOST_SHARED_LIBRARY) +#compiler for host +include $(CLEAR_VARS) +LOCAL_MODULE := stub/libgraph + +LOCAL_CFLAGS += -DFMK_SUPPORT_DUMP -O2 +LOCAL_CPPFLAGS += -fexceptions + +LOCAL_C_INCLUDES := $(COMMON_LOCAL_C_INCLUDES) +LOCAL_SRC_FILES := \ + ../../out/graph/lib64/stub/graph.cc \ + ../../out/graph/lib64/stub/operator.cc \ + ../../out/graph/lib64/stub/tensor.cc \ + ../../out/graph/lib64/stub/operator_factory.cc \ + + +LOCAL_SHARED_LIBRARIES := + +LOCAL_LDFLAGS := -lrt -ldl + +LOCAL_MULTILIB := 64 +LOCAL_PROPRIETARY_MODULE := true + +include $(BUILD_HOST_SHARED_LIBRARY) + +#compiler for host +include $(CLEAR_VARS) +LOCAL_MODULE := fwk_stub/libgraph + +LOCAL_CFLAGS += -DFMK_SUPPORT_DUMP -O2 +LOCAL_CPPFLAGS += -fexceptions + +LOCAL_C_INCLUDES := $(COMMON_LOCAL_C_INCLUDES) +LOCAL_SRC_FILES := \ + ../../out/graph/lib64/stub/attr_value.cc \ + ../../out/graph/lib64/stub/graph.cc \ + ../../out/graph/lib64/stub/operator.cc \ + ../../out/graph/lib64/stub/operator_factory.cc \ + ../../out/graph/lib64/stub/tensor.cc \ + + +LOCAL_SHARED_LIBRARIES := + +LOCAL_LDFLAGS := -lrt -ldl + +LOCAL_MULTILIB := 64 +LOCAL_PROPRIETARY_MODULE := true + +include $(BUILD_HOST_SHARED_LIBRARY) #compiler for device include $(CLEAR_VARS) @@ -99,6 +148,7 @@ LOCAL_SHARED_LIBRARIES := \ libc_sec \ libprotobuf \ libslog \ + liberror_manager \ LOCAL_LDFLAGS := -lrt -ldl @@ -111,6 +161,60 @@ LOCAL_PROPRIETARY_MODULE := true include $(BUILD_SHARED_LIBRARY) +#compiler for device +include $(CLEAR_VARS) +LOCAL_MODULE := stub/libgraph + +LOCAL_CFLAGS += -O2 + +LOCAL_C_INCLUDES := $(COMMON_LOCAL_C_INCLUDES) +LOCAL_SRC_FILES := \ + ../../out/graph/lib64/stub/graph.cc \ + ../../out/graph/lib64/stub/operator.cc \ + ../../out/graph/lib64/stub/tensor.cc \ + ../../out/graph/lib64/stub/operator_factory.cc \ + + +LOCAL_SHARED_LIBRARIES := + +LOCAL_LDFLAGS := -lrt -ldl + +ifeq ($(device_os),android) +LOCAL_LDFLAGS := -ldl +endif + +LOCAL_MULTILIB := 64 +LOCAL_PROPRIETARY_MODULE := true + +include $(BUILD_SHARED_LIBRARY) + +#compiler for device +include $(CLEAR_VARS) +LOCAL_MODULE := fwk_stub/libgraph + +LOCAL_CFLAGS += -O2 + +LOCAL_C_INCLUDES := $(COMMON_LOCAL_C_INCLUDES) +LOCAL_SRC_FILES := \ + ../../out/graph/lib64/stub/attr_value.cc \ + ../../out/graph/lib64/stub/graph.cc \ + ../../out/graph/lib64/stub/operator.cc \ + ../../out/graph/lib64/stub/operator_factory.cc \ + ../../out/graph/lib64/stub/tensor.cc \ + + +LOCAL_SHARED_LIBRARIES := + +LOCAL_LDFLAGS := -lrt -ldl + +ifeq ($(device_os),android) +LOCAL_LDFLAGS := -ldl +endif + +LOCAL_MULTILIB := 64 +LOCAL_PROPRIETARY_MODULE := true + +include $(BUILD_SHARED_LIBRARY) # compile for ut/st include $(CLEAR_VARS) @@ -125,6 +229,7 @@ LOCAL_SHARED_LIBRARIES := \ libc_sec \ libprotobuf \ libslog \ + liberror_manager \ LOCAL_LDFLAGS := -lrt -ldl @@ -150,6 +255,7 @@ LOCAL_STATIC_LIBRARIES := \ LOCAL_SHARED_LIBRARIES := \ libc_sec \ libslog \ + liberror_manager \ LOCAL_LDFLAGS := -lrt -ldl @@ -173,6 +279,7 @@ LOCAL_STATIC_LIBRARIES := \ LOCAL_SHARED_LIBRARIES := \ libc_sec \ libslog \ + liberror_manager \ LOCAL_LDFLAGS := -lrt -ldl diff --git a/src/common/graph/model_serialize.cc b/src/common/graph/model_serialize.cc index 19cb4538..4bd5769f 100644 --- a/src/common/graph/model_serialize.cc +++ b/src/common/graph/model_serialize.cc @@ -88,10 +88,8 @@ bool ModelSerializeImp::SerializeEdge(const NodePtr &node, proto::OpDef *op_def_ } bool ModelSerializeImp::SerializeOpDesc(const ConstOpDescPtr &op_desc, proto::OpDef *op_def_proto, bool is_dump) { - if (op_desc == nullptr || op_def_proto == nullptr) { - GELOGE(GRAPH_FAILED, "Input Para Invalid"); - return false; - } + GE_CHK_BOOL_EXEC(op_desc != nullptr, return false, "op_desc is null."); + GE_CHK_BOOL_EXEC(op_def_proto != nullptr, return false, "op_def_proto is null."); if (op_desc->op_def_.GetProtoMsg() != nullptr) { *op_def_proto = *op_desc->op_def_.GetProtoMsg(); // Delete unnecessary attr @@ -130,16 +128,17 @@ bool ModelSerializeImp::SerializeOpDesc(const ConstOpDescPtr &op_desc, proto::Op for (const std::string &name : op_desc->GetSubgraphInstanceNames()) { op_def_proto->add_subgraph_name(name); } - - proto::AttrDef key; - proto::AttrDef value; - for (auto &item : op_desc->output_name_idx_) { - key.mutable_list()->add_s(item.first); - value.mutable_list()->add_i(item.second); + if (!op_desc->output_name_idx_.empty()) { + proto::AttrDef key; + proto::AttrDef value; + for (auto &item : op_desc->output_name_idx_) { + key.mutable_list()->add_s(item.first); + value.mutable_list()->add_i(item.second); + } + auto op_desc_attr = op_def_proto->mutable_attr(); + op_desc_attr->insert({"_output_name_key", key}); + op_desc_attr->insert({"_output_name_value", value}); } - auto op_desc_attr = op_def_proto->mutable_attr(); - op_desc_attr->insert({"_output_name_key", key}); - op_desc_attr->insert({"_output_name_value", value}); } return true; } diff --git a/src/common/graph/node.cc b/src/common/graph/node.cc index e0939e7e..df8efd91 100644 --- a/src/common/graph/node.cc +++ b/src/common/graph/node.cc @@ -26,6 +26,7 @@ #include "utils/ge_ir_utils.h" #include "utils/node_utils.h" #include "utils/op_desc_utils.h" +#include "common/util/error_manager/error_manager.h" using std::string; using std::vector; @@ -154,7 +155,7 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY bool Node::NodeAnchorIsEqual(cons const auto &peer_node = left_anchor->GetPeerAnchors().at(j)->GetOwnerNode(); const auto &r_peer_node = right_anchor->GetPeerAnchors().at(j)->GetOwnerNode(); if (peer_node == nullptr || r_peer_node == nullptr) { - GELOGE(GRAPH_FAILED, "Error: anchor's peer node is null, node name: %s index[%zu] peer node index[%zu]. ", + GELOGE(GRAPH_FAILED, "anchor's peer node is null, node name: %s index[%zu] peer node index[%zu]. ", this->GetName().c_str(), i, j); return false; } @@ -434,8 +435,11 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY Node::Vistor Node::Get GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY InDataAnchorPtr Node::GetInDataAnchor(int idx) const { if (idx < 0 || idx >= static_cast(in_data_anchors_.size())) { - GELOGE(GRAPH_FAILED, "the node doesn't have %d th in_data_anchor, node %s:%s", idx, GetType().c_str(), - GetName().c_str()); + ErrorManager::GetInstance().ATCReportErrMessage( + "E19019", {"opname", "index", "anchorname", "optype"}, + {GetName().c_str(), std::to_string(idx), "in_data_anchor", GetType().c_str()}); + GELOGE(GRAPH_FAILED, "Op[%s] doesn't have index[%d]'s in_data_anchor which optype is %s.", GetName().c_str(), idx, + GetType().c_str()); return nullptr; } else { return in_data_anchors_[idx]; @@ -445,7 +449,10 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY InDataAnchorPtr Node::GetInDataAn GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY AnchorPtr Node::GetInAnchor(int idx) const { // Idx can't be less than -1 or >= in_data_anchors_.size(), -1 means index of control anchor_ if (idx < -1 || idx >= static_cast(in_data_anchors_.size())) { - GELOGW("the node doesn't have %d th in_anchor, node %s:%s", idx, GetType().c_str(), GetName().c_str()); + ErrorManager::GetInstance().ATCReportErrMessage( + "E19019", {"opname", "index", "anchorname", "optype"}, + {GetName().c_str(), std::to_string(idx), "in_anchor", GetType().c_str()}); + GELOGW("Op[%s] doesn't have index[%d]'s in_anchor which optype is %s.", GetName().c_str(), idx, GetType().c_str()); return nullptr; } else { // Return control anchor @@ -461,8 +468,15 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY AnchorPtr Node::GetInAnchor(int i GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY AnchorPtr Node::GetOutAnchor(int idx) const { // Idx can't be less than -1 or >= out_data_anchors_.size(), -1 means index of control anchor_ if (idx < -1 || idx >= static_cast(out_data_anchors_.size())) { - GELOGE(GRAPH_FAILED, "the node doesn't have %d th out_anchor, node %s:%s", idx, GetType().c_str(), - GetName().c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E19019", {"opname", "index", "anchorname", "optype"}, + { + GetName().c_str(), + std::to_string(idx), + "out_anchor", + GetType().c_str(), + }); + GELOGE(GRAPH_FAILED, "Op[%s] doesn't have index[%d]'s out_anchor which optype is %s.", GetName().c_str(), idx, + GetType().c_str()); return nullptr; } else { // Return control anchor @@ -477,8 +491,11 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY AnchorPtr Node::GetOutAnchor(int GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY OutDataAnchorPtr Node::GetOutDataAnchor(int idx) const { if (idx < 0 || idx >= static_cast(out_data_anchors_.size())) { - GELOGE(GRAPH_FAILED, "the node doesn't have %d th out_data_anchor, node %s:%s", idx, GetType().c_str(), - GetName().c_str()); + ErrorManager::GetInstance().ATCReportErrMessage( + "E19019", {"opname", "index", "anchorname", "optype"}, + {GetName().c_str(), std::to_string(idx), "out_data_anchor", GetType().c_str()}); + GELOGE(GRAPH_FAILED, "Op[%s] doesn't have index[%d]'s out_data_anchor which optype is %s.", GetName().c_str(), idx, + GetType().c_str()); return nullptr; } else { return out_data_anchors_[idx]; @@ -733,11 +750,15 @@ graphStatus Node::Verify() const { GELOGW("in anchor ptr is null"); continue; } - GE_CHK_BOOL_RET_STATUS( - op_->GetType() == data_type || op_->GetType() == aipp_data_type || op_->GetType() == const_type || - op_->GetType() == variable_type || op_->IsOptionalInput(in_anchor_ptr->GetIdx()) || - in_anchor_ptr->GetPeerAnchors().size() > 0, - GRAPH_FAILED, "operator %s's input %d is not linked.", GetName().c_str(), in_anchor_ptr->GetIdx()); + bool valid_anchor = op_->GetType() == data_type || op_->GetType() == aipp_data_type || + op_->GetType() == const_type || op_->GetType() == variable_type || + op_->IsOptionalInput(in_anchor_ptr->GetIdx()) || in_anchor_ptr->GetPeerAnchors().size() > 0; + if (!valid_anchor) { + ErrorManager::GetInstance().ATCReportErrMessage("E11019", {"name", "index"}, + {GetName(), std::to_string(in_anchor_ptr->GetIdx())}); + GELOGE(GRAPH_FAILED, "operator %s's input %d is not linked.", GetName().c_str(), in_anchor_ptr->GetIdx()); + return GRAPH_FAILED; + } } string frameworkop_type = "FrameworkOp"; diff --git a/src/common/graph/op_desc.cc b/src/common/graph/op_desc.cc index adb52162..e9436a32 100644 --- a/src/common/graph/op_desc.cc +++ b/src/common/graph/op_desc.cc @@ -19,6 +19,7 @@ #include "debug/ge_util.h" #include "external/graph/operator.h" #include "framework/common/debug/ge_log.h" +#include "common/util/error_manager/error_manager.h" #include "graph/ge_attr_value.h" #include "graph/ge_tensor.h" #include "graph/operator_factory_impl.h" @@ -470,6 +471,25 @@ GeTensorDesc OpDesc::GetInputDesc(const string &name) const { return *(inputs_desc_[it->second].get()); } +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY GeTensorDescPtr OpDesc::MutableInputDesc(uint32_t index) const { + GE_CHK_BOOL_RET_STATUS(index < inputs_desc_.size(), nullptr, "Can't find the input desc %u", index); + if (inputs_desc_[index] == nullptr) { + return nullptr; + } + GE_CHK_BOOL_RET_STATUS(inputs_desc_[index]->IsValid() == GRAPH_SUCCESS, nullptr, "input desc is invalid"); + return inputs_desc_[index]; +} + +GeTensorDescPtr OpDesc::MutableInputDesc(const string &name) const { + auto input_name_idx = GetAllInputName(); + auto it = input_name_idx.find(name); + if (it == input_name_idx.end()) { + GELOGW("Failed to get [%s] input desc", name.c_str()); + return nullptr; + } + return MutableInputDesc(it->second); +} + GE_FUNC_HOST_VISIBILITY OpDesc::Vistor OpDesc::GetAllInputNames() const { auto input_name_idx = GetAllInputName(); vector names; @@ -496,15 +516,6 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY void OpDesc::SetOpEngineName(cons GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY std::string OpDesc::GetOpEngineName() const { return engine_name_; } -GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY GeTensorDescPtr OpDesc::MutableInputDesc(uint32_t index) const { - GE_CHK_BOOL_RET_STATUS(index < inputs_desc_.size(), nullptr, "Can't find the input desc %u", index); - if (inputs_desc_[index] == nullptr) { - return nullptr; - } - GE_CHK_BOOL_RET_STATUS(inputs_desc_[index]->IsValid() == GRAPH_SUCCESS, nullptr, "input desc is invalid"); - return inputs_desc_[index]; -} - GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY OpDesc::Vistor OpDesc::GetAllInputsDesc() const { vector temp{}; for (const auto &it : inputs_desc_) { @@ -609,6 +620,15 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY GeTensorDescPtr OpDesc::MutableOu return outputs_desc_[index]; } +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY GeTensorDescPtr OpDesc::MutableOutputDesc(const string &name) const { + auto it = output_name_idx_.find(name); + if (it == output_name_idx_.end()) { + GELOGW("Failed to get [%s] output desc", name.c_str()); + return nullptr; + } + return MutableOutputDesc(it->second); +} + GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY uint32_t OpDesc::GetAllOutputsDescSize() const { return static_cast(outputs_desc_.size()); } @@ -882,15 +902,22 @@ graphStatus OpDesc::CommonVerify() const { // Checking shape of all inputs vector ishape = GetInputDescPtr(iname)->GetShape().GetDims(); for (int64_t dim : ishape) { - GE_CHK_BOOL_RET_STATUS(dim >= -2, GRAPH_FAILED, "operator input %s shape contains negative or zero dimension.", - iname.c_str()); + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG( + dim < -2, ErrorManager::GetInstance().ATCReportErrMessage( + "E19014", {"opname", "value", "reason"}, + {GetName(), "input " + iname + " shape", "contains negative or zero dimension"}); + return GRAPH_FAILED, "Op[%s]'s input %s shape contains negative or zero dimension.", GetName().c_str(), + iname.c_str()); } } // Check all attributes defined const auto &all_attributes = GetAllAttrs(); for (const auto &name : GetAllAttrNames()) { - GE_CHK_BOOL_RET_STATUS(all_attributes.find(name) != all_attributes.end(), GRAPH_FAILED, - "operator attribute %s is empty.", name.c_str()); + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG( + all_attributes.find(name) == all_attributes.end(), + ErrorManager::GetInstance().ATCReportErrMessage("E19014", {"opname", "value", "reason"}, + {GetName(), "attribute " + name, "is empty"}); + return GRAPH_FAILED, "operator attribute %s is empty.", name.c_str()); } return GRAPH_SUCCESS; diff --git a/src/common/graph/operator.cc b/src/common/graph/operator.cc index 1ac8d41d..3a9fd698 100644 --- a/src/common/graph/operator.cc +++ b/src/common/graph/operator.cc @@ -36,6 +36,8 @@ #include "graph/op_desc.h" #include "graph/runtime_inference_context.h" #include "graph/usr_types.h" +#include "graph/utils/node_utils.h" +#include "graph/debug/ge_attr_define.h" #include "utils/graph_utils.h" #include "utils/op_desc_utils.h" #include "utils/tensor_adapter.h" @@ -57,8 +59,7 @@ using std::vector; namespace ge { class OpIO { public: - explicit OpIO(const string &name, int index, const OperatorImplPtr &owner) - : name_(name), index_(index), owner_(owner) {} + OpIO(const string &name, int index, const OperatorImplPtr &owner) : name_(name), index_(index), owner_(owner) {} ~OpIO() = default; @@ -546,56 +547,46 @@ Operator &Operator::AddControlInput(const Operator &src_oprt) { } graphStatus Operator::GetInputConstData(const string &dst_name, Tensor &data) const { - if (operator_impl_ == nullptr) { - GELOGE(GRAPH_FAILED, "operator impl is nullptr."); - return GRAPH_FAILED; - } - ge::ConstNodePtr node_ptr = operator_impl_->GetNode(); - if (node_ptr) { + GE_CHECK_NOTNULL(operator_impl_); + auto node_ptr = operator_impl_->GetNode(); + if (node_ptr != nullptr) { // For inner compute graph auto op_desc = node_ptr->GetOpDesc(); - if (op_desc == nullptr) { - GELOGE(GRAPH_FAILED, "op_desc is nullptr."); - return GRAPH_FAILED; - } + GE_CHECK_NOTNULL(op_desc); auto index = op_desc->GetInputIndexByName(dst_name); auto in_data_anchor = node_ptr->GetInDataAnchor(index); - if (in_data_anchor == nullptr) { - GELOGE(GRAPH_FAILED, "in_data_anchor is nullptr."); - return GRAPH_FAILED; - } + GE_CHECK_NOTNULL(in_data_anchor); auto out_data_anchor = in_data_anchor->GetPeerOutAnchor(); - if (out_data_anchor == nullptr) { - GELOGE(GRAPH_FAILED, "out_data_anchor is nullptr."); - return GRAPH_FAILED; - } - std::shared_ptr peer_node_ptr = out_data_anchor->GetOwnerNode(); - if (peer_node_ptr == nullptr) { - GELOGE(GRAPH_FAILED, "peer_node_ptr is nullptr."); - return GRAPH_FAILED; - } - ge::OperatorImplPtr operator_impl_ptr = nullptr; - operator_impl_ptr = ComGraphMakeShared(peer_node_ptr); - if (operator_impl_ptr == nullptr) { - GELOGE(GRAPH_FAILED, "OperatorImpl make shared failed"); - return GRAPH_FAILED; - } - Operator const_op(std::move(operator_impl_ptr)); - if (peer_node_ptr->GetOpDesc() != nullptr) { - const auto &op_descType = peer_node_ptr->GetOpDesc()->GetType(); - if (op_descType == CONSTANTOP) { - return const_op.GetAttr(op::Constant::name_attr_value(), data); - } else if (op_descType == CONSTANT) { - return const_op.GetAttr(op::Const::name_attr_value(), data); + GE_CHECK_NOTNULL(out_data_anchor); + auto peer_node = out_data_anchor->GetOwnerNode(); + GE_CHECK_NOTNULL(peer_node); + auto peer_op_desc = peer_node->GetOpDesc(); + GE_CHECK_NOTNULL(peer_op_desc); + auto peer_op_type = peer_op_desc->GetType(); + if (peer_op_type == CONSTANTOP || peer_op_type == CONSTANT) { + auto const_op_impl = ComGraphMakeShared(peer_node); + GE_CHECK_NOTNULL(const_op_impl); + Operator const_op(std::move(const_op_impl)); + return const_op.GetAttr(ATTR_NAME_WEIGHTS, data); + } else if (peer_op_type == DATA) { + auto parent_node = NodeUtils::GetParentInput(peer_node); + while ((parent_node != nullptr) && (parent_node->GetType() == DATA)) { + parent_node = NodeUtils::GetParentInput(parent_node); + } + if ((parent_node != nullptr) && + ((parent_node->GetType() == CONSTANT) || (parent_node->GetType() == CONSTANTOP))) { + auto const_op_impl = ComGraphMakeShared(parent_node); + GE_CHECK_NOTNULL(const_op_impl); + Operator const_op(std::move(const_op_impl)); + return const_op.GetAttr(ATTR_NAME_WEIGHTS, data); } } - // Try get from runtime inference context auto session_id = std::to_string(GetContext().SessionId()); RuntimeInferenceContext *runtime_infer_ctx = nullptr; if (RuntimeInferenceContext::GetContext(session_id, &runtime_infer_ctx) == GRAPH_SUCCESS) { GELOGD("To get constant from runtime inference context. session_id = %s", session_id.c_str()); - auto ret = runtime_infer_ctx->GetTensor(peer_node_ptr->GetOpDesc()->GetId(), out_data_anchor->GetIdx(), data); + auto ret = runtime_infer_ctx->GetTensor(peer_node->GetOpDesc()->GetId(), out_data_anchor->GetIdx(), data); if (ret == GRAPH_SUCCESS) { return GRAPH_SUCCESS; } @@ -604,6 +595,8 @@ graphStatus Operator::GetInputConstData(const string &dst_name, Tensor &data) co // For outer graph return GetInputConstDataOut(dst_name, data); } + auto op_name = operator_impl_->GetName(); + GELOGW("node[%s]'s input[%s]'s peer node is not const", op_name.c_str(), dst_name.c_str()); return GRAPH_FAILED; } graphStatus Operator::GetInputConstDataOut(const string &dst_name, Tensor &data) const { diff --git a/src/common/graph/option/ge_context.cc b/src/common/graph/option/ge_context.cc index f5ebdeee..f5f5e4c9 100644 --- a/src/common/graph/option/ge_context.cc +++ b/src/common/graph/option/ge_context.cc @@ -85,6 +85,8 @@ uint32_t GEContext::DeviceId() { return device_id_; } uint64_t GEContext::TraceId() { return trace_id_; } +void GEContext::SetSessionId(uint64_t session_id) { session_id_ = session_id; } + void GEContext::SetCtxDeviceId(uint32_t device_id) { device_id_ = device_id; } } // namespace ge diff --git a/src/common/graph/ref_relation.cc b/src/common/graph/ref_relation.cc index b3cf37af..906cb5f9 100644 --- a/src/common/graph/ref_relation.cc +++ b/src/common/graph/ref_relation.cc @@ -242,6 +242,10 @@ void RefRelations::Impl::GetDataAndNetoutputOfSubGraph(const ge::ComputeGraph &r int sub_graph_idx = 0; for (const auto &name : sub_graph_names) { auto sub_graph = root_graph.GetSubgraph(name); + if (sub_graph == nullptr) { + GELOGW("Can not find the sub graph %s for root graph %s.", name.c_str(), root_graph.GetName().c_str()); + continue; + } for (const auto &sub_graph_node : sub_graph->GetDirectNode()) { auto sub_graph_node_type = sub_graph_node->GetType(); diff --git a/src/common/graph/shape_refiner.cc b/src/common/graph/shape_refiner.cc index edf426a5..dc1bc541 100644 --- a/src/common/graph/shape_refiner.cc +++ b/src/common/graph/shape_refiner.cc @@ -37,6 +37,115 @@ namespace ge { namespace { +const uint32_t kWhileBodySubGraphIdx = 1; + +graphStatus ReverseBrushWhileBodySubGraph(const ConstNodePtr &node) { + GELOGD("Enter reverse brush while body subgraph process!"); + + auto sub_graph_body = NodeUtils::GetSubgraph(*node, kWhileBodySubGraphIdx); + if (sub_graph_body == nullptr) { + GELOGE(GRAPH_FAILED, "Get while body graph failed!"); + return GRAPH_FAILED; + } + + for (const auto &node_sub : sub_graph_body->GetAllNodes()) { + if (node_sub->GetInDataNodes().size() == 0) { + continue; + } + + for (size_t i = 0; i < node_sub->GetAllInDataAnchorsSize(); i++) { + auto input_desc = node_sub->GetOpDesc()->MutableInputDesc(i); + (void)input_desc->SetUnknownDimNumShape(); + } + for (size_t i = 0; i < node_sub->GetAllOutDataAnchorsSize(); i++) { + auto output_desc = node_sub->GetOpDesc()->MutableOutputDesc(i); + (void)output_desc->SetUnknownDimNumShape(); + } + } + + return GRAPH_SUCCESS; +} + +graphStatus UpdateParentNodeForBranch(const ConstNodePtr &node, + std::vector> &ref_out_tensors) { + GELOGD("Enter update parent node shape for class branch op process"); + // check sub_graph shape.If not same ,do unknown shape process + for (size_t i = 0; i < ref_out_tensors.size(); i++) { + if (ref_out_tensors[i].empty()) { + continue; + } + auto ref_out_tensor = ref_out_tensors[i].at(0); + ge::GeShape &ref_out_tensor_shape = ref_out_tensor.MutableShape(); + for (auto &tensor : ref_out_tensors[i]) { + if (ref_out_tensor.GetDataType() != tensor.GetDataType()) { + GELOGE(GRAPH_FAILED, "node[%s] does not support diff dtype output", node->GetName().c_str()); + return GRAPH_FAILED; + } + auto shape = tensor.MutableShape(); + if (shape.GetDims().size() != ref_out_tensor_shape.GetDims().size()) { + GELOGD("node is %s, i : %d, shape size: %lu, ref_out_tensor_shape size: %lu", node->GetName().c_str(), i, + shape.GetShapeSize(), ref_out_tensor_shape.GetShapeSize()); + ref_out_tensor_shape = GeShape(UNKNOWN_RANK); + break; + } + for (size_t j = 0; j < ref_out_tensor_shape.GetDims().size(); j++) { + if (ref_out_tensor_shape.GetDim(j) == shape.GetDim(j)) { + continue; + } + GELOGD("node is %s, i : %d, j: %d ,shape size: %lu, ref_out_tensor_shape size: %lu", node->GetName().c_str(), i, + j, shape.GetShapeSize(), ref_out_tensor_shape.GetShapeSize()); + (void)ref_out_tensor_shape.SetDim(j, UNKNOWN_DIM); + } + } + (void)node->GetOpDesc()->UpdateOutputDesc(i, ref_out_tensor); + } + return GRAPH_SUCCESS; +} + +graphStatus UpdateParentNodeForWhile(const ConstNodePtr &node, std::vector> &ref_data_tensors, + std::vector> &ref_out_tensors) { + GELOGD("Enter update parent node shape for class while op process"); + if (ref_data_tensors.size() != ref_out_tensors.size()) { + GELOGE(GRAPH_FAILED, "while op [%s] input number[%zu] and output number[%zu] is not same!", node->GetName().c_str(), + ref_data_tensors.size(), ref_out_tensors.size()); + return GRAPH_FAILED; + } + for (size_t i = 0; i < ref_data_tensors.size(); i++) { + if (ref_out_tensors[i].size() != 1) { + GELOGE(GRAPH_FAILED, "while op, every output should only find one output tensor in all graph!"); + return GRAPH_FAILED; + } + } + bool is_need_reverse_brush = false; + // check input and output + for (size_t i = 0; i < ref_out_tensors.size(); i++) { + if (ref_out_tensors[i].empty()) { + continue; + } + auto ref_out_tensor = ref_out_tensors[i].at(0); + auto tmp_shape = ref_out_tensor.MutableShape(); + // ref_i's data and output tensor shape should be same + for (auto &tensor : ref_data_tensors[i]) { + if (ref_out_tensor.GetDataType() != tensor.GetDataType()) { + GELOGE(GRAPH_FAILED, "node[%s] does not support diff dtype or format output.", node->GetName().c_str()); + return GRAPH_FAILED; + } + auto shape = tensor.MutableShape(); + if (shape.GetDims() != tmp_shape.GetDims()) { + ref_out_tensor.SetUnknownDimNumShape(); + is_need_reverse_brush = true; + break; + } + } + (void)node->GetOpDesc()->UpdateOutputDesc(i, ref_out_tensor); + } + // reverse refresh while body shape + if (is_need_reverse_brush) { + return ReverseBrushWhileBodySubGraph(node); + } + return GRAPH_SUCCESS; +} + graphStatus UpdateSubGraphDataNodes(const ConstNodePtr &node) { auto op_desc = node->GetOpDesc(); auto sub_graph_names = op_desc->GetSubgraphInstanceNames(); @@ -98,6 +207,37 @@ graphStatus UpdateSubGraphDataNodes(const ConstNodePtr &node) { } return GRAPH_SUCCESS; } + +graphStatus FindSubgraphDataAndNetoutput(std::shared_ptr &sub_graph, NodePtr &netoutput, + const ConstNodePtr &node, + std::vector> &ref_data_tensors) { + auto sub_nodes = sub_graph->GetDirectNode(); + for (size_t i = sub_nodes.size(); i > 0; --i) { + auto sub_node = sub_nodes.at(i - 1); + if (sub_node->GetType() == NETOUTPUT) { + netoutput = sub_node; + } + if (sub_node->GetType() == DATA) { + if (sub_node->GetOpDesc() == nullptr) { + return GRAPH_FAILED; + } + + int ref_i; + if (!AttrUtils::GetInt(sub_node->GetOpDesc(), ATTR_NAME_PARENT_NODE_INDEX, ref_i)) { + GELOGE(GRAPH_FAILED, "subgraph data node[%s] has no parent node!", sub_node->GetName().c_str()); + return GRAPH_FAILED; + } + if (ref_i < 0 || static_cast(ref_i) >= node->GetAllInDataAnchorsSize()) { + GELOGE(GRAPH_FAILED, "data node[%s]'s ref index[%d] is not in range [0, %zu)!", sub_node->GetName().c_str(), + ref_i, node->GetAllInDataAnchorsSize()); + return GRAPH_FAILED; + } + ref_data_tensors[ref_i].emplace_back(sub_node->GetOpDesc()->GetOutputDesc(0)); + } + } + return GRAPH_SUCCESS; +} + graphStatus UpdateParentNodeOutTensor(const ConstNodePtr &node) { auto op_desc = node->GetOpDesc(); auto sub_graph_names = op_desc->GetSubgraphInstanceNames(); @@ -105,7 +245,10 @@ graphStatus UpdateParentNodeOutTensor(const ConstNodePtr &node) { return GRAPH_SUCCESS; } + std::vector> ref_data_tensors(node->GetAllInDataAnchorsSize()); + std::vector> ref_out_tensors(node->GetAllOutDataAnchorsSize()); auto root_graph = GraphUtils::FindRootGraph(node->GetOwnerComputeGraph()); + for (const auto &name : sub_graph_names) { if (name.empty()) { GELOGW("The node %s contains empty subgraph instance name", node->GetName().c_str()); @@ -117,13 +260,9 @@ graphStatus UpdateParentNodeOutTensor(const ConstNodePtr &node) { return GRAPH_FAILED; } NodePtr netoutput = nullptr; - auto sub_nodes = sub_graph->GetDirectNode(); - for (size_t i = sub_nodes.size(); i > 0; --i) { - auto sub_node = sub_nodes.at(i - 1); - if (sub_node->GetType() == NETOUTPUT) { - netoutput = sub_node; - break; - } + auto ret = FindSubgraphDataAndNetoutput(sub_graph, netoutput, node, ref_data_tensors); + if (ret != GRAPH_SUCCESS) { + return ret; } if (netoutput == nullptr) { GE_LOGE("No NetOutput node on sub graph %s, parent node %s", name.c_str(), node->GetName().c_str()); @@ -150,19 +289,17 @@ graphStatus UpdateParentNodeOutTensor(const ConstNodePtr &node) { continue; } GELOGI("Parent node index of edge desc is %d", ref_i); - auto output_desc = op_desc->MutableOutputDesc(static_cast(ref_i)); - if (output_desc == nullptr) { - GE_LOGE( - "The ref index(%d) on the input %d of netoutput %s on the sub graph %s " - "parent node %s are incompatible, outputs num %u", - ref_i, edge_anchor->GetIdx(), netoutput->GetName().c_str(), name.c_str(), node->GetName().c_str(), - node->GetAllOutDataAnchorsSize()); + if (ref_i < 0 || static_cast(ref_i) >= node->GetAllOutDataAnchorsSize()) { return GRAPH_FAILED; } - op_desc->UpdateOutputDesc(edge_anchor->GetIdx(), *edge_desc); + ref_out_tensors[ref_i].emplace_back(*edge_desc); } } - return GRAPH_SUCCESS; + + if (node->GetType() == WHILE) { + return UpdateParentNodeForWhile(node, ref_data_tensors, ref_out_tensors); + } + return UpdateParentNodeForBranch(node, ref_out_tensors); } } // namespace void ShapeRefiner::PrintInOutTensorShape(const ge::NodePtr &node, const std::string &phase) { @@ -170,6 +307,9 @@ void ShapeRefiner::PrintInOutTensorShape(const ge::NodePtr &node, const std::str GELOGE(GRAPH_FAILED, "node is null"); return; } + if (!IsLogEnable(GE, DLOG_DEBUG)) { + return; + } ge::OpDescPtr op_desc = node->GetOpDesc(); GE_IF_BOOL_EXEC(op_desc == nullptr, GELOGE(GRAPH_FAILED, "op_desc is null."); return ); std::string str; diff --git a/src/common/graph/utils/ge_ir_utils.h b/src/common/graph/utils/ge_ir_utils.h index 9b16be18..b572ab38 100644 --- a/src/common/graph/utils/ge_ir_utils.h +++ b/src/common/graph/utils/ge_ir_utils.h @@ -1,18 +1,18 @@ /** - * Copyright 2019-2020 Huawei Technologies Co., Ltd - * + * Copyright 2020 Huawei Technologies Co., Ltd + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * http://www.apache.org/licenses/LICENSE-2.0 - * + * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - */ +*/ #ifndef COMMON_GRAPH_UTILS_GE_IR_UTILS_H_ #define COMMON_GRAPH_UTILS_GE_IR_UTILS_H_ diff --git a/src/common/graph/utils/graph_utils.cc b/src/common/graph/utils/graph_utils.cc index ca2ebcdc..a6980358 100644 --- a/src/common/graph/utils/graph_utils.cc +++ b/src/common/graph/utils/graph_utils.cc @@ -38,6 +38,7 @@ #include "utils/ge_ir_utils.h" #include "utils/node_utils.h" #include "debug/ge_op_types.h" +#include "external/ge/ge_api_types.h" #include "graph/debug/ge_attr_define.h" #include "graph/utils/op_desc_utils.h" #include "graph/utils/tensor_utils.h" @@ -410,8 +411,8 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY graphStatus GraphUtils::InsertTra /// @return graphStatus /// GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY graphStatus -GraphUtils::InsertNodeBefore(const OutDataAnchorPtr &src, const std::vector &dsts, - const NodePtr &insert_node, uint32_t input_index, uint32_t output_index) { +GraphUtils::InsertNodeAfter(const OutDataAnchorPtr &src, const std::vector &dsts, + const NodePtr &insert_node, uint32_t input_index, uint32_t output_index) { GE_CHECK_NOTNULL(src); GE_CHECK_NOTNULL(insert_node); @@ -570,7 +571,7 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY void GraphUtils::DumpGEGraph(cons static int max_dumpfile_num = 0; if (max_dumpfile_num == 0) { string opt = "0"; - (void)GetContext().GetOption("ge.maxDumpFileNum", opt); + (void)GetContext().GetOption(OPTION_GE_MAX_DUMP_FILE_NUM, opt); max_dumpfile_num = std::strtol(opt.c_str(), nullptr, kBaseOfIntegerValue); } if (max_dumpfile_num != 0 && file_idx > max_dumpfile_num) { @@ -670,7 +671,7 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY void GraphUtils::WriteProtoToText if (maxDumpFileSize == 0) { string opt = "0"; // Can not check return value - (void)GetContext().GetOption("ge.maxDumpFileSize", opt); + (void)GetContext().GetOption(OPTION_GE_MAX_DUMP_FILE_SIZE, opt); maxDumpFileSize = atol(opt.c_str()); } if (maxDumpFileSize != 0 && fileSize != -1 && fileSize > maxDumpFileSize) { @@ -740,7 +741,7 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY void GraphUtils::DumpGEGraphToOnn static int max_dumpfile_num = 0; if (max_dumpfile_num == 0) { string opt = "0"; - (void)GetContext().GetOption("ge.maxDumpFileNum", opt); + (void)GetContext().GetOption(OPTION_GE_MAX_DUMP_FILE_NUM, opt); max_dumpfile_num = std::strtol(opt.c_str(), nullptr, kBaseOfIntegerValue); } if (max_dumpfile_num != 0 && file_index > max_dumpfile_num) { @@ -920,7 +921,7 @@ graphStatus RelinkDataIO(const NodePtr &node, const std::vector &io_map, In InNodesToOut GetFullConnectIONodes(const NodePtr &node) { InNodesToOut in_nodes_to_out; if (node == nullptr) { - GELOGE(GRAPH_FAILED, "Node is nullptr,node is %s", node->GetName().c_str()); + GELOGE(GRAPH_FAILED, "Node is nullptr"); return in_nodes_to_out; } auto in_nodes_list = node->GetInNodes(); @@ -1308,6 +1309,36 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY graphStatus GraphUtils::MoveOutCt return GRAPH_SUCCESS; } +/// +/// Copy all in-data edges from `src_node` to `dst_node`. +/// @param src_node +/// @param dst_node +/// @return +/// +GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY graphStatus GraphUtils::CopyInDataEdges(const NodePtr &src_node, + NodePtr &dst_node) { + if ((src_node == nullptr) || (dst_node == nullptr)) { + GELOGE(GRAPH_FAILED, "Parameter is nullptr"); + return GRAPH_PARAM_INVALID; + } + auto src_data_in_nodes = src_node->GetInDataNodes(); + if (src_data_in_nodes.empty()) { + return GRAPH_SUCCESS; + } + for (const auto &in_data_anchor : src_node->GetAllInDataAnchors()) { + auto input_desc = src_node->GetOpDesc()->GetInputDesc(in_data_anchor->GetIdx()); + auto ret = + GraphUtils::AddEdge(in_data_anchor->GetPeerOutAnchor(), dst_node->GetInDataAnchor(in_data_anchor->GetIdx())); + if (ret != GRAPH_SUCCESS) { + GELOGE(GRAPH_FAILED, "Failed to add data edge from %s to %s when copy in data edge from %s to %s", + in_data_anchor->GetPeerOutAnchor()->GetOwnerNode()->GetName().c_str(), dst_node->GetName().c_str(), + src_node->GetName().c_str(), dst_node->GetName().c_str()); + return ret; + } + } + return GRAPH_SUCCESS; +} + GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY graphStatus GraphUtils::AppendInputNode(const ComputeGraphPtr &graph, const NodePtr &node) { if (graph->AddInputNode(node) == nullptr) { @@ -1339,7 +1370,7 @@ graphStatus GraphUtils::GetRefMapping(const ComputeGraphPtr &graph, std::map> &symbol_to_anchors, std::map &anchor_to_symbol) { GE_CHECK_NOTNULL(graph); - for (auto &node : graph->GetAllNodes()) { + for (const auto &node : graph->GetAllNodes()) { // in_data_anchor if (HandleInAnchorMapping(node, symbol_to_anchors, anchor_to_symbol) != GRAPH_SUCCESS) { GE_LOGE("Find ref_mapping for in_data_anchors of node %s failed.", node->GetName().c_str()); @@ -1396,16 +1427,16 @@ graphStatus GraphUtils::HandleInAnchorMapping(const NodePtr &node, return HandleSubgraphInput(node, symbol_to_anchors, anchor_to_symbol); } - std::string type = node->GetType(); + const std::string &type = node->GetType(); if ((type == MERGE) || (type == STREAMMERGE)) { return HandleMergeInput(node, symbol_to_anchors, anchor_to_symbol); } - for (auto &in_data_anchor : node->GetAllInDataAnchors()) { + for (const auto &in_data_anchor : node->GetAllInDataAnchors()) { NodeIndexIO cur_node_info(node, in_data_anchor->GetIdx(), kIn); OutDataAnchorPtr peer_out_anchor = in_data_anchor->GetPeerOutAnchor(); if (peer_out_anchor == nullptr) { - std::string symbol = cur_node_info.ToString(); + const std::string &symbol = cur_node_info.ToString(); GELOGD("Add anchor %s, symbol %s.", cur_node_info.ToString().c_str(), symbol.c_str()); symbol_to_anchors[symbol] = {cur_node_info}; anchor_to_symbol[symbol] = symbol; @@ -1432,7 +1463,7 @@ graphStatus GraphUtils::HandleOutAnchorMapping(const NodePtr &node, std::map> &symbol_to_anchors, std::map &anchor_to_symbol) { GE_CHECK_NOTNULL(node); - for (auto &out_data_anchor : node->GetAllOutDataAnchors()) { + for (const auto &out_data_anchor : node->GetAllOutDataAnchors()) { NodeIndexIO cur_node_info(node, out_data_anchor->GetIdx(), kOut); if (anchor_to_symbol.find(cur_node_info.ToString()) != anchor_to_symbol.end()) { continue; @@ -1446,7 +1477,7 @@ graphStatus GraphUtils::HandleOutAnchorMapping(const NodePtr &node, return GRAPH_FAILED; } } else { - std::string symbol = cur_node_info.ToString(); + const std::string &symbol = cur_node_info.ToString(); GELOGD("Add anchor %s, symbol %s.", cur_node_info.ToString().c_str(), symbol.c_str()); symbol_to_anchors.emplace(std::make_pair(symbol, std::list{cur_node_info})); anchor_to_symbol.emplace(std::make_pair(symbol, symbol)); @@ -1506,7 +1537,7 @@ graphStatus GraphUtils::HandleMergeInput(const NodePtr &node, GE_CHECK_NOTNULL(node); std::vector exist_node_infos; std::vector cur_node_infos; - for (auto &in_data_anchor : node->GetAllInDataAnchors()) { + for (const auto &in_data_anchor : node->GetAllInDataAnchors()) { auto peer_out_anchor = in_data_anchor->GetPeerOutAnchor(); if (peer_out_anchor == nullptr) { std::string next_name; @@ -1529,10 +1560,10 @@ graphStatus GraphUtils::HandleMergeInput(const NodePtr &node, size_t anchor_nums = 0; NodeIndexIO max_node_index_io(nullptr, 0, kOut); - for (auto &temp_node_info : exist_node_infos) { + for (const auto &temp_node_info : exist_node_infos) { auto iter1 = anchor_to_symbol.find(temp_node_info.ToString()); if (iter1 != anchor_to_symbol.end()) { - std::string temp_symbol = iter1->second; + const std::string &temp_symbol = iter1->second; auto iter2 = symbol_to_anchors.find(temp_symbol); if (iter2 != symbol_to_anchors.end()) { if (iter2->second.size() > anchor_nums) { @@ -1544,7 +1575,7 @@ graphStatus GraphUtils::HandleMergeInput(const NodePtr &node, } std::string symbol; - for (auto &temp_node_info : exist_node_infos) { + for (const auto &temp_node_info : exist_node_infos) { if ((UnionSymbolMapping(max_node_index_io, temp_node_info, symbol_to_anchors, anchor_to_symbol, symbol) != GRAPH_SUCCESS) || symbol.empty()) { @@ -1556,7 +1587,7 @@ graphStatus GraphUtils::HandleMergeInput(const NodePtr &node, auto iter = symbol_to_anchors.find(symbol); if (iter != symbol_to_anchors.end()) { - for (auto &temp_node_info : cur_node_infos) { + for (const auto &temp_node_info : cur_node_infos) { GELOGD("Add anchor %s, symbol %s.", temp_node_info.ToString().c_str(), symbol.c_str()); iter->second.emplace_back(temp_node_info); anchor_to_symbol.emplace(std::make_pair(temp_node_info.ToString(), symbol)); @@ -1584,7 +1615,7 @@ graphStatus GraphUtils::HandleSubgraphOutput(const NodePtr &node, OpDescPtr op_desc = node->GetOpDesc(); GE_CHECK_NOTNULL(op_desc); - for (auto &in_data_anchor : node->GetAllInDataAnchors()) { + for (const auto &in_data_anchor : node->GetAllInDataAnchors()) { OutDataAnchorPtr peer_out_anchor = in_data_anchor->GetPeerOutAnchor(); GE_CHECK_NOTNULL(peer_out_anchor); @@ -1627,8 +1658,8 @@ graphStatus GraphUtils::HandleSubgraphOutput(const NodePtr &node, graphStatus GraphUtils::UnionSymbolMapping(const NodeIndexIO &exist_node_info1, const NodeIndexIO &exist_node_info2, std::map> &symbol_to_anchors, std::map &anchor_to_symbol, std::string &symbol) { - std::string symbol1 = anchor_to_symbol[exist_node_info1.ToString()]; - std::string symbol2 = anchor_to_symbol[exist_node_info2.ToString()]; + const std::string &symbol1 = anchor_to_symbol[exist_node_info1.ToString()]; + const std::string &symbol2 = anchor_to_symbol[exist_node_info2.ToString()]; if (symbol1 == symbol2) { symbol = symbol1; GELOGI("no need to union."); @@ -1684,7 +1715,7 @@ graphStatus GraphUtils::UpdateRefMapping(const NodeIndexIO &cur_node_info, const return GRAPH_FAILED; } - std::string symbol = iter1->second; + const std::string &symbol = iter1->second; auto iter2 = symbol_to_anchors.find(symbol); if (iter2 == symbol_to_anchors.end()) { GE_LOGE("symbol %s not found.", symbol.c_str()); @@ -1712,7 +1743,7 @@ bool GraphUtils::IsRefFromInput(const OutDataAnchorPtr &out_data_anchor, int32_t // pass-through op NodePtr node = out_data_anchor->GetOwnerNode(); - std::string type = node->GetType(); + const std::string &type = node->GetType(); const std::set pass_through_set = {NETOUTPUT, WHILE, _WHILE, STATELESSWHILE}; if ((pass_through_set.count(type) > 0) || (NodeUtils::IsSubgraphInput(node))) { reuse_in_index = output_index; @@ -1755,7 +1786,7 @@ bool GraphUtils::IsRefFromInput(const OutDataAnchorPtr &out_data_anchor, int32_t uint32_t reuse_input_index = 0; if (TensorUtils::GetReuseInputIndex(*output_op_desc, reuse_input_index) == GRAPH_SUCCESS) { reuse_in_index = static_cast(reuse_input_index); - GELOGI("ReuseInput name[%s] output[%u] reuse input[%d].", op_desc->GetName().c_str(), output_index, + GELOGI("ReuseInput name[%s] output[%d] reuse input[%d].", op_desc->GetName().c_str(), output_index, reuse_in_index); return true; } @@ -2297,7 +2328,7 @@ void CompleteGraphBuilder::AddRetValNodes(graphStatus &error_code, std::string & return; } - std::string name = node->GetName() + "_RetVal"; + std::string name = node->GetName() + "_RetVal_" + std::to_string(index); OpDescPtr ret_val_desc = shared_ptr(new (std::nothrow) OpDesc(name, FRAMEWORKOP)); if (ret_val_desc == nullptr) { error_code = GRAPH_FAILED; diff --git a/src/common/graph/utils/node_utils.cc b/src/common/graph/utils/node_utils.cc index e4fb8b82..20bcacfb 100644 --- a/src/common/graph/utils/node_utils.cc +++ b/src/common/graph/utils/node_utils.cc @@ -296,15 +296,18 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY graphStatus NodeUtils::UpdatePeer return GRAPH_FAILED; } for (const auto &out_anchor : node_ptr->GetAllOutDataAnchors()) { - GeTensorDesc output_tensor = op_desc->GetOutputDesc(out_anchor->GetIdx()); - ge::TensorUtils::SetRealDimCnt(output_tensor, static_cast(output_tensor.GetShape().GetDims().size())); - output_tensor.SetOriginShape(output_tensor.GetShape()); - output_tensor.SetOriginDataType(output_tensor.GetDataType()); + auto output_tensor = op_desc->MutableOutputDesc(out_anchor->GetIdx()); + ge::TensorUtils::SetRealDimCnt(*output_tensor, static_cast(output_tensor->GetShape().GetDims().size())); + bool is_unknown_graph = node_ptr->GetOwnerComputeGraph()->GetGraphUnknownFlag(); + if (!is_unknown_graph) { + output_tensor->SetOriginShape(output_tensor->GetShape()); + output_tensor->SetOriginDataType(output_tensor->GetDataType()); + } GELOGD("node name is %s, origin shape is %ld, origin format is %s, origin data type is %s", - node_ptr->GetName().c_str(), output_tensor.GetOriginShape().GetShapeSize(), - TypeUtils::FormatToSerialString(output_tensor.GetOriginFormat()).c_str(), - TypeUtils::DataTypeToSerialString(output_tensor.GetOriginDataType()).c_str()); - (void)op_desc->UpdateOutputDesc(out_anchor->GetIdx(), output_tensor); + node_ptr->GetName().c_str(), output_tensor->GetOriginShape().GetShapeSize(), + TypeUtils::FormatToSerialString(output_tensor->GetOriginFormat()).c_str(), + TypeUtils::DataTypeToSerialString(output_tensor->GetOriginDataType()).c_str()); + for (const auto &peer_anchor : out_anchor->GetPeerInDataAnchors()) { if (peer_anchor->GetOwnerNode()->GetOpDesc() == nullptr) { GELOGE(GRAPH_FAILED, "peer_anchor opdesc is null"); @@ -316,17 +319,17 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY graphStatus NodeUtils::UpdatePeer continue; } GELOGI("Peer input opdesc name is %s, need to flush: shape size is %zu, datatype is %d, original datatype is %d", - peer_anchor->GetOwnerNode()->GetOpDesc()->GetName().c_str(), output_tensor.GetShape().GetDimNum(), - output_tensor.GetDataType(), output_tensor.GetOriginDataType()); - peer_input_desc->SetShape(output_tensor.GetShape()); - peer_input_desc->SetOriginShape(output_tensor.GetOriginShape()); - peer_input_desc->SetDataType(output_tensor.GetDataType()); - peer_input_desc->SetOriginDataType(output_tensor.GetOriginDataType()); + peer_anchor->GetOwnerNode()->GetOpDesc()->GetName().c_str(), output_tensor->GetShape().GetDimNum(), + output_tensor->GetDataType(), output_tensor->GetOriginDataType()); + peer_input_desc->SetShape(output_tensor->GetShape()); + peer_input_desc->SetOriginShape(output_tensor->GetOriginShape()); + peer_input_desc->SetDataType(output_tensor->GetDataType()); + peer_input_desc->SetOriginDataType(output_tensor->GetOriginDataType()); std::vector> shape_range; - (void)output_tensor.GetShapeRange(shape_range); + (void)output_tensor->GetShapeRange(shape_range); peer_input_desc->SetShapeRange(shape_range); ge::TensorUtils::SetRealDimCnt(*peer_input_desc, - static_cast(output_tensor.GetShape().GetDims().size())); + static_cast(output_tensor->GetShape().GetDims().size())); GELOGI("Peer input opdesc name is %s, shape size is %zu, datatype is %d, original datatype is %d", peer_anchor->GetOwnerNode()->GetOpDesc()->GetName().c_str(), peer_input_desc->GetShape().GetDimNum(), peer_input_desc->GetDataType(), peer_input_desc->GetOriginDataType()); @@ -401,10 +404,13 @@ graphStatus NodeUtils::UpdateInputShape(const Node &node, uint32_t index, const graphStatus NodeUtils::GetNodeUnknownShapeStatus(const Node &node, bool &is_unknow) { auto desc = node.GetOpDesc(); GE_CHECK_NOTNULL(desc); - + // check self + is_unknow = OpShapeIsUnknown(desc); + if (is_unknow) { + return GRAPH_SUCCESS; + } auto sub_graph_names = desc->GetSubgraphInstanceNames(); if (sub_graph_names.empty()) { - is_unknow = OpShapeIsUnknown(desc); return GRAPH_SUCCESS; } else { auto owner_graph = node.GetOwnerComputeGraph(); @@ -555,6 +561,53 @@ NodePtr NodeUtils::GetParentInput(const NodePtr &node) { return peer_out_anchor->GetOwnerNode(); } +/// +/// @brief Check is varying_input for while node +/// @param [in] node: Data node for subgraph +/// @return bool +/// +bool NodeUtils::IsWhileVaryingInput(const ge::NodePtr &node) { + if (node == nullptr) { + return false; + } + if (node->GetType() != DATA) { + return false; // not input_node for subgraph + } + + const NodePtr &parent_node = node->GetOwnerComputeGraph()->GetParentNode(); + if (parent_node == nullptr) { + return false; // root graph + } + + if (kWhileOpTypes.count(parent_node->GetType()) == 0) { + return false; // not input_node for while subgraph + } + + uint32_t index_i = 0; + if (!AttrUtils::GetInt(node->GetOpDesc(), ATTR_NAME_PARENT_NODE_INDEX, index_i)) { + GELOGW("Node %s has no attr PARENT_NODE_INDEX.", node->GetName().c_str()); + return false; + } + bool varying_flag = true; + for (const auto &item : node->GetOutDataNodesAndAnchors()) { + if (item.first->GetType() != NETOUTPUT) { + continue; + } + OpDescPtr op_desc = item.first->GetOpDesc(); + uint32_t index_o = 0; + if ((op_desc == nullptr) || + !AttrUtils::GetInt(op_desc->GetInputDesc(item.second->GetIdx()), ATTR_NAME_PARENT_NODE_INDEX, index_o)) { + continue; // input for while-cond subgraph + } + if (index_i != index_o) { + continue; // varying input for while-body subgraph + } + varying_flag = false; + break; + } + return varying_flag; +} + /// /// @brief Get subgraph input is constant. /// @param [in] node @@ -637,4 +690,86 @@ Status NodeUtils::RemoveSubgraphsOnNode(const NodePtr &node) { return GRAPH_SUCCESS; } +/// +/// @brief Get subgraph input data node by index. +/// @param [in] node +/// @return Node +/// +vector NodeUtils::GetSubgraphDataNodesByIndex(const Node &node, int index) { + vector in_data_node_vec; + auto op_desc = node.GetOpDesc(); + GE_CHECK_NOTNULL_EXEC(op_desc, return in_data_node_vec); + auto subgraph_names = op_desc->GetSubgraphInstanceNames(); + if (subgraph_names.empty()) { + GELOGW("Node %s is single node without sub graph.", node.GetName().c_str()); + return in_data_node_vec; + } + auto compute_graph = node.GetOwnerComputeGraph(); + for (const std::string &instance_name : subgraph_names) { + auto subgraph = compute_graph->GetSubgraph(instance_name); + for (const auto &node_in_subgraph : subgraph->GetDirectNode()) { + int parent_index = 0; + if (NodeUtils::IsSubgraphInput(node_in_subgraph)) { + (void)AttrUtils::GetInt(node_in_subgraph->GetOpDesc(), ATTR_NAME_PARENT_NODE_INDEX, parent_index); + if (parent_index == index) { + in_data_node_vec.emplace_back(node_in_subgraph); + } + } + } + } + return in_data_node_vec; +} +/// +/// @brief Get subgraph input data node by index. +/// @param [in] node +/// @return Node +/// +vector NodeUtils::GetSubgraphOutputNodes(const Node &node) { + vector out_data_node_vec; + auto op_desc = node.GetOpDesc(); + GE_CHECK_NOTNULL_EXEC(op_desc, return out_data_node_vec); + auto subgraph_names = op_desc->GetSubgraphInstanceNames(); + if (subgraph_names.empty()) { + GELOGI("Node %s is single node without sub graph.", node.GetName().c_str()); + return out_data_node_vec; + } + auto compute_graph = node.GetOwnerComputeGraph(); + for (const std::string &instance_name : subgraph_names) { + auto subgraph = compute_graph->GetSubgraph(instance_name); + for (const auto &node_in_subgraph : subgraph->GetDirectNode()) { + if (NodeUtils::IsSubgraphOutput(node_in_subgraph)) { + out_data_node_vec.emplace_back(node_in_subgraph); + } + } + } + return out_data_node_vec; +} + +NodePtr NodeUtils::GetInDataNodeByIndex(const Node &node, int index) { + if (node.GetInDataAnchor(index) == nullptr) { + return nullptr; + } + if (node.GetInDataAnchor(index)->GetPeerOutAnchor() == nullptr) { + return nullptr; + } + return node.GetInDataAnchor(index)->GetPeerOutAnchor()->GetOwnerNode(); +} + +vector NodeUtils::GetOutDataNodesByIndex(const Node &node, int index) { + vector out_data_nodes; + auto out_data_anchor = node.GetOutDataAnchor(index); + if (out_data_anchor == nullptr) { + return out_data_nodes; + } + for (const auto peer_in_anchor : out_data_anchor->GetPeerInDataAnchors()) { + if (peer_in_anchor == nullptr) { + continue; + } + if (peer_in_anchor->GetOwnerNode() == nullptr) { + continue; + } + out_data_nodes.emplace_back(peer_in_anchor->GetOwnerNode()); + } + return out_data_nodes; +} } // namespace ge diff --git a/src/common/graph/utils/op_desc_utils.cc b/src/common/graph/utils/op_desc_utils.cc index 6264ddb9..c5de264f 100644 --- a/src/common/graph/utils/op_desc_utils.cc +++ b/src/common/graph/utils/op_desc_utils.cc @@ -197,24 +197,33 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY vector OpDescUtils:: continue; } auto in_node = out_anchor->GetOwnerNode(); - if ((in_node->GetType() == CONSTANT) || (in_node->GetType() == CONSTANTOP)) { - ret.push_back(in_node); - } else if (in_node->GetType() == DATA) { - const ComputeGraphPtr &graph = node.GetOwnerComputeGraph(); - GE_CHK_BOOL_EXEC(graph != nullptr, continue, "Owner graph is null"); - - const NodePtr &parent_node = graph->GetParentNode(); - if (parent_node == nullptr) { - continue; // Root graph. - } - - if (kWhileOpTypes.count(parent_node->GetType()) > 0) { - continue; // Subgraph of While cond or body. + while (true) { + if (in_node == nullptr) { + break; } - - NodePtr input_node = NodeUtils::GetParentInput(in_node); - if ((input_node != nullptr) && ((input_node->GetType() == CONSTANT) || (input_node->GetType() == CONSTANTOP))) { - ret.push_back(input_node); + if ((in_node->GetType() == CONSTANT) || (in_node->GetType() == CONSTANTOP)) { + ret.push_back(in_node); + break; + } else if (in_node->GetType() == DATA) { + if (NodeUtils::IsWhileVaryingInput(in_node)) { + break; + } + in_node = NodeUtils::GetParentInput(in_node); + } else if ((in_node->GetType() == ENTER) || (in_node->GetType() == REFENTER)) { + bool is_constant = false; + (void)AttrUtils::GetBool(in_node->GetOpDesc(), ENTER_ATTR_CONSTANT_FLAG, is_constant); + if (!is_constant) { + break; + } + // Enter node has and only has one input + if (in_node->GetInDataNodes().size() != 1) { + GELOGW("Check number of input_nodes for Enter node %s failed, size=%zu.", node.GetName().c_str(), + in_node->GetInDataNodes().size()); + break; + } + in_node = in_node->GetInDataNodes().at(0); + } else { + break; } } } @@ -435,10 +444,27 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY vector OpDescUtils:: GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY vector OpDescUtils::MutableWeights(const ge::Node &node) { vector ret; - GE_CHK_BOOL_EXEC(node.GetOpDesc() != nullptr, return ret, "node.GetOpDesc is nullptr!"); + auto op_desc = node.GetOpDesc(); + GE_CHK_BOOL_EXEC(op_desc != nullptr, return ret, "op_desc is nullptr!"); + // Place holder operator, try to get the weight from parent node + // when parent node is const operator + if (node.GetType() == PLACEHOLDER) { + std::string parent_op; + (void)AttrUtils::GetStr(op_desc, "parentOpType", parent_op); + // This if judgment is necessary because the current subgraph optimization is multithreaded + // and the parent node of the PLD operation should be a stable type, such as const + if (parent_op == CONSTANT || parent_op == CONSTANTOP) { + NodePtr parent_node = nullptr; + parent_node = op_desc->TryGetExtAttr("parentNode", parent_node); + if (parent_node != nullptr) { + op_desc = parent_node->GetOpDesc(); + GELOGD("pld[%s] get weight from const[%s]", node.GetName().c_str(), op_desc->GetName().c_str()); + } + } + } // Const operator, take the weight directly - if (node.GetOpDesc()->GetType() == CONSTANT || (node.GetOpDesc()->GetType() == CONSTANTOP)) { - auto weight = MutableWeights(node.GetOpDesc()); + if (op_desc->GetType() == CONSTANT || (op_desc->GetType() == CONSTANTOP)) { + auto weight = MutableWeights(op_desc); if (weight == nullptr) { GELOGI("const op has no weight, op name:%s", node.GetName().c_str()); return ret; diff --git a/src/common/graph/utils/tensor_utils.cc b/src/common/graph/utils/tensor_utils.cc index 674cab55..26ac8cc8 100644 --- a/src/common/graph/utils/tensor_utils.cc +++ b/src/common/graph/utils/tensor_utils.cc @@ -19,6 +19,7 @@ #include "debug/ge_log.h" #include "framework/common/debug/ge_log.h" +#include "common/util/error_manager/error_manager.h" #include "graph/ge_tensor.h" #include "graph/types.h" #include "graph/utils/type_utils.h" @@ -105,7 +106,10 @@ static graphStatus CalcElementCntByDims(const std::vector &dims, int64_ element_cnt = 1; for (int64_t dim : dims) { if (CheckMultiplyOverflowInt64(element_cnt, dim)) { - GELOGE(GRAPH_FAILED, "CalcElementCntByDims failed, as when multiplying %ld and %ld.", element_cnt, dim); + ErrorManager::GetInstance().ATCReportErrMessage( + "E19013", {"function", "var1", "var2"}, + {"CheckMultiplyOverflowInt64", std::to_string(element_cnt), std::to_string(dim)}); + GELOGE(GRAPH_FAILED, "CalcElementCntByDims failed, when multiplying %ld and %ld.", element_cnt, dim); return GRAPH_FAILED; } element_cnt *= dim; @@ -273,7 +277,6 @@ static graphStatus CalcTensorElementCnt(const std::vector &dims, Format case FORMAT_FRACTAL_Z: graph_status = CalcElementCntOfFractalZ(dims, data_type, element_cnt); break; - case FORMAT_NC1HWC0_C04: case FORMAT_FRACTAL_NZ: case FORMAT_FRACTAL_ZZ: case FORMAT_NDHWC: @@ -285,6 +288,7 @@ static graphStatus CalcTensorElementCnt(const std::vector &dims, Format case FORMAT_NDC1HWC0: case FORMAT_FRACTAL_Z_C04: case FORMAT_FRACTAL_ZN_LSTM: + case FORMAT_NC1HWC0_C04: graph_status = CalcElementCntByDims(dims, element_cnt); break; default: diff --git a/src/common/graph/utils/type_utils.cc b/src/common/graph/utils/type_utils.cc index e4986931..5215b141 100644 --- a/src/common/graph/utils/type_utils.cc +++ b/src/common/graph/utils/type_utils.cc @@ -147,7 +147,8 @@ static const std::map kStringToFormatMap = { {"FRACTAL_ZN_LSTM", FORMAT_FRACTAL_ZN_LSTM}, {"FRACTAL_Z_G", FORMAT_FRACTAL_Z_G}, {"FORMAT_RESERVED", FORMAT_RESERVED}, - {"ALL", FORMAT_ALL}}; + {"ALL", FORMAT_ALL}, + {"NULL", FORMAT_NULL}}; static const std::map kDataTypeToStringMap = { {DT_UNDEFINED, "DT_UNDEFINED"}, // Used to indicate a DataType field has not been set. diff --git a/src/ge/CMakeLists.txt b/src/ge/CMakeLists.txt index 894eaf1e..8d20caf2 100755 --- a/src/ge/CMakeLists.txt +++ b/src/ge/CMakeLists.txt @@ -60,6 +60,7 @@ file(GLOB TRAIN_SRC_LIST RELATIVE ${CMAKE_CURRENT_LIST_DIR} "common/formats/formats.cc" "common/formats/utils/formats_trans_utils.cc" "common/fp16_t.cc" + "common/ge/op_tiling_manager.cc" "common/ge/plugin_manager.cc" "common/helper/model_cache_helper.cc" "common/profiling/profiling_manager.cc" @@ -94,7 +95,6 @@ file(GLOB TRAIN_SRC_LIST RELATIVE ${CMAKE_CURRENT_LIST_DIR} "graph/load/new_model_manager/task_info/super_kernel/super_kernel.cc" "graph/load/new_model_manager/task_info/super_kernel/super_kernel_factory.cc" "graph/load/new_model_manager/task_info/task_info.cc" - "graph/load/output/output.cc" "graph/manager/*.cc" "graph/manager/model_manager/event_manager.cc" "graph/manager/util/debug.cc" @@ -159,8 +159,11 @@ file(GLOB TRAIN_SRC_LIST RELATIVE ${CMAKE_CURRENT_LIST_DIR} "hybrid/node_executor/aicpu/aicpu_ext_info.cc" "hybrid/node_executor/aicpu/aicpu_node_executor.cc" "hybrid/node_executor/compiledsubgraph/known_node_executor.cc" + "hybrid/node_executor/controlop/control_op_executor.cc" + "hybrid/node_executor/hccl/hccl_node_executor.cc" "hybrid/node_executor/hostcpu/ge_local_node_executor.cc" "hybrid/node_executor/node_executor.cc" + "hybrid/node_executor/partitioned_call/partitioned_call_node_executor.cc" "hybrid/node_executor/task_context.cc" "init/gelib.cc" "model/ge_model.cc" @@ -204,6 +207,7 @@ file(GLOB INFER_SRC_LIST RELATIVE ${CMAKE_CURRENT_LIST_DIR} "common/formats/formats.cc" "common/formats/utils/formats_trans_utils.cc" "common/fp16_t.cc" + "common/ge/op_tiling_manager.cc" "common/ge/plugin_manager.cc" "common/helper/model_cache_helper.cc" "common/profiling/profiling_manager.cc" @@ -236,7 +240,6 @@ file(GLOB INFER_SRC_LIST RELATIVE ${CMAKE_CURRENT_LIST_DIR} "graph/load/new_model_manager/task_info/super_kernel/super_kernel.cc" "graph/load/new_model_manager/task_info/super_kernel/super_kernel_factory.cc" "graph/load/new_model_manager/task_info/task_info.cc" - "graph/load/output/output.cc" "graph/manager/*.cc" "graph/manager/model_manager/event_manager.cc" "graph/manager/util/debug.cc" diff --git a/src/ge/client/ge_api.cc b/src/ge/client/ge_api.cc index ae6a9892..120c144a 100644 --- a/src/ge/client/ge_api.cc +++ b/src/ge/client/ge_api.cc @@ -28,6 +28,7 @@ #include "graph/opsproto_manager.h" #include "graph/utils/type_utils.h" #include "graph/manager/util/rt_context_util.h" +#include "graph/common/ge_call_wrapper.h" #include "register/op_registry.h" #include "common/ge/tbe_plugin_manager.h" @@ -41,8 +42,8 @@ namespace { const int32_t kMaxStrLen = 128; } -static bool kGeInitialized = false; -static std::mutex kGeReleaseMutex; // GEFinalize and ~Session use +static bool g_ge_initialized = false; +static std::mutex g_ge_release_mutex; // GEFinalize and ~Session use namespace ge { void GetOpsProtoPath(std::string &opsproto_path) { @@ -61,31 +62,6 @@ void GetOpsProtoPath(std::string &opsproto_path) { opsproto_path = (path_base + "ops/op_proto/custom/" + ":") + (path_base + "ops/op_proto/built-in/"); } -Status CheckDumpAndReuseMemory(const std::map &options) { - const int kDecimal = 10; - auto dump_op_env = std::getenv("DUMP_OP"); - int dump_op_flag = (dump_op_env != nullptr) ? std::strtol(dump_op_env, nullptr, kDecimal) : 0; - auto disableReuseMemoryIter = options.find("ge.exec.disableReuseMemory"); - if (disableReuseMemoryIter != options.end()) { - if (disableReuseMemoryIter->second == "0") { - GELOGD("ge.exec.disableReuseMemory=0, reuse memory is open"); - if (dump_op_flag) { - GELOGW("Will dump incorrect op data with GE Option ge.exec.disableReuseMemory=0"); - } - } else if (disableReuseMemoryIter->second == "1") { - GELOGD("ge.exec.disableReuseMemory=1, reuse memory is close"); - } else { - GELOGE(PARAM_INVALID, "CheckDumpAndReuseMemory ge.exec.disableReuseMemory is valid"); - return FAILED; - } - } else { - if (dump_op_flag) { - GELOGW("Will dump incorrect op data with default reuse memory"); - } - } - return SUCCESS; -} - Status CheckOptionsValid(const std::map &options) { // check job_id is valid auto job_id_iter = options.find(OPTION_EXEC_JOB_ID); @@ -96,11 +72,6 @@ Status CheckOptionsValid(const std::map &options) { } } - // Check ge.exec.disableReuseMemory and env DUMP_OP - if (CheckDumpAndReuseMemory(options) != SUCCESS) { - return FAILED; - } - return SUCCESS; } @@ -108,7 +79,7 @@ Status CheckOptionsValid(const std::map &options) { Status GEInitialize(const std::map &options) { GELOGT(TRACE_INIT, "GEInitialize start"); // 0.check init status - if (kGeInitialized) { + if (g_ge_initialized) { GELOGW("GEInitialize is called more than once"); return SUCCESS; } @@ -147,9 +118,9 @@ Status GEInitialize(const std::map &options) { } // 7.check return status, return - if (!kGeInitialized) { + if (!g_ge_initialized) { // Initialize success, first time calling initialize - kGeInitialized = true; + g_ge_initialized = true; } GELOGT(TRACE_STOP, "GEInitialize finished"); @@ -160,12 +131,12 @@ Status GEInitialize(const std::map &options) { Status GEFinalize() { GELOGT(TRACE_INIT, "GEFinalize start"); // check init status - if (!kGeInitialized) { + if (!g_ge_initialized) { GELOGW("GEFinalize is called before GEInitialize"); return SUCCESS; } - std::lock_guard lock(kGeReleaseMutex); + std::lock_guard lock(g_ge_release_mutex); // call Finalize Status ret = SUCCESS; Status middle_ret; @@ -187,10 +158,10 @@ Status GEFinalize() { ret = middle_ret; } - if (kGeInitialized && ret == SUCCESS) { + if (g_ge_initialized && ret == SUCCESS) { // Unified destruct rt_context - RtContextUtil::GetInstance().DestroyrtContexts(); - kGeInitialized = false; + RtContextUtil::GetInstance().DestroyAllRtContexts(); + g_ge_initialized = false; } GELOGT(TRACE_STOP, "GEFinalize finished"); @@ -202,7 +173,7 @@ Session::Session(const std::map &options) { GELOGT(TRACE_INIT, "Session Constructor start"); // check init status sessionId_ = 0; - if (!kGeInitialized) { + if (!g_ge_initialized) { GELOGE(GE_CLI_GE_NOT_INITIALIZED); return; } @@ -232,13 +203,13 @@ Session::Session(const std::map &options) { Session::~Session() { GELOGT(TRACE_INIT, "Session Destructor start"); // 0.check init status - if (!kGeInitialized) { + if (!g_ge_initialized) { GELOGW("GE is not yet initialized or is finalized."); return; } Status ret = FAILED; - std::lock_guard lock(kGeReleaseMutex); + std::lock_guard lock(g_ge_release_mutex); try { uint64_t session_id = sessionId_; // call DestroySession diff --git a/src/ge/common/convert/pb2json.cc b/src/ge/common/convert/pb2json.cc index 832a8278..0a5d24ee 100644 --- a/src/ge/common/convert/pb2json.cc +++ b/src/ge/common/convert/pb2json.cc @@ -72,9 +72,6 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void Pb2Json::Message2Json(cons void Pb2Json::OneField2Json(const ProtobufMsg &message, const ProtobufFieldDescriptor *field, const ProtobufReflection *reflection, const set &black_fields, Json &json, bool enum2str) { - if (field == nullptr || reflection == nullptr) { - return; - } switch (field->type()) { case ProtobufFieldDescriptor::TYPE_MESSAGE: { const ProtobufMsg &tmp_message = reflection->GetMessage(message, field); @@ -118,8 +115,12 @@ void Pb2Json::OneField2Json(const ProtobufMsg &message, const ProtobufFieldDescr case ProtobufFieldDescriptor::TYPE_FLOAT: char str[kSignificantDigits]; - sprintf_s(str, kSignificantDigits, "%g", reflection->GetFloat(message, field)); - json[field->name()] = str; + if (sprintf_s(str, kSignificantDigits, "%g", reflection->GetFloat(message, field)) != -1) { + json[field->name()] = str; + } else { + json[field->name()] = reflection->GetFloat(message, field); + } + break; case ProtobufFieldDescriptor::TYPE_STRING: diff --git a/src/ge/common/formats/format_transfers/datatype_transfer.cc b/src/ge/common/formats/format_transfers/datatype_transfer.cc index 0bd4b8e5..08c6889f 100644 --- a/src/ge/common/formats/format_transfers/datatype_transfer.cc +++ b/src/ge/common/formats/format_transfers/datatype_transfer.cc @@ -29,7 +29,6 @@ namespace ge { namespace formats { - namespace { enum DataTypeTransMode { kTransferWithDatatypeFloatToFloat16, diff --git a/src/ge/common/formats/format_transfers/datatype_transfer.h b/src/ge/common/formats/format_transfers/datatype_transfer.h index 0702592f..4d93fd6c 100644 --- a/src/ge/common/formats/format_transfers/datatype_transfer.h +++ b/src/ge/common/formats/format_transfers/datatype_transfer.h @@ -27,7 +27,6 @@ namespace ge { namespace formats { - struct CastArgs { const uint8_t *data; size_t src_data_size; diff --git a/src/ge/common/formats/format_transfers/format_transfer_dhwcn_fracz3D.cc b/src/ge/common/formats/format_transfers/format_transfer_dhwcn_fracz3D.cc index dc8e1033..76d8696a 100644 --- a/src/ge/common/formats/format_transfers/format_transfer_dhwcn_fracz3D.cc +++ b/src/ge/common/formats/format_transfers/format_transfer_dhwcn_fracz3D.cc @@ -179,6 +179,5 @@ Status FormatTransferDhwcnFractalZ3D::TransShape(Format src_format, const std::v } REGISTER_FORMAT_TRANSFER(FormatTransferDhwcnFractalZ3D, FORMAT_DHWCN, FORMAT_FRACTAL_Z_3D) - } // namespace formats } // namespace ge diff --git a/src/ge/common/formats/format_transfers/format_transfer_dhwnc_fracz3D_transpose.cc b/src/ge/common/formats/format_transfers/format_transfer_dhwnc_fracz3D_transpose.cc index 11e3d270..9de2e3a0 100644 --- a/src/ge/common/formats/format_transfers/format_transfer_dhwnc_fracz3D_transpose.cc +++ b/src/ge/common/formats/format_transfers/format_transfer_dhwnc_fracz3D_transpose.cc @@ -180,6 +180,5 @@ Status FormatTransferDhwncFractalZ3DTranspose::TransShape(Format src_format, con } REGISTER_FORMAT_TRANSFER(FormatTransferDhwncFractalZ3DTranspose, FORMAT_DHWNC, FORMAT_FRACTAL_Z_3D_TRANSPOSE) - } // namespace formats } // namespace ge diff --git a/src/ge/common/formats/format_transfers/format_transfer_fractal_nz.cc b/src/ge/common/formats/format_transfers/format_transfer_fractal_nz.cc index ff7b84a4..65798f29 100644 --- a/src/ge/common/formats/format_transfers/format_transfer_fractal_nz.cc +++ b/src/ge/common/formats/format_transfers/format_transfer_fractal_nz.cc @@ -56,7 +56,7 @@ Status TransShapeToFracNz(const ShapeVector &src_shape, DataType data_type, Shap dst_shape.clear(); hw_shape.clear(); auto w0 = GetCubeSizeByDataType(data_type); - auto h0 = GetCubeSizeByDataType(data_type); + int64_t h0 = kCubeSize; switch (src_shape.size()) { case 1: dst_shape.push_back(Ceil(src_shape[0], w0)); diff --git a/src/ge/common/formats/format_transfers/format_transfer_fractal_z.cc b/src/ge/common/formats/format_transfers/format_transfer_fractal_z.cc index f3d06496..f2ec29da 100644 --- a/src/ge/common/formats/format_transfers/format_transfer_fractal_z.cc +++ b/src/ge/common/formats/format_transfers/format_transfer_fractal_z.cc @@ -19,6 +19,7 @@ #include #include +#include "common/debug/log.h" #include "common/formats/utils/formats_definitions.h" #include "common/formats/utils/formats_trans_utils.h" #include "framework/common/debug/ge_log.h" @@ -107,8 +108,8 @@ Status TransFormatFromNchwToFz(const TransArgs &args, TransResult &result) { int64_t hw = h * w; int64_t chw = c * hw; - int64_t hwc0 = hw * c0; int64_t nchw = n * chw; + int64_t hwc0 = hw * c0; // horizontal fractal matrix count (N) int64_t hf_cnt = Ceil(n, static_cast(kNiSize)); @@ -119,18 +120,15 @@ Status TransFormatFromNchwToFz(const TransArgs &args, TransResult &result) { int64_t total_ele_cnt = hf_cnt * vf_cnt * fractal_ele_cnt; int size = GetSizeByDataType(args.src_data_type); int64_t dst_size = total_ele_cnt * size; - if (dst_size == 0) { - result.length = static_cast(dst_size); - return SUCCESS; - } + GE_CHK_BOOL_EXEC_NOLOG(dst_size != 0, result.length = static_cast(dst_size); return SUCCESS;); std::shared_ptr dst(new (std::nothrow) uint8_t[dst_size], std::default_delete()); - if (dst == nullptr) { + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG( + dst == nullptr, GELOGE(OUT_OF_MEMORY, "Failed to trans format from %s to %s, can not alloc the memory for dst buf %ld", TypeUtils::FormatToSerialString(args.src_format).c_str(), TypeUtils::FormatToSerialString(args.dst_format).c_str(), dst_size); - return OUT_OF_MEMORY; - } + return OUT_OF_MEMORY;); for (int64_t vfi = 0; vfi < vf_cnt; vfi++) { // vertical fractal matrix base index @@ -156,12 +154,20 @@ Status TransFormatFromNchwToFz(const TransArgs &args, TransResult &result) { auto protected_size = dst_size - offset < static_cast(SECUREC_MEM_MAX_LEN) ? dst_size - offset : static_cast(SECUREC_MEM_MAX_LEN); - errno_t ret; + errno_t ret = EOK; if (need_pad_zero) { ret = memset_s(dst.get() + offset, static_cast(protected_size), 0, static_cast(size)); } else { - ret = memcpy_s(dst.get() + offset, static_cast(protected_size), args.data + src_offset * size, - static_cast(size)); + if (protected_size < size) { + GELOGE(INTERNAL_ERROR, "Failed to operate the dst memory, protected_size is %ld and size is %ld", + protected_size, size); + return INTERNAL_ERROR; + } + char *dst_data = reinterpret_cast(dst.get() + offset); + const char *src_data = reinterpret_cast(args.data + src_offset * size); + for (int64_t index = 0; index < size; index++) { + *dst_data++ = *src_data++; + } } if (ret != EOK) { GELOGE(INTERNAL_ERROR, "Failed to operate the dst memory at offset %ld, error-code %d pad mode %d", offset, @@ -199,18 +205,15 @@ Status TransFormatHwcnToFz(const TransArgs &args, TransResult &result) { dst_size *= dim; } dst_size *= data_size; - if (dst_size == 0) { - result.length = static_cast(dst_size); - return SUCCESS; - } + GE_CHK_BOOL_EXEC_NOLOG(dst_size != 0, result.length = static_cast(dst_size); return SUCCESS;); std::shared_ptr dst(new (std::nothrow) uint8_t[dst_size], std::default_delete()); - if (dst == nullptr) { + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG( + dst == nullptr, GELOGE(OUT_OF_MEMORY, "Failed to trans format from %s to %s, can not alloc the memory for dst buf %ld", TypeUtils::FormatToSerialString(args.src_format).c_str(), TypeUtils::FormatToSerialString(args.dst_format).c_str(), dst_size); - return OUT_OF_MEMORY; - } + return OUT_OF_MEMORY;); for (int64_t c1i = 0; c1i < c1; c1i++) { for (int64_t hi = 0; hi < h; hi++) { @@ -223,14 +226,22 @@ Status TransFormatHwcnToFz(const TransArgs &args, TransResult &result) { ? dst_size - dst_offset : static_cast(SECUREC_MEM_MAX_LEN); auto pad_zero = ((c1i * c0 + c0i) >= c) || (n1n0i >= n); - errno_t ret; + errno_t ret = EOK; if (pad_zero) { ret = memset_s(dst.get() + dst_offset, static_cast(protected_size), 0, static_cast(data_size)); } else { + if (protected_size < data_size) { + GELOGE(INTERNAL_ERROR, "Failed to operate the dst memory, protected_size is %ld and size is %ld", + protected_size, data_size); + return INTERNAL_ERROR; + } int64_t src_idx = hi * wcn + wi * cn + (c1i * c0 + c0i) * n + n1n0i; - ret = memcpy_s(dst.get() + dst_offset, static_cast(protected_size), - args.data + src_idx * data_size, static_cast(data_size)); + char *dst_data = reinterpret_cast(dst.get() + dst_offset); + const char *src_data = reinterpret_cast(args.data + src_idx * data_size); + for (int64_t index = 0; index < data_size; index++) { + *dst_data++ = *src_data++; + } } if (ret != EOK) { GELOGE(INTERNAL_ERROR, "Failed to operate the dst memory at offset %ld, error-code %d, pad mode %d", @@ -269,18 +280,15 @@ Status TransFormatNhwcToFz(const TransArgs &args, TransResult &result) { dst_size *= dim; } dst_size *= data_size; - if (dst_size == 0) { - result.length = static_cast(dst_size); - return SUCCESS; - } + GE_CHK_BOOL_EXEC_NOLOG(dst_size != 0, result.length = static_cast(dst_size); return SUCCESS;); std::shared_ptr dst(new (std::nothrow) uint8_t[dst_size], std::default_delete()); - if (dst == nullptr) { + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG( + dst == nullptr, GELOGE(OUT_OF_MEMORY, "Failed to trans format from %s to %s, can not alloc the memory for dst buf %ld", TypeUtils::FormatToSerialString(args.src_format).c_str(), TypeUtils::FormatToSerialString(args.dst_format).c_str(), dst_size); - return OUT_OF_MEMORY; - } + return OUT_OF_MEMORY;); for (int64_t c1i = 0; c1i < c1; c1i++) { for (int64_t hi = 0; hi < h; hi++) { @@ -293,14 +301,22 @@ Status TransFormatNhwcToFz(const TransArgs &args, TransResult &result) { ? dst_size - dst_offset : static_cast(SECUREC_MEM_MAX_LEN); auto pad_zero = ((c1i * c0 + c0i) >= c) || (n1n0i >= n); - errno_t ret; + errno_t ret = EOK; if (pad_zero) { ret = memset_s(dst.get() + dst_offset, static_cast(protected_size), 0, static_cast(data_size)); } else { + if (protected_size < data_size) { + GELOGE(INTERNAL_ERROR, "Failed to operate the dst memory, protected_size is %ld and size is %ld", + protected_size, data_size); + return INTERNAL_ERROR; + } int64_t src_idx = n1n0i * hwc + hi * wc + wi * c + (c1i * c0 + c0i); - ret = memcpy_s(dst.get() + dst_offset, static_cast(protected_size), - args.data + src_idx * data_size, static_cast(data_size)); + char *dst_data = reinterpret_cast(dst.get() + dst_offset); + const char *src_data = reinterpret_cast(args.data + src_idx * data_size); + for (int64_t index = 0; index < data_size; index++) { + *dst_data++ = *src_data++; + } } if (ret != EOK) { GELOGE(INTERNAL_ERROR, "Failed to operate the dst memory at offset %ld, error-code %d, pad mode %d", @@ -337,16 +353,16 @@ Status FormatTransferFractalZ::TransFormat(const TransArgs &args, TransResult &r return PARAM_INVALID; } - if (args.src_format == FORMAT_NCHW && args.dst_format == FORMAT_FRACTAL_Z) { - return TransFormatFromNchwToFz(args, result); + if (args.src_format == FORMAT_NHWC && args.dst_format == FORMAT_FRACTAL_Z) { + return TransFormatNhwcToFz(args, result); } if (args.src_format == FORMAT_HWCN && args.dst_format == FORMAT_FRACTAL_Z) { return TransFormatHwcnToFz(args, result); } - if (args.src_format == FORMAT_NHWC && args.dst_format == FORMAT_FRACTAL_Z) { - return TransFormatNhwcToFz(args, result); + if (args.src_format == FORMAT_NCHW && args.dst_format == FORMAT_FRACTAL_Z) { + return TransFormatFromNchwToFz(args, result); } return UNSUPPORTED; @@ -358,14 +374,14 @@ Status FormatTransferFractalZ::TransShape(Format src_format, const std::vector 0 ? SUCCESS : UNSUPPORTED; } @@ -109,7 +108,7 @@ Status TransFormatFromNchwToFzC04(const TransArgs &args, TransResult &result) { return NOT_CHANGED; } - /* prepare for padding in chw*/ + // prepare for padding in chw int64_t tmp = h * w * c; int64_t n_o = Ceil(n, static_cast(c0)); int64_t c_o = c0; @@ -309,6 +308,5 @@ Status FormatTransferNchwToFZC04::TransShape(Format src_format, const std::vecto } REGISTER_FORMAT_TRANSFER(FormatTransferNchwToFZC04, FORMAT_NCHW, FORMAT_FRACTAL_Z_C04) - } // namespace formats } // namespace ge diff --git a/src/ge/common/formats/utils/formats_definitions.h b/src/ge/common/formats/utils/formats_definitions.h index d889c33c..2faa60e1 100644 --- a/src/ge/common/formats/utils/formats_definitions.h +++ b/src/ge/common/formats/utils/formats_definitions.h @@ -19,7 +19,6 @@ namespace ge { namespace formats { - static const int kCubeSize = 16; static const int kNiSize = 16; static const int64_t kShapeItemNumMAX = 1024UL * 1024UL * 1024UL * 1024UL; @@ -47,7 +46,6 @@ enum FracZDimIndex { kFracZHWC1, kFracZN0, kFracZNi, kFracZC0, kFracZDimsNum }; enum DhwcnDimIndex { kDhwcnD, kDhwcnH, kDhwcnW, kDhwcnC, kDhwcnN, kDhwcnDimsNum }; enum DhwncDimIndex { kDhwncD, kDhwncH, kDhwncW, kDhwncN, kDhwncC, kDhwncDimsNum }; - } // namespace formats } // namespace ge #endif // GE_COMMON_FORMATS_UTILS_FORMATS_DEFINITIONS_H_ diff --git a/src/ge/common/formats/utils/formats_trans_utils.h b/src/ge/common/formats/utils/formats_trans_utils.h index a8fbd09b..7b902c3e 100644 --- a/src/ge/common/formats/utils/formats_trans_utils.h +++ b/src/ge/common/formats/utils/formats_trans_utils.h @@ -69,7 +69,6 @@ T Ceil(T n1, T n2) { } return (n2 != 0) ? (n1 - 1) / n2 + 1 : 0; } - } // namespace formats } // namespace ge #endif // GE_COMMON_FORMATS_UTILS_FORMATS_TRANS_UTILS_H_ diff --git a/src/ge/common/fp16_t.h b/src/ge/common/fp16_t.h index 34908b95..0fda2cd2 100644 --- a/src/ge/common/fp16_t.h +++ b/src/ge/common/fp16_t.h @@ -600,5 +600,5 @@ int16_t GetManBitLength(T man) { } return len; } -}; // namespace ge +} // namespace ge #endif // GE_COMMON_FP16_T_H_ diff --git a/src/ge/common/ge/op_tiling_manager.cc b/src/ge/common/ge/op_tiling_manager.cc new file mode 100644 index 00000000..7fb7a8fc --- /dev/null +++ b/src/ge/common/ge/op_tiling_manager.cc @@ -0,0 +1,81 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/ge/op_tiling_manager.h" +#include "framework/common/debug/log.h" +#include + +namespace { +const char *const kEnvName = "ASCEND_OPP_PATH"; +const std::string kDefaultPath = "/usr/local/Ascend/opp"; +const std::string kDefaultBuiltInTilingPath = "/op_impl/built-in/liboptiling.so"; +const std::string kDefaultCustomTilingPath = "/op_impl/custom/liboptiling.so"; +const uint8_t kPrefixIndex = 9; +} // namespace + +namespace ge { +void OpTilingManager::ClearHandles() noexcept { + for (const auto &handle : handles_) { + if (dlclose(handle.second) != 0) { + GELOGE(FAILED, "Failed to close handle of %s: %s", handle.first.c_str(), dlerror()); + } + } + handles_.clear(); +} + +OpTilingManager::~OpTilingManager() { ClearHandles(); } + +std::string OpTilingManager::GetPath() { + const char *opp_path_env = std::getenv(kEnvName); + std::string opp_path = kDefaultPath; + if (opp_path_env != nullptr) { + char resolved_path[PATH_MAX]; + if (realpath(opp_path_env, resolved_path) == NULL) { + GELOGE(PARAM_INVALID, "Failed load tiling lib as env 'ASCEND_OPP_PATH'(%s) is invalid path.", opp_path_env); + return std::string(); + } + opp_path = resolved_path; + } + return opp_path; +} + +void OpTilingManager::LoadSo() { + std::string opp_path = GetPath(); + if (opp_path.empty()) { + GELOGW("Skip load tiling lib."); + return; + } + std::string built_in_tiling_lib = opp_path + kDefaultBuiltInTilingPath; + std::string custom_tiling_lib = opp_path + kDefaultCustomTilingPath; + std::string built_in_name = kDefaultBuiltInTilingPath.substr(kPrefixIndex); + std::string custom_name = kDefaultCustomTilingPath.substr(kPrefixIndex); + + void *handle_bi = dlopen(built_in_tiling_lib.c_str(), RTLD_NOW | RTLD_GLOBAL); + if (handle_bi == nullptr) { + GELOGW("Failed to dlopen %s!", dlerror()); + } else { + handles_[built_in_name] = handle_bi; + } + + void *handle_ct = dlopen(custom_tiling_lib.c_str(), RTLD_NOW | RTLD_GLOBAL); + if (handle_ct == nullptr) { + GELOGW("Failed to dlopen %s!", dlerror()); + } else { + handles_[custom_name] = handle_ct; + } +} + +} // namespace ge diff --git a/src/ge/graph/passes/identify_reference_pass.h b/src/ge/common/ge/op_tiling_manager.h similarity index 62% rename from src/ge/graph/passes/identify_reference_pass.h rename to src/ge/common/ge/op_tiling_manager.h index 5f284b4c..320e1411 100644 --- a/src/ge/graph/passes/identify_reference_pass.h +++ b/src/ge/common/ge/op_tiling_manager.h @@ -14,16 +14,25 @@ * limitations under the License. */ -#ifndef GE_GRAPH_PASSES_IDENTIFY_REFERENCE_PASS_H_ -#define GE_GRAPH_PASSES_IDENTIFY_REFERENCE_PASS_H_ +#ifndef GE_COMMON_GE_OP_TILING_MANAGER_H_ +#define GE_COMMON_GE_OP_TILING_MANAGER_H_ -#include "graph/passes/base_pass.h" +#include namespace ge { -class IdentifyReferencePass : public BaseNodePass { +using SoToHandleMap = std::map; + +class OpTilingManager { public: - Status Run(NodePtr &node) override; + OpTilingManager() = default; + ~OpTilingManager(); + void LoadSo(); + + private: + static std::string GetPath(); + void ClearHandles() noexcept; + SoToHandleMap handles_; }; } // namespace ge -#endif // GE_GRAPH_PASSES_IDENTIFY_REFERENCE_PASS_H_ +#endif // GE_COMMON_GE_OP_TILING_MANAGER_H_ diff --git a/src/ge/common/helper/model_helper.cc b/src/ge/common/helper/model_helper.cc index 2f95cbb1..e1f7c75f 100644 --- a/src/ge/common/helper/model_helper.cc +++ b/src/ge/common/helper/model_helper.cc @@ -17,6 +17,7 @@ #include "framework/common/helper/model_helper.h" #include "common/ge/ge_util.h" +#include "common/util/error_manager/error_manager.h" #include "framework/common/debug/log.h" #include "framework/common/util.h" #include "framework/common/debug/ge_log.h" @@ -267,6 +268,7 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY Status ModelHelper::LoadModel(c } auto partition_table = reinterpret_cast(model_addr_tmp_); if (partition_table->num == kOriginalOmPartitionNum) { + model_addr_tmp_ = nullptr; GELOGE(FAILED, "om model is error,please use executable om model"); return FAILED; } @@ -390,107 +392,6 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY GeModelPtr ModelHelper::GetGeMo return out_model; } -// Transit func for model to ge_model. It will be removed when load and build support ge_model in future -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY Status ModelHelper::TransModelToGeModel(const ModelPtr &model, - GeModelPtr &ge_model) { - if (model == nullptr) { - GELOGE(FAILED, "Model is null"); - return FAILED; - } - ge_model = ge::MakeShared(); - GE_CHECK_NOTNULL(ge_model); - ge_model->SetGraph(model->GetGraph()); - ge_model->SetName(model->GetName()); - ge_model->SetVersion(model->GetVersion()); - ge_model->SetPlatformVersion(model->GetPlatformVersion()); - ge_model->SetAttr(model->MutableAttrMap()); - - // Copy weight info - auto compute_graph = ge::GraphUtils::GetComputeGraph(model->GetGraph()); - // ge::Buffer weight; - ge::Buffer weight; - (void)ge::AttrUtils::GetZeroCopyBytes(compute_graph, ge::ATTR_NAME_WEIGHTS_DATA, weight); - ge_model->SetWeight(weight); - // Copy task info - if (model->HasAttr(MODEL_ATTR_TASKS)) { - ge::Buffer task_buffer; - GE_CHK_BOOL_RET_STATUS(ge::AttrUtils::GetZeroCopyBytes(model, MODEL_ATTR_TASKS, task_buffer), FAILED, - "Get bytes failed."); - - std::shared_ptr task = ge::MakeShared(); - GE_CHECK_NOTNULL(task); - GE_IF_BOOL_EXEC(task_buffer.GetData() == nullptr, GELOGE(FAILED, "Get data fail"); return FAILED); - GE_IF_BOOL_EXEC(task_buffer.GetSize() == 0, GELOGE(FAILED, "Get size fail"); return FAILED); - - GE_CHK_BOOL_EXEC(ReadProtoFromArray(task_buffer.GetData(), static_cast(task_buffer.GetSize()), task.get()), - return INTERNAL_ERROR, "ReadProtoFromArray failed."); - - ge_model->SetModelTaskDef(task); - } - // Copy tbe kernel info - // TBEKernelStore kernel_store; - TBEKernelStore kernel_store; - if (compute_graph != nullptr && compute_graph->GetDirectNodesSize() != 0) { - for (const ge::NodePtr &n : compute_graph->GetDirectNode()) { - auto node_op_desc = n->GetOpDesc(); - GE_IF_BOOL_EXEC(node_op_desc == nullptr, continue); - TBEKernelPtr tbe_kernel = node_op_desc->TryGetExtAttr(ge::OP_EXTATTR_NAME_TBE_KERNEL, TBEKernelPtr()); - GE_IF_BOOL_EXEC(tbe_kernel == nullptr, continue); - kernel_store.AddTBEKernel(tbe_kernel); - GELOGI("Add tbe kernel bin %s", tbe_kernel->GetName().c_str()); - } - } - if (!kernel_store.Build()) { - GELOGE(FAILED, "TBE Kernels store build failed!"); - return FAILED; - } - ge_model->SetTBEKernelStore(kernel_store); - - return SUCCESS; -} - -// trasit func for ge_model to Model. will be removed when load and build support ge_model in future -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY Status ModelHelper::TransGeModelToModel(const GeModelPtr &ge_model, - ModelPtr &model) { - if (ge_model == nullptr) { - GELOGE(FAILED, "Ge_model is null"); - return FAILED; - } - model = ge::MakeShared(); - GE_CHECK_NOTNULL(model); - model->SetGraph(ge_model->GetGraph()); - model->SetName(ge_model->GetName()); - model->SetVersion(ge_model->GetVersion()); - model->SetPlatformVersion(ge_model->GetPlatformVersion()); - model->SetAttr(ge_model->MutableAttrMap()); - // Copy weight info - auto compute_graph = ge::GraphUtils::GetComputeGraph(model->GetGraph()); - bool ret = ge::AttrUtils::SetZeroCopyBytes(compute_graph, ge::ATTR_NAME_WEIGHTS_DATA, ge_model->GetWeight()); - if (!ret) { - GELOGE(FAILED, "Copy weight buffer failed!"); - return FAILED; - } - // Copy task info - std::shared_ptr model_task = ge_model->GetModelTaskDefPtr(); - - if (model_task != nullptr) { - int size = model_task->ByteSize(); - ge::Buffer buffer(static_cast(size)); - if (buffer.GetSize() == 0) { - GELOGE(MEMALLOC_FAILED, "alloc model attr task buffer failed!"); - return MEMALLOC_FAILED; - } - // no need to check value - (void)model_task->SerializePartialToArray(buffer.GetData(), size); - ret = ge::AttrUtils::SetZeroCopyBytes(model, MODEL_ATTR_TASKS, std::move(buffer)); - if (!ret) { - GELOGE(FAILED, "Copy task buffer failed!"); - return FAILED; - } - } - return SUCCESS; -} - Status ModelHelper::ReleaseLocalModelData() noexcept { Status result = SUCCESS; if (model_addr_tmp_ != nullptr) { diff --git a/src/ge/common/math/fp16_math.h b/src/ge/common/math/fp16_math.h index 5bc9ac6d..c3a4eb28 100644 --- a/src/ge/common/math/fp16_math.h +++ b/src/ge/common/math/fp16_math.h @@ -92,5 +92,5 @@ fp16_t max(fp16_t fp1, fp16_t fp2); /// @brief Calculate the minimum fp16_t of fp1 and fp2 /// @return Returns minimum fp16_t of fp1 and fp2 fp16_t min(fp16_t fp1, fp16_t fp2); -}; // namespace ge +} // namespace ge #endif // GE_COMMON_MATH_FP16_MATH_H_ \ No newline at end of file diff --git a/src/ge/common/math_util.h b/src/ge/common/math_util.h index 5e783e81..a12be9e0 100644 --- a/src/ge/common/math_util.h +++ b/src/ge/common/math_util.h @@ -27,7 +27,6 @@ #include "mmpa/mmpa_api.h" namespace ge { - /** * @ingroup domi_calibration * @brief Initializes an input array to a specified value @@ -67,7 +66,6 @@ Status NnSet(const int32_t n, const Dtype alpha, Dtype *output) { } return SUCCESS; } - } // end namespace ge #endif // GE_COMMON_MATH_UTIL_H_ diff --git a/src/ge/common/model_saver.cc b/src/ge/common/model_saver.cc index 11d9e804..821fde60 100644 --- a/src/ge/common/model_saver.cc +++ b/src/ge/common/model_saver.cc @@ -60,8 +60,8 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY Status ModelSaver::SaveJsonToFi mode_t mode = S_IRUSR | S_IWUSR; int32_t fd = mmOpen2(real_path, O_RDWR | O_CREAT | O_TRUNC, mode); if (fd == EN_ERROR || fd == EN_INVALID_PARAM) { - ErrorManager::GetInstance().ATCReportErrMessage("E19001", {"filepath", "errMsg"}, {file_path, strerror(errno)}); - GELOGE(FAILED, "Open file failed. file path : %s, %s", file_path, strerror(errno)); + ErrorManager::GetInstance().ATCReportErrMessage("E19001", {"file", "errmsg"}, {file_path, strerror(errno)}); + GELOGE(FAILED, "Open file[%s] failed. %s", file_path, strerror(errno)); return FAILED; } const char *model_char = model_str.c_str(); @@ -69,8 +69,7 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY Status ModelSaver::SaveJsonToFi // Write data to file mmSsize_t mmpa_ret = mmWrite(fd, const_cast((const void *)model_char), len); if (mmpa_ret == EN_ERROR || mmpa_ret == EN_INVALID_PARAM) { - ErrorManager::GetInstance().ATCReportErrMessage("E19003", {"mmpa_ret", "errMsg"}, - {std::to_string(mmpa_ret), strerror(errno)}); + ErrorManager::GetInstance().ATCReportErrMessage("E19004", {"file", "errmsg"}, {file_path, strerror(errno)}); // Need to both print the error info of mmWrite and mmClose, so return ret after mmClose GELOGE(FAILED, "Write to file failed. errno = %d, %s", mmpa_ret, strerror(errno)); ret = FAILED; diff --git a/src/ge/common/profiling/profiling_manager.cc b/src/ge/common/profiling/profiling_manager.cc index 0944b5e0..04d23546 100644 --- a/src/ge/common/profiling/profiling_manager.cc +++ b/src/ge/common/profiling/profiling_manager.cc @@ -336,16 +336,17 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void ProfilingManager::Profilin std::string data; for (const auto &task : task_desc_info) { + std::string model_name = task.model_name; std::string op_name = task.op_name; uint32_t block_dim = task.block_dim; uint32_t task_id = task.task_id; uint32_t stream_id = task.stream_id; - data = op_name.append(" ").append(std::to_string(block_dim) - .append(" ") - .append(std::to_string(task_id)) - .append(" ") - .append(std::to_string(stream_id)) - .append("\n")); + data = model_name.append(" ").append(op_name).append(" ").append(std::to_string(block_dim) + .append(" ") + .append(std::to_string(task_id)) + .append(" ") + .append(std::to_string(stream_id)) + .append("\n")); Msprof::Engine::ReporterData reporter_data{}; reporter_data.deviceId = device_id; @@ -376,7 +377,12 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void ProfilingManager::Profilin std::string data; for (const auto &graph : compute_graph_desc_info) { - data.append("op_name:").append(graph.op_name).append(" op_type:").append(graph.op_type); + data.append("model_name:") + .append(graph.model_name) + .append(" op_name:") + .append(graph.op_name) + .append(" op_type:") + .append(graph.op_type); for (size_t i = 0; i < graph.input_format.size(); ++i) { data.append(" input_id:") .append(std::to_string(i)) diff --git a/src/ge/common/properties_manager.cc b/src/ge/common/properties_manager.cc index cf1ada05..0c2b1db6 100644 --- a/src/ge/common/properties_manager.cc +++ b/src/ge/common/properties_manager.cc @@ -20,15 +20,204 @@ #include #include +#include "common/ge/ge_util.h" #include "common/util.h" #include "framework/common/debug/ge_log.h" #include "framework/common/debug/log.h" #include "framework/common/ge_types.h" #include "framework/common/types.h" #include "graph/debug/ge_attr_define.h" +#include "graph/ge_context.h" #include "graph/utils/attr_utils.h" namespace ge { +namespace { +const string kEnableFlag = "1"; + +const uint32_t kAicoreOverflow = (0x1 << 0); +const uint32_t kAtomicOverflow = (0x1 << 1); +const uint32_t kAllOverflow = (kAicoreOverflow | kAtomicOverflow); +} // namespace + +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY DumpProperties::DumpProperties(const DumpProperties &other) { + CopyFrom(other); +} + +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY DumpProperties &DumpProperties::operator=( + const DumpProperties &other) { + CopyFrom(other); + return *this; +} + +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void DumpProperties::InitByOptions() { + enable_dump_.clear(); + enable_dump_debug_.clear(); + dump_path_.clear(); + dump_step_.clear(); + dump_mode_.clear(); + is_op_debug_ = false; + op_debug_mode_ = 0; + + string enable_dump; + (void)GetContext().GetOption(OPTION_EXEC_ENABLE_DUMP, enable_dump); + enable_dump_ = enable_dump; + + string enable_dump_debug; + (void)GetContext().GetOption(OPTION_EXEC_ENABLE_DUMP_DEBUG, enable_dump_debug); + enable_dump_debug_ = enable_dump_debug; + + if ((enable_dump_ == kEnableFlag) || (enable_dump_debug_ == kEnableFlag)) { + string dump_path; + if (GetContext().GetOption(OPTION_EXEC_DUMP_PATH, dump_path) == GRAPH_SUCCESS) { + if (!dump_path.empty() && dump_path[dump_path.size() - 1] != '/') { + dump_path = dump_path + "/"; + } + dump_path = dump_path + CurrentTimeInStr() + "/"; + GELOGI("Get dump path %s successfully", dump_path.c_str()); + SetDumpPath(dump_path); + } else { + GELOGW("DUMP_PATH is not set"); + } + } + + if (enable_dump_ == kEnableFlag) { + string dump_step; + if (GetContext().GetOption(OPTION_EXEC_DUMP_STEP, dump_step) == GRAPH_SUCCESS) { + GELOGD("Get dump step %s successfully", dump_step.c_str()); + SetDumpStep(dump_step); + } + string dump_mode; + if (GetContext().GetOption(OPTION_EXEC_DUMP_MODE, dump_mode) == GRAPH_SUCCESS) { + GELOGD("Get dump mode %s successfully", dump_mode.c_str()); + SetDumpMode(dump_mode); + } + AddPropertyValue(DUMP_ALL_MODEL, {}); + } + + SetDumpDebugOptions(); +} + +// The following is the new dump scenario of the fusion operator +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void DumpProperties::AddPropertyValue( + const std::string &model, const std::set &layers) { + for (const std::string &layer : layers) { + GELOGI("This model %s config to dump layer %s", model.c_str(), layer.c_str()); + } + + model_dump_properties_map_[model] = layers; +} + +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void DumpProperties::DeletePropertyValue(const std::string &model) { + auto iter = model_dump_properties_map_.find(model); + if (iter != model_dump_properties_map_.end()) { + model_dump_properties_map_.erase(iter); + } +} + +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY std::set DumpProperties::GetAllDumpModel() const { + std::set model_list; + for (auto &iter : model_dump_properties_map_) { + model_list.insert(iter.first); + } + + return model_list; +} + +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY std::set DumpProperties::GetPropertyValue( + const std::string &model) const { + auto iter = model_dump_properties_map_.find(model); + if (iter != model_dump_properties_map_.end()) { + return iter->second; + } + return {}; +} + +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY bool DumpProperties::IsLayerNeedDump( + const std::string &model, const std::string &om_name, const std::string &op_name) const { + // if dump all + if (model_dump_properties_map_.find(DUMP_ALL_MODEL) != model_dump_properties_map_.end()) { + return true; + } + + // if this model need dump + auto om_name_iter = model_dump_properties_map_.find(om_name); + auto model_name_iter = model_dump_properties_map_.find(model); + if (om_name_iter != model_dump_properties_map_.end() || model_name_iter != model_dump_properties_map_.end()) { + // if no dump layer info, dump all layer in this model + auto model_iter = om_name_iter != model_dump_properties_map_.end() ? om_name_iter : model_name_iter; + if (model_iter->second.empty()) { + return true; + } + + return model_iter->second.find(op_name) != model_iter->second.end(); + } + + GELOGD("Model %s is not seated to be dump.", model.c_str()); + return false; +} + +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void DumpProperties::SetDumpPath(const std::string &path) { + dump_path_ = path; +} + +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY std::string DumpProperties::GetDumpPath() const { return dump_path_; } + +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void DumpProperties::SetDumpStep(const std::string &step) { + dump_step_ = step; +} + +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY std::string DumpProperties::GetDumpStep() const { return dump_step_; } + +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void DumpProperties::SetDumpMode(const std::string &mode) { + dump_mode_ = mode; +} + +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY std::string DumpProperties::GetDumpMode() const { return dump_mode_; } + +void DumpProperties::CopyFrom(const DumpProperties &other) { + if (&other != this) { + enable_dump_ = other.enable_dump_; + enable_dump_debug_ = other.enable_dump_debug_; + dump_path_ = other.dump_path_; + dump_step_ = other.dump_step_; + dump_mode_ = other.dump_mode_; + + model_dump_properties_map_ = other.model_dump_properties_map_; + is_op_debug_ = other.is_op_debug_; + op_debug_mode_ = other.op_debug_mode_; + } +} + +void DumpProperties::SetDumpDebugOptions() { + if (enable_dump_debug_ == kEnableFlag) { + string dump_debug_mode; + if (GetContext().GetOption(OPTION_EXEC_DUMP_DEBUG_MODE, dump_debug_mode) == GRAPH_SUCCESS) { + GELOGD("Get dump debug mode %s successfully", dump_debug_mode.c_str()); + } else { + GELOGW("Dump debug mode is not set."); + return; + } + + if (dump_debug_mode == OP_DEBUG_AICORE) { + GELOGD("ge.exec.dumpDebugMode=aicore_overflow, op debug is open."); + is_op_debug_ = true; + op_debug_mode_ = kAicoreOverflow; + } else if (dump_debug_mode == OP_DEBUG_ATOMIC) { + GELOGD("ge.exec.dumpDebugMode=atomic_overflow, op debug is open."); + is_op_debug_ = true; + op_debug_mode_ = kAtomicOverflow; + } else if (dump_debug_mode == OP_DEBUG_ALL) { + GELOGD("ge.exec.dumpDebugMode=all, op debug is open."); + is_op_debug_ = true; + op_debug_mode_ = kAllOverflow; + } else { + GELOGW("ge.exec.dumpDebugMode is invalid."); + } + } else { + GELOGI("ge.exec.enableDumpDebug is false or is not set."); + } +} + PropertiesManager::PropertiesManager() : is_inited_(false), delimiter("=") {} PropertiesManager::~PropertiesManager() {} @@ -159,131 +348,22 @@ PropertiesManager::GetPropertyMap() { // Set separator FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void PropertiesManager::SetPropertyDelimiter(const std::string &de) { + std::lock_guard lock(mutex_); delimiter = de; } -// The following is the new dump scenario of the fusion operator -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void PropertiesManager::AddDumpPropertyValue( - const std::string &model, const std::set &layers) { - for (const std::string &layer : layers) { - GELOGI("This model %s config to dump layer %s", model.c_str(), layer.c_str()); - } - - std::lock_guard lock(dump_mutex_); - model_dump_properties_map_[model] = layers; -} - -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void PropertiesManager::DeleteDumpPropertyValue( - const std::string &model) { - std::lock_guard lock(dump_mutex_); - auto iter = model_dump_properties_map_.find(model); - if (iter != model_dump_properties_map_.end()) { - model_dump_properties_map_.erase(iter); - } -} - -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void PropertiesManager::ClearDumpPropertyValue() { - std::lock_guard lock(dump_mutex_); - model_dump_properties_map_.clear(); -} - -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY std::set PropertiesManager::GetAllDumpModel() { - std::set model_list; - std::lock_guard lock(dump_mutex_); - for (auto &iter : model_dump_properties_map_) { - model_list.insert(iter.first); - } - - return model_list; -} - -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY std::set PropertiesManager::GetDumpPropertyValue( - const std::string &model) { - std::lock_guard lock(dump_mutex_); - auto iter = model_dump_properties_map_.find(model); - if (iter != model_dump_properties_map_.end()) { - return iter->second; - } - return {}; -} - -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY bool PropertiesManager::IsLayerNeedDump(const std::string &model, - const std::string &om_name, - const std::string &op_name) { - std::lock_guard lock(dump_mutex_); - // if dump all - if (model_dump_properties_map_.find(ge::DUMP_ALL_MODEL) != model_dump_properties_map_.end()) { - return true; - } - - // if this model need dump - auto om_name_iter = model_dump_properties_map_.find(om_name); - auto model_name_iter = model_dump_properties_map_.find(model); - if (om_name_iter != model_dump_properties_map_.end() || model_name_iter != model_dump_properties_map_.end()) { - // if no dump layer info, dump all layer in this model - auto model_iter = om_name_iter != model_dump_properties_map_.end() ? om_name_iter : model_name_iter; - if (model_iter->second.empty()) { - return true; - } - - return model_iter->second.find(op_name) != model_iter->second.end(); - } - - GELOGD("Model %s is not seated to be dump.", model.c_str()); - return false; +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY DumpProperties &PropertiesManager::GetDumpProperties( + uint64_t session_id) { + std::lock_guard lock(mutex_); + // If session_id is not found in dump_properties_map_, operator[] will insert one. + return dump_properties_map_[session_id]; } -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY bool PropertiesManager::QueryModelDumpStatus( - const std::string &model) { - std::lock_guard lock(dump_mutex_); - auto iter = model_dump_properties_map_.find(model); - if (iter != model_dump_properties_map_.end()) { - return true; - } else if (model_dump_properties_map_.find(ge::DUMP_ALL_MODEL) != model_dump_properties_map_.end()) { - return true; +FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void PropertiesManager::RemoveDumpProperties(uint64_t session_id) { + std::lock_guard lock(mutex_); + auto iter = dump_properties_map_.find(session_id); + if (iter != dump_properties_map_.end()) { + dump_properties_map_.erase(iter); } - return false; -} - -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void PropertiesManager::SetDumpOutputModel( - const std::string &output_mode) { - std::lock_guard lock(dump_mutex_); - this->output_mode_ = output_mode; -} - -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY std::string PropertiesManager::GetDumpOutputModel() { - std::lock_guard lock(dump_mutex_); - return this->output_mode_; -} - -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void PropertiesManager::SetDumpOutputPath( - const std::string &output_path) { - std::lock_guard lock(dump_mutex_); - this->output_path_ = output_path; -} - -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY std::string PropertiesManager::GetDumpOutputPath() { - std::lock_guard lock(dump_mutex_); - return this->output_path_; -} - -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void PropertiesManager::SetDumpStep(const std::string &dump_step) { - std::lock_guard lock(dump_mutex_); - this->dump_step_ = dump_step; -} - -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY std::string PropertiesManager::GetDumpStep() { - std::lock_guard lock(dump_mutex_); - return this->dump_step_; -} - -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY void PropertiesManager::SetDumpMode(const std::string &dump_mode) { - std::lock_guard lock(dump_mutex_); - this->dump_mode_ = dump_mode; -} - -FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY std::string PropertiesManager::GetDumpMode() { - std::lock_guard lock(dump_mutex_); - return this->dump_mode_; } } // namespace ge diff --git a/src/ge/common/properties_manager.h b/src/ge/common/properties_manager.h index 7cbb5949..3b1547f5 100644 --- a/src/ge/common/properties_manager.h +++ b/src/ge/common/properties_manager.h @@ -32,6 +32,50 @@ static const char *USE_FUSION __attribute__((unused)) = "FMK_USE_FUSION"; static const char *TIMESTAT_ENABLE __attribute__((unused)) = "DAVINCI_TIMESTAT_ENABLE"; static const char *ANNDROID_DEBUG __attribute__((unused)) = "ANNDROID_DEBUG"; +class DumpProperties { + public: + DumpProperties() = default; + ~DumpProperties() = default; + DumpProperties(const DumpProperties &dump); + DumpProperties &operator=(const DumpProperties &dump); + + void InitByOptions(); + + void AddPropertyValue(const std::string &model, const std::set &layers); + void DeletePropertyValue(const std::string &model); + + std::set GetAllDumpModel() const; + std::set GetPropertyValue(const std::string &model) const; + bool IsLayerNeedDump(const std::string &model, const std::string &om_name, const std::string &op_name) const; + + void SetDumpPath(const std::string &path); + std::string GetDumpPath() const; + + void SetDumpStep(const std::string &step); + std::string GetDumpStep() const; + + void SetDumpMode(const std::string &mode); + std::string GetDumpMode() const; + + bool IsOpDebugOpen() const { return is_op_debug_; } + uint32_t GetOpDebugMode() const { return op_debug_mode_; } + + private: + void CopyFrom(const DumpProperties &other); + void SetDumpDebugOptions(); + + string enable_dump_; + string enable_dump_debug_; + + std::string dump_path_; + std::string dump_step_; + std::string dump_mode_; + std::map> model_dump_properties_map_; + + bool is_op_debug_ = false; + uint32_t op_debug_mode_ = 0; +}; + class PropertiesManager { public: // Singleton @@ -81,21 +125,8 @@ class PropertiesManager { */ void SetPropertyDelimiter(const std::string &de); - void AddDumpPropertyValue(const std::string &model, const std::set &layers); - std::set GetAllDumpModel(); - std::set GetDumpPropertyValue(const std::string &model); - bool IsLayerNeedDump(const std::string &model, const std::string &om_name, const std::string &op_name); - void DeleteDumpPropertyValue(const std::string &model); - void ClearDumpPropertyValue(); - bool QueryModelDumpStatus(const std::string &model); - void SetDumpOutputModel(const std::string &output_model); - std::string GetDumpOutputModel(); - void SetDumpOutputPath(const std::string &output_path); - std::string GetDumpOutputPath(); - void SetDumpStep(const std::string &dump_step); - std::string GetDumpStep(); - void SetDumpMode(const std::string &dump_mode); - std::string GetDumpMode(); + DumpProperties &GetDumpProperties(uint64_t session_id); + void RemoveDumpProperties(uint64_t session_id); private: // Private construct, destructor @@ -119,12 +150,7 @@ class PropertiesManager { std::map properties_map_; std::mutex mutex_; - std::string output_mode_; - std::string output_path_; - std::string dump_step_; - std::string dump_mode_; - std::map> model_dump_properties_map_; // model_dump_layers_map_ - std::mutex dump_mutex_; + std::map dump_properties_map_; }; } // namespace ge diff --git a/src/ge/common/tbe_kernel_store.h b/src/ge/common/tbe_kernel_store.h index da231358..51d69af2 100644 --- a/src/ge/common/tbe_kernel_store.h +++ b/src/ge/common/tbe_kernel_store.h @@ -28,7 +28,6 @@ #include "graph/op_kernel_bin.h" namespace ge { - using TBEKernel = ge::OpKernelBin; using TBEKernelPtr = std::shared_ptr; diff --git a/src/ge/common/types.cc b/src/ge/common/types.cc index 97761dea..80dea8a0 100644 --- a/src/ge/common/types.cc +++ b/src/ge/common/types.cc @@ -26,6 +26,11 @@ const std::string DUMP_LAYER = "layer"; const std::string DUMP_FILE_PATH = "path"; const std::string DUMP_MODE = "dump_mode"; +// op debug mode +const std::string OP_DEBUG_AICORE = "aicore_overflow"; +const std::string OP_DEBUG_ATOMIC = "atomic_overflow"; +const std::string OP_DEBUG_ALL = "all"; + const int DEFAULT_FORMAT = static_cast(ge::FORMAT_NCHW); // Supported public property names const std::string PROP_OME_START_TIME = "ome_start_time"; // start time @@ -277,8 +282,8 @@ REGISTER_OPTYPE_DEFINE(GETSPAN, "GetSpan"); REGISTER_OPTYPE_DEFINE(STOPGRADIENT, "StopGradient"); REGISTER_OPTYPE_DEFINE(PREVENTGRADIENT, "PreventGradient"); REGISTER_OPTYPE_DEFINE(GUARANTEECONST, "GuaranteeConst"); -REGISTER_OPTYPE_DEFINE(BROADCASTGRADIENTARGS, "BroadcastGradientArgs") -REGISTER_OPTYPE_DEFINE(BROADCASTARGS, "BroadcastArgs") +REGISTER_OPTYPE_DEFINE(BROADCASTGRADIENTARGS, "BroadcastGradientArgs"); +REGISTER_OPTYPE_DEFINE(BROADCASTARGS, "BroadcastArgs"); REGISTER_OPTYPE_DEFINE(CONFUSIONMATRIX, "ConfusionMatrix"); REGISTER_OPTYPE_DEFINE(RANK, "Rank"); REGISTER_OPTYPE_DEFINE(PLACEHOLDER, "PlaceHolder"); @@ -286,6 +291,7 @@ REGISTER_OPTYPE_DEFINE(END, "End"); REGISTER_OPTYPE_DEFINE(BASICLSTMCELL, "BasicLSTMCell"); REGISTER_OPTYPE_DEFINE(GETNEXT, "GetNext"); REGISTER_OPTYPE_DEFINE(INITDATA, "InitData"); +REGISTER_OPTYPE_DEFINE(REFIDENTITY, "RefIdentity"); /***************Ann special operator*************************/ REGISTER_OPTYPE_DEFINE(ANN_MEAN, "AnnMean"); @@ -479,72 +485,72 @@ const uint64_t ALLOC_MEMORY_MAX_SIZE = 536870912; // Max size of 512M. #endif /// -///@brief Magic number of model file +/// @brief Magic number of model file /// const uint32_t MODEL_FILE_MAGIC_NUM = 0x444F4D49; // magic number /// -///@brief Model head length +/// @brief Model head length /// const uint32_t MODEL_FILE_HEAD_LEN = 256; /// -///@ingroup domi_omg -///@brief Input node type +/// @ingroup domi_omg +/// @brief Input node type /// const std::string INPUT_TYPE = "Input"; /// -///@ingroup domi_omg -///@brief AIPP label, label AIPP conv operator +/// @ingroup domi_omg +/// @brief AIPP label, label AIPP conv operator /// const std::string AIPP_CONV_FLAG = "Aipp_Conv_Flag"; /// -///@ingroup domi_omg -///@brief AIPP label, label aipp data operator +/// @ingroup domi_omg +/// @brief AIPP label, label aipp data operator /// const std::string AIPP_DATA_FLAG = "Aipp_Data_Flag"; /// -///@ingroup domi_omg -///@brief Record the w dimension of model input corresponding to dynamic AIPP +/// @ingroup domi_omg +/// @brief Record the w dimension of model input corresponding to dynamic AIPP /// const std::string AIPP_RELATED_DATA_DIM_W = "aipp_related_data_dim_w"; /// -///@ingroup domi_omg -///@brief Record the H dimension of model input corresponding to dynamic AIPP +/// @ingroup domi_omg +/// @brief Record the H dimension of model input corresponding to dynamic AIPP /// const std::string AIPP_RELATED_DATA_DIM_H = "aipp_related_data_dim_h"; /// -///@ingroup domi_omg -///@brief The tag of the data operator. Mark this input to the dynamic AIPP operator +/// @ingroup domi_omg +/// @brief The tag of the data operator. Mark this input to the dynamic AIPP operator /// const std::string INPUT_TO_DYNAMIC_AIPP = "input_to_dynamic_aipp"; /// -///@ingroup domi_omg -///@brief DATA node type +/// @ingroup domi_omg +/// @brief DATA node type /// const std::string DATA_TYPE = "Data"; /// -///@ingroup domi_omg -///@brief DATA node type +/// @ingroup domi_omg +/// @brief DATA node type /// const std::string AIPP_DATA_TYPE = "AippData"; /// -///@ingroup domi_omg -///@brief Frame operator type +/// @ingroup domi_omg +/// @brief Frame operator type /// const std::string FRAMEWORK_OP_TYPE = "FrameworkOp"; /// -///@ingroup domi_omg -///@brief Data node type +/// @ingroup domi_omg +/// @brief Data node type /// const std::string ANN_DATA_TYPE = "AnnData"; const std::string ANN_NETOUTPUT_TYPE = "AnnNetOutput"; @@ -552,136 +558,139 @@ const std::string ANN_DEPTHCONV_TYPE = "AnnDepthConv"; const std::string ANN_CONV_TYPE = "AnnConvolution"; const std::string ANN_FC_TYPE = "AnnFullConnection"; /// -///@ingroup domi_omg -///@brief Convolution node type +/// @ingroup domi_omg +/// @brief Convolution node type /// const std::string NODE_NAME_NET_OUTPUT = "Node_Output"; const std::string NODE_NAME_END_GRAPH = "Node_EndGraph"; +const std::string NODE_NAME_OP_DEBUG = "Node_OpDebug"; +const std::string OP_TYPE_OP_DEBUG = "Opdebug"; + /// -///@ingroup domi_omg -///@brief Convolution node type +/// @ingroup domi_omg +/// @brief Convolution node type /// const std::string OP_TYPE_CONVOLUTION = "Convolution"; /// -///@ingroup domi_omg -///@brief Add convolution node name to AIPP +/// @ingroup domi_omg +/// @brief Add convolution node name to AIPP /// const std::string AIPP_CONV_OP_NAME = "aipp_conv_op"; /// -///@ingroup domi_omg -///@brief Operator configuration item separator +/// @ingroup domi_omg +/// @brief Operator configuration item separator /// const std::string OP_CONF_DELIMITER = ":"; /// -///@ingroup domi_omg -///@brief attr value name +/// @ingroup domi_omg +/// @brief attr value name /// const std::string ATTR_NAME_VALUE1 = "value1"; /// -///@ingroup domi_omg -///@brief attr value name, 6d_2_4d C +/// @ingroup domi_omg +/// @brief attr value name, 6d_2_4d C /// const std::string ATTR_NAME_INPUT_CVALUE = "input_cvalue"; /// -///@ingroup domi_omg -///@brief alpha default value +/// @ingroup domi_omg +/// @brief alpha default value /// const float ALPHA_DEFAULT_VALUE = 1.0; /// -///@ingroup domi_omg -///@brief beta default value +/// @ingroup domi_omg +/// @brief beta default value /// const float BETA_DEFAULT_VALUE = 0.0; /// -///@ingroup domi_omg -///@brief coef default value +/// @ingroup domi_omg +/// @brief coef default value /// const float COEF_DEFAULT_VALUE = 0.0; /// -///@ingroup domi_omg -///@brief Relu6 coef value +/// @ingroup domi_omg +/// @brief Relu6 coef value /// const float RELU6_COEF = 6.0; /// -///@ingroup domi_omg -///@brief stride default value +/// @ingroup domi_omg +/// @brief stride default value /// const uint32_t STRIDE_DEFAULT_VALUE = 1; /// -///@ingroup domi_omg -///@brief pad default value +/// @ingroup domi_omg +/// @brief pad default value /// const uint32_t PAD_DEFAULT_VALUE = 0; /// -///@ingroup domi_omg -///@brief dilation default value +/// @ingroup domi_omg +/// @brief dilation default value /// const int DILATION_DEFAULT_VALUE = 1; /// -///@ingroup domi_omg -///@brief kernel default value +/// @ingroup domi_omg +/// @brief kernel default value /// const uint32_t KERNEL_DEFAULT_VALUE = 0; /// -///@ingroup domi_omg -///@brief defaule convolution group size +/// @ingroup domi_omg +/// @brief defaule convolution group size /// const uint32_t DEFAULT_CONV_GROUP = 1; /// -///@ingroup domi_omg -///@brief Default deconvolution adj +/// @ingroup domi_omg +/// @brief Default deconvolution adj /// const uint32_t DEFAULT_DECONV_ADJ = 0; /// -///@ingroup domi_omg -///@brief Represents value 1 +/// @ingroup domi_omg +/// @brief Represents value 1 /// const uint32_t NUM_ONE = 1; /// -///@ingroup domi_omg -///@brief spatial dim size default value +/// @ingroup domi_omg +/// @brief spatial dim size default value /// const int32_t SPATIAL_DIM_DEFAULT_SIZE = 2; /// -///@ingroup domi_omg -///@brief dim extended default value +/// @ingroup domi_omg +/// @brief dim extended default value /// const int32_t DIM_DEFAULT_VALUE = 1; /// -///@ingroup domi_omg -///@brief The first weight list in opdef is filter +/// @ingroup domi_omg +/// @brief The first weight list in opdef is filter /// const int32_t WEIGHT_FILTER_INDEX = 0; /// -///@ingroup domi_omg -///@brief The second weight list in opdef is bias +/// @ingroup domi_omg +/// @brief The second weight list in opdef is bias /// const int32_t WEIGHT_BIAS_INDEX = 1; const int32_t TENSOR_ND_SUPPORT_SIZE = 8; /// -///@ingroup domi_omg -///@brief NCHW index default value +/// @ingroup domi_omg +/// @brief NCHW index default value /// const uint32_t NCHW_DIM_N = 0; const uint32_t NCHW_DIM_C = 1; @@ -689,8 +698,8 @@ const uint32_t NCHW_DIM_H = 2; const uint32_t NCHW_DIM_W = 3; /// -///@ingroup domi_omg -///@brief KCHW index default value +/// @ingroup domi_omg +/// @brief KCHW index default value /// const uint32_t KCHW_DIM_K = 0; const uint32_t KCHW_DIM_C = 1; @@ -698,8 +707,8 @@ const uint32_t KCHW_DIM_H = 2; const uint32_t KCHW_DIM_W = 3; /// -///@ingroup domi_omg -///@brief HWCK index default value +/// @ingroup domi_omg +/// @brief HWCK index default value /// const uint32_t HWCK_DIM_H = 0; const uint32_t HWCK_DIM_W = 1; @@ -707,8 +716,8 @@ const uint32_t HWCK_DIM_C = 2; const uint32_t HWCK_DIM_K = 3; /// -///@ingroup domi_omg -///@brief NHWC index default value +/// @ingroup domi_omg +/// @brief NHWC index default value /// const uint32_t NHWC_DIM_N = 0; const uint32_t NHWC_DIM_H = 1; @@ -716,8 +725,8 @@ const uint32_t NHWC_DIM_W = 2; const uint32_t NHWC_DIM_C = 3; /// -///@ingroup domi_omg -///@brief CHWN index default value +/// @ingroup domi_omg +/// @brief CHWN index default value /// const uint32_t CHWN_DIM_N = 3; const uint32_t CHWN_DIM_C = 0; @@ -725,23 +734,23 @@ const uint32_t CHWN_DIM_H = 1; const uint32_t CHWN_DIM_W = 2; /// -///@ingroup domi_omg -///@brief CHW index default value +/// @ingroup domi_omg +/// @brief CHW index default value /// const uint32_t CHW_DIM_C = 0; const uint32_t CHW_DIM_H = 1; const uint32_t CHW_DIM_W = 2; /// -///@ingroup domi_omg -///@brief HWC index default value +/// @ingroup domi_omg +/// @brief HWC index default value /// const uint32_t HWC_DIM_H = 0; const uint32_t HWC_DIM_W = 1; const uint32_t HWC_DIM_C = 2; /// -///@ingroup domi_omg -///@brief Pad index default value +/// @ingroup domi_omg +/// @brief Pad index default value /// const uint32_t PAD_H_HEAD = 0; const uint32_t PAD_H_TAIL = 1; @@ -749,35 +758,35 @@ const uint32_t PAD_W_HEAD = 2; const uint32_t PAD_W_TAIL = 3; /// -///@ingroup domi_omg -///@brief window index default value +/// @ingroup domi_omg +/// @brief window index default value /// const uint32_t WINDOW_H = 0; const uint32_t WINDOW_W = 1; /// -///@ingroup domi_omg -///@brief stride index default value +/// @ingroup domi_omg +/// @brief stride index default value /// const uint32_t STRIDE_H = 0; const uint32_t STRIDE_W = 1; /// -///@ingroup domi_omg -///@brief dilation index default value +/// @ingroup domi_omg +/// @brief dilation index default value /// const uint32_t DILATION_H = 0; const uint32_t DILATION_W = 1; /// -///@ingroup domi_omg -///@brief the num of XRBG channel +/// @ingroup domi_omg +/// @brief the num of XRBG channel /// const uint32_t XRGB_CHN_NUM = 4; /// -///@ingroup domi_omg -///@brief global pooling default value +/// @ingroup domi_omg +/// @brief global pooling default value /// const bool DEFAULT_GLOBAL_POOLING = false; @@ -801,4 +810,4 @@ const uint32_t STREAM_SWITCH_INPUT_NUM = 2; const std::string NODE_NAME_GLOBAL_STEP = "ge_global_step"; const std::string NODE_NAME_GLOBAL_STEP_ASSIGNADD = "global_step_assignadd"; -}; // namespace ge +} // namespace ge diff --git a/src/ge/common/util.cc b/src/ge/common/util.cc index 50ed2f33..69dc7442 100644 --- a/src/ge/common/util.cc +++ b/src/ge/common/util.cc @@ -56,6 +56,7 @@ const int kWarningThreshold = 536870912 * 2; // 536870912 represent 512M /// The maximum length of the file. /// Based on the security coding specification and the current actual (protobuf) model size, it is determined as 2G-1 const int kMaxFileSizeLimit = INT_MAX; +const char *const kPathValidReason = "The path can only contains 'a-z' 'A-Z' '0-9' '-' '.' '_' and chinese character"; } // namespace namespace ge { @@ -77,7 +78,7 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY bool ReadProtoFromBinaryFile(co std::ifstream fs(real_path, std::ifstream::in | std::ifstream::binary); if (!fs.is_open()) { - ErrorManager::GetInstance().ATCReportErrMessage("E19004", {"realpath"}, {file}); + ErrorManager::GetInstance().ATCReportErrMessage("E19001", {"file", "errmsg"}, {file, "ifstream is_open failed"}); GELOGE(ge::FAILED, "Open real path[%s] failed.", file); return false; } @@ -90,7 +91,7 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY bool ReadProtoFromBinaryFile(co fs.close(); if (!ret) { - ErrorManager::GetInstance().ATCReportErrMessage("E19005", {"filepath"}, {file}); + ErrorManager::GetInstance().ATCReportErrMessage("E19005", {"file"}, {file}); GELOGE(ge::FAILED, "Parse file[%s] failed.", file); return ret; } @@ -114,17 +115,18 @@ long GetFileLength(const std::string &input_file) { GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(real_path.empty(), return -1, "input_file path '%s' not valid", input_file.c_str()); unsigned long long file_length = 0; - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(mmGetFileSize(input_file.c_str(), &file_length) != EN_OK, - ErrorManager::GetInstance().ATCReportErrMessage("E10037", {"filepath"}, {input_file}); - return -1, "Open file[%s] failed", input_file.c_str()); + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG( + mmGetFileSize(input_file.c_str(), &file_length) != EN_OK, + ErrorManager::GetInstance().ATCReportErrMessage("E19001", {"file", "errmsg"}, {input_file, strerror(errno)}); + return -1, "Open file[%s] failed. %s", input_file.c_str(), strerror(errno)); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG((file_length == 0), - ErrorManager::GetInstance().ATCReportErrMessage("E10038", {"filepath"}, {input_file}); + ErrorManager::GetInstance().ATCReportErrMessage("E19015", {"filepath"}, {input_file}); return -1, "File[%s] size is 0, not valid.", input_file.c_str()); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG( file_length > kMaxFileSizeLimit, ErrorManager::GetInstance().ATCReportErrMessage( - "E10039", {"filepath", "filesize", "maxlen"}, + "E19016", {"filepath", "filesize", "maxlen"}, {input_file, std::to_string(file_length), std::to_string(kMaxFileSizeLimit)}); return -1, "File[%s] size %lld is out of limit: %d.", input_file.c_str(), file_length, kMaxFileSizeLimit); return static_cast(file_length); @@ -219,7 +221,7 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY int CreateDirectory(const std:: if (ret != 0) { if (errno != EEXIST) { ErrorManager::GetInstance().ATCReportErrMessage("E19006", {"path"}, {directory_path}); - GELOGW("Cannot create directory %s. Make sure the directory exists and writable.", directory_path.c_str()); + GELOGW("Can not create directory %s. Make sure the directory exists and writable.", directory_path.c_str()); return ret; } } @@ -230,7 +232,7 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY int CreateDirectory(const std:: if (ret != 0) { if (errno != EEXIST) { ErrorManager::GetInstance().ATCReportErrMessage("E19006", {"path"}, {directory_path}); - GELOGW("Cannot create directory %s. Make sure the directory exists and writable.", directory_path.c_str()); + GELOGW("Can not create directory %s. Make sure the directory exists and writable.", directory_path.c_str()); return ret; } } @@ -258,16 +260,16 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY bool ReadProtoFromText(const ch "incorrect parameter. nullptr == file || nullptr == message"); std::string real_path = RealPath(file); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(real_path.empty(), - ErrorManager::GetInstance().ATCReportErrMessage("E10036", {"filepath"}, {file}); - return false, "Get path[%s]'s real path failed", file); + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(real_path.empty(), ErrorManager::GetInstance().ATCReportErrMessage( + "E19000", {"path", "errmsg"}, {file, strerror(errno)}); + return false, "Path[%s]'s realpath is empty, errmsg[%s]", file, strerror(errno)); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(GetFileLength(real_path) == -1, return false, "file size not valid."); std::ifstream fs(real_path.c_str(), std::ifstream::in); if (!fs.is_open()) { - ErrorManager::GetInstance().ATCReportErrMessage("E10040", {"realpth", "protofile"}, {real_path, file}); + ErrorManager::GetInstance().ATCReportErrMessage("E19017", {"realpth", "protofile"}, {real_path, file}); GELOGE(ge::FAILED, "Fail to open proto file real path is '%s' when orginal file path is '%s'.", real_path.c_str(), file); return false; @@ -275,7 +277,7 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY bool ReadProtoFromText(const ch google::protobuf::io::IstreamInputStream input(&fs); bool ret = google::protobuf::TextFormat::Parse(&input, message); - GE_IF_BOOL_EXEC(!ret, ErrorManager::GetInstance().ATCReportErrMessage("E10041", {"protofile"}, {file}); + GE_IF_BOOL_EXEC(!ret, ErrorManager::GetInstance().ATCReportErrMessage("E19018", {"protofile"}, {file}); GELOGE(ret, "Parse file[%s] through [google::protobuf::TextFormat::Parse] failed, " "please check whether the file is a valid protobuf format file.", @@ -360,14 +362,14 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY bool CheckInputPathValid(const // The specified path is empty std::map args_map; if (file_path.empty()) { - ErrorManager::GetInstance().ATCReportErrMessage("E10000", {"parameter"}, {atc_param}); + ErrorManager::GetInstance().ATCReportErrMessage("E10004", {"parameter"}, {atc_param}); GELOGW("Input parameter's value is empty."); return false; } std::string real_path = RealPath(file_path.c_str()); // Unable to get absolute path (does not exist or does not have permission to access) if (real_path.empty()) { - ErrorManager::GetInstance().ATCReportErrMessage("E10002", {"path", "errmsg"}, {file_path.c_str(), strerror(errno)}); + ErrorManager::GetInstance().ATCReportErrMessage("E19000", {"path", "errmsg"}, {file_path, strerror(errno)}); GELOGW("Path[%s]'s realpath is empty, errmsg[%s]", file_path.c_str(), strerror(errno)); return false; } @@ -380,16 +382,14 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY bool CheckInputPathValid(const GE_CHK_BOOL_TRUE_EXEC_WITH_LOG( !ValidateStr(real_path, mode), - ErrorManager::GetInstance().ATCReportErrMessage("E10001", {"parameter", "path"}, {atc_param, real_path}); - return false, - "Input parameter[--%s]'s value[%s] is illegal. The path can only contains 'a-z' 'A-Z' '0-9' '-' '.' '_' " - "and chinese character.", - atc_param.c_str(), real_path.c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E10001", {"parameter", "value", "reason"}, + {atc_param, real_path, kPathValidReason}); + return false, "Invalid value for %s[%s], %s.", atc_param.c_str(), real_path.c_str(), kPathValidReason); // The absolute path points to a file that is not readable if (access(real_path.c_str(), R_OK) != 0) { - ErrorManager::GetInstance().ATCReportErrMessage("E10003", {"path", "errmsg"}, {file_path.c_str(), strerror(errno)}); - GELOGW("Read path[%s] failed, errmsg[%s]", file_path.c_str(), strerror(errno)); + ErrorManager::GetInstance().ATCReportErrMessage("E19003", {"file", "errmsg"}, {file_path.c_str(), strerror(errno)}); + GELOGW("Read file[%s] failed, errmsg[%s]", file_path.c_str(), strerror(errno)); return false; } @@ -400,7 +400,7 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY bool CheckOutputPathValid(const const std::string &atc_param) { // The specified path is empty if (file_path.empty()) { - ErrorManager::GetInstance().ATCReportErrMessage("E10000", {"parameter"}, {atc_param}); + ErrorManager::GetInstance().ATCReportErrMessage("E10004", {"parameter"}, {atc_param}); GELOGW("Input parameter's value is empty."); return false; } @@ -416,18 +416,14 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY bool CheckOutputPathValid(const GE_CHK_BOOL_TRUE_EXEC_WITH_LOG( !ValidateStr(real_path, mode), - ErrorManager::GetInstance().ATCReportErrMessage("E10001", {"parameter", "path"}, {atc_param, real_path}); - return false, - "Input parameter[--%s]'s value[%s] is illegal. The path can only contains 'a-z' 'A-Z' '0-9' '-' '.' '_' " - "and chinese character.", - atc_param.c_str(), real_path.c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E10001", {"parameter", "value", "reason"}, + {atc_param, real_path, kPathValidReason}); + return false, "Invalid value for %s[%s], %s.", atc_param.c_str(), real_path.c_str(), kPathValidReason); // File is not readable or writable if (access(real_path.c_str(), W_OK | F_OK) != 0) { - ErrorManager::GetInstance().ATCReportErrMessage("E10004", {"realpath", "path", "errmsg"}, - {real_path, file_path, strerror(errno)}); - GELOGW("Write file[%s] failed, input path is %s, errmsg[%s]", real_path.c_str(), file_path.c_str(), - strerror(errno)); + ErrorManager::GetInstance().ATCReportErrMessage("E19004", {"file", "errmsg"}, {real_path, strerror(errno)}); + GELOGW("Write file[%s] failed, errmsg[%s]", real_path.c_str(), strerror(errno)); return false; } } else { @@ -445,8 +441,8 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY bool CheckOutputPathValid(const std::string prefix_path = std::string(file_path).substr(0, static_cast(path_split_pos)); // Determine whether the specified path is valid by creating the path if (CreateDirectory(prefix_path) != 0) { - ErrorManager::GetInstance().ATCReportErrMessage("E10005", {"path"}, {file_path}); - GELOGW("Can not create prefix path for path[%s].", file_path.c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E19006", {"path"}, {file_path}); + GELOGW("Can not create directory[%s].", file_path.c_str()); return false; } } diff --git a/src/ge/engine_manager/dnnengine_manager.cc b/src/ge/engine_manager/dnnengine_manager.cc index c8843c09..9afb207f 100644 --- a/src/ge/engine_manager/dnnengine_manager.cc +++ b/src/ge/engine_manager/dnnengine_manager.cc @@ -24,6 +24,7 @@ #include "common/debug/log.h" #include "common/ge/ge_util.h" +#include "common/util/error_manager/error_manager.h" #include "framework/common/debug/ge_log.h" #include "graph/ge_context.h" #include "init/gelib.h" @@ -161,6 +162,10 @@ bool DNNEngineManager::IsEngineRegistered(const std::string &name) { return false; } +void DNNEngineManager::InitPerformanceStaistic() { checksupport_cost_.clear(); } + +const map &DNNEngineManager::GetCheckSupportCost() const { return checksupport_cost_; } + std::string DNNEngineManager::GetDNNEngineName(const OpDescPtr &op_desc) { GE_IF_BOOL_EXEC(op_desc == nullptr, GELOGE(GE_CLI_GE_NOT_INITIALIZED, "DNNEngineManager: op_desc is nullptr"); return ""); @@ -194,15 +199,20 @@ std::string DNNEngineManager::GetDNNEngineName(const OpDescPtr &op_desc) { if (kernel_info_store != kernel_map.end()) { std::string unsupported_reason; // It will be replaced by engine' checksupport + uint64_t start_time = GetCurrentTimestap(); if (kernel_info_store->second->CheckSupported(op_desc, unsupported_reason)) { + checksupport_cost_[kernel_name] += GetCurrentTimestap() - start_time; op_desc->SetOpEngineName(it.engine); op_desc->SetOpKernelLibName(kernel_name); GELOGD("DNNEngineManager:Set OpKernelLibName %s and engine name %s into op_desc %s", kernel_name.c_str(), it.engine.c_str(), op_desc->GetName().c_str()); return it.engine; } else { + checksupport_cost_[kernel_name] += GetCurrentTimestap() - start_time; bool is_custom_op = false; if ((ge::AttrUtils::GetBool(op_desc, kCustomOpFlag, is_custom_op)) && is_custom_op) { + ErrorManager::GetInstance().ATCReportErrMessage("E13001", {"kernelname", "optype", "opname"}, + {kernel_name, op_desc->GetType(), op_desc->GetName()}); GELOGE(FAILED, "The custom operator registered by the user does not support the logic function delivered by this " "network. Check support failed, kernel_name is %s, op type is %s, op name is %s", @@ -221,9 +231,13 @@ std::string DNNEngineManager::GetDNNEngineName(const OpDescPtr &op_desc) { } } for (const auto &it : unsupported_reasons) { + ErrorManager::GetInstance().ATCReportErrMessage("E13002", {"optype", "opskernel", "reason"}, + {op_desc->GetType(), it.first, it.second}); GELOGE(GE_GRAPH_ASSIGN_ENGINE_FAILED, "GetDNNEngineName:Op type %s of ops kernel %s is unsupported, reason:%s", op_desc->GetType().c_str(), it.first.c_str(), it.second.c_str()); } + ErrorManager::GetInstance().ATCReportErrMessage("E13003", {"opname", "optype"}, + {op_desc->GetName(), op_desc->GetType()}); GELOGE(GE_GRAPH_ASSIGN_ENGINE_FAILED, "Can't find any supported ops kernel and engine of %s, type is %s", op_desc->GetName().c_str(), op_desc->GetType().c_str()); return ""; @@ -384,7 +398,13 @@ Status DNNEngineManager::ReadJsonFile(const std::string &file_path, JsonHandle h return FAILED; } - ifs >> *json_file; + try { + ifs >> *json_file; + } catch (const json::exception &e) { + GELOGE(FAILED, "Read json file failed"); + ifs.close(); + return FAILED; + } ifs.close(); GELOGI("Read json file success"); return SUCCESS; diff --git a/src/ge/engine_manager/dnnengine_manager.h b/src/ge/engine_manager/dnnengine_manager.h index ab813398..15628ecf 100644 --- a/src/ge/engine_manager/dnnengine_manager.h +++ b/src/ge/engine_manager/dnnengine_manager.h @@ -63,6 +63,8 @@ class DNNEngineManager { // If can't find appropriate engine name, return "", report error string GetDNNEngineName(const OpDescPtr &op_desc); const map &GetSchedulers() const; + const map &GetCheckSupportCost() const; + void InitPerformanceStaistic(); private: DNNEngineManager(); @@ -78,6 +80,7 @@ class DNNEngineManager { std::map engines_map_; std::map engines_attrs_map_; std::map schedulers_; + std::map checksupport_cost_; bool init_flag_; }; } // namespace ge diff --git a/src/ge/executor/CMakeLists.txt b/src/ge/executor/CMakeLists.txt index cddf25b7..0cdb00e2 100755 --- a/src/ge/executor/CMakeLists.txt +++ b/src/ge/executor/CMakeLists.txt @@ -26,6 +26,7 @@ file(GLOB PROTO_LIST RELATIVE ${CMAKE_CURRENT_LIST_DIR} file(GLOB SRC_LIST RELATIVE ${CMAKE_CURRENT_LIST_DIR} "ge_executor.cc" + "../common/ge/op_tiling_manager.cc" "../common/ge/plugin_manager.cc" "../common/profiling/profiling_manager.cc" "../graph/execute/graph_execute.cc" @@ -59,7 +60,6 @@ file(GLOB SRC_LIST RELATIVE ${CMAKE_CURRENT_LIST_DIR} "../graph/load/new_model_manager/task_info/task_info.cc" "../graph/load/new_model_manager/tbe_handle_store.cc" "../graph/load/new_model_manager/zero_copy_task.cc" - "../graph/load/output/output.cc" "../graph/manager/graph_caching_allocator.cc" "../graph/manager/graph_manager_utils.cc" "../graph/manager/graph_mem_allocator.cc" diff --git a/src/ge/executor/ge_executor.cc b/src/ge/executor/ge_executor.cc index ad7ef1fe..098c57b6 100644 --- a/src/ge/executor/ge_executor.cc +++ b/src/ge/executor/ge_executor.cc @@ -452,7 +452,7 @@ Status GeExecutor::RunModel(const ge::RunModelData &input_data, ge::RunModelData // Get input and output descriptor Status GeExecutor::GetModelDescInfo(uint32_t model_id, std::vector &input_desc, - std::vector &output_desc) { + std::vector &output_desc, bool new_model_desc) { GELOGI("get model desc info begin."); if (!isInit_) { GELOGE(GE_EXEC_NOT_INIT, "GeExecutor has not been initialized!"); @@ -464,8 +464,8 @@ Status GeExecutor::GetModelDescInfo(uint32_t model_id, std::vector input_formats; std::vector output_formats; - Status ret = - GraphExecutor::GetInputOutputDescInfo(model_id, input_desc_infos, output_desc_infos, input_formats, output_formats); + Status ret = GraphExecutor::GetInputOutputDescInfo(model_id, input_desc_infos, output_desc_infos, input_formats, + output_formats, new_model_desc); if (ret != domi::SUCCESS) { GELOGE(ret, "GetInputOutputDescInfo failed. ret = %u", ret); return TransferDomiErrorCode(ret); @@ -854,5 +854,4 @@ Status GeExecutor::GetAllAippInputOutputDims(uint32_t model_id, uint32_t index, GELOGI("GetAllAippInputOutputDims succ."); return SUCCESS; } - } // namespace ge diff --git a/src/ge/executor/module.mk b/src/ge/executor/module.mk index efed8854..0eb87822 100644 --- a/src/ge/executor/module.mk +++ b/src/ge/executor/module.mk @@ -4,6 +4,7 @@ local_ge_executor_src_files := \ ge_executor.cc \ ../common/profiling/profiling_manager.cc \ ../common/ge/plugin_manager.cc \ + ../common/ge/op_tiling_manager.cc \ ../graph/load/graph_loader.cc \ ../graph/execute/graph_execute.cc \ ../omm/csa_interact.cc \ @@ -44,7 +45,6 @@ local_ge_executor_src_files := \ ../graph/load/new_model_manager/task_info/end_graph_task_info.cc \ ../graph/load/new_model_manager/task_info/super_kernel/super_kernel_factory.cc \ ../graph/load/new_model_manager/task_info/super_kernel/super_kernel.cc \ - ../graph/load/output/output.cc \ ../single_op/single_op_manager.cc \ ../single_op/single_op_model.cc \ ../single_op/single_op.cc \ @@ -53,6 +53,7 @@ local_ge_executor_src_files := \ ../single_op/task/build_task_utils.cc \ ../single_op/task/tbe_task_builder.cc \ ../single_op/task/aicpu_task_builder.cc \ + ../single_op/task/aicpu_kernel_task_builder.cc \ ../hybrid/hybrid_davinci_model_stub.cc\ local_ge_executor_c_include := \ diff --git a/src/ge/ge_inference.mk b/src/ge/ge_inference.mk index e12989c0..f18f733a 100644 --- a/src/ge/ge_inference.mk +++ b/src/ge/ge_inference.mk @@ -1,5 +1,5 @@ LOCAL_PATH := $(call my-dir) - +include $(LOCAL_PATH)/stub/Makefile COMMON_LOCAL_SRC_FILES := \ proto/fusion_model.proto \ proto/optimizer_priority.proto \ @@ -32,6 +32,7 @@ COMMON_LOCAL_SRC_FILES := \ GRAPH_MANAGER_LOCAL_SRC_FILES := \ common/ge/plugin_manager.cc\ + common/ge/op_tiling_manager.cc\ init/gelib.cc \ session/inner_session.cc \ session/session_manager.cc \ @@ -91,6 +92,7 @@ OMG_HOST_SRC_FILES := \ graph/passes/no_use_reshape_remove_pass.cc \ graph/passes/iterator_op_pass.cc \ graph/passes/atomic_addr_clean_pass.cc \ + graph/passes/mark_same_addr_pass.cc \ graph/common/omg_util.cc \ graph/common/bcast.cc \ graph/passes/dimension_compute_pass.cc \ @@ -145,6 +147,7 @@ OMG_HOST_SRC_FILES := \ graph/passes/stop_gradient_pass.cc \ graph/passes/prevent_gradient_pass.cc \ graph/passes/identity_pass.cc \ + graph/passes/ref_identity_delete_op_pass.cc \ graph/passes/placeholder_with_default_pass.cc \ graph/passes/snapshot_pass.cc \ graph/passes/guarantee_const_pass.cc \ @@ -153,7 +156,9 @@ OMG_HOST_SRC_FILES := \ graph/passes/folding_pass.cc \ graph/passes/cast_translate_pass.cc \ graph/passes/prune_pass.cc \ - graph/passes/switch_op_pass.cc \ + graph/passes/merge_to_stream_merge_pass.cc \ + graph/passes/switch_to_stream_switch_pass.cc \ + graph/passes/attach_stream_label_pass.cc \ graph/passes/multi_batch_pass.cc \ graph/passes/next_iteration_pass.cc \ graph/passes/control_trigger_pass.cc \ @@ -173,7 +178,6 @@ OMG_HOST_SRC_FILES := \ graph/passes/variable_op_pass.cc \ graph/passes/cast_remove_pass.cc \ graph/passes/transpose_transdata_pass.cc \ - graph/passes/identify_reference_pass.cc \ graph/passes/hccl_memcpy_pass.cc \ graph/passes/flow_ctrl_pass.cc \ graph/passes/link_gen_mask_nodes_pass.cc \ @@ -199,7 +203,6 @@ OME_HOST_SRC_FILES := \ graph/load/new_model_manager/tbe_handle_store.cc \ graph/load/new_model_manager/cpu_queue_schedule.cc \ graph/load/new_model_manager/zero_copy_task.cc \ - graph/load/output/output.cc \ graph/load/new_model_manager/data_dumper.cc \ graph/load/new_model_manager/task_info/task_info.cc \ graph/load/new_model_manager/task_info/event_record_task_info.cc \ @@ -224,6 +227,7 @@ OME_HOST_SRC_FILES := \ single_op/task/build_task_utils.cc \ single_op/task/tbe_task_builder.cc \ single_op/task/aicpu_task_builder.cc \ + single_op/task/aicpu_kernel_task_builder.cc \ single_op/single_op.cc \ single_op/single_op_model.cc \ single_op/stream_resource.cc \ @@ -353,6 +357,28 @@ LOCAL_SHARED_LIBRARIES := \ LOCAL_LDFLAGS := -lrt -ldl +include $(BUILD_HOST_SHARED_LIBRARY) + +#compiler for host infer +include $(CLEAR_VARS) + +LOCAL_MODULE := stub/libge_compiler + +LOCAL_CFLAGS += -DPROTOBUF_INLINE_NOT_IN_HEADERS=0 -DREUSE_MEMORY=1 -O2 +LOCAL_CFLAGS += -DFMK_HOST_INFER -DFMK_SUPPORT_DUMP +ifeq ($(DEBUG), 1) +LOCAL_CFLAGS += -g -O0 +endif + +LOCAL_C_INCLUDES := $(COMMON_LOCAL_C_INCLUDES) + +LOCAL_SRC_FILES := ../../out/ge/lib64/stub/ge_ir_build.cc + + +LOCAL_SHARED_LIBRARIES := + +LOCAL_LDFLAGS := -lrt -ldl + include $(BUILD_HOST_SHARED_LIBRARY) #compiler for device diff --git a/src/ge/ge_runner.mk b/src/ge/ge_runner.mk index a9cfdd82..fe19de02 100644 --- a/src/ge/ge_runner.mk +++ b/src/ge/ge_runner.mk @@ -23,6 +23,7 @@ LIBGE_LOCAL_SRC_FILES := \ common/formats/utils/formats_trans_utils.cc \ common/fp16_t.cc \ common/ge/plugin_manager.cc\ + common/ge/op_tiling_manager.cc\ common/helper/model_cache_helper.cc \ common/profiling/profiling_manager.cc \ engine_manager/dnnengine_manager.cc \ @@ -77,7 +78,6 @@ LIBGE_LOCAL_SRC_FILES := \ graph/load/new_model_manager/task_info/task_info.cc \ graph/load/new_model_manager/tbe_handle_store.cc \ graph/load/new_model_manager/zero_copy_task.cc \ - graph/load/output/output.cc \ graph/manager/graph_context.cc \ graph/manager/graph_manager.cc \ graph/manager/graph_manager_utils.cc \ @@ -99,6 +99,7 @@ LIBGE_LOCAL_SRC_FILES := \ graph/passes/aicpu_constant_folding_pass.cc \ graph/passes/assert_pass.cc \ graph/passes/atomic_addr_clean_pass.cc \ + graph/passes/mark_same_addr_pass.cc \ graph/partition/dynamic_shape_partition.cc \ graph/passes/base_pass.cc \ graph/passes/cast_remove_pass.cc \ @@ -158,8 +159,8 @@ LIBGE_LOCAL_SRC_FILES := \ graph/passes/get_original_format_pass.cc \ graph/passes/guarantee_const_pass.cc \ graph/passes/hccl_memcpy_pass.cc \ - graph/passes/identify_reference_pass.cc \ graph/passes/identity_pass.cc \ + graph/passes/ref_identity_delete_op_pass.cc \ graph/passes/infershape_pass.cc \ graph/passes/isolated_op_remove_pass.cc \ graph/passes/iterator_op_pass.cc \ @@ -191,7 +192,9 @@ LIBGE_LOCAL_SRC_FILES := \ graph/passes/data_pass.cc \ graph/passes/switch_data_edges_bypass.cc \ graph/passes/switch_logic_remove_pass.cc \ - graph/passes/switch_op_pass.cc \ + graph/passes/merge_to_stream_merge_pass.cc \ + graph/passes/switch_to_stream_switch_pass.cc \ + graph/passes/attach_stream_label_pass.cc \ graph/passes/switch_dead_branch_elimination.cc \ graph/passes/replace_transshape_pass.cc \ graph/passes/transop_breadth_fusion_pass.cc \ @@ -230,6 +233,7 @@ LIBGE_LOCAL_SRC_FILES := \ single_op/task/op_task.cc \ single_op/task/tbe_task_builder.cc \ single_op/task/aicpu_task_builder.cc \ + single_op/task/aicpu_kernel_task_builder.cc \ hybrid/common/tensor_value.cc \ hybrid/common/npu_memory_allocator.cc \ hybrid/executor/rt_callback_manager.cc \ @@ -239,12 +243,15 @@ LIBGE_LOCAL_SRC_FILES := \ hybrid/executor/hybrid_model_executor.cc \ hybrid/executor/hybrid_model_async_executor.cc \ hybrid/executor/hybrid_execution_context.cc \ + hybrid/executor/subgraph_context.cc \ + hybrid/executor/subgraph_executor.cc \ hybrid/executor/worker/task_compile_engine.cc \ hybrid/executor/worker/shape_inference_engine.cc \ hybrid/executor/worker/execution_engine.cc \ hybrid/model/hybrid_model.cc \ hybrid/model/hybrid_model_builder.cc \ hybrid/model/node_item.cc \ + hybrid/model/graph_item.cc \ hybrid/node_executor/aicore/aicore_node_executor.cc \ hybrid/node_executor/aicore/aicore_op_task.cc \ hybrid/node_executor/aicore/aicore_task_builder.cc \ @@ -253,6 +260,9 @@ LIBGE_LOCAL_SRC_FILES := \ hybrid/node_executor/aicpu/aicpu_node_executor.cc \ hybrid/node_executor/compiledsubgraph/known_node_executor.cc \ hybrid/node_executor/hostcpu/ge_local_node_executor.cc \ + hybrid/node_executor/controlop/control_op_executor.cc \ + hybrid/node_executor/partitioned_call/partitioned_call_node_executor.cc \ + hybrid/node_executor/hccl/hccl_node_executor.cc \ hybrid/node_executor/node_executor.cc \ hybrid/node_executor/task_context.cc \ hybrid/hybrid_davinci_model.cc \ @@ -338,6 +348,28 @@ LOCAL_SHARED_LIBRARIES += \ include $(BUILD_HOST_SHARED_LIBRARY) +#compiler for GeRunner +include $(CLEAR_VARS) + +LOCAL_MODULE := stub/libge_runner + +LOCAL_CFLAGS += -DPROTOBUF_INLINE_NOT_IN_HEADERS=0 -DREUSE_MEMORY=1 -O2 +LOCAL_CFLAGS += -DFMK_SUPPORT_DUMP -DDAVINCI_SUPPORT_PROFILING -DDAVINCI_CLOUD +ifeq ($(DEBUG), 1) +LOCAL_CFLAGS += -g -O0 +endif + + +LOCAL_C_INCLUDES := $(RUNNER_LOCAL_C_INCLUDES) + +LOCAL_SRC_FILES := ../../out/ge/lib64/stub/ge_api.cc + + +LOCAL_SHARED_LIBRARIES := + +LOCAL_LDFLAGS := -lrt -ldl + +include $(BUILD_HOST_SHARED_LIBRARY) # add engine_conf.json to host include $(CLEAR_VARS) @@ -407,6 +439,7 @@ LOCAL_CFLAGS += -DFMK_SUPPORT_DUMP -DDAVINCI_SUPPORT_PROFILING -DDAVINCI_CLOUD LOCAL_CFLAGS += -g -O0 LOCAL_C_INCLUDES := $(RUNNER_LOCAL_C_INCLUDES) + LOCAL_SRC_FILES := $(LIBGE_LOCAL_SRC_FILES) LOCAL_SRC_FILES += $(LIBCLIENT_LOCAL_SRC_FILES) diff --git a/src/ge/ge_runtime/model_runner.cc b/src/ge/ge_runtime/model_runner.cc index 59952e39..b6e43dd5 100644 --- a/src/ge/ge_runtime/model_runner.cc +++ b/src/ge/ge_runtime/model_runner.cc @@ -49,6 +49,15 @@ bool ModelRunner::LoadDavinciModel(uint32_t device_id, uint64_t session_id, uint return true; } +bool ModelRunner::LoadModelComplete(uint32_t model_id) { + auto model_iter = runtime_models_.find(model_id); + if (model_iter == runtime_models_.end()) { + GELOGE(PARAM_INVALID, "Model id %u not found.", model_id); + return false; + } + return model_iter->second->LoadComplete(); +} + const std::vector &ModelRunner::GetTaskIdList(uint32_t model_id) const { auto model_iter = runtime_models_.find(model_id); if (model_iter == runtime_models_.end()) { @@ -60,6 +69,28 @@ const std::vector &ModelRunner::GetTaskIdList(uint32_t model_id) const return model_iter->second->GetTaskIdList(); } +const std::vector &ModelRunner::GetStreamIdList(uint32_t model_id) const { + auto model_iter = runtime_models_.find(model_id); + if (model_iter == runtime_models_.end()) { + GELOGE(PARAM_INVALID, "Model id %u not found.", model_id); + static const std::vector empty_ret; + return empty_ret; + } + + return model_iter->second->GetStreamIdList(); +} + +const std::map> &ModelRunner::GetRuntimeInfoMap(uint32_t model_id) const { + auto model_iter = runtime_models_.find(model_id); + if (model_iter == runtime_models_.end()) { + GELOGW("Model id %u not found.", model_id); + static const std::map> empty_ret; + return empty_ret; + } + + return model_iter->second->GetRuntimeInfoMap(); +} + bool ModelRunner::UnloadModel(uint32_t model_id) { auto iter = runtime_models_.find(model_id); if (iter != runtime_models_.end()) { diff --git a/src/ge/ge_runtime/output.cc b/src/ge/ge_runtime/output.cc index 90c33bb4..5153f688 100644 --- a/src/ge/ge_runtime/output.cc +++ b/src/ge/ge_runtime/output.cc @@ -76,7 +76,7 @@ bool Output::CopyRslt(OutputData *rslt, uint32_t data_begin, uint32_t &data_inde DataBuffer data_buf = rslt->blobs[data_begin + data_count]; bool ret = SetDataBuf(data_buf, data_begin, data_count, i, support_mem_share); if (!ret) { - GELOGE(FAILED, "Copy data to host failed. index: %lu, addr: %p", i, v_input_data_addr_[i]); + GELOGE(FAILED, "Copy data to host error. index: %lu, addr: %p", i, v_input_data_addr_[i]); return ret; } data_index = data_begin + data_count; diff --git a/src/ge/ge_runtime/runtime_model.cc b/src/ge/ge_runtime/runtime_model.cc index c89ced91..bdf8f2a6 100644 --- a/src/ge/ge_runtime/runtime_model.cc +++ b/src/ge/ge_runtime/runtime_model.cc @@ -28,7 +28,6 @@ namespace ge { namespace model_runner { - RuntimeModel::~RuntimeModel() { GELOGI("RuntimeModel destructor start"); @@ -116,23 +115,34 @@ bool RuntimeModel::InitEvent(uint32_t event_num) { return true; } -bool RuntimeModel::InitLabel(uint32_t batch_num) { - GELOGI("batch number:%u.", batch_num); - for (uint32_t i = 0; (batch_num != 0 && i <= batch_num); ++i) { - rtLabel_t rt_lLabel = nullptr; - rtError_t rt_ret = rtLabelCreate(&rt_lLabel); - if (rt_ret != RT_ERROR_NONE) { - GELOGE(RT_FAILED, "Call rt api rtLabelCreate failed, i; %u; ret: 0x%X", i, rt_ret); - return false; +bool RuntimeModel::InitLabel(std::shared_ptr &davinci_model) { + GELOGI("batch number:%u.", davinci_model->GetBatchNum()); + label_list_.resize(davinci_model->GetBatchNum()); + for (auto &task_info : davinci_model->GetTaskInfoList()) { + if (task_info == nullptr) { + GELOGE(PARAM_INVALID, "task_info is null."); + continue; + } + + if (task_info->type() != TaskInfoType::LABEL_SET) { + continue; } + auto label_set_task_info = std::static_pointer_cast(task_info); - if (rt_lLabel == nullptr) { - GELOGE(RT_FAILED, "rtLabel is nullptr!"); + if (label_set_task_info->stream_id() >= stream_list_.size()) { + GELOGE(PARAM_INVALID, "Invalid stream id."); return false; } - label_list_.emplace_back(rt_lLabel); + rtLabel_t rt_label = nullptr; + rtError_t rt_ret = rtLabelCreateEx(&rt_label, stream_list_[label_set_task_info->stream_id()]); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(RT_FAILED, "Call rt api rtLabelCreate failed, ret: 0x%X", rt_ret); + return false; + } + label_list_[label_set_task_info->label_id()] = rt_label; } + return true; } @@ -164,7 +174,7 @@ bool RuntimeModel::InitResource(std::shared_ptr &davinci_model) { return false; } - if (!InitLabel(davinci_model->GetBatchNum())) { + if (!InitLabel(davinci_model)) { return false; } @@ -209,20 +219,41 @@ bool RuntimeModel::LoadTask() { return false; } task_id_list_.push_back(task_id); + stream_id_list_.push_back(stream_id); + if (task->Args() != nullptr) { + std::shared_ptr runtime_tuple = nullptr; + GE_MAKE_SHARED(runtime_tuple = std::make_shared(task_id, stream_id, task->Args()), return false); + auto emplace_ret = runtime_info_map_.emplace(task->task_name(), runtime_tuple); + if (!emplace_ret.second) { + GELOGW("Task name exist:%s", task->task_name().c_str()); + } + } } if (task_list_.empty()) { GELOGE(FAILED, "Task list is empty"); return false; } - GELOGI("Distribute task succ."); - auto rt_ret = rtModelLoadComplete(rt_model_handle_); + GELOGI("LoadTask succ."); + return true; +} + +bool RuntimeModel::LoadComplete() { + uint32_t task_id = 0; + uint32_t stream_id = 0; + auto rt_ret = rtModelGetTaskId(rt_model_handle_, &task_id, &stream_id); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(RT_FAILED, "Call rtModelGetTaskId failed, ret:0x%X", rt_ret); + return RT_FAILED; + } + task_id_list_.push_back(task_id); + stream_id_list_.push_back(stream_id); + + rt_ret = rtModelLoadComplete(rt_model_handle_); if (rt_ret != RT_ERROR_NONE) { GELOGE(RT_FAILED, "Call rt api rtModelLoadComplete failed, ret: 0x%X.", rt_ret); return false; } - - GELOGI("LoadTask succ."); return true; } @@ -270,10 +301,14 @@ bool RuntimeModel::Run() { return false; } - GELOGI("Run rtModelExecute success"); + GELOGI("Run rtModelExecute success, ret = 0x%X", ret); ret = rtStreamSynchronize(rt_model_stream_); if (ret != RT_ERROR_NONE) { + if (ret == RT_ERROR_END_OF_SEQUENCE) { + GELOGI("Model stream RT_ERROR_END_OF_SEQUENCE signal received, ret = 0x%X", ret); + return true; + } GELOGE(RT_FAILED, "Model stream sync failed, ret = 0x%X", ret); return false; } @@ -433,7 +468,7 @@ bool RuntimeModel::InitConstantInfo(std::shared_ptr &davinci_model } if (constant->output_tensors[0].size < constant->weight_data.size()) { - GELOGE(PARAM_INVALID, "Output size:%u is less than weight data size:%zu", constant->output_tensors[0].size, + GELOGE(PARAM_INVALID, "Output size:%u less than weight data size:%zu", constant->output_tensors[0].size, constant->weight_data.size()); return false; } @@ -448,11 +483,8 @@ bool RuntimeModel::InitConstantInfo(std::shared_ptr &davinci_model /// The logic of GetShapeSize is wrong, the scaler tensor's GetShapeSize is zero /// and that of unknown shape is zero too. /// Unknown shape will not appear here, so we can use zero judge a tensor is scaler or not. - int64_t elem_num = constant->weight_tensors[0].GetShapeSize(); - if (elem_num == 0 && constant->weight_tensors[0].size == 0) { - elem_num = 1; - } - + int64_t elem_num = + (constant->weight_tensors[0].GetShapeSize() == 0) ? 1 : constant->weight_tensors[0].GetShapeSize(); if (constant->weight_data.size() < sizeof(uint64_t)) { GELOGE(FAILED, "weight_data size is smaller than sizeof(uint64_t)"); return false; @@ -495,5 +527,6 @@ void RuntimeModel::CreateOutput(uint32_t index, const OpInfo &op_info, InputOutp const std::vector &RuntimeModel::GetTaskIdList() const { return task_id_list_; } +const std::vector &RuntimeModel::GetStreamIdList() const { return stream_id_list_; } } // namespace model_runner } // namespace ge diff --git a/src/ge/ge_runtime/runtime_model.h b/src/ge/ge_runtime/runtime_model.h index e8ff4057..67535296 100644 --- a/src/ge/ge_runtime/runtime_model.h +++ b/src/ge/ge_runtime/runtime_model.h @@ -27,7 +27,7 @@ namespace ge { namespace model_runner { - +using RuntimeInfo = std::tuple; class Task; class RuntimeModel { public: @@ -35,7 +35,10 @@ class RuntimeModel { ~RuntimeModel(); bool Load(uint32_t device_id, uint64_t session_id, std::shared_ptr &davinci_model); + bool LoadComplete(); const std::vector &GetTaskIdList() const; + const std::vector &GetStreamIdList() const; + const std::map> &GetRuntimeInfoMap() const { return runtime_info_map_; } bool Run(); bool CopyInputData(const InputData &input_data); bool GetInputOutputDescInfo(bool zero_copy, std::vector *input_desc, @@ -48,7 +51,7 @@ class RuntimeModel { bool LoadTask(); bool InitStream(std::shared_ptr &davinci_model); bool InitEvent(uint32_t event_num); - bool InitLabel(uint32_t batch_num); + bool InitLabel(std::shared_ptr &davinci_model); bool InitDataInfo(std::shared_ptr &davinci_model); bool InitOutputInfo(std::shared_ptr &davinci_model); bool InitConstantInfo(std::shared_ptr &davinci_model); @@ -77,6 +80,8 @@ class RuntimeModel { std::vector> constant_info_list_{}; std::vector task_id_list_{}; + std::vector stream_id_list_{}; + std::map> runtime_info_map_; }; } // namespace model_runner diff --git a/src/ge/ge_runtime/task/aicpu_task.cc b/src/ge/ge_runtime/task/aicpu_task.cc index 4cb71866..9b126ec0 100644 --- a/src/ge/ge_runtime/task/aicpu_task.cc +++ b/src/ge/ge_runtime/task/aicpu_task.cc @@ -85,11 +85,15 @@ bool AicpuTask::Distribute() { return false; } - GELOGI("Distribute AicpuTask start, args_size = %u, io_addrs_num = %u, so_name = %s, kernel_name = %s.", args_size, - io_addrs_num, task_info_->so_name().data(), task_info_->kernel_name().data()); - rt_ret = rtCpuKernelLaunch(reinterpret_cast(task_info_->so_name().data()), - reinterpret_cast(task_info_->kernel_name().data()), 1, args_, args_size, - nullptr, stream_); + input_output_addr_ = reinterpret_cast(reinterpret_cast(args_) + io_addr_offset); + + auto dump_flag = task_info_->dump_flag() ? RT_KERNEL_DUMPFLAG : RT_KERNEL_DEFAULT; + GELOGI( + "Distribute AicpuTask start, args_size = %u, io_addrs_num = %u, so_name = %s, kernel_name = %s, dump_flag = %d.", + args_size, io_addrs_num, task_info_->so_name().data(), task_info_->kernel_name().data(), dump_flag); + rt_ret = rtCpuKernelLaunchWithFlag(reinterpret_cast(task_info_->so_name().data()), + reinterpret_cast(task_info_->kernel_name().data()), 1, args_, + args_size, nullptr, stream_, dump_flag); if (rt_ret != RT_ERROR_NONE) { GELOGE(RT_FAILED, "Call rt api failed, ret: 0x%X", rt_ret); return false; diff --git a/src/ge/ge_runtime/task/aicpu_task.h b/src/ge/ge_runtime/task/aicpu_task.h index f5cdc617..cc21af8a 100644 --- a/src/ge/ge_runtime/task/aicpu_task.h +++ b/src/ge/ge_runtime/task/aicpu_task.h @@ -18,6 +18,7 @@ #define GE_GE_RUNTIME_TASK_AICPU_TASK_H_ #include +#include #include "ge_runtime/task/task.h" namespace ge { @@ -30,12 +31,17 @@ class AicpuTask : public TaskRepeater { bool Distribute() override; + void *Args() override { return input_output_addr_; } + + std::string task_name() const override { return task_info_->op_name(); } + private: static void ReleaseRtMem(void **ptr) noexcept; std::shared_ptr task_info_; void *stream_; void *args_; + void *input_output_addr_; }; } // namespace model_runner } // namespace ge diff --git a/src/ge/ge_runtime/task/hccl_task.cc b/src/ge/ge_runtime/task/hccl_task.cc index 54ae3bf3..3d5f8504 100644 --- a/src/ge/ge_runtime/task/hccl_task.cc +++ b/src/ge/ge_runtime/task/hccl_task.cc @@ -115,7 +115,6 @@ bool HcclTask::Distribute() { rt_ret = rtModelBindStream(rt_model_handle_, stream, RT_HEAD_STREAM); if (rt_ret != RT_ERROR_NONE) { GELOGE(RT_FAILED, "Call rt api failed, ret: 0x%X", rt_ret); - (void)rtStreamDestroy(stream); return false; } diff --git a/src/ge/ge_runtime/task/label_goto_task.cc b/src/ge/ge_runtime/task/label_goto_task.cc new file mode 100644 index 00000000..d357accb --- /dev/null +++ b/src/ge/ge_runtime/task/label_goto_task.cc @@ -0,0 +1,70 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ge_runtime/task/label_goto_task.h" +#include "ge_runtime/task/task_factory.h" + +namespace ge { +namespace model_runner { +LabelGotoTask::LabelGotoTask(const ModelContext &model_context, const std::shared_ptr &task_info) + : TaskRepeater(model_context, task_info), + task_info_(task_info), + stream_(nullptr), + label_(nullptr) { + if (task_info_ == nullptr) { + GELOGW("task_info_ is null!"); + return; + } + auto stream_list = model_context.stream_list(); + auto label_list = model_context.label_list(); + uint32_t stream_id = task_info->stream_id(); + uint32_t label_id = task_info->label_id(); + GELOGI("Stream list size:%zu, stream id:%u.", stream_list.size(), stream_id); + GELOGI("Label list size:%zu, label id:%u.", label_list.size(), label_id); + if (stream_id >= stream_list.size() || label_id >= label_list.size()) { + GELOGW("Stream/Label id invalid."); + return; + } + stream_ = stream_list[stream_id]; + label_ = label_list[label_id]; +} + +LabelGotoTask::~LabelGotoTask() {} + +bool LabelGotoTask::Distribute() { + GELOGI("LabelGotoTask Distribute start."); + if (stream_ == nullptr) { + GELOGE(PARAM_INVALID, "stream is null!"); + return false; + } + if (label_ == nullptr) { + GELOGE(PARAM_INVALID, "label is null!"); + return false; + } + rtError_t rt_ret = rtLabelGotoEx(label_, stream_); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(RT_FAILED, "Call rt api failed, ret: 0x%X", rt_ret); + return false; + } + + GELOGI("DistributeTask end."); + return true; +} + +REGISTER_TASK(TaskInfoType::LABEL_GOTO, LabelGotoTask, LabelGotoTaskInfo); + +} // namespace model_runner +} // namespace ge diff --git a/src/ge/ge_runtime/task/label_goto_task.h b/src/ge/ge_runtime/task/label_goto_task.h new file mode 100644 index 00000000..4fd6d1bc --- /dev/null +++ b/src/ge/ge_runtime/task/label_goto_task.h @@ -0,0 +1,41 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_GE_RUNTIME_TASK_LABEL_GOTO_TASK_H_ +#define GE_GE_RUNTIME_TASK_LABEL_GOTO_TASK_H_ + +#include +#include "ge_runtime/task/task.h" + +namespace ge { +namespace model_runner { +class LabelGotoTask : public TaskRepeater { + public: + LabelGotoTask(const ModelContext &model_context, const std::shared_ptr &task_info); + + ~LabelGotoTask() override; + + bool Distribute() override; + + private: + std::shared_ptr task_info_; + void *stream_; + void *label_; +}; +} // namespace model_runner +} // namespace ge + +#endif // GE_GE_RUNTIME_TASK_LABEL_GOTO_TASK_H_ diff --git a/src/ge/ge_runtime/task/label_set_task.cc b/src/ge/ge_runtime/task/label_set_task.cc new file mode 100644 index 00000000..3ab5802c --- /dev/null +++ b/src/ge/ge_runtime/task/label_set_task.cc @@ -0,0 +1,70 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ge_runtime/task/label_set_task.h" +#include "ge_runtime/task/task_factory.h" + +namespace ge { +namespace model_runner { +LabelSetTask::LabelSetTask(const ModelContext &model_context, const std::shared_ptr &task_info) + : TaskRepeater(model_context, task_info), + task_info_(task_info), + stream_(nullptr), + label_(nullptr) { + if (task_info_ == nullptr) { + GELOGW("task_info_ is null!"); + return; + } + auto stream_list = model_context.stream_list(); + auto label_list = model_context.label_list(); + uint32_t stream_id = task_info->stream_id(); + uint32_t label_id = task_info->label_id(); + GELOGI("Stream list size:%zu, stream id:%u.", stream_list.size(), stream_id); + GELOGI("Label list size:%zu, label id:%u.", label_list.size(), label_id); + if (stream_id >= stream_list.size() || label_id >= label_list.size()) { + GELOGW("Stream/Label id invalid."); + return; + } + stream_ = stream_list[stream_id]; + label_ = label_list[label_id]; +} + +LabelSetTask::~LabelSetTask() {} + +bool LabelSetTask::Distribute() { + GELOGI("LabelSetTask Distribute start."); + if (stream_ == nullptr) { + GELOGE(PARAM_INVALID, "stream is null!"); + return false; + } + if (label_ == nullptr) { + GELOGE(PARAM_INVALID, "label is null!"); + return false; + } + rtError_t rt_ret = rtLabelSet(label_, stream_); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(RT_FAILED, "Call rt api failed, ret: 0x%X", rt_ret); + return false; + } + + GELOGI("DistributeTask end."); + return true; +} + +REGISTER_TASK(TaskInfoType::LABEL_SET, LabelSetTask, LabelSetTaskInfo); + +} // namespace model_runner +} // namespace ge diff --git a/src/ge/ge_runtime/task/label_set_task.h b/src/ge/ge_runtime/task/label_set_task.h new file mode 100644 index 00000000..70bf1584 --- /dev/null +++ b/src/ge/ge_runtime/task/label_set_task.h @@ -0,0 +1,41 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_GE_RUNTIME_TASK_LABEL_SET_TASK_H_ +#define GE_GE_RUNTIME_TASK_LABEL_SET_TASK_H_ + +#include +#include "ge_runtime/task/task.h" + +namespace ge { +namespace model_runner { +class LabelSetTask : public TaskRepeater { + public: + LabelSetTask(const ModelContext &model_context, const std::shared_ptr &task_info); + + ~LabelSetTask() override; + + bool Distribute() override; + + private: + std::shared_ptr task_info_; + void *stream_; + void *label_; +}; +} // namespace model_runner +} // namespace ge + +#endif // GE_GE_RUNTIME_TASK_LABEL_SET_TASK_H_ diff --git a/src/ge/ge_runtime/task/label_switch_task.cc b/src/ge/ge_runtime/task/label_switch_task.cc new file mode 100644 index 00000000..a3c2d41a --- /dev/null +++ b/src/ge/ge_runtime/task/label_switch_task.cc @@ -0,0 +1,131 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ge_runtime/task/label_switch_task.h" +#include "ge_runtime/task/task_factory.h" + +namespace ge { +namespace model_runner { +LabelSwitchTask::LabelSwitchTask(const ModelContext &model_context, + const std::shared_ptr &task_info) + : TaskRepeater(model_context, task_info), + task_info_(task_info), + stream_(nullptr), + all_label_resource_(), + label_info_(nullptr) { + if (task_info_ == nullptr) { + GELOGW("task_info_ is null!"); + return; + } + + all_label_resource_ = model_context.label_list(); + auto stream_list = model_context.stream_list(); + uint32_t stream_id = task_info->stream_id(); + GELOGI("Stream list size:%zu, stream id:%u.", stream_list.size(), stream_id); + if (stream_id >= stream_list.size()) { + GELOGW("Stream id invalid."); + return; + } + stream_ = stream_list[stream_id]; +} + +LabelSwitchTask::~LabelSwitchTask() { + if (label_info_ != nullptr) { + rtError_t rt_ret = rtFree(label_info_); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(RT_FAILED, "rtFree fwkOpBuf failed! ret: 0x%X.", rt_ret); + } + label_info_ = nullptr; + } +} + +bool LabelSwitchTask::Distribute() { + GELOGI("LabelSwitchTask Distribute start."); + if (!CheckParamValid()) { + return false; + } + + const std::vector &label_index_list = task_info_->label_list(); + std::vector label_list(task_info_->label_size(), nullptr); + + for (size_t i = 0; i < task_info_->label_size(); ++i) { + uint32_t label_index = label_index_list[i]; + if (label_index >= all_label_resource_.size()) { + GELOGE(PARAM_INVALID, "label %zu index is %u, but there are %zu labels in total.", i, label_index, + all_label_resource_.size()); + return false; + } + label_list[i] = all_label_resource_[label_index]; + GELOGI("Case %zu: label id %zu.", i, label_index); + } + + uint32_t label_info_size = sizeof(rtLabelDevInfo) * task_info_->label_size(); + rtError_t rt_ret = rtMalloc(&label_info_, label_info_size, RT_MEMORY_HBM); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(RT_FAILED, "Call rt api failed, ret: 0x%X", rt_ret); + return false; + } + + rt_ret = rtLabelListCpy(label_list.data(), label_list.size(), label_info_, label_info_size); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(RT_FAILED, "Call rt api failed, ret: 0x%X", rt_ret); + return false; + } + + rt_ret = rtLabelSwitchByIndex(task_info_->cond(), label_list.size(), label_info_, stream_); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(RT_FAILED, "Call rt api failed, ret: 0x%X", rt_ret); + return false; + } + + GELOGI("DistributeTask end."); + return true; +} + +bool LabelSwitchTask::CheckParamValid() { + if (stream_ == nullptr) { + GELOGE(PARAM_INVALID, "stream is null!"); + return false; + } + + if (task_info_->label_list().empty()) { + GELOGE(PARAM_INVALID, "label_list is empty."); + return false; + } + + if (task_info_->label_size() != task_info_->label_list().size()) { + GELOGE(PARAM_INVALID, "label_list size %zu but label_size is %u.", task_info_->label_list().size(), + task_info_->label_size()); + return false; + } + + if (task_info_->label_size() >= UINT32_MAX / sizeof(rtLabelDevInfo)) { + GELOGE(PARAM_INVALID, "label_size %u will overflow.", task_info_->label_size()); + return false; + } + + if (label_info_ != nullptr) { + GELOGE(PARAM_INVALID, "label_info_ has dirty data."); + return false; + } + + return true; +} + +REGISTER_TASK(TaskInfoType::LABEL_SWITCH, LabelSwitchTask, LabelSwitchTaskInfo); + +} // namespace model_runner +} // namespace ge diff --git a/src/ge/ge_runtime/task/label_switch_task.h b/src/ge/ge_runtime/task/label_switch_task.h new file mode 100644 index 00000000..463faa31 --- /dev/null +++ b/src/ge/ge_runtime/task/label_switch_task.h @@ -0,0 +1,44 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_GE_RUNTIME_TASK_LABEL_SWITCH_TASK_H_ +#define GE_GE_RUNTIME_TASK_LABEL_SWITCH_TASK_H_ + +#include +#include "ge_runtime/task/task.h" + +namespace ge { +namespace model_runner { +class LabelSwitchTask : public TaskRepeater { + public: + LabelSwitchTask(const ModelContext &model_context, const std::shared_ptr &task_info); + + ~LabelSwitchTask() override; + + bool Distribute() override; + + private: + bool CheckParamValid(); + + std::shared_ptr task_info_; + void *stream_; + std::vector all_label_resource_; + void *label_info_; +}; +} // namespace model_runner +} // namespace ge + +#endif // GE_GE_RUNTIME_TASK_LABEL_SWITCH_TASK_H_ diff --git a/src/ge/ge_runtime/task/stream_switch_task.cc b/src/ge/ge_runtime/task/stream_switch_task.cc index 91141139..2adcb4bd 100644 --- a/src/ge/ge_runtime/task/stream_switch_task.cc +++ b/src/ge/ge_runtime/task/stream_switch_task.cc @@ -51,7 +51,7 @@ bool StreamSwitchTask::Distribute() { } if (static_cast(task_info_->true_stream_id()) >= stream_list_.size()) { - GELOGE(PARAM_INVALID, "true_stream_id %ld must be less than stream_list_ size %zu!", task_info_->true_stream_id(), + GELOGE(PARAM_INVALID, "true_stream_id %ld must less than stream_list_ size %zu!", task_info_->true_stream_id(), stream_list_.size()); return false; } diff --git a/src/ge/ge_runtime/task/task.h b/src/ge/ge_runtime/task/task.h index 7c748a7d..6c4df248 100644 --- a/src/ge/ge_runtime/task/task.h +++ b/src/ge/ge_runtime/task/task.h @@ -18,7 +18,9 @@ #define GE_GE_RUNTIME_TASK_TASK_H_ #include +#include #include +#include #include "runtime/rt_model.h" #include "ge_runtime/model_context.h" #include "ge_runtime/task_info.h" @@ -32,6 +34,10 @@ class Task { virtual ~Task() {} virtual bool Distribute() = 0; + + virtual void *Args() { return nullptr; } + + virtual std::string task_name() const { return ""; } }; template diff --git a/src/ge/ge_runtime/task/tbe_task.cc b/src/ge/ge_runtime/task/tbe_task.cc index 8a3c36a4..e7025ae8 100644 --- a/src/ge/ge_runtime/task/tbe_task.cc +++ b/src/ge/ge_runtime/task/tbe_task.cc @@ -95,15 +95,14 @@ bool TbeTask::Distribute() { return false; } - GELOGI("InitTbeTask end."); GELOGI("DistributeTbeTask start."); - rt_ret = rtKernelLaunch(stub_func_, task_info_->block_dim(), args_, args_size, nullptr, stream_); + auto dump_flag = task_info_->dump_flag() ? RT_KERNEL_DUMPFLAG : RT_KERNEL_DEFAULT; + rt_ret = rtKernelLaunchWithFlag(stub_func_, task_info_->block_dim(), args_, args_size, nullptr, stream_, dump_flag); if (rt_ret != RT_ERROR_NONE) { GELOGE(RT_FAILED, "Call rt api rtKernelLaunch failed, ret: 0x%X", rt_ret); return false; } - - GELOGI("DistributeTbeTask end."); + GELOGI("[DataDump] task name:%s, dump_flag:%d", task_info_->op_name().c_str(), dump_flag); return true; } diff --git a/src/ge/ge_runtime/task/tbe_task.h b/src/ge/ge_runtime/task/tbe_task.h index 994ba5e2..a8ce6268 100644 --- a/src/ge/ge_runtime/task/tbe_task.h +++ b/src/ge/ge_runtime/task/tbe_task.h @@ -30,6 +30,10 @@ class TbeTask : public TaskRepeater { bool Distribute() override; + void *Args() override { return args_; } + + std::string task_name() const override { return task_info_->op_name(); } + private: std::shared_ptr task_info_; void *stream_; diff --git a/src/ge/ge_train.mk b/src/ge/ge_train.mk deleted file mode 100644 index 767ce86b..00000000 --- a/src/ge/ge_train.mk +++ /dev/null @@ -1,333 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -COMMON_LOCAL_SRC_FILES := \ - proto/fusion_model.proto \ - proto/optimizer_priority.proto \ - session/inner_session.cc \ - session/session_manager.cc \ - common/ge/plugin_manager.cc\ - common/fp16_t.cc \ - common/formats/utils/formats_trans_utils.cc \ - common/formats/format_transfers/datatype_transfer.cc \ - common/formats/format_transfers/format_transfer_transpose.cc \ - common/formats/format_transfers/format_transfer_nchw_nc1hwc0.cc \ - common/formats/format_transfers/format_transfer_fractal_z.cc \ - common/formats/format_transfers/format_transfer_fractal_nz.cc \ - common/formats/format_transfers/format_transfer_fractal_zz.cc \ - common/formats/format_transfers/format_transfer_nhwc_nc1hwc0.cc \ - common/formats/format_transfers/format_transfer_nc1hwc0_nchw.cc \ - common/formats/format_transfers/format_transfer_nc1hwc0_nhwc.cc \ - common/formats/format_transfers/format_transfer_hwcn_c1hwncoc0.cc \ - common/formats/format_transfers/format_transfer_c1hwncoc0_hwcn.cc \ - common/formats/format_transfers/format_transfer_fracz_nchw.cc \ - common/formats/format_transfers/format_transfer_fracz_nhwc.cc \ - common/formats/format_transfers/format_transfer_fracz_hwcn.cc \ - common/formats/format_transfers/format_transfer_dhwcn_fracz3D.cc \ - common/formats/format_transfers/format_transfer_dhwnc_fracz3D_transpose.cc \ - common/formats/formats.cc \ - init/gelib.cc \ - engine_manager/dnnengine_manager.cc \ - opskernel_manager/ops_kernel_manager.cc \ - graph/manager/graph_manager.cc \ - graph/manager/graph_manager_utils.cc \ - graph/manager/graph_context.cc \ - graph/preprocess/graph_preprocess.cc \ - graph/preprocess/multi_batch_copy_graph.cc \ - graph/execute/graph_execute.cc \ - graph/load/graph_loader.cc \ - graph/optimize/graph_optimize.cc \ - graph/passes/folding_pass.cc \ - graph/optimize/summary_optimize.cc \ - graph/build/graph_builder.cc \ - graph/partition/engine_place.cc \ - graph/partition/graph_partition.cc \ - graph/partition/dynamic_shape_partition.cc \ - generator/ge_generator.cc \ - generator/generator_api.cc \ - common/profiling/profiling_manager.cc \ - ge_local_engine/engine/host_cpu_engine.cc \ - common/helper/model_cache_helper.cc \ - -OMG_HOST_SRC_FILES := \ - model/ge_model.cc \ - model/ge_root_model.cc \ - graph/common/transop_util.cc \ - graph/manager/graph_var_manager.cc \ - graph/manager/trans_var_data_utils.cc \ - omm/csa_interact.cc \ - graph/passes/pass_manager.cc \ - graph/passes/pass_utils.cc \ - graph/passes/base_pass.cc \ - graph/passes/resource_pair_add_control_pass.cc \ - graph/passes/resource_pair_remove_control_pass.cc \ - graph/passes/constant_folding_pass.cc \ - graph/passes/aicpu_constant_folding_pass.cc \ - graph/passes/reshape_remove_pass.cc \ - graph/passes/reshape_recovery_pass.cc \ - graph/passes/transop_breadth_fusion_pass.cc \ - graph/passes/transop_depth_fusion_pass.cc \ - graph/passes/same_transdata_breadth_fusion_pass.cc \ - graph/passes/transop_without_reshape_fusion_pass.cc \ - graph/passes/compile_nodes_pass.cc \ - graph/passes/transop_nearby_allreduce_fusion_pass.cc \ - graph/passes/variable_prepare_op_pass.cc \ - graph/passes/variable_ref_delete_op_pass.cc \ - graph/passes/variable_ref_useless_control_out_delete_pass.cc \ - graph/passes/variable_op_pass.cc \ - graph/passes/cast_remove_pass.cc \ - graph/passes/replace_transshape_pass.cc \ - graph/passes/transpose_transdata_pass.cc \ - graph/passes/identify_reference_pass.cc \ - graph/passes/variable_format_pass.cc \ - graph/passes/subgraph_pass.cc \ - graph/passes/data_pass.cc \ - graph/passes/net_output_pass.cc \ - graph/passes/constant_fuse_same_pass.cc \ - graph/passes/print_op_pass.cc \ - graph/passes/no_use_reshape_remove_pass.cc \ - graph/passes/iterator_op_pass.cc \ - graph/passes/atomic_addr_clean_pass.cc \ - graph/optimize/optimizer/allreduce_fusion_pass.cc \ - graph/common/omg_util.cc \ - graph/common/bcast.cc \ - graph/passes/dimension_compute_pass.cc \ - graph/passes/dimension_adjust_pass.cc \ - graph/passes/get_original_format_pass.cc \ - graph/passes/shape_operate_op_remove_pass.cc \ - graph/passes/unused_op_remove_pass.cc \ - graph/passes/assert_pass.cc \ - graph/passes/dropout_pass.cc \ - graph/passes/infershape_pass.cc \ - graph/passes/unused_const_pass.cc \ - graph/passes/isolated_op_remove_pass.cc \ - graph/passes/permute_pass.cc \ - graph/passes/ctrl_edge_transfer_pass.cc \ - host_kernels/broadcast_gradient_args_kernel.cc \ - host_kernels/greater_kernel.cc \ - host_kernels/gather_v2_kernel.cc \ - host_kernels/maximum_kernel.cc \ - host_kernels/floormod_kernel.cc \ - host_kernels/floordiv_kernel.cc \ - host_kernels/range_kernel.cc \ - host_kernels/shape_kernel.cc \ - host_kernels/size_kernel.cc \ - host_kernels/shape_n_kernel.cc \ - host_kernels/rank_kernel.cc \ - host_kernels/broadcast_args_kernel.cc \ - host_kernels/fill_kernel.cc \ - host_kernels/empty_kernel.cc \ - host_kernels/expanddims_kernel.cc \ - host_kernels/reshape_kernel.cc \ - host_kernels/squeeze_kernel.cc \ - host_kernels/kernel_utils.cc \ - host_kernels/cast_kernel.cc \ - host_kernels/transdata_kernel.cc \ - host_kernels/transpose_kernel.cc \ - host_kernels/permute_kernel.cc \ - host_kernels/pack_kernel.cc \ - host_kernels/concat_v2_kernel.cc \ - host_kernels/concat_offset_kernel.cc \ - host_kernels/strided_slice_kernel.cc \ - host_kernels/ssd_prior_box_kernel.cc \ - host_kernels/add_kernel.cc \ - host_kernels/unpack_kernel.cc \ - host_kernels/sub_kernel.cc \ - host_kernels/mul_kernel.cc \ - host_kernels/reduce_prod_kernel.cc \ - host_kernels/rsqrt_kernel.cc \ - host_kernels/slice_kernel.cc \ - host_kernels/slice_d_kernel.cc \ - host_kernels/dynamic_stitch_kernel.cc \ - graph/passes/stop_gradient_pass.cc \ - graph/passes/prevent_gradient_pass.cc \ - graph/passes/identity_pass.cc \ - graph/passes/placeholder_with_default_pass.cc \ - graph/passes/snapshot_pass.cc \ - graph/passes/guarantee_const_pass.cc \ - graph/passes/var_is_initialized_op_pass.cc \ - graph/passes/parallel_concat_start_op_pass.cc \ - graph/passes/cast_translate_pass.cc \ - graph/passes/addn_pass.cc \ - graph/passes/common_subexpression_elimination_pass.cc \ - graph/passes/transop_symmetry_elimination_pass.cc \ - graph/passes/save_pass.cc \ - graph/passes/switch_dead_branch_elimination.cc \ - graph/passes/merge_pass.cc \ - graph/passes/prune_pass.cc \ - graph/passes/flow_ctrl_pass.cc \ - graph/passes/control_trigger_pass.cc \ - graph/passes/switch_data_edges_bypass.cc \ - graph/passes/switch_op_pass.cc \ - graph/passes/multi_batch_pass.cc \ - graph/passes/switch_logic_remove_pass.cc \ - graph/passes/next_iteration_pass.cc \ - graph/passes/cond_pass.cc \ - graph/passes/cond_remove_pass.cc \ - graph/passes/for_pass.cc \ - graph/passes/enter_pass.cc \ - graph/passes/hccl_memcpy_pass.cc \ - graph/passes/link_gen_mask_nodes_pass.cc \ - graph/passes/replace_with_empty_const_pass.cc \ - graph/passes/hccl_group_pass.cc \ - -OME_SRC_FILES := \ - graph/manager/graph_mem_allocator.cc \ - graph/manager/graph_caching_allocator.cc \ - graph/manager/model_manager/event_manager.cc \ - graph/manager/util/debug.cc \ - graph/manager/util/rt_context_util.cc \ - graph/manager/util/variable_accelerate_ctrl.cc \ - graph/manager/util/hcom_util.cc \ - graph/load/new_model_manager/model_manager.cc \ - graph/load/new_model_manager/data_inputer.cc \ - graph/load/new_model_manager/davinci_model.cc \ - graph/load/new_model_manager/davinci_model_parser.cc \ - graph/load/new_model_manager/model_utils.cc \ - graph/load/new_model_manager/tbe_handle_store.cc \ - graph/load/new_model_manager/cpu_queue_schedule.cc \ - graph/load/new_model_manager/zero_copy_task.cc \ - graph/load/output/output.cc \ - graph/load/new_model_manager/data_dumper.cc \ - graph/load/new_model_manager/task_info/task_info.cc \ - graph/load/new_model_manager/task_info/event_record_task_info.cc \ - graph/load/new_model_manager/task_info/event_wait_task_info.cc \ - graph/load/new_model_manager/task_info/fusion_start_task_info.cc \ - graph/load/new_model_manager/task_info/fusion_stop_task_info.cc \ - graph/load/new_model_manager/task_info/hccl_task_info.cc \ - graph/load/new_model_manager/task_info/kernel_ex_task_info.cc \ - graph/load/new_model_manager/task_info/kernel_task_info.cc \ - graph/load/new_model_manager/task_info/label_set_task_info.cc \ - graph/load/new_model_manager/task_info/label_switch_by_index_task_info.cc \ - graph/load/new_model_manager/task_info/label_goto_ex_task_info.cc \ - graph/load/new_model_manager/task_info/memcpy_async_task_info.cc \ - graph/load/new_model_manager/task_info/memcpy_addr_async_task_info.cc \ - graph/load/new_model_manager/task_info/profiler_trace_task_info.cc \ - graph/load/new_model_manager/task_info/stream_active_task_info.cc \ - graph/load/new_model_manager/task_info/stream_switch_task_info.cc \ - graph/load/new_model_manager/task_info/stream_switchn_task_info.cc \ - graph/load/new_model_manager/task_info/end_graph_task_info.cc \ - graph/load/new_model_manager/task_info/super_kernel/super_kernel_factory.cc \ - graph/load/new_model_manager/task_info/super_kernel/super_kernel.cc \ - single_op/task/op_task.cc \ - single_op/task/build_task_utils.cc \ - single_op/task/tbe_task_builder.cc \ - single_op/task/aicpu_task_builder.cc \ - single_op/single_op.cc \ - single_op/single_op_model.cc \ - single_op/stream_resource.cc \ - single_op/single_op_manager.cc \ - hybrid/hybrid_davinci_model_stub.cc \ - - -COMMON_LOCAL_C_INCLUDES := \ - proto/om.proto \ - proto/task.proto \ - proto/insert_op.proto \ - proto/ge_ir.proto \ - proto/fwk_adapter.proto \ - proto/op_mapping_info.proto \ - proto/tensorflow/attr_value.proto \ - proto/tensorflow/function.proto \ - proto/tensorflow/graph.proto \ - proto/tensorflow/node_def.proto \ - proto/tensorflow/op_def.proto \ - proto/tensorflow/resource_handle.proto \ - proto/tensorflow/tensor.proto \ - proto/tensorflow/tensor_shape.proto \ - proto/tensorflow/types.proto \ - proto/tensorflow/versions.proto \ - $(LOCAL_PATH) ./ \ - $(TOPDIR)inc \ - $(TOPDIR)inc/external \ - $(TOPDIR)inc/external/graph \ - $(TOPDIR)inc/framework \ - $(TOPDIR)inc/framework/common \ - $(TOPDIR)inc/runtime \ - $(TOPDIR)libc_sec/include \ - $(TOPDIR)ops/built-in/op_proto/inc \ - third_party/json/include \ - third_party/protobuf/include \ - third_party/opencv/include \ - -NEW_OMG_HOST_SRC_FILES := \ - graph/preprocess/insert_op/util_insert_aipp_op.cc \ - graph/preprocess/insert_op/ge_aipp_op.cc \ - graph/build/model_builder.cc \ - graph/build/task_generator.cc \ - graph/build/stream_allocator.cc \ - graph/build/logical_stream_allocator.cc \ - graph/build/stream_graph_optimizer.cc \ - graph/build/run_context.cc \ - graph/build/label_allocator.cc \ - graph/label/label_maker.cc \ - graph/label/if_label_maker.cc \ - graph/label/case_label_maker.cc \ - graph/label/while_label_maker.cc \ - graph/label/partitioned_call_label_maker.cc \ - - - -#compiler for host train -include $(CLEAR_VARS) - -LOCAL_MODULE := libge_train - -LOCAL_CFLAGS += -DPROTOBUF_INLINE_NOT_IN_HEADERS=0 -DREUSE_MEMORY=1 -O2 -LOCAL_CFLAGS += -DDAVINCI_CLOUD -DDAVINCI_TRAIN -DFMK_SUPPORT_DUMP -DDAVINCI_SUPPORT_PROFILING -LOCAL_CFLAGS += -DFMK_SUPPORT_DEBUG -ifeq ($(DEBUG), 1) -LOCAL_CFLAGS += -g -O0 -endif - -LOCAL_C_INCLUDES := $(COMMON_LOCAL_C_INCLUDES) - -LOCAL_SRC_FILES := $(COMMON_LOCAL_SRC_FILES) -LOCAL_SRC_FILES += $(OMG_HOST_SRC_FILES) -LOCAL_SRC_FILES += $(OME_SRC_FILES) -LOCAL_SRC_FILES += $(NEW_OMG_HOST_SRC_FILES) - -LOCAL_STATIC_LIBRARIES := libge_memory \ - -LOCAL_SHARED_LIBRARIES := \ - libc_sec \ - libprotobuf \ - libslog \ - libmmpa \ - libgraph \ - libregister \ - libge_common \ - libhccl \ - libmsprof \ - - -LOCAL_LDFLAGS := -lrt -ldl - -LOCAL_SHARED_LIBRARIES += \ - libruntime \ - libresource \ - -include $(BUILD_HOST_SHARED_LIBRARY) - -# add engine_conf.json to host -include $(CLEAR_VARS) - -LOCAL_MODULE := engine_conf.json - -LOCAL_SRC_FILES := engine_manager/engine_conf.json - -LOCAL_MODULE_CLASS := ETC - -LOCAL_INSTALLED_PATH := $(HOST_OUT_ROOT)/engine_conf.json -include $(BUILD_HOST_PREBUILT) - -# add optimizer_priority.pbtxt to host -include $(CLEAR_VARS) - -LOCAL_MODULE := optimizer_priority.pbtxt - -LOCAL_SRC_FILES := opskernel_manager/optimizer_priority.pbtxt - -LOCAL_MODULE_CLASS := ETC - -LOCAL_INSTALLED_PATH := $(HOST_OUT_ROOT)/optimizer_priority.pbtxt -include $(BUILD_HOST_PREBUILT) diff --git a/src/ge/generator/ge_generator.cc b/src/ge/generator/ge_generator.cc index b01f7591..4869eb40 100644 --- a/src/ge/generator/ge_generator.cc +++ b/src/ge/generator/ge_generator.cc @@ -207,6 +207,13 @@ class GeGenerator::Impl { GraphManager graph_manager_; SaveParam save_param_; bool is_offline_ = true; + + private: + static std::string Trim(const std::string &str); + bool ParseVersion(const std::string &line, std::string &version); + bool GetVersionFromPath(const std::string &file_path, std::string &version); + bool SetAtcVersionInfo(AttrHolder &obj); + bool SetOppVersionInfo(AttrHolder &obj); }; Status GeGenerator::Initialize(const map &options) { @@ -288,6 +295,124 @@ Status GeGenerator::GenerateInfershapeGraph(const Graph &graph) { return SUCCESS; } +// Remove the space and tab before and after the string +std::string GeGenerator::Impl::Trim(const std::string &str) { + if (str.empty()) { + return str; + } + + std::string::size_type start = str.find_first_not_of(" \t\r\n"); + if (start == std::string::npos) { + return str; + } + + std::string::size_type end = str.find_last_not_of(" \t\r\n") + 1; + return str.substr(start, end); +} + +// Parsing the command line +bool GeGenerator::Impl::ParseVersion(const std::string &line, std::string &version) { + std::string flag = "Version="; + std::string temp = Trim(line); + + if (temp.empty()) { + GELOGW("line is empty."); + return false; + } + + std::string::size_type pos = temp.find(flag); + if (pos == std::string::npos) { + GELOGW("Incorrect line [%s], it must include [%s].", line.c_str(), flag.c_str()); + return false; + } + + if (temp.size() == flag.size()) { + GELOGW("version information is empty. %s", line.c_str()); + return false; + } + + version = temp.substr(pos + flag.size()); + GELOGI("Version=%s", version.c_str()); + + return true; +} + +bool GeGenerator::Impl::GetVersionFromPath(const std::string &file_path, std::string &version) { + // Normalize the path + string resolved_file_path = RealPath(file_path.c_str()); + if (resolved_file_path.empty()) { + GELOGW("Invalid input file path [%s], make sure that the file path is correct.", file_path.c_str()); + return false; + } + std::ifstream fs(resolved_file_path, std::ifstream::in); + if (!fs.is_open()) { + GELOGW("Open %s failed.", file_path.c_str()); + return false; + } + + std::string line; + if (getline(fs, line)) { + if (!ParseVersion(line, version)) { + GELOGW("Parse version failed. content is [%s].", line.c_str()); + fs.close(); + return false; + } + } else { + GELOGW("No version information found in the file path:%s", file_path.c_str()); + fs.close(); + return false; + } + + fs.close(); // close the file + return true; +} + +// Set package version information in the model +bool GeGenerator::Impl::SetAtcVersionInfo(AttrHolder &obj) { + std::string path_base = ge::GELib::GetPath(); + path_base = path_base.substr(0, path_base.rfind('/')); + path_base = path_base.substr(0, path_base.rfind('/') + 1); + + std::string version_path = path_base + "version.info"; + GELOGI("version_path is %s", version_path.c_str()); + std::string version; + if (!GetVersionFromPath(version_path, version)) { + GELOGW("Get atc version information failed!"); + return false; + } + // set version info + if (!ge::AttrUtils::SetStr(obj, ATTR_MODEL_ATC_VERSION, version)) { + GELOGW("Ge model set atc version failed!"); + return false; + } + GELOGI("Ge model set atc version information success."); + return true; +} + +// Set package version information in the model +bool GeGenerator::Impl::SetOppVersionInfo(AttrHolder &obj) { + const char *path_env = std::getenv("ASCEND_OPP_PATH"); + if (path_env == nullptr) { + GELOGW("Get environment variable ASCEND_OPP_PATH failed!"); + return false; + } + std::string version_path = path_env; + version_path += "/version.info"; + GELOGI("version_path is %s", version_path.c_str()); + std::string version; + if (!GetVersionFromPath(version_path, version)) { + GELOGW("Get opp version information failed!"); + return false; + } + // set version info + if (!ge::AttrUtils::SetStr(obj, ATTR_MODEL_OPP_VERSION, version)) { + GELOGW("Ge model set opp version failed!"); + return false; + } + GELOGI("Ge Model set opp version information success."); + return true; +} + Status GeGenerator::GenerateModel(const Graph &graph, const string &file_name_prefix, const vector &inputs, ModelBufferData &model, bool is_offline) { rtContext_t ctx = nullptr; @@ -315,6 +440,7 @@ Status GeGenerator::GenerateModel(const Graph &graph, const string &file_name_pr string model_name = ""; Status name_ret = model_helper.GetModelNameFromMergedGraphName(ge_root_model->GetRootGraph()->GetName(), model_name); if (name_ret != SUCCESS) { + ErrorManager::GetInstance().ATCReportErrMessage("E10000", {"parameter"}, {"output"}); GELOGE(FAILED, "Get model_name failed. Param --output is invalid"); return PARAM_INVALID; } @@ -464,6 +590,14 @@ Status GeGenerator::Impl::SaveParams(GeModelPtr &ge_model, const string &type, c } Status GeGenerator::Impl::SaveModel(const string &file_name_prefix, GeModelPtr &model, ModelBufferData &model_buff) { + // set atc version + if (!SetAtcVersionInfo(*(model.get()))) { + GELOGW("SetPackageVersionInfo of atc failed!"); + } + // set opp version + if (!SetOppVersionInfo(*(model.get()))) { + GELOGW("SetPackageVersionInfo of ops failed!"); + } ModelHelper model_helper; model_helper.SetSaveMode(is_offline_); Status ret = model_helper.SaveToOmModel(model, save_param_, file_name_prefix, model_buff); @@ -526,5 +660,4 @@ Status GeGenerator::Impl::GenerateInfershapeGraph(const Graph &graph, GraphId &g return SUCCESS; } - } // namespace ge diff --git a/src/ge/graph/build/graph_builder.cc b/src/ge/graph/build/graph_builder.cc index f2fa4ada..abcc253e 100644 --- a/src/ge/graph/build/graph_builder.cc +++ b/src/ge/graph/build/graph_builder.cc @@ -18,11 +18,14 @@ #include "common/ge/ge_util.h" #include "common/helper/model_helper.h" #include "common/opskernel/ops_kernel_info_types.h" +#include "graph/build/logical_stream_allocator.h" #include "graph/build/run_context.h" #include "graph/build/stream_graph_optimizer.h" #include "graph/manager/graph_var_manager.h" +#include "graph/passes/mark_same_addr_pass.h" #include "graph/utils/node_utils.h" #include "graph/utils/type_utils.h" +#include "graph/common/ge_call_wrapper.h" #include "init/gelib.h" #include "model/ge_model.h" @@ -34,6 +37,21 @@ const int32_t kInvalidPerfLevel = -1; namespace ge { GraphBuilder::GraphBuilder() : build_mode_(BuildMode::GEN_TASK_WITH_FUSION), hcom_parallel_(false) {} +Status GraphBuilder::MarkGraph(ComputeGraphPtr &graph) { + GE_CHECK_NOTNULL(graph); + bool is_unknown_shape = false; + for (const auto &node : graph->GetDirectNode()) { + GE_CHK_STATUS_RET(ge::NodeUtils::GetNodeUnknownShapeStatus(*node, is_unknown_shape), + "Get node[%s] shape status failed!", node->GetName().c_str()); + if (is_unknown_shape) { + break; + } + } + graph->SetGraphUnknownFlag(is_unknown_shape); + GELOGD("mark graph [%s] unknown status success! value is %d", graph->GetName().c_str(), is_unknown_shape); + return SUCCESS; +} + void GraphBuilder::SetOptions(const ge::GraphManagerOptions &options) { stream_max_parallel_num_ = options.stream_max_parallel_num; hcom_parallel_ = options.hcom_parallel; @@ -54,7 +72,7 @@ Status GraphBuilder::CalcOpParam(const ge::ComputeGraphPtr &graph) { return GE_CLI_GE_NOT_INITIALIZED; } - for (const auto &node_ptr : graph->GetAllNodes()) { + for (const auto &node_ptr : graph->GetNodes(graph->GetGraphUnknownFlag())) { GE_CHECK_NOTNULL(node_ptr->GetOpDesc()); std::string kernel_lib_name = node_ptr->GetOpDesc()->GetOpKernelLibName(); if (kernel_lib_name.empty()) { @@ -102,11 +120,7 @@ Status GraphBuilder::UpdateParentNodeOutputSize(const ge::ComputeGraphPtr &graph graph->GetName().c_str()); auto parent_op_desc = parent_node_ptr->GetOpDesc(); GE_CHECK_NOTNULL(parent_op_desc); - bool is_unknown_shape = false; - if (!AttrUtils::GetBool(parent_op_desc, ATTR_NAME_IS_UNKNOWN_SHAPE, is_unknown_shape)) { - GELOGE(PARAM_INVALID, "Get op %s unknown shape attr failed.", parent_op_desc->GetName().c_str()); - return PARAM_INVALID; - } + bool is_unknown_shape = graph->GetGraphUnknownFlag(); if (is_unknown_shape) { GELOGI("Current graph[%s] is unknown, no need to update parent node[%s] output size.", graph->GetName().c_str(), parent_node_ptr->GetName().c_str()); @@ -121,14 +135,14 @@ Status GraphBuilder::UpdateParentNodeOutputSize(const ge::ComputeGraphPtr &graph for (const auto &in_data_anchor : node_ptr->GetAllInDataAnchors()) { auto index = in_data_anchor->GetIdx(); ge::GeTensorDesc desc_temp = op_desc->GetInputDesc(index); - int64_t size = 0; - GE_IF_BOOL_EXEC(ge::TensorUtils::GetSize(desc_temp, size) != SUCCESS, GELOGI("Get size failed!")); uint32_t parent_index = 0; if (!AttrUtils::GetInt(desc_temp, ATTR_NAME_PARENT_NODE_INDEX, parent_index)) { - GELOGE(INTERNAL_ERROR, "NetOutput input tensor %d, attr %s not found.", index, - ATTR_NAME_PARENT_NODE_INDEX.c_str()); - return INTERNAL_ERROR; + GELOGI("NetOutput input tensor %d, attr %s not found.", index, ATTR_NAME_PARENT_NODE_INDEX.c_str()); + continue; } + + int64_t size = 0; + GE_IF_BOOL_EXEC(ge::TensorUtils::GetSize(desc_temp, size) != SUCCESS, GELOGI("Get size failed!")); ge::GeTensorDesc parent_desc_temp = parent_op_desc->GetOutputDesc(parent_index); ge::TensorUtils::SetSize(parent_desc_temp, size); GE_CHK_STATUS_RET(parent_op_desc->UpdateOutputDesc(parent_index, parent_desc_temp)); @@ -176,7 +190,7 @@ Status GraphBuilder::BuildForKnownShapeGraph(ComputeGraphPtr &comp_graph, auto subgraph_map = graph_partitioner_.GetSubGraphMap(); GE_TIMESTAMP_START(BuildSubgraph); - ge::ModelBuilder builder(comp_graph, subgraph_map, stream_max_parallel_num_, hcom_parallel_, build_mode_); + ge::ModelBuilder builder(session_id, comp_graph, subgraph_map, stream_max_parallel_num_, hcom_parallel_, build_mode_); GE_DUMP(comp_graph, "BeforePreBuildModel"); GE_TIMESTAMP_START(PreBuildModel); GE_CHK_STATUS_RET(builder.PreBuildModel(), "Graph[%s] builder PreBuildModel() return fail.", @@ -229,7 +243,7 @@ Status GraphBuilder::BuildForUnknownShapeGraph(ComputeGraphPtr &comp_graph, GeMo GE_TIMESTAMP_END(CalcOpParam, "GraphBuilder::CalcOpParam"); GE_DUMP(comp_graph, "AfterCalcOpParam"); Graph2SubGraphInfoList subgraph_map; - ge::ModelBuilder builder(comp_graph, subgraph_map, stream_max_parallel_num_, hcom_parallel_, build_mode_); + ge::ModelBuilder builder(session_id, comp_graph, subgraph_map, stream_max_parallel_num_, hcom_parallel_, build_mode_); ModelPtr model_ptr = MakeShared(); if (model_ptr == nullptr) { return MEMALLOC_FAILED; @@ -263,51 +277,38 @@ Status GraphBuilder::BuildForDynamicShapeGraph(ComputeGraphPtr &comp_graph, GeRootModelPtr &ge_root_model_ptr, GeModelPtr &ge_model_ptr, uint64_t session_id) { GELOGI("Start to build BuildForDynamicShape for dynamic shape."); - for (const auto &node : comp_graph->GetDirectNode()) { + // mark unknown shape attr + for (auto &sub_graph : comp_graph->GetAllSubgraphs()) { + auto status = MarkGraph(sub_graph); + if (status != SUCCESS) { + GELOGE(FAILED, "mark graph failed!"); + return status; + } + } + // Update Root Graph Data size + for (auto &node : comp_graph->GetDirectNode()) { auto op_desc = node->GetOpDesc(); GE_CHECK_NOTNULL(op_desc); + op_desc->SetStreamId(kInvalidStream); if (node->GetType() == DATA) { GE_CHK_STATUS_RET(CalcDynShapeRootGraphDataSize(op_desc), "Calc dynamic shape root graph data[%s] size failed.", op_desc->GetName().c_str()); } - - // ATTR_NAME_IS_UNKNOWN_SHAPE is set on "graph partion" stage, but afer fusion , the graph may - // be changed so here need to renew. For example , the scene followed: - // (known)partioncall(known) (known)partioncall(known) - // After fusion - // | --> - // (known)Unique(unknown)--->(unknow)Shape(unknown) (known)FuncDef(known) - // if scene like this , it should be process as known shape graph - bool is_unknown_shape = false; - GE_CHK_STATUS_RET(ge::NodeUtils::GetNodeUnknownShapeStatus(*node, is_unknown_shape), - "Get node[%s] shape status failed!", node->GetName().c_str()); - if (!is_unknown_shape) { - GE_CHK_BOOL_EXEC(ge::AttrUtils::SetBool(op_desc, ATTR_NAME_IS_UNKNOWN_SHAPE, is_unknown_shape), return FAILED, - "Renew node [%s] attr[%s] failed!", node->GetName().c_str(), ATTR_NAME_IS_UNKNOWN_SHAPE.c_str()); - GELOGD("renew node [%s] attr[%s] success! value is %d", node->GetName().c_str(), - ATTR_NAME_IS_UNKNOWN_SHAPE.c_str(), is_unknown_shape); - } - - vector subgraph_names = op_desc->GetSubgraphInstanceNames(); - for (auto subgraph_name : subgraph_names) { - ComputeGraphPtr subgraph = comp_graph->GetSubgraph(subgraph_name); - bool is_unknown_shape = false; - if (!AttrUtils::GetBool(op_desc, ATTR_NAME_IS_UNKNOWN_SHAPE, is_unknown_shape)) { - GELOGE(PARAM_INVALID, "Get op %s unknown shape attr failed.", op_desc->GetName().c_str()); - return PARAM_INVALID; - } - if (is_unknown_shape) { - // unknown shape build flow - GE_CHK_STATUS_RET(BuildForUnknownShapeGraph(subgraph, ge_model_ptr, session_id), - "Build for unknown shape graph failed."); - } else { - // known shape build flow - GE_CHK_STATUS_RET(BuildForKnownShapeGraph(subgraph, subgraph_ptr_list, ge_model_ptr, session_id), - "Build for known shape graph failed."); - } - ge_root_model_ptr->SetSubgraphInstanceNameToModel(subgraph_name, ge_model_ptr); + } + // + for (auto &sub_graph : comp_graph->GetAllSubgraphs()) { + if (sub_graph->GetGraphUnknownFlag()) { + // unknown shape build flow + GE_CHK_STATUS_RET(BuildForUnknownShapeGraph(sub_graph, ge_model_ptr, session_id), + "Build for unknown shape graph failed."); + } else { + // known shape build flow + GE_CHK_STATUS_RET(BuildForKnownShapeGraph(sub_graph, subgraph_ptr_list, ge_model_ptr, session_id), + "Build for known shape graph failed."); } + ge_root_model_ptr->SetSubgraphInstanceNameToModel(sub_graph->GetName(), ge_model_ptr); } + return SUCCESS; } @@ -327,8 +328,9 @@ Status GraphBuilder::GetTaskInfo(const ge::ModelBuilder &builder, const ModelPtr GELOGE(INTERNAL_ERROR, "Get weight memory size fail."); return INTERNAL_ERROR; } - auto *get_mem_base = - reinterpret_cast(reinterpret_cast(ge::VarManager::Instance(0)->GetVarMemMaxSize())); + + auto var_manager = VarManager::Instance(session_id); + auto *get_mem_base = reinterpret_cast(reinterpret_cast(var_manager->GetVarMemMaxSize())); uint8_t *get_weight_mem_base = get_mem_base; if (weight_size > 0) { get_weight_mem_base = get_mem_base + memory_size; @@ -354,11 +356,8 @@ Status GraphBuilder::GetTaskInfo(const ge::ModelBuilder &builder, const ModelPtr return ret; } GE_DUMP(comp_graph, "AfterOptimizeStreamedSubGraph"); - auto *get_var_mem_base = - reinterpret_cast(reinterpret_cast(ge::VarManager::Instance(0)->GetVarMemLogicBase())); - uint64_t var_size = (ge::VarManager::Instance(session_id)->GetVarMemSize(RT_MEMORY_HBM) > 0) - ? ge::VarManager::Instance(0)->GetVarMemMaxSize() - : 0; + auto *get_var_mem_base = reinterpret_cast(reinterpret_cast(var_manager->GetVarMemLogicBase())); + uint64_t var_size = (var_manager->GetVarMemSize(RT_MEMORY_HBM) > 0) ? var_manager->GetVarMemMaxSize() : 0; TaskGenerator task_generator(get_var_mem_base, var_size); ret = task_generator.GetTaskInfo(*model_ptr, comp_graph, session_id, run_context.GetRunContext()); @@ -368,6 +367,13 @@ Status GraphBuilder::GetTaskInfo(const ge::ModelBuilder &builder, const ModelPtr Status GraphBuilder::SetInputSize(const ge::NodePtr &node_ptr) { // set input_desc.size = src_node.output_desc.size if (node_ptr->GetType() == DATA) { + bool is_unknown_shape = false; + GE_CHK_STATUS_RET(ge::NodeUtils::GetNodeUnknownShapeStatus(*node_ptr, is_unknown_shape), + "Get data node[%s] shape status failed!", node_ptr->GetName().c_str()); + if (is_unknown_shape) { + GELOGD("data node: %s is unknown shape, do not set input size!", node_ptr->GetName().c_str()); + return SUCCESS; + } if (UpdateDataInputSize(node_ptr) != SUCCESS) { GELOGE(FAILED, "Update data input size failed."); return FAILED; @@ -398,7 +404,7 @@ Status GraphBuilder::SetInputSize(const ge::NodePtr &node_ptr) { GE_CHECK_NOTNULL(input_desc); ge::TensorUtils::SetSize(const_cast(*input_desc), size); GE_CHK_STATUS_RET(node_op_desc->UpdateInputDesc(in_data_anchor->GetIdx(), *input_desc)); - GELOGD("%s input desc, dim_size: %zu, mem_size: %u, format: %s, type: %s.", node_ptr->GetName().c_str(), + GELOGD("%s input desc, dim_size: %zu, mem_size: %ld, format: %s, type: %s.", node_ptr->GetName().c_str(), input_desc->GetShape().GetDimNum(), size, TypeUtils::FormatToSerialString(input_desc->GetFormat()).c_str(), TypeUtils::DataTypeToSerialString(input_desc->GetDataType()).c_str()); } diff --git a/src/ge/graph/build/graph_builder.h b/src/ge/graph/build/graph_builder.h index def3a28b..2597aa2a 100644 --- a/src/ge/graph/build/graph_builder.h +++ b/src/ge/graph/build/graph_builder.h @@ -67,6 +67,7 @@ class GraphBuilder { GeModelPtr &ge_model_ptr, uint64_t session_id = INVALID_SESSION_ID); Status BuildForUnknownShapeGraph(ComputeGraphPtr &comp_graph, GeModelPtr &ge_model_ptr, uint64_t session_id = INVALID_SESSION_ID); + Status MarkGraph(ComputeGraphPtr &graph); int build_mode_; std::map stream_max_parallel_num_; diff --git a/src/ge/graph/build/label_allocator.cc b/src/ge/graph/build/label_allocator.cc index 46c092f5..f8fbe28b 100644 --- a/src/ge/graph/build/label_allocator.cc +++ b/src/ge/graph/build/label_allocator.cc @@ -24,7 +24,6 @@ #include "graph/label/label_maker.h" namespace ge { - LabelAllocator::LabelAllocator(const ComputeGraphPtr &graph) : compute_graph_(graph) {} Status LabelAllocator::AssignFunctionalLabels(uint32_t &label_index) { @@ -76,5 +75,4 @@ bool LabelAllocator::CollectFunctionalNode(ComputeGraphPtr &graph, std::setGetOpDesc(), kAttrNameParentOpType, parent_op_type)) { - if ((parent_op_type != CONSTANT) && (parent_op_type != CONSTANTOP)) { - return true; - } - } - } - } - - return false; -} - Status AssignByLabelPass::Run(ComputeGraphPtr graph, const vector &subgraphs, Context &context) { bool changed = false; int64_t &next_stream = context.next_stream; @@ -133,21 +110,6 @@ Status IndependentStreamPass::Run(ComputeGraphPtr graph, const vector &subgraphs, Context &context) { bool changed = false; - if (IsHeadNodeExceeded(subgraphs)) { - int64_t &next_stream = context.next_stream; - for (const SubgraphPtr &subgraph : subgraphs) { - if (!HasAssignedStream(*subgraph)) { - subgraph->stream_id = next_stream; - changed = true; - } - } - if (changed) { - ++next_stream; - return SUCCESS; - } - return NOT_CHANGED; - } - map end_subgraph_map; map pld_subgraph_map; InitEndSubgraphMap(subgraphs, end_subgraph_map); @@ -190,24 +152,6 @@ Status AssignByDependencyPass::Run(ComputeGraphPtr graph, const vector &subgraphs) const { - size_t aicpu_node_num = 0; - for (const SubgraphPtr &subgraph : subgraphs) { - if (subgraph->engine_conf.id == kAICPUEngineName && !HasNonConstInputNode(*subgraph)) { - const SubGraphInfo &subgraph_info = subgraph->subgraph_info; - auto compute_graph = subgraph_info.GetSubGraph(); - aicpu_node_num += compute_graph->GetDirectNode().size() - subgraph_info.GetPld2EndMap().size() - - subgraph_info.GetEnd2PldMap().size(); - if (aicpu_node_num > kHeadNodeMaxNum) { - GELOGI("aicpu_node_num, %zu", aicpu_node_num); - return true; - } - } - } - - return false; -} - void AssignByDependencyPass::InitEndSubgraphMap(const vector &subgraphs, map &end_subgraph_map) { for (const auto &subgraph : subgraphs) { @@ -727,7 +671,7 @@ void LogicalStreamAllocator::RefreshContinuousStreams(const ComputeGraphPtr &gra int64_t stream_num = context_.next_stream; vector stream_has_node(stream_num); - for (const NodePtr &node : graph->GetAllNodes()) { + for (const NodePtr &node : graph->GetNodes(graph->GetGraphUnknownFlag())) { if (node != nullptr) { auto op_desc = node->GetOpDesc(); if (op_desc != nullptr) { @@ -748,7 +692,7 @@ void LogicalStreamAllocator::RefreshContinuousStreams(const ComputeGraphPtr &gra } } - for (const NodePtr &node : graph->GetAllNodes()) { + for (const NodePtr &node : graph->GetNodes(graph->GetGraphUnknownFlag())) { auto op_desc = node->GetOpDesc(); if (op_desc != nullptr) { int64_t stream_id = op_desc->GetStreamId(); diff --git a/src/ge/graph/build/logical_stream_allocator.h b/src/ge/graph/build/logical_stream_allocator.h index 71946630..280a4104 100644 --- a/src/ge/graph/build/logical_stream_allocator.h +++ b/src/ge/graph/build/logical_stream_allocator.h @@ -81,9 +81,6 @@ class LogicalStreamPass { bool HasStreamLabel(const Subgraph &subgraph) const; bool HasAssignedStream(const Subgraph &subgraph) const; - // Determine if the input of the subgraph is a constant. - bool HasNonConstInputNode(const Subgraph &subgraph) const; - private: std::string name_; }; @@ -121,7 +118,6 @@ class AssignByDependencyPass : public LogicalStreamPass { void UpdateAssignedSubgraphs(Context &context); void UpdateReusedSubgraphs(); - bool IsHeadNodeExceeded(const std::vector &subgraphs) const; bool CouldReuse(const SubgraphPtr &subgraph, const SubgraphPtr &pred_subgraph, const std::map &pld_subgraph_map); diff --git a/src/ge/graph/build/memory/block_mem_assigner.cc b/src/ge/graph/build/memory/block_mem_assigner.cc index df7912fa..1910618d 100644 --- a/src/ge/graph/build/memory/block_mem_assigner.cc +++ b/src/ge/graph/build/memory/block_mem_assigner.cc @@ -18,6 +18,7 @@ #include #include +#include "external/ge/ge_api_types.h" #include "framework/common/debug/ge_log.h" #include "graph/anchor.h" #include "graph/buffer.h" @@ -39,7 +40,6 @@ namespace { const char *const kAttrNameWorkspaceReuseFlag = "workspace_reuse_flag"; const char *const kL2FusionDynamicConvergeOp = "l2fusion_dynamic_converge_op"; const char *const kOpNoReuseMem = "no_reuse_mem_flag"; -const char *const kDisableReuseMemory = "ge.exec.disableReuseMemory"; const char *const OP_NO_REUSE_MEM = "OP_NO_REUSE_MEM"; const int kReuseMaxCount = 10; const int kReuseMaxOpNum = 10; @@ -133,21 +133,20 @@ bool MemoryBlock::IsSameLabel(std::string &first_batch_label) { } bool CanNotLifeReuse(MemoryBlock *block) { - if (block == nullptr || !block->reuse_mem_ || block->deleted_block_ || block->continuous_block_ || - block->GetLifeEnd() == kMaxLifeTime) { + if ((block == nullptr) || !block->reuse_mem_ || block->deleted_block_ || block->continuous_block_) { return true; } return false; } -void MemoryBlock::AddLifeReuseBlock(MemoryBlock *block) { +void MemoryBlock::AddLifeReuseBlock(MemoryBlock *block, DependStreamLife &total_node_depend_stream_life) { if (CanNotLifeReuse(this) || CanNotLifeReuse(block)) { return; } MemoryBlock *parent = nullptr; MemoryBlock *child = nullptr; // merge small block to large block - if ((block->GetLifeBegin() > GetLifeEnd()) && (block->stream_id_ == stream_id_)) { + if (block->GetDependLifeBegin(stream_id_, total_node_depend_stream_life) > GetLifeEnd()) { if ((child_offset_ + block->block_size_) <= block_size_) { parent = this; child = block; @@ -181,6 +180,87 @@ size_t MemoryBlock::GetLifeBegin() { return life_time; } +/// |-stream 1-| |-stream 2-| +/// |--block1--| |--block---| +/// |--block2--| |--block---| +/// |--block3--|\ |--block---| +/// |--block---| \ |--block---| +/// |--block---| \|--block---| +/// |--block---| |--block7--| +/// |--block---| |--block---| +/// block7's first node's input node's life begin > block2's life end, block7 can reuse block1~block2 +size_t MemoryBlock::GetDependLifeBegin(int64_t stream_id, DependStreamLife &total_node_depend_stream_life) { + AddDependLifeBegin(total_node_depend_stream_life); + auto it = depend_stream_life_.find(stream_id); + if (it == depend_stream_life_.end()) { + return 0; + } + return it->second; +} + +void AddDependLife(const ge::NodePtr &org_node, const ge::NodePtr &node, int64_t stream_id, + std::map &depend_stream_life, DependStreamLife &total_node_depend_stream_life) { + GE_CHECK_NOTNULL_EXEC(node, return ); + auto node_desc = node->GetOpDesc(); + GE_CHECK_NOTNULL_EXEC(node_desc, return ); + auto node_id = node_desc->GetId(); + auto stream_life = total_node_depend_stream_life.find(node_id); + if (stream_life != total_node_depend_stream_life.end()) { + for (auto &it : stream_life->second) { + if (depend_stream_life.find(it.first) == depend_stream_life.end()) { + depend_stream_life[it.first] = it.second; + } + } + return; + } + + for (const auto &in_anchor : node->GetAllInAnchors()) { + GE_CHECK_NOTNULL_EXEC(in_anchor, continue); + for (auto peer_out_anchor : in_anchor->GetPeerAnchors()) { + GE_CHECK_NOTNULL_EXEC(peer_out_anchor, continue); + auto peer_node = peer_out_anchor->GetOwnerNode(); + GE_CHECK_NOTNULL_EXEC(peer_node, continue); + auto peer_node_desc = peer_node->GetOpDesc(); + GE_CHECK_NOTNULL_EXEC(peer_node_desc, continue); + auto peer_node_stream_id = peer_node_desc->GetStreamId(); + if (peer_node_stream_id < 0) { + continue; + } + size_t peer_node_life_time = peer_node_desc->GetId(); + auto it = depend_stream_life.find(peer_node_stream_id); + if (it == depend_stream_life.end() || peer_node_life_time > it->second) { + depend_stream_life[peer_node_stream_id] = peer_node_life_time; + if (peer_node_stream_id != stream_id) { + GELOGI("Node:%s stream id:%ld depend node:%s stream id:%ld index[%d] life time[%zu].", + org_node->GetName().c_str(), stream_id, peer_node_desc->GetName().c_str(), peer_node_stream_id, + peer_out_anchor->GetIdx(), peer_node_life_time); + } + AddDependLife(org_node, peer_node, stream_id, depend_stream_life, total_node_depend_stream_life); + } + } + } + + // save on node to save next calculation + for (auto &it : depend_stream_life) { + if (total_node_depend_stream_life[node_id].find(it.first) == total_node_depend_stream_life[node_id].end()) { + total_node_depend_stream_life[node_id][it.first] = it.second; + } + } +} + +void MemoryBlock::AddDependLifeBegin(DependStreamLife &total_node_depend_stream_life) { + if (!depend_stream_life_.empty()) { + return; + } + if (!node_type_index_list_.empty()) { + auto node = node_type_index_list_.front().node; + if (node != nullptr) { + AddDependLife(node, node, stream_id_, depend_stream_life_, total_node_depend_stream_life); + } + } + depend_stream_life_[stream_id_] = GetLifeBegin(); +} + size_t MemoryBlock::GetLifeEnd() { if (!node_type_index_list_.empty()) { return node_type_index_list_.back().life_time_end; @@ -302,7 +382,7 @@ void BlockMemAssigner::GetOutAndWorkSpaceMem(vector &all_memory_size) { if (iter1 == anchor_to_symbol_.end()) { continue; } - std::string symbol = iter1->second; + const std::string &symbol = iter1->second; auto iter2 = symbol_size_.find(symbol); if (iter2 == symbol_size_.end()) { symbol_size_[symbol] = size; @@ -317,7 +397,7 @@ void BlockMemAssigner::GetOutAndWorkSpaceMem(vector &all_memory_size) { all_memory_size.insert(all_memory_size.end(), temp.begin(), temp.end()); } GELOGI("The last atomic_addr_clean node id: %ld", atomic_addr_clean_id_); - for (auto &pair : symbol_size_) { + for (const auto &pair : symbol_size_) { all_memory_size.emplace_back(pair.second); } sort(all_memory_size.begin(), all_memory_size.end()); @@ -427,14 +507,6 @@ bool CanReuseBySize(const map &reusable_block_counts, const Me return can_reuse; } -bool CanReuseByStream(const std::unordered_set &reuse_stream, MemoryBlock &reusable_block) { - bool can_reuse = false; - if (reuse_stream.find(reusable_block.stream_id_) != reuse_stream.cend()) { - can_reuse = true; - } - return can_reuse; -} - bool BlockMemAssigner::IsOutNodeSetContinuousInput(const NodePtr &n, uint32_t out_index, std::string &peer_name, uint32_t &peer_input_index) { if (n == nullptr || n->GetAllOutDataAnchors().size() <= 0) { @@ -495,11 +567,11 @@ void BlockMemAssigner::InitReuseFlag() { ge::CONSTANT, ge::CONSTANTOP}; static const std::set kPostReuseTypes = {ge::DATA_TYPE, ge::AIPP_DATA_TYPE, ge::ENTER, ge::REFENTER, ge::NEXTITERATION, ge::REFNEXTITERATION}; - for (auto &pair : symbol_to_anchors_) { + for (const auto &pair : symbol_to_anchors_) { std::string symbol = pair.first; bool pre_reuse_flag = true; bool post_reuse_flag = true; - for (auto &node_index_io : pair.second) { + for (const auto &node_index_io : pair.second) { if (node_index_io.io_type_ == kIn) { continue; } @@ -513,13 +585,13 @@ void BlockMemAssigner::InitReuseFlag() { if (node_index_io.node_->GetOutDataNodes().empty()) { out_flg = true; } - for (auto &in_anchor : out_anchor->GetPeerInDataAnchors()) { + for (const auto &in_anchor : out_anchor->GetPeerInDataAnchors()) { if (IsDirectOutputNode(in_anchor->GetOwnerNode(), in_anchor->GetIdx())) { out_flg = true; break; } } - std::string type = out_anchor->GetOwnerNode()->GetType(); + const std::string &type = out_anchor->GetOwnerNode()->GetType(); pre_reuse_flag = pre_reuse_flag && !out_flg && (kPreReuseTypes.count(type) == 0); post_reuse_flag = post_reuse_flag && (kPostReuseTypes.count(type) == 0); if (!pre_reuse_flag && !post_reuse_flag) { @@ -552,7 +624,7 @@ bool BlockMemAssigner::IsPreReuse(const NodePtr &node, uint32_t out_index) const return false; } - std::string symbol = iter1->second; + const std::string &symbol = iter1->second; auto iter2 = pre_reuse_flag_.find(symbol); if (iter2 == pre_reuse_flag_.end()) { return false; @@ -570,7 +642,7 @@ bool BlockMemAssigner::IsPostReuse(const MemoryBlock *mem_block) const { if (mem_block == nullptr) { return false; } - for (auto &symbol : mem_block->SymbolList()) { + for (const auto &symbol : mem_block->SymbolList()) { auto iter = post_reuse_flag_.find(symbol); if (iter == post_reuse_flag_.end()) { continue; @@ -593,8 +665,7 @@ bool BlockMemAssigner::IsSymbolExist(const NodeIndexIO &node_index_io) { if (iter == anchor_to_symbol_.end()) { return false; } - std::string symbol = iter->second; - return symbol_blocks_.find(symbol) != symbol_blocks_.end(); + return symbol_blocks_.find(iter->second) != symbol_blocks_.end(); } /// @@ -603,10 +674,10 @@ bool BlockMemAssigner::IsSymbolExist(const NodeIndexIO &node_index_io) { /// @return void /// void BlockMemAssigner::PrintSymbolMap() { - for (auto &pair : symbol_to_anchors_) { + for (const auto &pair : symbol_to_anchors_) { GELOGD("symbol=%s, max_size=%zu, pre_reuse=%s, post_reuse=%s", pair.first.c_str(), symbol_size_[pair.first], pre_reuse_flag_[pair.first] ? "true" : "false", post_reuse_flag_[pair.first] ? "true" : "false"); - for (auto &node_index_io : pair.second) { + for (const auto &node_index_io : pair.second) { GELOGD("anchor:%s", node_index_io.ToString().c_str()); } } @@ -622,15 +693,14 @@ MemoryBlock *BlockMemAssigner::ApplyMemory(size_t block_size, size_t real_size, bool is_reuse_memory = false; string ge_disable_reuse_mem_env = "0"; - (void)ge::GetContext().GetOption(kDisableReuseMemory, ge_disable_reuse_mem_env); + (void)ge::GetContext().GetOption(OPTION_EXEC_DISABLE_REUSED_MEMORY, ge_disable_reuse_mem_env); if (ge_disable_reuse_mem_env != "1") { bool reuse_mem_flag = !((workspace_reuse_flag.size() > out_index) && !workspace_reuse_flag[out_index]); is_reuse_memory = !node_op_desc->HasAttr(kL2FusionDynamicConvergeOp) && !node_op_desc->HasAttr(kOpNoReuseMem) && reuse_mem_flag && is_op_reuse_mem && (IsPreReuse(n, out_index)); auto stream_id = node_op_desc->GetStreamId(); - auto map_iter = reusable_streams_map_.find(stream_id); - if (is_reuse_memory && map_iter != reusable_streams_map_.end()) { - for (auto it = reusable_blocks_.begin(); it != reusable_blocks_.end(); ++it) { + if (is_reuse_memory) { + for (auto it = reusable_blocks_[stream_id].begin(); it != reusable_blocks_[stream_id].end(); ++it) { MemoryBlock *reusable_block = *it; if (!IsPostReuse(reusable_block)) { reusable_block->reuse_mem_ = false; @@ -640,10 +710,7 @@ MemoryBlock *BlockMemAssigner::ApplyMemory(size_t block_size, size_t real_size, // A node can reuse blocks of the same stream and preorder streams auto id = GetAtomicAddrCleanId(); - if (CanReuseBySize(reusable_block_counts_, *reusable_block, block_size, real_size, continuous, id) && - CanReuseByStream(map_iter->second, *reusable_block)) { - GELOGD("Cross stream mem reuse, target stream:%ld, current stream:%ld", reusable_block->stream_id_, - stream_id); + if (CanReuseBySize(reusable_block_counts_, *reusable_block, block_size, real_size, continuous, id)) { reusable_block->AddNodeTypeIndex({n, mem_type, out_index, false}, real_size, no_align_size); if (mem_type == kOutput) { auto iter = anchor_to_symbol_.find(NodeIndexIO(n, out_index, kOut).ToString()); @@ -654,7 +721,7 @@ MemoryBlock *BlockMemAssigner::ApplyMemory(size_t block_size, size_t real_size, reusable_block->continuous_block_ = continuous; reusable_block->ref_count_++; ReduceReusableBlockCount(*reusable_block, reusable_block_counts_); - reusable_blocks_.erase(it); + reusable_blocks_[stream_id].erase(it); return reusable_block; } } @@ -700,7 +767,7 @@ MemoryBlock *BlockMemAssigner::ApplyOutMemory(const NodePtr &n, uint32_t index, "Get no align size failed"); if (IsSymbolExist(node_index_io)) { - std::string symbol = anchor_to_symbol_[node_index_io.ToString()]; + const std::string &symbol = anchor_to_symbol_[node_index_io.ToString()]; block = symbol_blocks_[symbol]; block->AddNodeTypeIndex({n, kOutput, index, true}, size, no_align_size); block->ref_count_++; @@ -923,7 +990,7 @@ Status BlockMemAssigner::AssignOutputMemoryWithReuse(const NodePtr &node, vector (void)ge::AttrUtils::GetBool(op_desc, ATOMIC_ATTR_IS_ATOMIC_NODE, is_atomic); // Allocate memory for the current node and release node memory of the same size in the workspace GE_IF_BOOL_EXEC(ge_disable_reuse_mem_env_ != "1", - ReleaseMemorys(stream_workspace_blocks_[stream_id], reusable_blocks_);) + ReleaseMemorys(stream_workspace_blocks_[stream_id], reusable_blocks_[stream_id]);) for (uint32_t i = 0; i < static_cast(op_desc->GetOutputsSize()); i++) { int64_t size = 0; auto output_op_desc = op_desc->GetOutputDescPtr(i); @@ -977,10 +1044,7 @@ Status BlockMemAssigner::AssignOutputMemoryWithReuse(const NodePtr &node, vector /// @return Status result /// void BlockMemAssigner::AssignMemoryWithReuse(vector &ranges) { - // Init reusable streams map - InitReusableStreamMap(); - - (void)ge::GetContext().GetOption(kDisableReuseMemory, ge_disable_reuse_mem_env_); + (void)ge::GetContext().GetOption(OPTION_EXEC_DISABLE_REUSED_MEMORY, ge_disable_reuse_mem_env_); GEEVENT("Reuse memory %s", ge_disable_reuse_mem_env_ == "1" ? "close" : "open"); string op_no_reuse_mem_str; const char *op_no_reuse_mem = std::getenv(OP_NO_REUSE_MEM); @@ -1033,7 +1097,7 @@ void BlockMemAssigner::AssignMemoryWithReuse(vector &ranges) { GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(mem_block == nullptr, continue, "failed to apply memory block."); CheckWorkspaceReuse(workspace_reuse_flag, i, stream_id, mem_block); } - ReleaseInputNodeOutMemory(node_out_blocks_, reusable_blocks_, n); + ReleaseInputNodeOutMemory(node_out_blocks_, reusable_blocks_[stream_id], n); } GELOGD("Assigned memory blocks:"); @@ -1044,7 +1108,7 @@ void BlockMemAssigner::AssignMemoryWithReuse(vector &ranges) { bool merge_dynamic_batch = false; GE_IF_BOOL_EXEC(!(ge_disable_reuse_mem_env_ == "1"), merge_dynamic_batch = MergeDynamicBatchBlocks();) - GE_IF_BOOL_EXEC(!merge_dynamic_batch, ReuseBlocksByLifeTime();) + GE_IF_BOOL_EXEC((!(ge_disable_reuse_mem_env_ == "1") && !merge_dynamic_batch), ReuseBlocksByLifeTime(ranges.size());) AssignContinuousBlocks(); ResizeMemoryBlocks(); @@ -1221,7 +1285,11 @@ void BlockMemAssigner::AssignContinuousBlocks() { } } -void BlockMemAssigner::ReuseBlocksByLifeTime() { +void BlockMemAssigner::ReuseBlocksByLifeTime(size_t range_size) { + // 1 means block size is same so no need to do this + if (range_size <= 1) { + return; + } for (size_t i = 0; i < memory_blocks_.size(); ++i) { auto parent = memory_blocks_[i]; if (parent == nullptr || parent->deleted_block_) { @@ -1231,7 +1299,7 @@ void BlockMemAssigner::ReuseBlocksByLifeTime() { parent->reuse_mem_ = false; } for (size_t j = i + 1; j < memory_blocks_.size(); ++j) { - parent->AddLifeReuseBlock(memory_blocks_[j]); + parent->AddLifeReuseBlock(memory_blocks_[j], total_node_depend_stream_life_); } } } @@ -1318,10 +1386,10 @@ void SetOffsetSize(const NodeTypeIndex &node_type, const MemoryBlock *block, siz } GELOGI( "[IMAS]Set %s name[%s] %s[%u] offset to [%ld] streamid[%ld] size[%zu] realsize[%zu]" - " noalignsize[%zu] life time begin[%zu] life time end[%zu] child[%d] isref[%d].", + " noalignsize[%zu] life time begin[%zu] life time end[%zu] child[%d:%d:%d:%d] isref[%d].", graph_name.c_str(), op_desc->GetName().c_str(), node_type.GetMemType().c_str(), node_type.index, offset, op_desc->GetStreamId(), block->Size(), real_size, no_align_size, op_desc->GetId(), end, child_block, - node_type.ref_input); + block->reuse_mem_, block->continuous_block_, block->deleted_block_, node_type.ref_input); } void SetBlockOpMemOffset(MemoryBlock *block, bool child_block) { @@ -1380,139 +1448,6 @@ Status BlockMemAssigner::Assign() { return SUCCESS; } -void BlockMemAssigner::InitReusableStreamMap() { - // save a stream's id and its first Node and last node. - map> stream_head_tail_node_map; - // save a stream's id and its directly child stream. - map> stream_dependency_map; - // save a stream's id and its occupied memory. - unordered_map stream_mem_map; - - // Find streams's first and last node. - FindHeadAndTailNodesForStream(stream_head_tail_node_map, stream_mem_map); - - // If streamB's first node is the output of streamA's last node, then B depends on A. - FindDependentStream(stream_head_tail_node_map, stream_dependency_map); - - // If a stream has more than one child stream, select the one that occupies the closest memory - for (const auto &iter : stream_dependency_map) { - if (iter.second.empty()) { - continue; - } - int64_t target_size = stream_mem_map[iter.first]; - int64_t min_size_gap = LONG_MAX; - int64_t target_reuse_stream_id = 0; - for (auto id : iter.second) { - if (labs(stream_mem_map[id] - target_size) < min_size_gap) { - target_reuse_stream_id = id; - min_size_gap = labs(stream_mem_map[id] - target_size); - } - } - // If b can reuse a, then b should also be able to reuse all blocks that a can reuse. - reusable_streams_map_[target_reuse_stream_id].insert(reusable_streams_map_[iter.first].begin(), - reusable_streams_map_[iter.first].end()); - } -} - -void BlockMemAssigner::FindHeadAndTailNodesForStream(map> &stream_head_tail_node_map, - unordered_map &stream_mem_map) { - for (const auto &n : compute_graph_->GetAllNodes()) { - GE_IF_BOOL_EXEC(n->GetOpDesc() == nullptr, GELOGW("Op desc is nullptr"); continue); - auto stream_id = n->GetOpDesc()->GetStreamId(); - // traverse to find streams's first and last node. - if (stream_head_tail_node_map.find(stream_id) == stream_head_tail_node_map.end()) { - stream_head_tail_node_map[stream_id] = std::make_pair(n, n); - reusable_streams_map_[stream_id].insert(stream_id); // a node can reuse blocks from same stream. - } else { - stream_head_tail_node_map[stream_id].second = n; - } - - // Accumulate the output size of the node in the stream. - for (size_t i = 0; i < n->GetOpDesc()->GetOutputsSize(); i++) { - int64_t size = 0; - if (ge::TensorUtils::GetSize(*n->GetOpDesc()->GetOutputDescPtr(static_cast(i)), size) != SUCCESS) { - GELOGW("Get output size failed!"); - continue; - } - stream_mem_map[stream_id] += size; - } - // Accumulate the workspace size of the node in the stream. - for (auto size : n->GetOpDesc()->GetWorkspaceBytes()) { - stream_mem_map[stream_id] += size; - } - } -} - -void BlockMemAssigner::FindDependentStream(map> &stream_head_tail_node_map, - map> &stream_dependency_map) { - for (const auto &it1 : stream_head_tail_node_map) { - for (const auto &it2 : stream_head_tail_node_map) { - if (it1 == it2) { - continue; - } - NodePtr pre_node = it1.second.second; - NodePtr post_node = it2.second.first; - std::vector out_nodes; - // Direct link out_node - for (const auto &out_node : pre_node->GetOutNodes()) { - if ((out_node->GetOpDesc() == nullptr) || (post_node->GetOpDesc() == nullptr) || - (pre_node->GetOpDesc() == nullptr)) { - continue; - } - out_nodes.emplace_back(out_node); - } - - FindDependentStreamBetweenGraphs(pre_node, out_nodes); - - for (auto &out_node : out_nodes) { - if (out_node->GetOpDesc()->GetId() == post_node->GetOpDesc()->GetId()) { - stream_dependency_map[pre_node->GetOpDesc()->GetStreamId()].insert(post_node->GetOpDesc()->GetStreamId()); - } - } - } - } -} - -/// -/// @ingroup GE -/// @brief Find dependent link between parent_graph and sub_graph -/// @param [in] pre_node -/// @param [out] out_nodes -/// @return void -/// @author -/// -void BlockMemAssigner::FindDependentStreamBetweenGraphs(const NodePtr &pre_node, std::vector &out_nodes) { - if ((pre_node == nullptr) || (pre_node->GetOpDesc() == nullptr)) { - return; - } - - // FunctionOp & subgraph input - std::vector subgraph_names = pre_node->GetOpDesc()->GetSubgraphInstanceNames(); - for (auto &subgraph_name : subgraph_names) { - ComputeGraphPtr subgraph = compute_graph_->GetSubgraph(subgraph_name); - if (subgraph == nullptr) { - continue; - } - for (auto &node : subgraph->GetDirectNode()) { - OpDescPtr op_desc = node->GetOpDesc(); - if (op_desc == nullptr) { - continue; - } - if (op_desc->HasAttr(ATTR_NAME_PARENT_NODE_INDEX)) { - out_nodes.emplace_back(node); - } - } - } - - // subgraph output & parent_node output - if (NodeUtils::IsSubgraphOutput(pre_node)) { - NodePtr parent_node = pre_node->GetOwnerComputeGraph()->GetParentNode(); - for (const auto &out_node : parent_node->GetOutNodes()) { - out_nodes.emplace_back(out_node); - } - } -} - bool BlockMemAssigner::CheckIsZeroMemNodeType(const string &node_type) const { return (node_type == VARIABLE) || (node_type == CONSTANT) || (node_type == MULTISHAPE) || (node_type == HCOMBROADCAST) || (node_type == HCOMALLREDUCE) || (node_type == CONSTANTOP) || diff --git a/src/ge/graph/build/memory/block_mem_assigner.h b/src/ge/graph/build/memory/block_mem_assigner.h index 8ee4506e..4e9c3b05 100644 --- a/src/ge/graph/build/memory/block_mem_assigner.h +++ b/src/ge/graph/build/memory/block_mem_assigner.h @@ -34,6 +34,8 @@ namespace ge { const size_t kMaxLifeTime = 0xffffffff; +using DependStreamLife = std::map>; + enum MemoryType { kOutput, kWorkspace }; struct NodeTypeIndex { @@ -116,7 +118,7 @@ class MemoryBlock { bool IsSameLabel(std::string &first_batch_label); - void AddLifeReuseBlock(MemoryBlock *block); + void AddLifeReuseBlock(MemoryBlock *block, DependStreamLife &node_depend_stream_life); void SetLifeTimeEnd(size_t time); @@ -124,6 +126,10 @@ class MemoryBlock { size_t GetLifeEnd(); + void AddDependLifeBegin(DependStreamLife &node_depend_stream_life); + + size_t GetDependLifeBegin(int64_t stream_id, DependStreamLife &node_depend_stream_life); + int ref_count_; int64_t stream_id_; bool deleted_block_; @@ -194,47 +200,6 @@ class BlockMemAssigner : public MemAssigner { void GetNodeWorkSpaceSize(const ge::NodePtr &node, std::vector &workspace_memory); - /// - /// @ingroup GE - /// @brief Traversing the compute_graph_ to find the reuse relationship between streams - /// @param [in] reusable_stream_map map to save stream_id and its reusable stream_ids - /// @return void - /// @author - /// - void InitReusableStreamMap(); - - /// - /// @ingroup GE - /// @brief Traversing the compute_graph_ to find the first and last nodeptr of a stream. - /// @param [in] stream_head_tail_node_map map to save stream_id and its first and last nodeptr. - /// @param [in] stream_mem_map map to save stream_id and its memory capacity. - /// @return void - /// @author - /// - void FindHeadAndTailNodesForStream(std::map> &stream_head_tail_node_map, - std::unordered_map &stream_mem_map); - - /// - /// @ingroup GE - /// @brief Traversing the compute_graph_ to find the reuse relationship between streams. - /// @param [in] stream_head_tail_node_map map to save stream_id and its first and last nodeptr. - /// @param [in] stream_dependency_map map to save stream_id and stream_ids depends on it. - /// @return void - /// @author - /// - void FindDependentStream(std::map> &stream_head_tail_node_map, - std::map> &stream_dependency_map); - - /// - /// @ingroup GE - /// @brief Find dependent link between parent_graph and sub_graph - /// @param [in] pre_node - /// @param [out] out_nodes - /// @return void - /// @author - /// - void FindDependentStreamBetweenGraphs(const NodePtr &pre_node, std::vector &out_nodes); - /// /// @ingroup GE /// @brief Determine whether it is the type of zero memory node. @@ -395,9 +360,9 @@ class BlockMemAssigner : public MemAssigner { /// @return void /// @author /// - void ReuseBlocksByLifeTime(); + void ReuseBlocksByLifeTime(size_t range_size); - std::vector reusable_blocks_; + std::unordered_map> reusable_blocks_; std::map reusable_block_counts_; @@ -411,9 +376,6 @@ class BlockMemAssigner : public MemAssigner { std::unordered_map node_continuous_input_counts_; - // save stream_id and reusable stream_ids - std::unordered_map> reusable_streams_map_; - // reuse memory vector op_no_reuse_mem_vec_; @@ -426,6 +388,8 @@ class BlockMemAssigner : public MemAssigner { size_t life_time_; int64_t atomic_addr_clean_id_ = 0; + + DependStreamLife total_node_depend_stream_life_; }; } // namespace ge #endif // GE_GRAPH_BUILD_MEMORY_BLOCK_MEM_ASSIGNER_H_ diff --git a/src/ge/graph/build/memory/graph_mem_assigner.cc b/src/ge/graph/build/memory/graph_mem_assigner.cc index c4aca639..8393c474 100644 --- a/src/ge/graph/build/memory/graph_mem_assigner.cc +++ b/src/ge/graph/build/memory/graph_mem_assigner.cc @@ -222,9 +222,10 @@ Status GraphMemoryAssigner::ReAssignMemory(bool is_loop_graph, size_t &mem_offse mem_offset = memory_offset_[0].mem_offset_; - if (mem_offset > VarManager::Instance(0)->GetGraphMemoryMaxSize()) { + auto session_id = compute_graph_->GetSessionID(); + if (mem_offset > VarManager::Instance(session_id)->GetGraphMemoryMaxSize()) { GELOGE(ge::FAILED, "Current memoffset %zu is greater than memory manager malloc max size %zu", mem_offset, - VarManager::Instance(0)->GetGraphMemoryMaxSize()); + VarManager::Instance(session_id)->GetGraphMemoryMaxSize()); return ge::FAILED; } return SUCCESS; @@ -1222,10 +1223,16 @@ ge::Status GraphMemoryAssigner::UpdateOpInputOffset(const NodePtr &node, vector< peer_out_anchor->GetOwnerNode()->GetOpDesc()->GetName().c_str(), peer_out_anchor->GetIdx(), input_list.back()); } else { + int64_t output_offset = output_list.at(peer_out_anchor->GetIdx()); + if (peer_out_anchor->GetOwnerNode()->GetType() == CONSTANT) { + GeTensorDesc tensor_desc = tmp_op_desc->GetInputDesc(input_index); + GE_CHK_STATUS(TensorUtils::GetDataOffset(tensor_desc, output_offset)); + } + GELOGI("node[%s] input[%d] is set from node[%s] out index[%d] offset[%ld]", tmp_op_desc->GetName().c_str(), input_index, peer_out_anchor->GetOwnerNode()->GetOpDesc()->GetName().c_str(), peer_out_anchor->GetIdx(), - output_list.at(peer_out_anchor->GetIdx())); - input_list.emplace_back(output_list.at(peer_out_anchor->GetIdx())); + output_offset); + input_list.emplace_back(output_offset); } } } diff --git a/src/ge/graph/build/memory/var_mem_assign_util.cc b/src/ge/graph/build/memory/var_mem_assign_util.cc index 111adc7a..a352cf65 100644 --- a/src/ge/graph/build/memory/var_mem_assign_util.cc +++ b/src/ge/graph/build/memory/var_mem_assign_util.cc @@ -299,21 +299,33 @@ Status VarMemAssignUtil::SetOutTransNodeToAssign(const ge::NodePtr &node, const Status VarMemAssignUtil::AssignMemory2HasRefAttrNode(ge::ComputeGraphPtr &compute_graph) { for (const ge::NodePtr &n : compute_graph->GetAllNodes()) { string ref_var_src_var_name; - GE_CHECK_NOTNULL(n->GetOpDesc()); - bool is_ref = ge::AttrUtils::GetStr(n->GetOpDesc(), REF_VAR_SRC_VAR_NAME, ref_var_src_var_name); - GE_IF_BOOL_EXEC(is_ref, - GE_CHK_STATUS_RET(AssignData2VarRef(n, ref_var_src_var_name, compute_graph->GetSessionID()))); + auto op_desc = n->GetOpDesc(); + GE_CHECK_NOTNULL(op_desc); + for (uint32_t idx = 0; idx < op_desc->GetOutputsSize(); idx += 1) { + const auto out_desc = op_desc->MutableOutputDesc(idx); + if (ge::AttrUtils::GetStr(out_desc, REF_VAR_SRC_VAR_NAME, ref_var_src_var_name)) { + GE_CHK_STATUS_RET(AssignData2VarRef(n, ref_var_src_var_name, compute_graph->GetSessionID(), idx)); + } + } } return SUCCESS; } Status VarMemAssignUtil::AssignData2VarRef(const ge::NodePtr &has_ref_attr_node, const string &src_var_name, - uint64_t session_id) { - if (!TransOpUtil::IsTransOp(has_ref_attr_node)) { - return SUCCESS; - } + uint64_t session_id, uint32_t out_index) { // Get ref_var_src_var address - ge::NodePtr var_ref_src_var = has_ref_attr_node->GetOwnerComputeGraph()->FindNode(src_var_name); + auto root_graph = GraphUtils::FindRootGraph(has_ref_attr_node->GetOwnerComputeGraph()); + GE_CHECK_NOTNULL(root_graph); + ge::NodePtr var_ref_src_var = root_graph->FindNode(src_var_name); + if (var_ref_src_var == nullptr) { + for (auto sub_graph : root_graph->GetAllSubgraphs()) { + auto node_ptr = sub_graph->FindNode(src_var_name); + if (node_ptr != nullptr) { + var_ref_src_var = node_ptr; + break; + } + } + } GE_IF_BOOL_EXEC(var_ref_src_var == nullptr || var_ref_src_var->GetOpDesc() == nullptr, return FAILED); GeTensorDesc src_tensor_desc = var_ref_src_var->GetOpDesc()->GetOutputDesc(0); uint8_t *dev_ptr = nullptr; @@ -322,14 +334,8 @@ Status VarMemAssignUtil::AssignData2VarRef(const ge::NodePtr &has_ref_attr_node, vector ref_attr_node_output_list = has_ref_attr_node->GetOpDesc()->GetOutputOffset(); GE_CHECK_SIZE(ref_attr_node_output_list.size()); - int out_index = 0; - bool is_get = ge::AttrUtils::GetInt(var_ref_src_var->GetOpDesc(), REF_VAR_PRE_PEER_OUT_INDEX, out_index); - if (!is_get) { - GELOGI("%s failed to get attr [REF_VAR_PRE_PEER_OUT_INDEX]", var_ref_src_var->GetName().c_str()); - } - - GE_CHK_BOOL_RET_STATUS(static_cast(out_index) < ref_attr_node_output_list.size(), FAILED, - "out_index %d >= ref_attr_node_output_list.size() %zu", out_index, + GE_CHK_BOOL_RET_STATUS(out_index < ref_attr_node_output_list.size(), FAILED, + "out_index %u >= ref_attr_node_output_list.size() %zu", out_index, ref_attr_node_output_list.size()); ref_attr_node_output_list[out_index] = static_cast(reinterpret_cast(dev_ptr)); diff --git a/src/ge/graph/build/memory/var_mem_assign_util.h b/src/ge/graph/build/memory/var_mem_assign_util.h index 036fed06..cb38af29 100644 --- a/src/ge/graph/build/memory/var_mem_assign_util.h +++ b/src/ge/graph/build/memory/var_mem_assign_util.h @@ -46,8 +46,8 @@ class VarMemAssignUtil { static Status DealTransNode(const ge::NodePtr &final_trans_node); static Status DealExportTransNode(const ge::NodePtr &node, const ge::NodePtr &final_trans_node); - static Status AssignData2VarRef(const ge::NodePtr &variable_ref, const std::string &src_var_name, - uint64_t session_id); + static Status AssignData2VarRef(const ge::NodePtr &variable_ref, const std::string &src_var_name, uint64_t session_id, + uint32_t out_index); static Status SetOutTransNodeToAssign(const ge::NodePtr &node, const ge::NodePtr &final_trans_node, size_t index); }; diff --git a/src/ge/graph/build/model_builder.cc b/src/ge/graph/build/model_builder.cc index 62abd4ab..a765d8e7 100644 --- a/src/ge/graph/build/model_builder.cc +++ b/src/ge/graph/build/model_builder.cc @@ -15,10 +15,10 @@ */ #include "graph/build/model_builder.h" +#include #include #include #include -#include #include "common/ge/ge_util.h" #include "framework/common/debug/ge_log.h" #include "graph/anchor.h" @@ -27,6 +27,7 @@ #include "graph/build/label_allocator.h" #include "graph/build/stream_allocator.h" #include "graph/common/omg_util.h" +#include "graph/common/ge_call_wrapper.h" #include "graph/debug/ge_attr_define.h" #include "graph/ge_attr_value.h" #include "graph/ge_context.h" @@ -85,9 +86,11 @@ bool IsGeLocalOp(const ge::ConstOpDescPtr &op_desc) { } // namespace namespace ge { -ModelBuilder::ModelBuilder(ge::ComputeGraphPtr compute_graph, const Graph2SubGraphInfoList &subgraphs, - const map &stream_max_parallel_num, bool hcom_parallel, int mode) - : mem_offset_(0), +ModelBuilder::ModelBuilder(uint64_t session_id, ge::ComputeGraphPtr compute_graph, + const Graph2SubGraphInfoList &subgraphs, const map &stream_max_parallel_num, + bool hcom_parallel, int mode) + : session_id_(session_id), + mem_offset_(0), weight_offset_(kWeightsStartOffset), compute_graph_(std::move(compute_graph)), subgraphs_(subgraphs), @@ -242,7 +245,7 @@ Status ModelBuilder::SetInputOutputDesc() { Status ret; GELOGI("Start to SetInputOutputDesc."); - for (const ge::NodePtr &n : compute_graph_->GetAllNodes()) { + for (const ge::NodePtr &n : compute_graph_->GetNodes(compute_graph_->GetGraphUnknownFlag())) { auto node_op_desc = n->GetOpDesc(); GE_IF_BOOL_EXEC(node_op_desc == nullptr, continue); @@ -291,7 +294,7 @@ Status ModelBuilder::SetInputOutputDesc() { } void ModelBuilder::AddNodeInputProperty() { - for (const ge::NodePtr &node : compute_graph_->GetAllNodes()) { + for (const ge::NodePtr &node : compute_graph_->GetNodes(compute_graph_->GetGraphUnknownFlag())) { auto node_op_desc = node->GetOpDesc(); GE_IF_BOOL_EXEC(node_op_desc == nullptr, GELOGW("node_op_desc is nullptr!"); return ); vector src_name_list; @@ -318,7 +321,7 @@ void ModelBuilder::AddNodeInputProperty() { node_op_desc->SetSrcIndex(src_index_list); } - for (const ge::NodePtr &node : compute_graph_->GetAllNodes()) { + for (const ge::NodePtr &node : compute_graph_->GetNodes(compute_graph_->GetGraphUnknownFlag())) { auto node_op_desc = node->GetOpDesc(); GE_IF_BOOL_EXEC(node_op_desc == nullptr, GELOGW("node_op_desc is nullptr!"); return ); GE_IF_BOOL_EXEC(node_op_desc->GetType() == NETOUTPUT, continue); @@ -356,7 +359,7 @@ void ModelBuilder::AddNodeInputProperty() { Status ModelBuilder::AdjustInputTensorFlag() { GELOGI("Start to AdjustInputTensorFlag."); - for (const ge::NodePtr &n : compute_graph_->GetAllNodes()) { + for (const ge::NodePtr &n : compute_graph_->GetNodes(compute_graph_->GetGraphUnknownFlag())) { if ((n->GetType() == DATA_TYPE) || (n->GetType() == AIPP_DATA_TYPE)) { GELOGD("Data node: %s.", n->GetName().c_str()); for (const auto &anchor : n->GetAllOutDataAnchors()) { @@ -432,6 +435,21 @@ Status ModelBuilder::BuildModelDef(ge::Model &model) { GE_CHK_BOOL_EXEC(ge::AttrUtils::SetBool(&model, ATTR_NAME_SWITCH_FOR_L1_FUSION, is_l1_fusion_enable_), GELOGE(FAILED, "SetBool of ATTR_NAME_SWITCH_FOR_L1_FUSION failed."); return FAILED); + const DumpProperties &dump_properties = PropertiesManager::Instance().GetDumpProperties(session_id_); + bool is_op_debug = dump_properties.IsOpDebugOpen(); + GELOGI("Get op debug:%d", is_op_debug); + if (is_op_debug) { + if (!ge::AttrUtils::SetBool(&model, ATTR_OP_DEBUG_FLAG, is_op_debug)) { + GELOGE(FAILED, "SetBool of ATTR_OP_DEBUG_FLAG failed."); + return FAILED; + } + uint32_t op_debug_mode = dump_properties.GetOpDebugMode(); + GELOGI("Get op debug mode:%d", op_debug_mode); + if (!ge::AttrUtils::SetInt(&model, ATTR_OP_DEBUG_MODE, op_debug_mode)) { + GELOGE(FAILED, "SetBool of ATTR_OP_DEBUG_MODE failed."); + return FAILED; + } + } model.SetName(compute_graph_->GetName()); model.SetGraph(ge::GraphUtils::CreateGraphFromComputeGraph(compute_graph_)); @@ -448,7 +466,7 @@ Status ModelBuilder::BuildModelDef(ge::Model &model) { } void ModelBuilder::ClearOriginalFormat() { - for (const ge::NodePtr &n : compute_graph_->GetAllNodes()) { + for (const ge::NodePtr &n : compute_graph_->GetNodes(compute_graph_->GetGraphUnknownFlag())) { auto node_op_desc = n->GetOpDesc(); if (node_op_desc != nullptr) { if (node_op_desc->HasAttr(ATTR_NAME_FORMAT)) { @@ -487,7 +505,7 @@ Status ModelBuilder::MergeWeights() { weight_buffer_ = buffer; auto base_addr = weight_buffer_.GetData(); - for (const ge::NodePtr &node : compute_graph_->GetAllNodes()) { + for (const ge::NodePtr &node : compute_graph_->GetNodes(compute_graph_->GetGraphUnknownFlag())) { auto op_desc = node->GetOpDesc(); GE_IF_BOOL_EXEC(op_desc == nullptr, continue); if (node->GetType() != CONSTANT) { @@ -527,8 +545,8 @@ Status ModelBuilder::MergeWeights() { weight_data.size()); return FAILED; } - uintptr_t dst_ptr = (uintptr_t)base_addr + offset; - uintptr_t src_ptr = (uintptr_t)weight_data.data(); + uintptr_t dst_ptr = reinterpret_cast(base_addr) + offset; + uintptr_t src_ptr = reinterpret_cast(weight_data.data()); size_t left_size = weight_data.size(); while (left_size > SECUREC_MEM_MAX_LEN) { auto err = memcpy_s(reinterpret_cast(dst_ptr), SECUREC_MEM_MAX_LEN, reinterpret_cast(src_ptr), @@ -565,7 +583,7 @@ Status ModelBuilder::SaveDataToModel(ge::Model &model, ge::GeModel &ge_model) { // Add TBE Kernels std::set name_set; - for (const ge::NodePtr &n : compute_graph_->GetAllNodes()) { + for (const ge::NodePtr &n : compute_graph_->GetNodes(compute_graph_->GetGraphUnknownFlag())) { auto node_op_desc = n->GetOpDesc(); GE_IF_BOOL_EXEC(node_op_desc == nullptr, continue); TBEKernelPtr tbe_kernel = node_op_desc->TryGetExtAttr(ge::OP_EXTATTR_NAME_TBE_KERNEL, TBEKernelPtr()); @@ -659,7 +677,7 @@ Status ModelBuilder::BuildModelForGetTask(ge::Model &model) { // Compile single op in graph build stage GE_TIMESTAMP_START(CompileSingleOp); GE_CHK_STATUS_RET(CompileSingleOp(), "ATC builder CompileSingleOp() return fail."); - GE_TIMESTAMP_END(CompileSingleOp, "GraphBuilder::CompileSingleOp"); + GE_TIMESTAMP_EVENT_END(CompileSingleOp, "GraphBuilder::CompileSingleOp"); // Refresh real streams and insert event nodes. GE_TIMESTAMP_START(RefreshRealStream); @@ -700,7 +718,7 @@ Status ModelBuilder::CompileSingleOp() { GE_TIMESTAMP_CALLNUM_START(BatchCompileOp); std::unordered_map> node_vector_map; - for (auto &node : compute_graph_->GetAllNodes()) { + for (auto &node : compute_graph_->GetNodes(compute_graph_->GetGraphUnknownFlag())) { auto op_desc = node->GetOpDesc(); if (op_desc == nullptr) { continue; @@ -737,7 +755,7 @@ Status ModelBuilder::CompileSingleOp() { GE_CHECK_NOTNULL(kernel_info); GE_TIMESTAMP_RESTART(BatchCompileOp); auto ret = kernel_info->CompileOp(node_vector); - GEEVENT("[GEPERFTRACE] The node size of compile op of %s is %zu", kernel_lib_name.c_str(), node_vector.size()); + GELOGI("[GEPERFTRACE] The node size of compile op of %s is %zu", kernel_lib_name.c_str(), node_vector.size()); GE_TIMESTAMP_ADD(BatchCompileOp); if (ret != ge::SUCCESS) { GELOGE(ret, "Compile op failed, kernel lib name is %s", kernel_lib_name.c_str()); diff --git a/src/ge/graph/build/model_builder.h b/src/ge/graph/build/model_builder.h index 21e611ee..86b34c6d 100644 --- a/src/ge/graph/build/model_builder.h +++ b/src/ge/graph/build/model_builder.h @@ -37,7 +37,7 @@ namespace ge { class ModelBuilder { public: - ModelBuilder(ge::ComputeGraphPtr whole_graph, const Graph2SubGraphInfoList &subgraphs, + ModelBuilder(uint64_t session_id, ge::ComputeGraphPtr whole_graph, const Graph2SubGraphInfoList &subgraphs, const std::map &stream_max_parallel_num, bool hcom_parallel, int mode = static_cast(domi::BuildMode::GEN_TASK_WITHOUT_FUSION)); @@ -82,6 +82,8 @@ class ModelBuilder { Status CompileSingleOp(); + uint64_t session_id_; + size_t mem_offset_; size_t weight_offset_; diff --git a/src/ge/graph/build/run_context.cc b/src/ge/graph/build/run_context.cc index f2a41271..cece31ea 100644 --- a/src/ge/graph/build/run_context.cc +++ b/src/ge/graph/build/run_context.cc @@ -173,5 +173,4 @@ Status RunContextUtil::CreateRunContext(Model &model, const ComputeGraphPtr &gra } RunContext &RunContextUtil::GetRunContext() { return run_context_; } - } // namespace ge diff --git a/src/ge/graph/build/stream_allocator.cc b/src/ge/graph/build/stream_allocator.cc index f6323434..d49bb61b 100644 --- a/src/ge/graph/build/stream_allocator.cc +++ b/src/ge/graph/build/stream_allocator.cc @@ -146,12 +146,6 @@ Status StreamAllocator::RefreshRealStream(int64_t &stream_num, int64_t &event_nu return status; } - status = AddActiveEntryStream(); - if (status != SUCCESS) { - GELOGE(status, "AddActiveEntryStream failed!"); - return status; - } - status = RefreshContinuousEvents(); if (status != SUCCESS) { GELOGE(status, "RefreshContinuousEvents failed!"); @@ -167,7 +161,7 @@ Status StreamAllocator::RefreshRealStream(int64_t &stream_num, int64_t &event_nu DumpEvents(); GE_DUMP(whole_graph_, "AfterRefreshRealStream"); - for (const NodePtr &node : whole_graph_->GetAllNodes()) { + for (const NodePtr &node : whole_graph_->GetNodes(whole_graph_->GetGraphUnknownFlag())) { GE_CHECK_NOTNULL(node->GetOpDesc()); auto stream_id = node->GetOpDesc()->GetStreamId(); if (stream_id == kInvalidStream) { @@ -199,7 +193,7 @@ Status StreamAllocator::AssignSingleStream() { } int64_t task_count = 0; - for (const NodePtr &node : whole_graph_->GetAllNodes()) { + for (const NodePtr &node : whole_graph_->GetNodes(whole_graph_->GetGraphUnknownFlag())) { string op_type = node->GetType(); if (IsHcclOp(op_type)) { task_count += kTaskNumPerHcclNode; @@ -236,7 +230,7 @@ Status StreamAllocator::AssignSingleStream() { } Status StreamAllocator::SetActiveStreamsByLabel() { - for (const auto &node : whole_graph_->GetAllNodes()) { + for (const auto &node : whole_graph_->GetNodes(whole_graph_->GetGraphUnknownFlag())) { OpDescPtr op_desc = node->GetOpDesc(); GE_CHECK_NOTNULL(op_desc); string stream_label; @@ -248,7 +242,7 @@ Status StreamAllocator::SetActiveStreamsByLabel() { } } - for (const auto &node : whole_graph_->GetAllNodes()) { + for (const auto &node : whole_graph_->GetNodes(whole_graph_->GetGraphUnknownFlag())) { GE_CHECK_NOTNULL(node->GetOpDesc()); vector activated_label_list; if (!AttrUtils::GetListStr(node->GetOpDesc(), ATTR_NAME_ACTIVE_LABEL_LIST, activated_label_list) || @@ -326,7 +320,7 @@ Status StreamAllocator::SetActiveStreamsForSubgraphs() { // Insert the send/recv event id to the graph Status StreamAllocator::InsertSyncEvents() { - for (const auto &cur_node : whole_graph_->GetAllNodes()) { + for (const auto &cur_node : whole_graph_->GetNodes(whole_graph_->GetGraphUnknownFlag())) { // Take the adjacent points, then judge whether need to insert the event for (const OutDataAnchorPtr &anchor : cur_node->GetAllOutDataAnchors()) { for (const InDataAnchorPtr &peer_in_anchor : anchor->GetPeerInDataAnchors()) { @@ -380,6 +374,11 @@ Status StreamAllocator::InsertOneEventInTwoNodes(const NodePtr &cur_node, const return SUCCESS; } + if ((cur_node->GetType() == ENTER) || (cur_node->GetType() == REFENTER)) { + GELOGD("No need to insert event after enter_node %s.", cur_node->GetName().c_str()); + return SUCCESS; + } + if (next_stream_id == kInvalidStream) { GELOGE(FAILED, "Stream id of next_node %s should not be %ld", next_node->GetName().c_str(), kInvalidStream); return FAILED; @@ -446,7 +445,7 @@ Status StreamAllocator::InsertEventsForSubgraph() { Status StreamAllocator::OptimizeSyncEvents() { map> stream_nodes; - for (const auto &node : whole_graph_->GetAllNodes()) { + for (const auto &node : whole_graph_->GetNodes(whole_graph_->GetGraphUnknownFlag())) { GE_CHECK_NOTNULL(node->GetOpDesc()); int64_t stream_id = node->GetOpDesc()->GetStreamId(); stream_nodes[stream_id].emplace_back(node); @@ -671,7 +670,7 @@ Status StreamAllocator::SplitStreams(vector> &split_streams) { GE_CHK_STATUS_RET(GetMaxStreamAndTask(false, max_stream_count, max_task_count), "Get max stream and task count failed."); - for (const auto &cur_node : whole_graph_->GetAllNodes()) { + for (const auto &cur_node : whole_graph_->GetNodes(whole_graph_->GetGraphUnknownFlag())) { GE_CHECK_NOTNULL(cur_node); auto op_desc = cur_node->GetOpDesc(); GE_CHECK_NOTNULL(op_desc); @@ -774,42 +773,23 @@ bool StreamAllocator::NeedSpiltNewStream(int64_t stream_node_num, int64_t max_no Status StreamAllocator::UpdateActiveStreams(const vector> &split_streams) { UpdateLabelStreams(split_streams); - for (auto &node : whole_graph_->GetAllNodes()) { + for (auto &node : whole_graph_->GetNodes(whole_graph_->GetGraphUnknownFlag())) { if ((node->GetType() == STREAMSWITCH) || (node->GetType() == STREAMSWITCHN)) { - if (InsertActiveNodesAfterSwitch(node) != SUCCESS) { - GELOGE(FAILED, "Insert active nodes after switch node failed."); + if (UpdateActiveStreamsForSwitchNode(node) != SUCCESS) { + GELOGE(FAILED, "Update active streams for switch node: %s failed.", node->GetName().c_str()); return FAILED; } } else { - vector active_streams; - GE_CHECK_NOTNULL(node->GetOpDesc()); - if (AttrUtils::GetListInt(node->GetOpDesc(), ATTR_NAME_ACTIVE_STREAM_LIST, active_streams)) { - vector new_active_streams = active_streams; - for (const uint32_t logical_stream : active_streams) { - if (static_cast(logical_stream) >= split_streams.size()) { - GELOGE(FAILED, "logical stream is out of range."); - return FAILED; - } - const set &new_split_streams = split_streams[logical_stream]; - if (!new_split_streams.empty()) { - for (int64_t split_stream : new_split_streams) { - new_active_streams.emplace_back(static_cast(split_stream)); - GELOGI("Add stream %ld to active_stream_list of node %s of graph %s", split_stream, - node->GetName().c_str(), node->GetOwnerComputeGraph()->GetName().c_str()); - } - } - } - if (!AttrUtils::SetListInt(node->GetOpDesc(), ATTR_NAME_ACTIVE_STREAM_LIST, new_active_streams)) { - GELOGE(FAILED, "Set active streams for node %s failed.", node->GetName().c_str()); - return FAILED; - } + if (UpdateActiveStreamsForActiveNode(split_streams, node) != SUCCESS) { + GELOGE(FAILED, "Update active streams for active node: %s failed.", node->GetName().c_str()); + return FAILED; } } } Status status = UpdateActiveStreamsForSubgraphs(); if (status != SUCCESS) { - GELOGE(status, "SetActiveStreamsForSubgraph failed!"); + GELOGE(status, "Update active streams for subgraphs failed!"); return status; } @@ -840,7 +820,7 @@ void StreamAllocator::UpdateLabelStreams(const vector> &split_strea } } -Status StreamAllocator::InsertActiveNodesAfterSwitch(NodePtr &switch_node) { +Status StreamAllocator::UpdateActiveStreamsForSwitchNode(NodePtr &switch_node) { vector active_nodes; if (InsertActiveNodesAfterSwitch(switch_node, active_nodes) != SUCCESS) { GELOGE(FAILED, "Insert active nodes after node %s failed.", switch_node->GetName().c_str()); @@ -906,6 +886,38 @@ Status StreamAllocator::InsertActiveNodesAfterSwitch(NodePtr &switch_node, vecto return SUCCESS; } +Status StreamAllocator::UpdateActiveStreamsForActiveNode(const vector> &split_streams, NodePtr &node) { + GE_CHECK_NOTNULL(node->GetOpDesc()); + vector active_streams; + if (AttrUtils::GetListInt(node->GetOpDesc(), ATTR_NAME_ACTIVE_STREAM_LIST, active_streams)) { + vector new_active_streams = active_streams; + for (uint32_t logical_stream : active_streams) { + if (static_cast(logical_stream) >= split_streams.size()) { + GELOGE(FAILED, "logical stream is out of range."); + return FAILED; + } + const set &new_split_streams = split_streams[logical_stream]; + for (int64_t split_stream : new_split_streams) { + for (const auto &node_stream : node_split_stream_map_) { + if (split_stream == node_stream.second) { + if (node_stream.first->GetOwnerComputeGraph() == node->GetOwnerComputeGraph()) { + new_active_streams.emplace_back(static_cast(split_stream)); + GELOGI("Add stream %ld to active_stream_list of node %s of graph %s", split_stream, + node->GetName().c_str(), node->GetOwnerComputeGraph()->GetName().c_str()); + } + break; + } + } + } + } + if (!AttrUtils::SetListInt(node->GetOpDesc(), ATTR_NAME_ACTIVE_STREAM_LIST, new_active_streams)) { + GELOGE(FAILED, "Set active streams for node %s failed.", node->GetName().c_str()); + return FAILED; + } + } + return SUCCESS; +} + Status StreamAllocator::UpdateActiveStreamsForSubgraphs() const { // Update active stream list for active nodes for (auto &node_stream_pair : node_split_stream_map_) { @@ -926,14 +938,19 @@ Status StreamAllocator::UpdateActiveStreamsForSubgraphs() const { } const auto &active_node = it->second; GE_CHECK_NOTNULL(active_node); - auto op_desc = active_node->GetOpDesc(); - GE_CHECK_NOTNULL(op_desc); + auto active_op = active_node->GetOpDesc(); + GE_CHECK_NOTNULL(active_op); vector active_streams; - (void)AttrUtils::GetListInt(op_desc, ATTR_NAME_ACTIVE_STREAM_LIST, active_streams); + (void)AttrUtils::GetListInt(active_op, ATTR_NAME_ACTIVE_STREAM_LIST, active_streams); set new_active_streams(active_streams.begin(), active_streams.end()); - new_active_streams.emplace(static_cast(node_stream_pair.second)); + // specific_activated_streams_ has already contained new split activated stream + int64_t new_split_stream = node_stream_pair.second; + if (IsActivated(new_split_stream)) { + continue; + } + new_active_streams.emplace(static_cast(new_split_stream)); active_streams.assign(new_active_streams.begin(), new_active_streams.end()); - if (!AttrUtils::SetListInt(op_desc, ATTR_NAME_ACTIVE_STREAM_LIST, active_streams)) { + if (!AttrUtils::SetListInt(active_op, ATTR_NAME_ACTIVE_STREAM_LIST, active_streams)) { GELOGE(FAILED, "Set active streams for node %s failed.", active_node->GetName().c_str()); return FAILED; } @@ -942,6 +959,20 @@ Status StreamAllocator::UpdateActiveStreamsForSubgraphs() const { return SUCCESS; } +bool StreamAllocator::IsActivated(int64_t stream_id) const { + for (const auto &node : whole_graph_->GetNodes(whole_graph_->GetGraphUnknownFlag())) { + auto op_desc = node->GetOpDesc(); + vector active_streams; + if (op_desc == nullptr || !AttrUtils::GetListInt(op_desc, ATTR_NAME_ACTIVE_STREAM_LIST, active_streams)) { + continue; + } + if (std::find(active_streams.begin(), active_streams.end(), stream_id) != active_streams.end()) { + return true; + } + } + return false; +} + Status StreamAllocator::SetActiveStreamsForLoop() { vector loop_active_streams; for (int64_t stream_id = 0; stream_id < stream_num_; stream_id++) { @@ -950,7 +981,7 @@ Status StreamAllocator::SetActiveStreamsForLoop() { } } // Set the stream that needs to be activated - for (const auto &node : whole_graph_->GetAllNodes()) { + for (const auto &node : whole_graph_->GetNodes(whole_graph_->GetGraphUnknownFlag())) { GE_CHECK_NOTNULL(node->GetOpDesc()); bool is_loop_active = false; if (AttrUtils::GetBool(node->GetOpDesc(), ATTR_NAME_IS_LOOP_ACTIVE, is_loop_active) && is_loop_active) { @@ -973,7 +1004,7 @@ Status StreamAllocator::SetActiveStreamsForLoop() { } Status StreamAllocator::CheckStreamActived() const { - for (const auto &node : whole_graph_->GetAllNodes()) { + for (const auto &node : whole_graph_->GetNodes(whole_graph_->GetGraphUnknownFlag())) { GE_CHECK_NOTNULL(node->GetOpDesc()); vector active_streams; if (AttrUtils::GetListInt(node->GetOpDesc(), ATTR_NAME_ACTIVE_STREAM_LIST, active_streams)) { @@ -989,108 +1020,6 @@ Status StreamAllocator::CheckStreamActived() const { return SUCCESS; } -// Add active entry stream for special env. -Status StreamAllocator::AddActiveEntryStream() { - auto gelib = GELib::GetInstance(); - bool head_stream = (gelib == nullptr) ? false : gelib->HeadStream(); - GELOGI("Configured head stream: %u", head_stream); - if (!head_stream) { - return SUCCESS; - } - - // Collect streams active by StreamSwitch/StreamActive node. - std::set deactive_stream; - for (ge::NodePtr &node : whole_graph_->GetAllNodes()) { - GE_CHECK_NOTNULL(node->GetOpDesc()); - Status ret = CollectDeactiveStream(node->GetOpDesc(), deactive_stream); - if (ret != SUCCESS) { - return ret; - } - } - - // Collect default active stream, Add to active entry stream. - std::vector active_stream_list; - for (int64_t stream_id = 0; stream_id < stream_num_; ++stream_id) { - if (deactive_stream.count(stream_id) == 0) { - active_stream_list.push_back(stream_id); - } - } - - int64_t new_stream_id = stream_num_; - stream_num_++; - return InsertActiveEntryStream(active_stream_list, new_stream_id); -} - -// Collect deactive stream from flowctrl op. -Status StreamAllocator::CollectDeactiveStream(const OpDescPtr &op_desc, std::set &deactive_streams) const { - GE_CHECK_NOTNULL(op_desc); - std::string op_type = op_desc->GetType(); - if (op_type == STREAMSWITCH) { - std::vector active_stream_list; - // If GetListInt fail, active_stream_list is empty. - (void)ge::AttrUtils::GetListInt(op_desc, ATTR_NAME_ACTIVE_STREAM_LIST, active_stream_list); - if (active_stream_list.size() != kMaxSwitchStreamNum) { - GELOGE(INTERNAL_ERROR, "Stream num of switch true branch must be %u.", kMaxSwitchStreamNum); - return INTERNAL_ERROR; - } - - deactive_streams.insert(active_stream_list[0]); - GELOGI("Flowctrl_op node:%s, flowctrl stream id:%u.", op_desc->GetName().c_str(), active_stream_list[0]); - } else if (op_type == STREAMACTIVE) { - if (op_desc->HasAttr(ATTR_NAME_SWITCH_BRANCH_NODE_LABEL)) { - std::vector active_stream_list; - if (!AttrUtils::GetListInt(op_desc, ATTR_NAME_ACTIVE_STREAM_LIST, active_stream_list)) { - GELOGE(INTERNAL_ERROR, "StreamActiveOp get attr ACTIVE_STREAM fail."); - return INTERNAL_ERROR; - } - - for (uint32_t deactive_stream : active_stream_list) { - deactive_streams.insert(deactive_stream); - GELOGI("Flowctrl_op node:%s, flowctrl stream id:%u.", op_desc->GetName().c_str(), deactive_stream); - } - } - } - - return SUCCESS; -} - -// Insert StreamActive Op for Entry Stream. -Status StreamAllocator::InsertActiveEntryStream(const std::vector &active_streams, int64_t stream_id) { - string node_name = whole_graph_->GetName() + "_ActiveEntryStream_" + string(STREAMACTIVE); - OpDescPtr op_desc = ge::MakeShared(node_name, STREAMACTIVE); - if (op_desc == nullptr) { - GELOGE(FAILED, "Failed to new opdesc."); - return FAILED; - } - GELOGI("Create StreamActive op:%s.", op_desc->GetName().c_str()); - - GE_CHK_BOOL_EXEC( - AttrUtils::SetListStr(op_desc, ATTR_NAME_DATA_DUMP_ORIGIN_OP_NAMES, std::move(std::vector())), - GELOGE(FAILED, "SetListStr failed."); - return FAILED); - - NodePtr active_node = whole_graph_->AddNodeFront(op_desc); - GE_IF_BOOL_EXEC(active_node == nullptr, - GELOGE(FAILED, "Create StreamActive op: %s failed.", op_desc->GetName().c_str()); - return INTERNAL_ERROR); - GE_CHECK_NOTNULL(active_node->GetOpDesc()); - // Add one stream for ActiveEntryStream Task. - active_node->GetOpDesc()->SetStreamId(stream_id); - - GE_CHK_BOOL_EXEC(AttrUtils::SetBool(op_desc, "is_aicpu_stream", true), GELOGE(FAILED, "SetBool failed."); - return FAILED); - GE_CHK_BOOL_EXEC(AttrUtils::SetListInt(active_node->GetOpDesc(), ATTR_NAME_ACTIVE_STREAM_LIST, active_streams), - GELOGE(FAILED, "SetListInt failed."); - return FAILED); - - std::vector group_names; - GE_CHK_BOOL_EXEC(AttrUtils::SetListStr(active_node->GetOpDesc(), ATTR_NAME_SWITCH_BRANCH_NODE_LABEL, group_names), - GELOGE(FAILED, "SetLisStr failed."); - return FAILED); - - return SUCCESS; -} - // Refresh events to continuous events Status StreamAllocator::RefreshContinuousEvents() { // Establish a mapping relationship from old to new event id @@ -1136,7 +1065,7 @@ Status StreamAllocator::RefreshContinuousEvents() { // Insert the real send/recv node in the graph Status StreamAllocator::InsertSyncEventNodes() { - for (const auto &node : whole_graph_->GetAllNodes()) { + for (const auto &node : whole_graph_->GetNodes(whole_graph_->GetGraphUnknownFlag())) { // Add the node corresponding to the recv event vector recv_event_id_list; GetRecvEventIdList(node, recv_event_id_list); @@ -1223,7 +1152,7 @@ Status StreamAllocator::ReorderEventNodes() const { void StreamAllocator::DumpEvents() { map> after_refresh_stream_nodes; - for (const auto &node : whole_graph_->GetAllNodes()) { + for (const auto &node : whole_graph_->GetNodes(whole_graph_->GetGraphUnknownFlag())) { GE_IF_BOOL_EXEC(node->GetOpDesc() == nullptr, continue); int64_t stream_id = node->GetOpDesc()->GetStreamId(); after_refresh_stream_nodes[stream_id].emplace_back(node); diff --git a/src/ge/graph/build/stream_allocator.h b/src/ge/graph/build/stream_allocator.h index ae79430a..a201a138 100644 --- a/src/ge/graph/build/stream_allocator.h +++ b/src/ge/graph/build/stream_allocator.h @@ -59,18 +59,16 @@ class StreamAllocator { Status SplitStreams(std::vector> &split_streams); bool NeedSpiltNewStream(int64_t stream_node_num, int64_t max_node_num_one_stream, const OpDescPtr &op_desc) const; - Status UpdateActiveStreams(const std::vector> &splited_streams); + Status UpdateActiveStreams(const std::vector> &split_streams); void UpdateLabelStreams(const std::vector> &split_streams); - Status InsertActiveNodesAfterSwitch(NodePtr &switch_node); + Status UpdateActiveStreamsForSwitchNode(NodePtr &switch_node); Status InsertActiveNodesAfterSwitch(NodePtr &switch_nodes, std::vector &switch_active_nodes); + Status UpdateActiveStreamsForActiveNode(const std::vector> &split_streams, NodePtr &node); Status UpdateActiveStreamsForSubgraphs() const; + bool IsActivated(int64_t stream_id) const; Status SetActiveStreamsForLoop(); Status CheckStreamActived() const; - Status AddActiveEntryStream(); - Status CollectDeactiveStream(const OpDescPtr &op_desc, std::set &deactive_streams) const; - Status InsertActiveEntryStream(const std::vector &active_streams, int64_t stream_id); - Status RefreshContinuousEvents(); Status InsertSyncEventNodes(); diff --git a/src/ge/graph/build/task_generator.cc b/src/ge/graph/build/task_generator.cc index 2ce4e89d..41a845a2 100644 --- a/src/ge/graph/build/task_generator.cc +++ b/src/ge/graph/build/task_generator.cc @@ -29,6 +29,7 @@ #include "graph/utils/node_utils.h" #include "graph/utils/tensor_utils.h" #include "graph/utils/type_utils.h" +#include "graph/common/ge_call_wrapper.h" #include "init/gelib.h" using domi::LogTimeStampDef; @@ -47,7 +48,6 @@ const char *const kIsOutputVar = "OUTPUT_IS_VAR"; const char *const kProfilingMode = "PROFILING_MODE"; const char *const kProfilingFpPoint = "FP_POINT"; const char *const kProfilingBpPoint = "BP_POINT"; -const char *const kOffOptimize = "off_optimize"; const uint32_t kProfilingArStep = 2; const uint64_t kProfilingFpStartLogid = 1; const uint64_t kProfilingBpEndLogid = 2; @@ -75,21 +75,7 @@ Status TaskGenerator::GetTaskInfo(Model &model, ComputeGraphPtr &graph, uint64_t std::vector task_def_list; std::map op_name_map; GE_DUMP(graph, "GenerateTaskBefore"); - bool is_unknown_shape = false; - NodePtr parent_node = graph->GetParentNode(); - if (parent_node != nullptr) { - auto op_desc = parent_node->GetOpDesc(); - GE_CHECK_NOTNULL(op_desc); - (void)AttrUtils::GetBool(op_desc, ATTR_NAME_IS_UNKNOWN_SHAPE, is_unknown_shape); - } - Status ret = SUCCESS; - if (is_unknown_shape) { - GELOGI("Beign to generate unknown shape task. Graph name is %s.", graph->GetName().c_str()); - ret = GenerateUnknownShapeTask(run_context, graph, task_def_list, op_name_map); - } else { - GELOGI("Beign to generate known shape task. Graph name is %s.", graph->GetName().c_str()); - ret = GenerateTask(run_context, graph, task_def_list, op_name_map); - } + Status ret = GenerateTask(run_context, graph, task_def_list, op_name_map); GE_DUMP(graph, "GenerateTaskAfter"); if (ret != SUCCESS) { @@ -109,7 +95,7 @@ Status TaskGenerator::GetTaskInfo(Model &model, ComputeGraphPtr &graph, uint64_t GELOGE(FAILED, "SetListStr failed."); return FAILED); - GELOGI("Generate task success, task_def_list.size:%zu, op_name_map.size:%zu", task_def_list.size(), + GELOGI("Call GenerateTask Success, task_def_list.size:%zu, op_name_map.size:%zu", task_def_list.size(), op_name_map.size()); // Init and serialize model_task_def @@ -131,7 +117,7 @@ Status TaskGenerator::GetTaskInfo(Model &model, ComputeGraphPtr &graph, uint64_t return ret; } - GELOGI("Get TaskInfo success. session id is %lu", session_id); + GELOGI("Get TaskInfo success. session_id=%lu", session_id); return SUCCESS; } @@ -198,7 +184,7 @@ Status TaskGenerator::UpdateOpIsVarAttr(const OpDescPtr &op_desc, uint64_t sessi Status TaskGenerator::SaveFusionNodes(map> &fusion_nodes, ComputeGraphPtr &graph) { std::map nodes_with_group_attr; - for (auto &node : graph->GetAllNodes()) { + for (auto &node : graph->GetNodes(graph->GetGraphUnknownFlag())) { OpDescPtr op_desc = node->GetOpDesc(); GE_CHECK_NOTNULL(op_desc); int64_t group_id = kInvalidGroupId; @@ -249,12 +235,13 @@ Status TaskGenerator::SaveFusionNodes(map> &fusion Status TaskGenerator::GenerateTask(RunContext &run_context, ComputeGraphPtr &graph, vector &task_def_list, map &op_name_map) { + GELOGD("Beign to generate task, graph name is %s.", graph->GetName().c_str()); std::shared_ptr ge_lib = GELib::GetInstance(); if ((ge_lib == nullptr) || !ge_lib->InitFlag()) { GELOGE(GE_CLI_GE_NOT_INITIALIZED, "GenerateTask failed."); return GE_CLI_GE_NOT_INITIALIZED; } - GE_CHK_STATUS_RET(MarkNodeAndSetIndex(graph), "Mark node and set index failed."); + GE_CHK_STATUS_RET(MarkNodeAndSetIndex(graph), "MarkNodeAndSetIndex failed."); ProfilingPoint profiling_point; vector all_reduce_nodes; GE_CHK_STATUS_RET(FindProfilingTaskIndex(graph, profiling_point, all_reduce_nodes)); @@ -264,15 +251,21 @@ Status TaskGenerator::GenerateTask(RunContext &run_context, ComputeGraphPtr &gra GE_TIMESTAMP_CALLNUM_START(GenerateTask); // map store fusion nodes map> fusion_nodes; - string buffer_optimize = kOffOptimize; + string buffer_optimize = "off_optimize"; (void)ge::GetContext().GetOption(BUFFER_OPTIMIZE, buffer_optimize); - if (buffer_optimize != kOffOptimize) { + if (buffer_optimize != "off_optimize") { GE_CHK_STATUS_RET(SaveFusionNodes(fusion_nodes, graph)); } std::unordered_set fusion_nodes_seen; int64_t group_key; uint32_t node_index = 0; - for (auto &node : graph->GetAllNodes()) { + rtStream_t stream = nullptr; + bool is_unknown_shape = graph->GetGraphUnknownFlag(); + if (is_unknown_shape) { + GE_CHK_STATUS_RET(SetUnknownShapeStream(run_context, stream), "Set unknown shape stream failed."); + } + + for (auto &node : graph->GetNodes(graph->GetGraphUnknownFlag())) { OpDescPtr op_desc = node->GetOpDesc(); GE_CHECK_NOTNULL(op_desc); node_index++; @@ -302,7 +295,6 @@ Status TaskGenerator::GenerateTask(RunContext &run_context, ComputeGraphPtr &gra GELOGI("Node[name:%s, type:%s] does not need to generate task.", name.c_str(), type.c_str()); continue; } - OpsKernelInfoStorePtr kernel_info_store = ops_kernel_manager.GetOpsKernelInfoStore(op_kernel_lib_name); if (kernel_info_store == nullptr) { GELOGE(INTERNAL_ERROR, "No ops kernel store found. node:%s(%s), op_kernel_lib_name=%s.", name.c_str(), @@ -311,18 +303,17 @@ Status TaskGenerator::GenerateTask(RunContext &run_context, ComputeGraphPtr &gra } GE_CHK_STATUS_RET(UpdateAnchorStatus(node), "Call UpdateAnchorStatus node:%s(%s) failed", name.c_str(), type.c_str()); - int64_t op_id = op_desc->GetId(); - int64_t stream_id = op_desc->GetStreamId(); - if (stream_id < 0 || stream_id >= static_cast(run_context.graphStreamList.size())) { - GELOGE(INTERNAL_ERROR, "node[name:%s(%s), id:%ld] stream id is invalid, stream list size=%zu", name.c_str(), - type.c_str(), op_id, run_context.graphStreamList.size()); - return INTERNAL_ERROR; - } - // Profiling task size_t task_list_size_before = task_def_list.size(); GE_CHK_STATUS_RET(InsertProfilingTaskBefore(op_desc, profiling_point, all_reduce_nodes, node_index, task_def_list)); - run_context.stream = run_context.graphStreamList[stream_id]; + int64_t op_id = op_desc->GetId(); + // Compatible with dynamic shape scenes, the default is 0 + int64_t stream_id = 0; + if (!is_unknown_shape) { + stream_id = op_desc->GetStreamId(); + GE_CHK_STATUS_RET(SetKnownShapeStream(run_context, stream_id), "node[name:%s(%s), id:%ld] stream id is invalid.", + name.c_str(), type.c_str(), op_id); + } GELOGD("Call %s to generate node[name:%s(%s), id:%ld, stream_id:%ld] task.", op_kernel_lib_name.c_str(), name.c_str(), type.c_str(), op_id, stream_id); GE_TIMESTAMP_RESTART(GenerateTask); @@ -355,131 +346,14 @@ Status TaskGenerator::GenerateTask(RunContext &run_context, ComputeGraphPtr &gra GE_CHECK_NOTNULL(task_def_ptr); task_def_ptr->set_ops_kernel_store_ptr(reinterpret_cast(ops_kernel_info_store_ptr)); } - GELOGD("Call %s to generate node[name:%s(%s), id:%ld, stream_id:%ld] task finished, generate %zu task(s).", op_kernel_lib_name.c_str(), name.c_str(), type.c_str(), op_id, stream_id, task_list_size_after - task_list_size_before); } - GE_TIMESTAMP_CALLNUM_END(GenerateTask, "GraphBuild::GenerateTask"); - return SUCCESS; -} - -Status TaskGenerator::GenerateUnknownShapeTask(RunContext &run_context, ComputeGraphPtr &graph, - vector &task_def_list, - map &op_name_map) { - std::shared_ptr ge_lib = GELib::GetInstance(); - if ((ge_lib == nullptr) || !ge_lib->InitFlag()) { - GELOGE(GE_CLI_GE_NOT_INITIALIZED, "GenerateTask failed."); - return GE_CLI_GE_NOT_INITIALIZED; - } - GE_CHK_STATUS_RET(MarkNodeAndSetIndex(graph), "Mark node and set index failed."); - ProfilingPoint profiling_point; - vector all_reduce_nodes; - GE_CHK_STATUS_RET(FindProfilingTaskIndex(graph, profiling_point, all_reduce_nodes)); - - const OpsKernelManager &ops_kernel_manager = ge_lib->OpsKernelManagerObj(); - - GE_TIMESTAMP_CALLNUM_START(GenerateTask); - // map store fusion nodes - map> fusion_nodes; - string buffer_optimize = kOffOptimize; - (void)ge::GetContext().GetOption(BUFFER_OPTIMIZE, buffer_optimize); - if (buffer_optimize != kOffOptimize) { - GE_CHK_STATUS_RET(SaveFusionNodes(fusion_nodes, graph)); - } - std::unordered_set fusion_nodes_seen; - int64_t group_key; - uint32_t node_index = 0; - rtStream_t stream = nullptr; - GE_CHK_RT_RET(rtStreamCreate(&stream, 0)); - run_context.stream = stream; - if (rtModelBindStream(run_context.model, stream, 0) != RT_ERROR_NONE) { - GELOGE(FAILED, "Call rt api failed."); - GE_CHK_RT(rtStreamDestroy(stream)); - return FAILED; - } - for (auto &node : graph->GetAllNodes()) { - OpDescPtr op_desc = node->GetOpDesc(); - GE_CHECK_NOTNULL(op_desc); - node_index++; - string name = node->GetName(); - string type = node->GetType(); - bool attr_notask = false; - bool get_attr_notask_flag = ge::AttrUtils::GetBool(op_desc, ATTR_NAME_NOTASK, attr_notask); - GE_IF_BOOL_EXEC(get_attr_notask_flag && attr_notask, - GELOGI("Node[name:%s, type:%s] does not need to generate task.", name.c_str(), type.c_str()); - continue); - - GE_CHK_STATUS_RET(UpdateOpIsVarAttr(op_desc, graph->GetSessionID())); - string op_kernel_lib_name = op_desc->GetOpKernelLibName(); - // For fusion ddb pass, task def must be continuous. - // Part2: Call - auto fusion_task_info = - FusionTaskInfo{run_context, graph, node, op_desc, node_index, ge_lib, - ops_kernel_manager, task_def_list, op_name_map, profiling_point, all_reduce_nodes}; - GE_CHK_STATUS_RET(GenerateTaskForFusionNode(fusion_task_info, fusion_nodes, fusion_nodes_seen), - "Call GenerateTaskForFusionNode node:%s(%s) failed", name.c_str(), type.c_str()); - // continue directly - if (ge::AttrUtils::GetInt(op_desc, ATTR_NAME_FUSION_GROUP_KEY, group_key)) { - GELOGI("Fusion node[name:%s, type:%s] do not need generate task again.", name.c_str(), type.c_str()); - continue; - } - if (op_kernel_lib_name.empty()) { - GELOGI("Node[name:%s, type:%s] does not need to generate task.", name.c_str(), type.c_str()); - continue; - } - OpsKernelInfoStorePtr kernel_info_store = ops_kernel_manager.GetOpsKernelInfoStore(op_kernel_lib_name); - if (kernel_info_store == nullptr) { - GELOGE(INTERNAL_ERROR, "No ops kernel store found. node:%s(%s), op_kernel_lib_name=%s.", name.c_str(), - type.c_str(), op_kernel_lib_name.c_str()); - return INTERNAL_ERROR; - } - GE_CHK_STATUS_RET(UpdateAnchorStatus(node), "Call UpdateAnchorStatus node:%s(%s) failed", name.c_str(), - type.c_str()); - int64_t op_id = op_desc->GetId(); - int64_t stream_id = op_desc->GetStreamId(); - // Profiling task - size_t task_list_size_before = task_def_list.size(); - GE_CHK_STATUS_RET(InsertProfilingTaskBefore(op_desc, profiling_point, all_reduce_nodes, node_index, task_def_list)); - - GELOGD("Call %s to generate node[name:%s(%s), id:%ld, stream_id:%ld] task.", op_kernel_lib_name.c_str(), - name.c_str(), type.c_str(), op_id, stream_id); - GE_TIMESTAMP_RESTART(GenerateTask); - auto ret = kernel_info_store->GenerateTask(*node, run_context, task_def_list); - GE_TIMESTAMP_ADD(GenerateTask); - if (ret != SUCCESS) { - GELOGE(ret, "Call %s to generate node[name:%s(%s), id:%ld, stream_id:%ld] task failed.", - op_kernel_lib_name.c_str(), name.c_str(), type.c_str(), op_id, stream_id); - return ret; - } - // Profiling task - GE_CHK_STATUS_RET(InsertProfilingTaskAfter(op_desc, profiling_point, all_reduce_nodes, node_index, task_def_list)); - size_t task_list_size_after = task_def_list.size(); - // If tasks is reduced - if (task_list_size_after < task_list_size_before) { - GELOGE(FAILED, "Call %s to generate node[name:%s(%s), id:%ld, stream_id:%ld] task. but task num from %zu to %zu.", - op_kernel_lib_name.c_str(), name.c_str(), type.c_str(), op_id, stream_id, task_list_size_before, - task_list_size_after); - return FAILED; - } - - // Reset stream id to ge stream id, as graph load must use ge stream to reassign stream - void *ops_kernel_info_store_ptr = kernel_info_store.get(); - for (size_t idx = task_list_size_before; idx < task_list_size_after; ++idx) { - op_name_map[idx] = name; - // Set opsKernelInfoStorePtr and op_index, the two fields be use in DistributeTask and InitTaskInfo - TaskDef *task_def_ptr = &task_def_list[idx]; - GE_CHECK_NOTNULL(task_def_ptr); - task_def_ptr->set_ops_kernel_store_ptr(reinterpret_cast(ops_kernel_info_store_ptr)); - } - - GELOGD("Call %s to generate node[name:%s(%s), id:%ld, stream_id:%ld] task finished, generate %zu task(s).", - op_kernel_lib_name.c_str(), name.c_str(), type.c_str(), op_id, stream_id, - task_list_size_after - task_list_size_before); + if (is_unknown_shape) { + GE_CHK_STATUS_RET(DestroyUnknownShapeStream(run_context, stream), "Destory unknown shape stream failed."); } - GE_CHK_RT(rtModelUnbindStream(run_context.model, stream)); - GE_CHK_RT(rtStreamDestroy(stream)); - GE_TIMESTAMP_CALLNUM_END(GenerateTask, "GraphBuild::GenerateTask"); + GE_TIMESTAMP_CALLNUM_EVENT_END(GenerateTask, "GraphBuild::GenerateTask"); return SUCCESS; } @@ -628,7 +502,11 @@ Status TaskGenerator::MarkNodeAndSetIndex(ComputeGraphPtr &graph) { return GE_CLI_GE_NOT_INITIALIZED; } - const auto all_nodes = graph->GetAllNodes(); + const auto all_nodes = graph->GetNodes(graph->GetGraphUnknownFlag()); + if (all_nodes.empty()) { + GELOGE(GE_GRAPH_GRAPH_NODE_NULL, "Graph's node is empty"); + return GE_GRAPH_GRAPH_NODE_NULL; + } int64_t node_index = 0; for (auto &node : all_nodes) { @@ -715,7 +593,7 @@ Status TaskGenerator::AutoFindFpOpIndex(const ComputeGraphPtr &graph, ProfilingP OpDescPtr fp_op_desc = nullptr; uint32_t current_idx = 0; uint32_t first_fp = 0; - for (auto &node : graph->GetAllNodes()) { + for (auto &node : graph->GetNodes(graph->GetGraphUnknownFlag())) { OpDescPtr op_desc = node->GetOpDesc(); GE_CHECK_NOTNULL(op_desc); string op_kernel_lib_name = op_desc->GetOpKernelLibName(); @@ -742,7 +620,7 @@ Status TaskGenerator::AutoFindFpOpIndex(const ComputeGraphPtr &graph, ProfilingP return SUCCESS; } GELOGI("Find fp_op_desc is %s, id is %ld", fp_op_desc->GetName().c_str(), fp_op_desc->GetId()); - for (auto &node : graph->GetAllNodes()) { + for (auto &node : graph->GetNodes(graph->GetGraphUnknownFlag())) { OpDescPtr op_desc = node->GetOpDesc(); GE_CHECK_NOTNULL(op_desc); current_idx++; @@ -763,7 +641,7 @@ Status TaskGenerator::AutoFindBpOpIndex(const ComputeGraphPtr &graph, ProfilingP uint32_t last_bp = 0; uint32_t iter_end = 0; uint32_t current_idx = 0; - for (auto &node : graph->GetAllNodes()) { + for (auto &node : graph->GetNodes(graph->GetGraphUnknownFlag())) { OpDescPtr op_desc = node->GetOpDesc(); GE_CHECK_NOTNULL(op_desc); current_idx++; @@ -807,7 +685,7 @@ Status TaskGenerator::AutoFindBpOpIndex(const ComputeGraphPtr &graph, ProfilingP GE_CHECK_NOTNULL(bp_op_desc); current_idx = 0; - for (auto &node : graph->GetAllNodes()) { + for (auto &node : graph->GetNodes(graph->GetGraphUnknownFlag())) { OpDescPtr op_desc = node->GetOpDesc(); GE_CHECK_NOTNULL(op_desc); current_idx++; @@ -826,7 +704,7 @@ Status TaskGenerator::FindFpOfEnv(const ComputeGraphPtr &graph, const std::strin GELOGI("Start FindFpOfEnv"); uint32_t current_idx = 0; uint32_t first_fp = 0; - for (auto &node : graph->GetAllNodes()) { + for (auto &node : graph->GetNodes(graph->GetGraphUnknownFlag())) { OpDescPtr op_desc = node->GetOpDesc(); GE_CHECK_NOTNULL(node->GetOpDesc()); current_idx++; @@ -851,7 +729,7 @@ Status TaskGenerator::FindBpOfEnv(const ComputeGraphPtr &graph, const std::strin uint32_t current_idx = 0; uint32_t iter_end = 0; uint32_t last_bp = 0; - for (auto &node : graph->GetAllNodes()) { + for (auto &node : graph->GetNodes(graph->GetGraphUnknownFlag())) { OpDescPtr op_desc = node->GetOpDesc(); GE_CHECK_NOTNULL(node->GetOpDesc()); current_idx++; @@ -927,10 +805,10 @@ Status TaskGenerator::FindProfilingTaskIndex(const ComputeGraphPtr &graph, Profi bool train_graph = graph->GetNeedIteration(); if (profiling_point.fp_index == 0 && train_graph) { - GELOGE(FAILED, "First forward op name can't be found in graph for training trace."); + GELOGW("First forward op name can't be found in graph for training trace."); } if (profiling_point.bp_index == 0 && train_graph) { - GELOGE(FAILED, "Last backward op name can't be found in graph for training trace."); + GELOGW("Last backward op name can't be found in graph for training trace."); } return SUCCESS; } @@ -1068,4 +946,31 @@ bool TaskGenerator::IsProfPoint(const OpDescPtr &op, const std::string &name) { return false; } +Status TaskGenerator::SetUnknownShapeStream(RunContext &run_context, rtStream_t &stream) { + GE_CHK_RT_RET(rtStreamCreate(&stream, 0)); + run_context.stream = stream; + rtError_t rt_ret = rtModelBindStream(run_context.model, stream, 0); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(FAILED, "Call rt api failed, ret: 0x%X", rt_ret); + GE_CHK_RT_RET(rtStreamDestroy(stream)); + return FAILED; + } + return SUCCESS; +} + +Status TaskGenerator::DestroyUnknownShapeStream(RunContext &run_context, rtStream_t &stream) { + GE_CHK_RT(rtModelUnbindStream(run_context.model, stream)); + GE_CHK_RT_RET(rtStreamDestroy(stream)); + return SUCCESS; +} + +Status TaskGenerator::SetKnownShapeStream(RunContext &run_context, int64_t stream_id) { + if (stream_id < 0 || stream_id >= static_cast(run_context.graphStreamList.size())) { + GELOGE(INTERNAL_ERROR, "Stream id[%ld] is invalid, stream list size=%zu", stream_id, + run_context.graphStreamList.size()); + return INTERNAL_ERROR; + } + run_context.stream = run_context.graphStreamList[stream_id]; + return SUCCESS; +} } // namespace ge diff --git a/src/ge/graph/build/task_generator.h b/src/ge/graph/build/task_generator.h index 02721e00..b2ca4470 100644 --- a/src/ge/graph/build/task_generator.h +++ b/src/ge/graph/build/task_generator.h @@ -93,18 +93,6 @@ class TaskGenerator { Status GenerateTask(RunContext &run_context, ComputeGraphPtr &graph, std::vector &task_def_list, std::map &op_name_map); - /// - /// call engine to generate unknown shape task. - /// @param run_context run context - /// @param graph compute graph - /// @param task_def_list task def list generate by engine - /// @param op_name_map relation of task index and op - /// @return SUCCESS:seccess - /// Other: failed - /// - Status GenerateUnknownShapeTask(RunContext &run_context, ComputeGraphPtr &graph, - std::vector &task_def_list, std::map &op_name_map); - /// /// AddModelTaskToModel /// @param model_task_def model task @@ -154,6 +142,12 @@ class TaskGenerator { Status SaveFusionNodes(map> &fusion_nodes, ComputeGraphPtr &graph); + Status SetUnknownShapeStream(RunContext &run_context, rtStream_t &stream); + + Status DestroyUnknownShapeStream(RunContext &run_context, rtStream_t &stream); + + Status SetKnownShapeStream(RunContext &run_context, int64_t stream_id); + uint8_t *var_mem_base_ = nullptr; uint64_t var_mem_size_ = 0; }; diff --git a/src/ge/graph/common/ge_call_wrapper.h b/src/ge/graph/common/ge_call_wrapper.h index a21d642e..a2bb6b88 100644 --- a/src/ge/graph/common/ge_call_wrapper.h +++ b/src/ge/graph/common/ge_call_wrapper.h @@ -18,6 +18,41 @@ #define GE_GE_CALL_WRAPPER_H_ #include "framework/common/debug/ge_log.h" +#define GE_TIMESTAMP_START(stage) uint64_t startUsec_##stage = ge::GetCurrentTimestap() + +#define GE_TIMESTAMP_END(stage, stage_name) \ + do { \ + uint64_t endUsec_##stage = ge::GetCurrentTimestap(); \ + GELOGI("[GEPERFTRACE] The time cost of %s is [%lu] micro second.", (stage_name), \ + (endUsec_##stage - startUsec_##stage)); \ + } while (0); + +#define GE_TIMESTAMP_EVENT_END(stage, stage_name) \ + do { \ + uint64_t endUsec_##stage = ge::GetCurrentTimestap(); \ + GEEVENT("[GEPERFTRACE] The time cost of %s is [%lu] micro second.", (stage_name), \ + (endUsec_##stage - startUsec_##stage)); \ + } while (0); + +#define GE_TIMESTAMP_CALLNUM_START(stage) \ + uint64_t startUsec_##stage = ge::GetCurrentTimestap(); \ + uint64_t call_num_of##stage = 0; \ + uint64_t time_of##stage = 0 + +#define GE_TIMESTAMP_RESTART(stage) (startUsec_##stage = ge::GetCurrentTimestap()) + +#define GE_TIMESTAMP_ADD(stage) \ + time_of##stage += ge::GetCurrentTimestap() - startUsec_##stage; \ + call_num_of##stage++ + +#define GE_TIMESTAMP_CALLNUM_END(stage, stage_name) \ + GELOGI("[GEPERFTRACE] The time cost of %s is [%lu] micro second, call num is %lu", (stage_name), time_of##stage, \ + call_num_of##stage) + +#define GE_TIMESTAMP_CALLNUM_EVENT_END(stage, stage_name) \ + GEEVENT("[GEPERFTRACE] The time cost of %s is [%lu] micro second, call num is %lu", (stage_name), time_of##stage, \ + call_num_of##stage) + #define RUN_WITH_TIMESTAMP_NAME(var_name, prefix, func, ...) \ do { \ GE_TIMESTAMP_START(var_name); \ @@ -29,10 +64,23 @@ } \ } while (0) +#define RUN_WITH_PERF_TIMESTAMP_NAME(var_name, prefix, func, ...) \ + do { \ + GE_TIMESTAMP_START(var_name); \ + auto ret_inner_macro = func(__VA_ARGS__); \ + GE_TIMESTAMP_EVENT_END(var_name, #prefix "::" #func) \ + if (ret_inner_macro != ge::SUCCESS) { \ + GELOGE(ret_inner_macro, "Failed to process " #prefix "_" #func); \ + return ret_inner_macro; \ + } \ + } while (0) + #define JOIN_NAME_INNER(a, b) a##b #define JOIN_NAME(a, b) JOIN_NAME_INNER(a, b) #define COUNTER_NAME(a) JOIN_NAME(a, __COUNTER__) #define GE_RUN(prefix, func, ...) \ RUN_WITH_TIMESTAMP_NAME(COUNTER_NAME(ge_timestamp_##prefix), prefix, func, __VA_ARGS__) +#define GE_RUN_PERF(prefix, func, ...) \ + RUN_WITH_PERF_TIMESTAMP_NAME(COUNTER_NAME(ge_timestamp_##prefix), prefix, func, __VA_ARGS__) #endif // GE_GE_CALL_WRAPPER_H_ diff --git a/src/ge/graph/execute/graph_execute.cc b/src/ge/graph/execute/graph_execute.cc index 9293b9af..5ff89c07 100644 --- a/src/ge/graph/execute/graph_execute.cc +++ b/src/ge/graph/execute/graph_execute.cc @@ -120,7 +120,7 @@ Status GraphExecutor::FreeInOutBuffer() { } } -Status GraphExecutor::MallocInOutBuffer(const std::vector &buffer_size, std::vector &data_addr) { +Status GraphExecutor::MallocInOutBuffer(const std::vector &buffer_size, std::vector &data_addr) { if (malloc_flag_) { auto all_size_same = true; if (buffer_size.size() == buffer_size_.size()) { @@ -169,7 +169,7 @@ Status GraphExecutor::PrepareInputData(const std::vector &input_tensor graph_input_data.timestamp = 0; std::size_t inputSize = input_tensor.size(); std::size_t output_size = output_desc.size(); - std::vector bufferSizeVec; + std::vector bufferSizeVec; std::vector addrVec; for (std::size_t i = 0; i < inputSize; ++i) { @@ -211,7 +211,7 @@ Status GraphExecutor::PrepareInputData(const std::vector &input_tensor for (std::size_t j = 0; j < output_size; j++) { auto desc = output_desc[j]; - uint32_t buffer_size = desc.size; + uint64_t buffer_size = desc.size; DataBuffer out_data_buf; out_data_buf.data = reinterpret_cast(addrVec[inputSize + j]); @@ -225,6 +225,13 @@ Status GraphExecutor::PrepareInputData(const std::vector &input_tensor Status GraphExecutor::SyncExecuteModel(uint32_t model_id, const std::vector &input_tensor, std::vector &output_tensor) { + auto model_manager = ge::ModelManager::GetInstance(); + GE_CHECK_NOTNULL(model_manager); + if (model_manager->IsDynamicShape(model_id)) { + GELOGI("[ExecuteGraph] GetInputOutputDescInfo via dynamic shape model executor, modelId=%u", model_id); + return model_manager->SyncExecuteModel(model_id, input_tensor, output_tensor); + } + // Prepare input and output std::vector inputs_desc; std::vector output_desc; @@ -450,11 +457,13 @@ Status GraphExecutor::GetInputOutputDescInfo(const uint32_t model_id, vector &input_desc, vector &output_desc, - std::vector &input_formats, std::vector &out_formats) { + std::vector &input_formats, std::vector &out_formats, + bool new_model_desc) { try { auto model_manager = ge::ModelManager::GetInstance(); GE_CHECK_NOTNULL(model_manager); - Status ret = model_manager->GetInputOutputDescInfo(model_id, input_desc, output_desc, input_formats, out_formats); + Status ret = model_manager->GetInputOutputDescInfo(model_id, input_desc, output_desc, input_formats, out_formats, + new_model_desc); if (ret != SUCCESS) { GELOGE(ret, "GetInputOutputDescInfo failed."); CsaInteract::GetInstance().WriteErrorCode(ret, ERROR_MODULE_FMK, JOBSUBSTATE_GRAPH_EXEC); @@ -573,5 +582,4 @@ Status GraphExecutor::GetAllAippInputOutputDims(uint32_t model_id, uint32_t inde return SUCCESS; } - } // namespace ge diff --git a/src/ge/graph/execute/graph_execute.h b/src/ge/graph/execute/graph_execute.h index ae467515..6919a439 100644 --- a/src/ge/graph/execute/graph_execute.h +++ b/src/ge/graph/execute/graph_execute.h @@ -71,7 +71,7 @@ class GraphExecutor { static Status GetInputOutputDescInfo(const uint32_t model_id, vector &input_desc, vector &output_desc, std::vector &input_formats, - std::vector &output_formats); + std::vector &output_formats, bool new_model_desc = false); static Status GetAIPPInfo(uint32_t model_id, uint32_t index, AippConfigInfo &aipp_info); @@ -110,7 +110,7 @@ class GraphExecutor { Status FreeInOutBuffer(); - Status MallocInOutBuffer(const std::vector &buffer_size, std::vector &data_addr); + Status MallocInOutBuffer(const std::vector &buffer_size, std::vector &data_addr); bool init_flag_; @@ -129,7 +129,7 @@ class GraphExecutor { bool malloc_flag_; std::vector buffer_addr_; - std::vector buffer_size_; + std::vector buffer_size_; }; } // namespace ge diff --git a/src/ge/graph/load/graph_loader.cc b/src/ge/graph/load/graph_loader.cc index 1f4cbcf9..4a986308 100644 --- a/src/ge/graph/load/graph_loader.cc +++ b/src/ge/graph/load/graph_loader.cc @@ -350,7 +350,8 @@ Status GraphLoader::GetMemoryInfo(int64_t &free) { return RT_FAILED; } // Add small page memory size - free = static_cast(free_mem + VarManager::Instance(0)->GetUseMaxMemorySize() - total_mem); + free = + static_cast(free_mem + VarManager::Instance(GetContext().SessionId())->GetUseMaxMemorySize() - total_mem); GELOGI("GetMemoryInfo free[%zu], total[%zu], return free[%ld]", free_mem, total_mem, free); return SUCCESS; } diff --git a/src/ge/graph/load/new_model_manager/cpu_queue_schedule.cc b/src/ge/graph/load/new_model_manager/cpu_queue_schedule.cc index 06111015..a0011b34 100644 --- a/src/ge/graph/load/new_model_manager/cpu_queue_schedule.cc +++ b/src/ge/graph/load/new_model_manager/cpu_queue_schedule.cc @@ -339,7 +339,7 @@ Status CpuTaskActiveEntry::Distribute() { return RT_FAILED; } - GELOGI("Cpu kernel launch wait end task success."); + GELOGI("Cpu kernel launch active entry task success."); return SUCCESS; } diff --git a/src/ge/graph/load/new_model_manager/data_dumper.cc b/src/ge/graph/load/new_model_manager/data_dumper.cc index 653a3fa1..a4fe8898 100644 --- a/src/ge/graph/load/new_model_manager/data_dumper.cc +++ b/src/ge/graph/load/new_model_manager/data_dumper.cc @@ -21,7 +21,6 @@ #include #include -#include "common/debug/log.h" #include "common/properties_manager.h" #include "framework/common/debug/ge_log.h" #include "framework/common/util.h" @@ -37,9 +36,36 @@ namespace { const uint32_t kAicpuLoadFlag = 1; const uint32_t kAicpuUnloadFlag = 0; +const int64_t kOpDebugSize = 2048; +const int64_t kOpDebugShape = 2048; +const int8_t kDecimal = 10; +const uint32_t kAddrLen = sizeof(void *); const char *const kDumpOutput = "output"; const char *const kDumpInput = "input"; const char *const kDumpAll = "all"; + +// parse for format like nodename:input:index +static bool ParseNameIndex(const std::string &node_name_index, std::string &node_name, std::string &input_or_output, + size_t &index) { + auto sep = node_name_index.rfind(':'); + if (sep == std::string::npos) { + return false; + } + auto index_str = node_name_index.substr(sep + 1); + index = static_cast(std::strtol(index_str.c_str(), nullptr, kDecimal)); + auto node_name_without_index = node_name_index.substr(0, sep); + sep = node_name_without_index.rfind(':'); + if (sep == std::string::npos) { + return false; + } + node_name = node_name_without_index.substr(0, sep); + input_or_output = node_name_without_index.substr(sep + 1); + return !(input_or_output != kDumpInput && input_or_output != kDumpOutput); +} + +static bool IsTensorDescWithSkipDumpAddrType(bool has_mem_type_attr, vector v_memory_type, size_t i) { + return has_mem_type_attr && (v_memory_type[i] == RT_MEMORY_L1); +} } // namespace static int32_t GetIrDataType(ge::DataType data_type) { @@ -138,6 +164,13 @@ void DataDumper::SaveEndGraphId(uint32_t task_id, uint32_t stream_id) { end_graph_stream_id_ = stream_id; } +void DataDumper::SaveOpDebugId(uint32_t task_id, uint32_t stream_id, void *op_debug_addr, bool is_op_debug) { + op_debug_task_id_ = task_id; + op_debug_stream_id_ = stream_id; + op_debug_addr_ = op_debug_addr; + is_op_debug_ = is_op_debug; +} + void DataDumper::SaveDumpTask(uint32_t task_id, uint32_t stream_id, const std::shared_ptr &op_desc, uintptr_t args) { if (op_desc == nullptr) { @@ -202,56 +235,121 @@ static void SetOpMappingLoopAddr(uintptr_t step_id, uintptr_t loop_per_iter, uin } } -Status DataDumper::DumpOutput(const InnerDumpInfo &inner_dump_info, aicpu::dump::Task &task) { - GELOGI("Start dump output"); - if (inner_dump_info.is_task) { - // tbe or aicpu op - const auto &output_descs = inner_dump_info.op->GetAllOutputsDesc(); - const auto input_size = inner_dump_info.op->GetAllInputsDesc().size(); - const std::vector output_addrs = ModelUtils::GetOutputDataAddrs(runtime_param_, inner_dump_info.op, false); - if (output_descs.size() != output_addrs.size()) { - GELOGE(PARAM_INVALID, "Invalid output desc addrs size %zu, op %s has %zu output desc.", output_addrs.size(), - inner_dump_info.op->GetName().c_str(), output_descs.size()); - return PARAM_INVALID; - } +Status DataDumper::GenerateOutput(aicpu::dump::Output &output, const OpDesc::Vistor &tensor_descs, + const uintptr_t &addr, size_t index) { + output.set_data_type(static_cast(GetIrDataType(tensor_descs.at(index).GetDataType()))); + output.set_format(static_cast(tensor_descs.at(index).GetFormat())); - for (size_t i = 0; i < output_descs.size(); ++i) { - aicpu::dump::Output output; - output.set_data_type(static_cast(GetIrDataType(output_descs.at(i).GetDataType()))); - output.set_format(static_cast(output_descs.at(i).GetFormat())); + for (auto dim : tensor_descs.at(index).GetShape().GetDims()) { + output.mutable_shape()->add_dim(dim); + } + int64_t output_size = 0; + if (TensorUtils::GetTensorSizeInBytes(tensor_descs.at(index), output_size) != SUCCESS) { + GELOGE(PARAM_INVALID, "Get output size filed"); + return PARAM_INVALID; + } + GELOGD("Get output size in dump is %ld", output_size); + std::string origin_name; + int32_t origin_output_index = -1; + (void)AttrUtils::GetStr(&tensor_descs.at(index), ATTR_NAME_DATA_DUMP_ORIGIN_NAME, origin_name); + (void)AttrUtils::GetInt(&tensor_descs.at(index), ATTR_NAME_DATA_DUMP_ORIGIN_OUTPUT_INDEX, origin_output_index); + output.set_size(output_size); + output.set_original_name(origin_name); + output.set_original_output_index(origin_output_index); + output.set_original_output_format(static_cast(tensor_descs.at(index).GetOriginFormat())); + output.set_original_output_data_type(static_cast(tensor_descs.at(index).GetOriginDataType())); + output.set_address(static_cast(addr)); + return SUCCESS; +} - for (auto dim : output_descs.at(i).GetShape().GetDims()) { - output.mutable_shape()->add_dim(dim); - } +Status DataDumper::DumpRefOutput(const DataDumper::InnerDumpInfo &inner_dump_info, aicpu::dump::Output &output, + size_t i, const std::string &node_name_index) { + std::string dump_op_name; + std::string input_or_output; + size_t index; + // parser and find which node's input or output tensor desc is chosen for dump info + if (!ParseNameIndex(node_name_index, dump_op_name, input_or_output, index)) { + GELOGE(PARAM_INVALID, "Op [%s] output desc[%zu] with invalid ATTR_DATA_DUMP_REF attr[%s].", + inner_dump_info.op->GetName().c_str(), i, node_name_index.c_str()); + return PARAM_INVALID; + } + GE_CHECK_NOTNULL(compute_graph_); + auto replace_node = compute_graph_->FindNode(dump_op_name); + GE_RT_PARAM_INVALID_WITH_LOG_IF_TRUE(replace_node == nullptr, + "Op [%s] output desc[%zu] with invalid ATTR_DATA_DUMP_REF attr[%s]," + " cannot find redirect node[%s].", + inner_dump_info.op->GetName().c_str(), i, node_name_index.c_str(), + dump_op_name.c_str()); + auto replace_opdesc = replace_node->GetOpDesc(); + GE_CHECK_NOTNULL(replace_opdesc); + auto iter = ref_info_.find(replace_opdesc); + GE_RT_PARAM_INVALID_WITH_LOG_IF_TRUE(iter == ref_info_.end(), + "Op [%s] output desc[%zu] cannot find any saved redirect node[%s]'s info.", + inner_dump_info.op->GetName().c_str(), i, replace_opdesc->GetName().c_str()); + GE_CHECK_NOTNULL(iter->second); + auto addr = reinterpret_cast(iter->second); + if (input_or_output == kDumpInput) { + const auto &replace_input_descs = replace_opdesc->GetAllInputsDesc(); + addr += kAddrLen * index; + GE_CHK_STATUS_RET(GenerateOutput(output, replace_input_descs, addr, index), "Generate output failed"); + } else if (input_or_output == kDumpOutput) { + const auto &replace_output_descs = replace_opdesc->GetAllOutputsDesc(); + const auto replace_input_size = replace_opdesc->GetAllInputsDesc().size(); + addr += (index + replace_input_size) * kAddrLen; + GE_CHK_STATUS_RET(GenerateOutput(output, replace_output_descs, addr, index), "Generate output failed"); + } + GELOGD("Op [%s] output desc[%zu] dump info is replaced by node[%s] [%s] tensor_desc [%zu]", + inner_dump_info.op->GetName().c_str(), i, dump_op_name.c_str(), input_or_output.c_str(), index); + return SUCCESS; +} - int64_t output_size = 0; - if (TensorUtils::GetTensorSizeInBytes(output_descs.at(i), output_size) != SUCCESS) { - GELOGE(PARAM_INVALID, "Get output size filed"); - return PARAM_INVALID; - } - GELOGI("Get output size in dump is %ld", output_size); - std::string origin_name; - int32_t origin_output_index = -1; - (void)AttrUtils::GetStr(&output_descs.at(i), ATTR_NAME_DATA_DUMP_ORIGIN_NAME, origin_name); - (void)AttrUtils::GetInt(&output_descs.at(i), ATTR_NAME_DATA_DUMP_ORIGIN_OUTPUT_INDEX, origin_output_index); - GE_IF_BOOL_EXEC(output_size <= 0, GELOGE(PARAM_INVALID, "Output size %ld is less than zero", output_size); - return PARAM_INVALID) - output.set_size(output_size); - output.set_original_name(origin_name); - output.set_original_output_index(origin_output_index); - output.set_original_output_format(static_cast(output_descs.at(i).GetOriginFormat())); - output.set_original_output_data_type(static_cast(output_descs.at(i).GetOriginDataType())); - output.set_address(static_cast(inner_dump_info.args + (i + input_size) * sizeof(void *))); - - task.mutable_output()->Add(std::move(output)); +Status DataDumper::DumpOutputWithTask(const InnerDumpInfo &inner_dump_info, aicpu::dump::Task &task) { + const auto &output_descs = inner_dump_info.op->GetAllOutputsDesc(); + const std::vector output_addrs = ModelUtils::GetOutputDataAddrs(runtime_param_, inner_dump_info.op); + if (output_descs.size() != output_addrs.size()) { + GELOGE(PARAM_INVALID, "Invalid output desc addrs size %zu, op %s has %zu output desc.", output_addrs.size(), + inner_dump_info.op->GetName().c_str(), output_descs.size()); + return PARAM_INVALID; + } + std::vector v_memory_type; + bool has_mem_type_attr = ge::AttrUtils::GetListInt(inner_dump_info.op, ATTR_NAME_OUTPUT_MEM_TYPE_LIST, v_memory_type); + GE_RT_PARAM_INVALID_WITH_LOG_IF_TRUE(has_mem_type_attr && (v_memory_type.size() != output_descs.size()), + "DumpOutputWithTask[%s], output size[%zu], output memory type size[%zu]", + inner_dump_info.op->GetName().c_str(), output_descs.size(), + v_memory_type.size()); + + for (size_t i = 0; i < output_descs.size(); ++i) { + aicpu::dump::Output output; + std::string node_name_index; + const auto &output_desc = output_descs.at(i); + // check dump output tensor desc is redirected by attr ATTR_DATA_DUMP_REF + if (AttrUtils::GetStr(&output_desc, ATTR_DATA_DUMP_REF, node_name_index)) { + GE_CHK_STATUS_RET(DumpRefOutput(inner_dump_info, output, i, node_name_index), "DumpRefOutput failed"); + } else { + GE_IF_BOOL_EXEC( + IsTensorDescWithSkipDumpAddrType(has_mem_type_attr, v_memory_type, i), + GELOGD("DumpOutputWithTask[%s] output[%zu] is l1 addr, skip it", inner_dump_info.op->GetName().c_str(), i); + continue;); + + const auto input_size = inner_dump_info.op->GetInputsSize(); + auto addr = inner_dump_info.args + (i + input_size) * kAddrLen; + GE_CHK_STATUS_RET(GenerateOutput(output, output_descs, addr, i), "Generate output failed"); } - return SUCCESS; + task.mutable_output()->Add(std::move(output)); } + return SUCCESS; +} +Status DataDumper::DumpOutput(const InnerDumpInfo &inner_dump_info, aicpu::dump::Task &task) { + GELOGI("Start dump output"); + if (inner_dump_info.is_task) { + // tbe or aicpu op, these ops are with task + return DumpOutputWithTask(inner_dump_info, task); + } // else data, const or variable op aicpu::dump::Output output; auto output_tensor = inner_dump_info.op->GetOutputDescPtr(inner_dump_info.output_anchor_index); - const std::vector output_addrs = ModelUtils::GetOutputDataAddrs(runtime_param_, inner_dump_info.op, false); + const std::vector output_addrs = ModelUtils::GetOutputDataAddrs(runtime_param_, inner_dump_info.op); if (output_tensor == nullptr) { GELOGE(PARAM_INVALID, "output_tensor is null, index: %d, size: %zu.", inner_dump_info.output_anchor_index, inner_dump_info.op->GetOutputsSize()); @@ -269,9 +367,6 @@ Status DataDumper::DumpOutput(const InnerDumpInfo &inner_dump_info, aicpu::dump: int32_t origin_output_index = -1; (void)AttrUtils::GetStr(output_tensor, ATTR_NAME_DATA_DUMP_ORIGIN_NAME, origin_name); (void)AttrUtils::GetInt(output_tensor, ATTR_NAME_DATA_DUMP_ORIGIN_OUTPUT_INDEX, origin_output_index); - GE_IF_BOOL_EXEC(inner_dump_info.data_size <= 0, - GELOGE(PARAM_INVALID, "The size of data %ld is less than zero", inner_dump_info.data_size); - return PARAM_INVALID) output.set_size(inner_dump_info.data_size); output.set_original_name(origin_name); output.set_original_output_index(origin_output_index); @@ -282,7 +377,7 @@ Status DataDumper::DumpOutput(const InnerDumpInfo &inner_dump_info, aicpu::dump: GELOGE(FAILED, "Index is out of range."); return FAILED; } - auto data_addr = inner_dump_info.args + sizeof(void *) * static_cast(inner_dump_info.input_anchor_index); + auto data_addr = inner_dump_info.args + kAddrLen * static_cast(inner_dump_info.input_anchor_index); output.set_address(static_cast(data_addr)); task.mutable_output()->Add(std::move(output)); @@ -290,37 +385,98 @@ Status DataDumper::DumpOutput(const InnerDumpInfo &inner_dump_info, aicpu::dump: return SUCCESS; } +Status DataDumper::GenerateInput(aicpu::dump::Input &input, const OpDesc::Vistor &tensor_descs, + const uintptr_t &addr, size_t index) { + input.set_data_type(static_cast(GetIrDataType(tensor_descs.at(index).GetDataType()))); + input.set_format(static_cast(tensor_descs.at(index).GetFormat())); + + for (auto dim : tensor_descs.at(index).GetShape().GetDims()) { + input.mutable_shape()->add_dim(dim); + } + int64_t input_size = 0; + if (AttrUtils::GetInt(tensor_descs.at(index), ATTR_NAME_INPUT_ORIGIN_SIZE, input_size)) { + GELOGI("Get aipp input size according to attr is %ld", input_size); + } else if (TensorUtils::GetTensorSizeInBytes(tensor_descs.at(index), input_size) != SUCCESS) { + GELOGE(PARAM_INVALID, "Get input size filed"); + return PARAM_INVALID; + } + GELOGD("Get input size in dump is %ld", input_size); + input.set_size(input_size); + input.set_address(static_cast(addr)); + return SUCCESS; +} + +Status DataDumper::DumpRefInput(const DataDumper::InnerDumpInfo &inner_dump_info, aicpu::dump::Input &input, size_t i, + const std::string &node_name_index) { + std::string dump_op_name; + std::string input_or_output; + size_t index; + // parser and find which node's input or output tensor desc is chosen for dump info + if (!ParseNameIndex(node_name_index, dump_op_name, input_or_output, index)) { + GELOGE(PARAM_INVALID, "Op [%s] input desc[%zu] with invalid ATTR_DATA_DUMP_REF attr[%s].", + inner_dump_info.op->GetName().c_str(), i, node_name_index.c_str()); + return PARAM_INVALID; + } + GE_CHECK_NOTNULL(compute_graph_); + auto replace_node = compute_graph_->FindNode(dump_op_name); + GE_RT_PARAM_INVALID_WITH_LOG_IF_TRUE(replace_node == nullptr, + "Op [%s] input desc[%zu] with invalid ATTR_DATA_DUMP_REF attr[%s]," + " cannot find redirect node[%s].", + inner_dump_info.op->GetName().c_str(), i, node_name_index.c_str(), + dump_op_name.c_str()); + auto replace_opdesc = replace_node->GetOpDesc(); + GE_CHECK_NOTNULL(replace_opdesc); + auto iter = ref_info_.find(replace_opdesc); + GE_RT_PARAM_INVALID_WITH_LOG_IF_TRUE(iter == ref_info_.end(), + "Op [%s] input desc[%zu] cannot find any saved redirect node[%s]'s info.", + inner_dump_info.op->GetName().c_str(), i, replace_opdesc->GetName().c_str()); + GE_CHECK_NOTNULL(iter->second); + auto addr = reinterpret_cast(iter->second); + if (input_or_output == kDumpInput) { + const auto &replace_input_descs = replace_opdesc->GetAllInputsDesc(); + addr += kAddrLen * index; + GE_CHK_STATUS_RET(GenerateInput(input, replace_input_descs, addr, index), "Generate input failed"); + } else if (input_or_output == kDumpOutput) { + const auto &replace_output_descs = replace_opdesc->GetAllOutputsDesc(); + const auto replace_input_size = replace_opdesc->GetAllInputsDesc().size(); + addr += (index + replace_input_size) * kAddrLen; + GE_CHK_STATUS_RET(GenerateInput(input, replace_output_descs, addr, index), "Generate input failed"); + } + GELOGD("Op [%s] input desc[%zu] dump info is replaced by node[%s] [%s] tensor_desc [%zu]", + inner_dump_info.op->GetName().c_str(), i, dump_op_name.c_str(), input_or_output.c_str(), index); + return SUCCESS; +} + Status DataDumper::DumpInput(const InnerDumpInfo &inner_dump_info, aicpu::dump::Task &task) { GELOGI("Start dump input"); const auto &input_descs = inner_dump_info.op->GetAllInputsDesc(); - const std::vector input_addrs = ModelUtils::GetInputDataAddrs(runtime_param_, inner_dump_info.op, false); + const std::vector input_addrs = ModelUtils::GetInputDataAddrs(runtime_param_, inner_dump_info.op); if (input_descs.size() != input_addrs.size()) { GELOGE(PARAM_INVALID, "Invalid input desc addrs size %zu, op %s has %zu input desc.", input_addrs.size(), inner_dump_info.op->GetName().c_str(), input_descs.size()); return PARAM_INVALID; } + std::vector v_memory_type; + bool has_mem_type_attr = ge::AttrUtils::GetListInt(inner_dump_info.op, ATTR_NAME_INPUT_MEM_TYPE_LIST, v_memory_type); + GE_RT_PARAM_INVALID_WITH_LOG_IF_TRUE(has_mem_type_attr && (v_memory_type.size() != input_descs.size()), + "DumpInput[%s], input size[%zu], input memory type size[%zu]", + inner_dump_info.op->GetName().c_str(), input_descs.size(), v_memory_type.size()); for (size_t i = 0; i < input_descs.size(); ++i) { aicpu::dump::Input input; - input.set_data_type(static_cast(GetIrDataType(input_descs.at(i).GetDataType()))); - input.set_format(static_cast(input_descs.at(i).GetFormat())); - - for (auto dim : input_descs.at(i).GetShape().GetDims()) { - input.mutable_shape()->add_dim(dim); + std::string node_name_index; + // check dump input tensor desc is redirected by attr ATTR_DATA_DUMP_REF + if (AttrUtils::GetStr(&input_descs.at(i), ATTR_DATA_DUMP_REF, node_name_index)) { + GE_CHK_STATUS_RET(DumpRefInput(inner_dump_info, input, i, node_name_index), "DumpRefInput failed"); + // normal dump without attr + } else { + GE_IF_BOOL_EXEC(IsTensorDescWithSkipDumpAddrType(has_mem_type_attr, v_memory_type, i), + GELOGD("DumpInput[%s] input[%zu] is l1 addr, skip it", inner_dump_info.op->GetName().c_str(), i); + continue;); + + auto addr = inner_dump_info.args + kAddrLen * i; + GE_CHK_STATUS_RET(GenerateInput(input, input_descs, addr, i), "Generate input failed"); } - - int64_t input_size = 0; - if (AttrUtils::GetInt(&input_descs.at(i), ATTR_NAME_INPUT_ORIGIN_SIZE, input_size)) { - GELOGI("Get aipp input size according to attr is %ld", input_size); - } else if (TensorUtils::GetTensorSizeInBytes(input_descs.at(i), input_size) != SUCCESS) { - GELOGE(PARAM_INVALID, "Get input size filed"); - return PARAM_INVALID; - } - GELOGI("Get input size in dump is %ld", input_size); - GE_IF_BOOL_EXEC(input_size <= 0, GELOGE(PARAM_INVALID, "Input size %ld is less than zero", input_size); - return PARAM_INVALID;) - input.set_size(input_size); - input.set_address(static_cast(inner_dump_info.args + sizeof(void *) * i)); task.mutable_input()->Add(std::move(input)); } return SUCCESS; @@ -400,36 +556,38 @@ Status DataDumper::ExecuteUnLoadDumpInfo(aicpu::dump::OpMappingInfo &op_mapping_ GELOGI("UnloadDumpInfo success, proto size is: %zu.", proto_size); return SUCCESS; } + Status DataDumper::LoadDumpInfo() { std::string dump_list_key; PrintCheckLog(dump_list_key); if (op_list_.empty()) { - return SUCCESS; + GELOGW("op_list_ is empty"); } aicpu::dump::OpMappingInfo op_mapping_info; - auto dump_path = PropertiesManager::Instance().GetDumpOutputPath(); - op_mapping_info.set_dump_path(PropertiesManager::Instance().GetDumpOutputPath() + std::to_string(device_id_) + "/"); + auto dump_path = dump_properties_.GetDumpPath() + std::to_string(device_id_) + "/"; + op_mapping_info.set_dump_path(dump_path); op_mapping_info.set_model_name(dump_list_key); op_mapping_info.set_model_id(model_id_); op_mapping_info.set_flag(kAicpuLoadFlag); - op_mapping_info.set_dump_step(PropertiesManager::Instance().GetDumpStep()); + op_mapping_info.set_dump_step(dump_properties_.GetDumpStep()); SetOpMappingLoopAddr(global_step_, loop_per_iter_, loop_cond_, op_mapping_info); - GELOGI("Dump step is %s and dump path is %s in load dump info", PropertiesManager::Instance().GetDumpStep().c_str(), + GELOGI("Dump step is %s and dump path is %s in load dump info", dump_properties_.GetDumpStep().c_str(), dump_path.c_str()); for (const auto &op_iter : op_list_) { - aicpu::dump::Task task; auto op_desc = op_iter.op; + GELOGD("Op %s in model %s begin to add task in op_mapping_info", op_desc->GetName().c_str(), dump_list_key.c_str()); + aicpu::dump::Task task; task.set_end_graph(false); task.set_task_id(op_iter.task_id); task.set_stream_id(op_iter.stream_id); task.mutable_op()->set_op_name(op_desc->GetName()); task.mutable_op()->set_op_type(op_desc->GetType()); - if (PropertiesManager::Instance().GetDumpMode() == kDumpOutput) { + if (dump_properties_.GetDumpMode() == kDumpOutput) { if (DumpOutput(op_iter, task) != SUCCESS) { GELOGE(FAILED, "Dump output failed"); return FAILED; @@ -437,7 +595,7 @@ Status DataDumper::LoadDumpInfo() { op_mapping_info.mutable_task()->Add(std::move(task)); continue; } - if (PropertiesManager::Instance().GetDumpMode() == kDumpInput) { + if (dump_properties_.GetDumpMode() == kDumpInput) { if (op_iter.is_task) { if (DumpInput(op_iter, task) != SUCCESS) { GELOGE(FAILED, "Dump input failed"); @@ -447,7 +605,7 @@ Status DataDumper::LoadDumpInfo() { op_mapping_info.mutable_task()->Add(std::move(task)); continue; } - if (PropertiesManager::Instance().GetDumpMode() == kDumpAll) { + if (dump_properties_.GetDumpMode() == kDumpAll) { auto ret = DumpOutput(op_iter, task); if (ret != SUCCESS) { GELOGE(FAILED, "Dump output failed when in dumping all"); @@ -467,19 +625,22 @@ Status DataDumper::LoadDumpInfo() { SetEndGraphIdToAicpu(end_graph_task_id_, end_graph_stream_id_, op_mapping_info); - auto ret = ExecuteLoadDumpInfo(op_mapping_info); - if (ret != SUCCESS) { - GELOGE(FAILED, "Execute load dump info failed"); - return FAILED; + SetOpDebugIdToAicpu(op_debug_task_id_, op_debug_stream_id_, op_debug_addr_, op_mapping_info); + + if (!op_list_.empty() || is_op_debug_) { + auto ret = ExecuteLoadDumpInfo(op_mapping_info); + if (ret != SUCCESS) { + GELOGE(FAILED, "Execute load dump info failed"); + return FAILED; + } } return SUCCESS; } void DataDumper::SetEndGraphIdToAicpu(uint32_t task_id, uint32_t stream_id, aicpu::dump::OpMappingInfo &op_mapping_info) { - if (PropertiesManager::Instance().GetDumpMode() == kDumpOutput || - PropertiesManager::Instance().GetDumpMode() == kDumpInput || - PropertiesManager::Instance().GetDumpMode() == kDumpAll) { + if (dump_properties_.GetDumpMode() == kDumpOutput || dump_properties_.GetDumpMode() == kDumpInput || + dump_properties_.GetDumpMode() == kDumpAll) { GELOGI("Add end_graph_info to aicpu, task_id is %u, stream_id is %u", end_graph_task_id_, end_graph_stream_id_); aicpu::dump::Task task; task.set_end_graph(true); @@ -491,6 +652,37 @@ void DataDumper::SetEndGraphIdToAicpu(uint32_t task_id, uint32_t stream_id, } } +void DataDumper::SetOpDebugIdToAicpu(uint32_t task_id, uint32_t stream_id, void *op_debug_addr, + aicpu::dump::OpMappingInfo &op_mapping_info) { + if (is_op_debug_) { + GELOGI("add op_debug_info to aicpu, task_id is %u, stream_id is %u", task_id, stream_id); + aicpu::dump::Task task; + task.set_end_graph(false); + task.set_task_id(task_id); + task.set_stream_id(stream_id); + task.mutable_op()->set_op_name(NODE_NAME_OP_DEBUG); + task.mutable_op()->set_op_type(OP_TYPE_OP_DEBUG); + + // set output + aicpu::dump::Output output; + output.set_data_type(DT_UINT8); + output.set_format(FORMAT_ND); + + output.mutable_shape()->add_dim(kOpDebugShape); + + output.set_original_name(NODE_NAME_OP_DEBUG); + output.set_original_output_index(0); + output.set_original_output_format(FORMAT_ND); + output.set_original_output_data_type(DT_UINT8); + // due to lhisi virtual addr bug, cannot use args now + output.set_address(static_cast(reinterpret_cast(op_debug_addr))); + output.set_size(kOpDebugSize); + + task.mutable_output()->Add(std::move(output)); + op_mapping_info.mutable_task()->Add(std::move(task)); + } +} + Status DataDumper::UnloadDumpInfo() { if (!load_flag_) { GELOGI("No need to UnloadDumpInfo."); @@ -517,15 +709,17 @@ Status DataDumper::UnloadDumpInfo() { } void DataDumper::PrintCheckLog(string &dump_list_key) { - std::set model_list = PropertiesManager::Instance().GetAllDumpModel(); + std::set model_list = dump_properties_.GetAllDumpModel(); if (model_list.empty()) { GELOGI("No model need dump."); return; } - GELOGI("%zu op need dump in %s.", op_list_.size(), model_name_.c_str()); bool not_find_by_omname = model_list.find(om_name_) == model_list.end(); bool not_find_by_modelname = model_list.find(model_name_) == model_list.end(); + dump_list_key = not_find_by_omname ? model_name_ : om_name_; + GELOGI("%zu op need dump in %s.", op_list_.size(), dump_list_key.c_str()); + if (model_list.find(DUMP_ALL_MODEL) == model_list.end()) { if (not_find_by_omname && not_find_by_modelname) { std::string model_list_str; @@ -533,12 +727,12 @@ void DataDumper::PrintCheckLog(string &dump_list_key) { model_list_str += "[" + model + "]."; } - GELOGW("Model %s will not be set to dump, dump list: %s", model_name_.c_str(), model_list_str.c_str()); + GELOGW("Model %s will not be set to dump, dump list: %s", dump_list_key.c_str(), model_list_str.c_str()); return; } } - dump_list_key = not_find_by_omname ? model_name_ : om_name_; - std::set config_dump_op_list = PropertiesManager::Instance().GetDumpPropertyValue(dump_list_key); + + std::set config_dump_op_list = dump_properties_.GetPropertyValue(dump_list_key); std::set dump_op_list; for (auto &inner_dump_info : op_list_) { // oplist value OpDescPtr is not nullptr diff --git a/src/ge/graph/load/new_model_manager/data_dumper.h b/src/ge/graph/load/new_model_manager/data_dumper.h index ee5b3241..0648a8ce 100644 --- a/src/ge/graph/load/new_model_manager/data_dumper.h +++ b/src/ge/graph/load/new_model_manager/data_dumper.h @@ -23,7 +23,9 @@ #include #include "framework/common/ge_inner_error_codes.h" +#include "common/properties_manager.h" #include "graph/node.h" +#include "graph/compute_graph.h" #include "proto/ge_ir.pb.h" #include "proto/op_mapping_info.pb.h" #include "runtime/mem.h" @@ -44,7 +46,9 @@ class DataDumper { device_id_(0), global_step_(0), loop_per_iter_(0), - loop_cond_(0) {} + loop_cond_(0), + compute_graph_(nullptr), + ref_info_() {} ~DataDumper(); @@ -56,6 +60,10 @@ class DataDumper { void SetDeviceId(uint32_t device_id) { device_id_ = device_id; } + void SetComputeGraph(const ComputeGraphPtr &compute_graph) { compute_graph_ = compute_graph; }; + + void SetRefInfo(const std::map &ref_info) { ref_info_ = ref_info; }; + void SetLoopAddr(void *global_step, void *loop_per_iter, void *loop_cond); void SaveDumpInput(const std::shared_ptr &node); @@ -65,11 +73,15 @@ class DataDumper { void SaveEndGraphId(uint32_t task_id, uint32_t stream_id); void SetOmName(const std::string &om_name) { om_name_ = om_name; } + void SaveOpDebugId(uint32_t task_id, uint32_t stream_id, void *op_debug_addr, bool is_op_debug); Status LoadDumpInfo(); Status UnloadDumpInfo(); + void SetDumpProperties(const DumpProperties &dump_properties) { dump_properties_ = dump_properties; } + const DumpProperties &GetDumpProperties() const { return dump_properties_; } + private: void ReleaseDevMem(void **ptr) noexcept; @@ -97,12 +109,32 @@ class DataDumper { uintptr_t global_step_; uintptr_t loop_per_iter_; uintptr_t loop_cond_; + ComputeGraphPtr compute_graph_; + std::map ref_info_; + + uint32_t op_debug_task_id_ = 0; + uint32_t op_debug_stream_id_ = 0; + void *op_debug_addr_ = nullptr; + bool is_op_debug_ = false; + + DumpProperties dump_properties_; Status DumpOutput(const InnerDumpInfo &inner_dump_info, aicpu::dump::Task &task); + Status DumpRefOutput(const DataDumper::InnerDumpInfo &inner_dump_info, aicpu::dump::Output &output, size_t i, + const std::string &node_name_index); + Status DumpOutputWithTask(const InnerDumpInfo &inner_dump_info, aicpu::dump::Task &task); Status DumpInput(const InnerDumpInfo &inner_dump_info, aicpu::dump::Task &task); + Status DumpRefInput(const DataDumper::InnerDumpInfo &inner_dump_info, aicpu::dump::Input &input, size_t i, + const std::string &node_name_index); Status ExecuteLoadDumpInfo(aicpu::dump::OpMappingInfo &op_mapping_info); void SetEndGraphIdToAicpu(uint32_t task_id, uint32_t stream_id, aicpu::dump::OpMappingInfo &op_mapping_info); + void SetOpDebugIdToAicpu(uint32_t task_id, uint32_t stream_id, void *op_debug_addr, + aicpu::dump::OpMappingInfo &op_mapping_info); Status ExecuteUnLoadDumpInfo(aicpu::dump::OpMappingInfo &op_mapping_info); + Status GenerateInput(aicpu::dump::Input &input, const OpDesc::Vistor &tensor_descs, + const uintptr_t &addr, size_t index); + Status GenerateOutput(aicpu::dump::Output &output, const OpDesc::Vistor &tensor_descs, + const uintptr_t &addr, size_t index); }; struct DataDumper::InnerDumpInfo { uint32_t task_id; diff --git a/src/ge/graph/load/new_model_manager/davinci_model.cc b/src/ge/graph/load/new_model_manager/davinci_model.cc index 45acee07..c43c37eb 100644 --- a/src/ge/graph/load/new_model_manager/davinci_model.cc +++ b/src/ge/graph/load/new_model_manager/davinci_model.cc @@ -42,11 +42,11 @@ #include "graph/graph.h" #include "graph/load/new_model_manager/cpu_queue_schedule.h" #include "graph/load/new_model_manager/tbe_handle_store.h" -#include "graph/load/output/output.h" #include "graph/manager/graph_mem_allocator.h" #include "graph/manager/graph_var_manager.h" #include "graph/manager/trans_var_data_utils.h" #include "graph/manager/util/debug.h" +#include "graph/common/ge_call_wrapper.h" #include "graph/model_serialize.h" #include "graph/node.h" #include "graph/utils/graph_utils.h" @@ -59,6 +59,7 @@ #include "runtime/event.h" #include "runtime/mem.h" #include "runtime/stream.h" +#include "runtime/rt_model.h" #include "securec.h" // create std::thread, catch exceptions using try/catch @@ -80,7 +81,6 @@ const uint32_t kOutputNum = 1; const uint32_t kTrueBranchStreamNum = 1; const uint32_t kThreadNum = 16; const uint32_t kAddrLen = sizeof(void *); -const char *const kNeedDestroySpecifiedAicpuKernel = "need_destroy_specified_aicpu_kernel"; const int kDecimal = 10; const int kBytes = 8; const uint32_t kDataMemAlignSizeCompare = 64; @@ -89,42 +89,10 @@ const char *const kDefaultBatchLable = "Batch_default"; inline bool IsDataOp(const std::string &node_type) { return node_type == DATA_TYPE || node_type == AIPP_DATA_TYPE || node_type == ANN_DATA_TYPE; } -inline bool IsCallDumpInputOp(const OpDescPtr &op_desc) { - bool skip_task_generate = false; - (void)ge::AttrUtils::GetBool(op_desc, ATTR_NO_TASK_AND_DUMP_NEEDED, skip_task_generate); - return skip_task_generate; -} - -void CreateInputDimsInfo(const OpDescPtr &op_desc, Format format, InputOutputDescInfo &input) { - uint32_t n, c, h, w; - n = format == FORMAT_NHWC ? NHWC_DIM_N : NCHW_DIM_N; - c = format == FORMAT_NHWC ? NHWC_DIM_C : NCHW_DIM_C; - h = format == FORMAT_NHWC ? NHWC_DIM_H : NCHW_DIM_H; - w = format == FORMAT_NHWC ? NHWC_DIM_W : NCHW_DIM_W; - - if (!op_desc->HasAttr(ATTR_MBATCH_ORIGIN_INPUT_DIMS)) { - if (op_desc->GetInputDescPtr(0)->GetShape().GetDimNum() == static_cast(NORMAL_TENSOR_SIZE)) { - input.shape_info.num = op_desc->GetInputDescPtr(0)->GetShape().GetDim(n); - input.shape_info.height = op_desc->GetInputDescPtr(0)->GetShape().GetDim(h); - input.shape_info.width = op_desc->GetInputDescPtr(0)->GetShape().GetDim(w); - input.shape_info.channel = op_desc->GetInputDescPtr(0)->GetShape().GetDim(c); - } - for (size_t k = 0; k < op_desc->GetInputDescPtr(0)->GetShape().GetDimNum(); k++) { - input.shape_info.dims.push_back(op_desc->GetInputDescPtr(0)->GetShape().GetDim(k)); - } - } else { - vector origin_input_dims; - (void)AttrUtils::GetListInt(op_desc, ATTR_MBATCH_ORIGIN_INPUT_DIMS, origin_input_dims); - if (origin_input_dims.size() == static_cast(NORMAL_TENSOR_SIZE)) { - input.shape_info.num = origin_input_dims[n]; - input.shape_info.height = origin_input_dims[h]; - input.shape_info.width = origin_input_dims[w]; - input.shape_info.channel = origin_input_dims[c]; - } - for (size_t k = 0; k < origin_input_dims.size(); ++k) { - input.shape_info.dims.push_back(origin_input_dims[k]); - } - } +inline bool IsNoTaskAndDumpNeeded(const OpDescPtr &op_desc) { + bool save_dump_info = false; + (void)ge::AttrUtils::GetBool(op_desc, ATTR_NO_TASK_AND_DUMP_NEEDED, save_dump_info); + return save_dump_info; } } // namespace @@ -157,10 +125,10 @@ DavinciModel::DavinciModel(int32_t priority, const std::shared_ptrGetModelTaskDefPtr(); return SUCCESS; } +/// +/// @ingroup ge +/// @brief Reduce memory usage after task sink. +/// @return: void +/// +void DavinciModel::Shrink() { + ge_model_.reset(); // delete object. + + // Old dump need op list, clear when closed. + char *ge_dump_env = std::getenv("DUMP_OP"); + int dump_op_switch = (ge_dump_env != nullptr) ? std::strtol(ge_dump_env, nullptr, kDecimal) : 0; + if (dump_op_switch == 0) { + op_list_.clear(); + } +} + Status DavinciModel::InitModelMem(void *dev_ptr, size_t mem_size, void *weight_ptr, size_t weight_size) { if (is_model_has_inited_) { GELOGI("call InitModelMem more than once ."); return FAILED; } is_model_has_inited_ = true; - std::size_t data_size = TotalMemSize(); - ge::Buffer weights = ge_model_->GetWeight(); - uint8_t *weights_addr = weights.GetData(); + std::size_t data_size = TotalMemSize(); + const Buffer &weights = ge_model_->GetWeight(); std::size_t weights_size = weights.GetSize(); - GE_CHECK_LE(weights_size, ALLOC_MEMORY_MAX_SIZE); if ((dev_ptr != nullptr) && (mem_size < TotalMemSize())) { @@ -312,7 +308,7 @@ Status DavinciModel::InitModelMem(void *dev_ptr, size_t mem_size, void *weight_p } GELOGI("[IMAS]InitModelMem graph_%u MallocMemory type[W] memaddr[%p] mem_size[%zu]", runtime_param_.graph_id, weights_mem_base_, weights_size); - GE_CHK_RT_RET(rtMemcpy(weights_mem_base_, weights_size, weights_addr, weights_size, RT_MEMCPY_HOST_TO_DEVICE)) + GE_CHK_RT_RET(rtMemcpy(weights_mem_base_, weights_size, weights.GetData(), weights_size, RT_MEMCPY_HOST_TO_DEVICE)); GELOGI("copy weights data to device"); } @@ -367,19 +363,15 @@ void DavinciModel::InitRuntimeParams() { session_id_ = runtime_param_.session_id; GELOGI( - "InitRuntimeParams(), memory_size:%lu, weight_size:%lu, session_id:%u, var_size:%lu, logic_var_base:%lu, " - "logic_mem_base:%lu.", - runtime_param_.mem_size, runtime_param_.weight_size, runtime_param_.session_id, runtime_param_.var_size, - runtime_param_.logic_var_base, runtime_param_.logic_mem_base); - - GELOGI("InitRuntimeParams(), stream_num:%lu, event_num:%u, label_num:%u", runtime_param_.stream_num, - runtime_param_.event_num, runtime_param_.label_num); + "InitRuntimeParams(), session_id:%u, stream_num:%lu, event_num:%u, label_num:%u, " + "logic_mem_base:0x%lx, logic_weight_base:0x%lx, logic_var_base:0x%lx, " + "memory_size:%lu, weight_size:%lu, var_size:%lu", + runtime_param_.session_id, runtime_param_.stream_num, runtime_param_.event_num, runtime_param_.label_num, + runtime_param_.logic_mem_base, runtime_param_.logic_weight_base, runtime_param_.logic_var_base, + runtime_param_.mem_size, runtime_param_.weight_size, runtime_param_.var_size); } void DavinciModel::CheckHasHcomOp() { - // definiteness queue schedule, all stream by TS. - GE_IF_BOOL_EXEC(!input_queue_ids_.empty() || !output_queue_ids_.empty(), return ); - Graph graph = ge_model_->GetGraph(); auto compute_graph = GraphUtils::GetComputeGraph(graph); if (compute_graph == nullptr) { @@ -395,11 +387,6 @@ void DavinciModel::CheckHasHcomOp() { (op_desc->GetType() == HVDCALLBACKBROADCAST) || (op_desc->GetType() == HVDWAIT)), uint32_t stream_id = static_cast(op_desc->GetStreamId()); (void)hcom_streams_.emplace(stream_id); GELOGD("hcom stream: %u.", stream_id); continue); - - bool is_aicpu_stream = false; - GE_IF_BOOL_EXEC(AttrUtils::GetBool(op_desc, "is_aicpu_stream", is_aicpu_stream) && is_aicpu_stream, - uint32_t stream_id = static_cast(op_desc->GetStreamId()); - (void)aicpu_streams_.emplace(stream_id); GELOGD("aicpu stream: %u.", stream_id); continue); } } @@ -410,20 +397,13 @@ void DavinciModel::CheckHasHcomOp() { /// Status DavinciModel::BindModelStream() { // Stream not in active_stream_indication_ is active stream. - if (!input_queue_ids_.empty() || !output_queue_ids_.empty()) { - // Asynchronous Queue, need add S0, deactive all model stream. + if ((!input_queue_ids_.empty() || !output_queue_ids_.empty()) || (deploy_type_ == AICPU_DEPLOY_CROSS_THREAD)) { for (size_t i = 0; i < stream_list_.size(); ++i) { if (active_stream_indication_.count(i) == 0) { active_stream_list_.push_back(stream_list_[i]); active_stream_indication_.insert(i); // deactive all model stream. } } - } else { - for (size_t i = 0; i < stream_list_.size(); ++i) { - if (active_stream_indication_.count(i) == 0) { - active_stream_list_.push_back(stream_list_[i]); - } - } } for (size_t i = 0; i < stream_list_.size(); ++i) { @@ -441,23 +421,29 @@ Status DavinciModel::BindModelStream() { Status DavinciModel::DoTaskSink() { // task sink is supported as model_task_def is set - if (model_task_def_) { - GELOGI("do task_sink."); - GE_CHK_STATUS_RET(BindModelStream(), "Bind model stream failed."); + const auto &model_task_def = ge_model_->GetModelTaskDefPtr(); + if (model_task_def == nullptr) { + return SUCCESS; + } - if (known_node_) { - GE_CHK_STATUS_RET(MallocKnownArgs(), "Mallloc known node args failed."); - } + GE_CHK_RT_RET(rtGetAicpuDeploy(&deploy_type_)); + GELOGI("do task_sink. AiCpu deploy type is: %x.", deploy_type_); - GE_CHK_STATUS_RET(InitTaskInfo(*model_task_def_.get()), "InitTaskInfo failed."); + GE_CHK_STATUS_RET(BindModelStream(), "Bind model stream failed."); - GE_CHK_STATUS_RET(LoadWithQueue(), "LoadWithQueue failed."); + if (known_node_) { + GE_CHK_STATUS_RET(MallocKnownArgs(), "Mallloc known node args failed."); + } - GE_CHK_STATUS_RET(DistributeTask(), "Distribute failed."); + GE_CHK_STATUS_RET(InitTaskInfo(*model_task_def.get()), "InitTaskInfo failed."); - GE_CHK_RT_RET(rtModelLoadComplete(rt_model_handle_)); - } + GE_CHK_STATUS_RET(InitEntryTask(), "InitEntryTask failed."); + + GE_CHK_STATUS_RET(DistributeTask(), "Distribute failed."); + + GE_CHK_RT_RET(rtModelLoadComplete(rt_model_handle_)); + SetCopyOnlyOutput(); return SUCCESS; } @@ -475,12 +461,96 @@ Status DavinciModel::SetTSDevice() { return SUCCESS; } +Status DavinciModel::OpDebugRegister() { + bool is_op_debug = false; + (void)ge::AttrUtils::GetBool(ge_model_, ATTR_OP_DEBUG_FLAG, is_op_debug); + GELOGD("The value of op_debug in ge_model_ is %d.", is_op_debug); + if (is_op_debug) { + debug_reg_mutex_.lock(); + rtError_t rt_ret = rtMalloc(&op_debug_addr_, kOpDebugMemorySize, RT_MEMORY_DDR); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(RT_FAILED, "rtMalloc error, ret: 0x%X", rt_ret); + return RT_FAILED; + } + + uint64_t debug_addrs_tmp = static_cast(reinterpret_cast(op_debug_addr_)); + + // For data dump, aicpu needs the pointer to pointer that save the real debug address. + rt_ret = rtMalloc(&p2p_debug_addr_, kDebugP2pSize, RT_MEMORY_HBM); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(RT_FAILED, "rtMalloc error, ret: 0x%X", rt_ret); + return RT_FAILED; + } + rt_ret = rtMemcpy(p2p_debug_addr_, sizeof(uint64_t), &debug_addrs_tmp, sizeof(uint64_t), RT_MEMCPY_HOST_TO_DEVICE); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(rt_ret, "rtMemcpy to p2p_addr error: 0x%X", rt_ret); + return FAILED; + } + + uint32_t op_debug_mode = 0; + (void)ge::AttrUtils::GetInt(ge_model_, ATTR_OP_DEBUG_MODE, op_debug_mode); + GELOGD("The value of op_debug_mode in ge_model_ is %u.", op_debug_mode); + uint32_t debug_task_id = 0; + uint32_t debug_stream_id = 0; + rt_ret = rtDebugRegister(rt_model_handle_, op_debug_mode, op_debug_addr_, &debug_stream_id, &debug_task_id); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(RT_FAILED, "rtDebugRegister error, ret: 0x%X", rt_ret); + return RT_FAILED; + } + GELOGI("debug_task_id:%d, debug_stream_id:%u", debug_task_id, debug_stream_id); + is_op_debug_reg_ = true; + + data_dumper_.SaveOpDebugId(debug_task_id, debug_stream_id, p2p_debug_addr_, is_op_debug); + } + + return SUCCESS; +} + +void DavinciModel::OpDebugUnRegister() { + GELOGI("OpDebugUnRegister, is_op_debug_reg_ = %d", is_op_debug_reg_); + if (is_op_debug_reg_) { + debug_reg_mutex_.unlock(); + + rtError_t rt_ret = RT_ERROR_NONE; + if (rt_model_handle_ != nullptr) { + rt_ret = rtDebugUnRegister(rt_model_handle_); + if (rt_ret != RT_ERROR_NONE) { + GELOGW("rtDebugUnRegister failed, ret: 0x%X", rt_ret); + } + } + + if (op_debug_addr_ != nullptr) { + rt_ret = rtFree(op_debug_addr_); + if (rt_ret != RT_ERROR_NONE) { + GELOGW("rtFree failed, ret: 0x%X", rt_ret); + } + op_debug_addr_ = nullptr; + } + + if (p2p_debug_addr_ != nullptr) { + rt_ret = rtFree(p2p_debug_addr_); + if (rt_ret != RT_ERROR_NONE) { + GELOGW("rtFree failed, ret: 0x%X", rt_ret); + } + p2p_debug_addr_ = nullptr; + } + + is_op_debug_reg_ = false; + } + + return; +} + // initialize op sequence and call initialization function of each op respectively Status DavinciModel::Init(void *dev_ptr, size_t mem_size, void *weight_ptr, size_t weight_size) { // validating params GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(priority_ < 0 || priority_ > 7, return PARAM_INVALID, "Priority must between 0-7, now is %d", priority_); GE_CHK_BOOL_RET_STATUS(ge_model_ != nullptr, PARAM_INVALID, "GeModel is null."); + Graph graph = ge_model_->GetGraph(); + ComputeGraphPtr compute_graph = GraphUtils::GetComputeGraph(graph); + GE_CHK_BOOL_RET_STATUS(compute_graph != nullptr, INTERNAL_ERROR, "Get compute graph is nullptr."); + // Initializing runtime_param_ InitRuntimeParams(); @@ -509,8 +579,6 @@ Status DavinciModel::Init(void *dev_ptr, size_t mem_size, void *weight_ptr, size if (hcom_streams_.find(i) != hcom_streams_.end()) { GE_CHK_RT_RET(rtStreamCreateWithFlags(&stream, priority_, stream_flags | RT_STREAM_FORCE_COPY)); - } else if (aicpu_streams_.find(i) != aicpu_streams_.end()) { - GE_CHK_RT_RET(rtStreamCreateWithFlags(&stream, priority_, stream_flags | RT_STREAM_AICPU)); } else { GE_CHK_RT_RET(rtStreamCreateWithFlags(&stream, priority_, stream_flags)); } @@ -531,20 +599,19 @@ Status DavinciModel::Init(void *dev_ptr, size_t mem_size, void *weight_ptr, size // create model_handle to load model GE_CHK_RT_RET(rtModelCreate(&rt_model_handle_, 0)); GE_CHK_RT_RET(rtModelGetId(rt_model_handle_, &runtime_model_id_)); + // inference will use default graph_id 0; + runtime_param_.graph_id = compute_graph->GetGraphID(); - Graph graph = ge_model_->GetGraph(); - compute_graph_ = GraphUtils::GetComputeGraph(graph); - GE_CHK_BOOL_RET_STATUS(compute_graph_ != nullptr, INTERNAL_ERROR, "Get compute graph is nullptr."); - - runtime_param_.graph_id = compute_graph_->GetGraphID(); + // op debug register + GE_CHK_STATUS_RET(OpDebugRegister(), "OpDebugRegister failed"); GE_TIMESTAMP_START(TransAllVarData); - GE_CHK_STATUS_RET(TransAllVarData(compute_graph_, runtime_param_.graph_id), "TransAllVarData failed."); + GE_CHK_STATUS_RET(TransAllVarData(compute_graph, runtime_param_.graph_id), "TransAllVarData failed."); GE_TIMESTAMP_END(TransAllVarData, "GraphLoader::TransAllVarData"); - GE_CHK_STATUS_RET(CopyVarData(compute_graph_), "copy var data failed."); + GE_CHK_STATUS_RET(TransVarDataUtils::CopyVarData(compute_graph, session_id_, device_id_), "copy var data failed."); GE_TIMESTAMP_START(InitModelMem); - GELOGI("known_node is %d", known_node_); + GELOGI("Known node is %d", known_node_); if (!known_node_) { GE_CHK_STATUS_RET_NOLOG(InitModelMem(dev_ptr, mem_size, weight_ptr, weight_size)); data_inputer_ = new (std::nothrow) DataInputer(); @@ -552,14 +619,16 @@ Status DavinciModel::Init(void *dev_ptr, size_t mem_size, void *weight_ptr, size } GE_TIMESTAMP_END(InitModelMem, "GraphLoader::InitModelMem"); - for (const ge::NodePtr &node : compute_graph_->GetDirectNode()) { - GE_IF_BOOL_EXEC(node->GetOpDesc() == nullptr, continue); - GE_IF_BOOL_EXEC(node->GetOpDesc()->GetType() != VARIABLE, continue); + for (const ge::NodePtr &node : compute_graph->GetDirectNode()) { + auto op_desc = node->GetOpDesc(); + GE_IF_BOOL_EXEC(op_desc == nullptr, continue); + GetFixedAddrAttr(op_desc); + GE_IF_BOOL_EXEC(op_desc->GetType() != VARIABLE, continue); GE_IF_BOOL_EXEC(IsBroadCastOpData(node), - (void)ge::AttrUtils::SetStr(node->GetOpDesc(), VAR_ATTR_VAR_IS_BROADCAST, "var_is_restore");); + (void)ge::AttrUtils::SetStr(op_desc, VAR_ATTR_VAR_IS_BROADCAST, "var_is_restore");); } // for profiling - op_name_map_ = compute_graph_->GetGraphOpName(); + op_name_map_ = compute_graph->GetGraphOpName(); vector op_name; GE_IF_BOOL_EXEC(ge::AttrUtils::GetListStr(ge_model_, ATTR_MODEL_TASK_INDEX_OP_NAME, op_name), @@ -568,14 +637,14 @@ Status DavinciModel::Init(void *dev_ptr, size_t mem_size, void *weight_ptr, size for (size_t idx = 0; idx < op_name.size(); idx++) { op_name_map_[idx] = op_name[idx]; } - GELOGI("infer profiling: op_name_size(%zu)", op_name.size()); + GELOGI("Infer profiling: op_name_size(%zu)", op_name.size()); } - if (InitNodes(compute_graph_) != SUCCESS) { + if (InitNodes(compute_graph) != SUCCESS) { return FAILED; } - SetDataDumperArgs(); + SetDataDumperArgs(compute_graph); GE_TIMESTAMP_START(DoTaskSink); auto ret = DoTaskSink(); GE_TIMESTAMP_END(DoTaskSink, "GraphLoader::DoTaskSink"); @@ -583,22 +652,23 @@ Status DavinciModel::Init(void *dev_ptr, size_t mem_size, void *weight_ptr, size /// In zero copy model, if a aicpu operator is connected to the first or last layer, before model execution, /// the aicpu opertor needs to destroy history record, and update operator memory address. /// The model with specified aicpu operators is only marked here, and destruction is in ModelManager::ExecuteModel(). - if (MarkSpecifiedAicpuKernel() != SUCCESS) { - GELOGE(FAILED, "Mark model with specified aicpu operators failed."); - return FAILED; - } + need_destroy_aicpu_kernel_ = IsAicpuKernelConnectSpecifiedLayer(); + (void)ge::AttrUtils::GetListStr(ge_model_, ATTR_MODEL_OUT_NODES_NAME, out_node_name_); // collect profiling for ge if (ProfilingManager::Instance().ProfilingOn()) { std::vector compute_graph_desc_info; - Status ret1 = GetComputeGraphInfo(compute_graph_desc_info); + Status ret1 = GetComputeGraphInfo(compute_graph, compute_graph_desc_info); if (ret1 != SUCCESS) { GELOGE(ret1, "GetComputeGraphInfo failed."); return ret1; } ProfilingManager::Instance().ReportProfilingData(GetTaskDescInfo(), compute_graph_desc_info); + GE_CHK_STATUS(SinkModelProfile(), "Sink model profile failed."); } - GELOGI("davinci model init success."); + + Shrink(); + GELOGI("Davinci model init success."); return ret; } @@ -655,26 +725,14 @@ bool DavinciModel::IsAicpuKernelConnectSpecifiedLayer() { return false; } -/// -/// @ingroup ge -/// @brief mark ge model with specified aicpu operators . -/// @return Status -/// -Status DavinciModel::MarkSpecifiedAicpuKernel() { - bool result = IsAicpuKernelConnectSpecifiedLayer(); - if (!result) { - // No aicpu operator needing destroy. - GELOGD("No specified aicpu operator that connects to data or netoutput."); - return SUCCESS; - } - bool ret = ge::AttrUtils::SetBool(ge_model_, kNeedDestroySpecifiedAicpuKernel, result); - if (!ret) { - GELOGW("Add attr[%s] in ge model failed, and may lead to specified aicpu operators destruction failure.", - kNeedDestroySpecifiedAicpuKernel); +Status DavinciModel::UpdateSessionId(uint64_t session_id) { + GE_CHECK_NOTNULL(ge_model_); + if (!AttrUtils::SetInt(ge_model_, MODEL_ATTR_SESSION_ID, static_cast(session_id))) { + GELOGW("Set attr[%s] failed in updating session_id.", MODEL_ATTR_SESSION_ID.c_str()); } - GELOGI("Mark ge model success, the model has specified aicpu operators, ge model name: %s.", - ge_model_->GetName().c_str()); + + GELOGD("Update session id: %lu.", session_id); return SUCCESS; } @@ -721,12 +779,6 @@ Status DavinciModel::InitNodes(const ComputeGraphPtr &compute_graph) { continue; } - if (IsCallDumpInputOp(op_desc)) { - GELOGI("node[%s] is no task op , call SaveDumpInput to save it's output node info", op_desc->GetName().c_str()); - data_dumper_.SaveDumpInput(node); - continue; - } - if (op_desc->GetType() == NETOUTPUT) { if (InitNetOutput(node) != SUCCESS) { GELOGE(PARAM_INVALID, "NetOutput init failed, Name: %s", op_desc->GetName().c_str()); @@ -744,6 +796,29 @@ Status DavinciModel::InitNodes(const ComputeGraphPtr &compute_graph) { continue; } + if (IsNoTaskAndDumpNeeded(op_desc)) { + GELOGD("node[%s] without task, and save op_desc and addr for dump", op_desc->GetName().c_str()); + const RuntimeParam &rts_param = GetRuntimeParam(); + const vector input_data_addrs = ModelUtils::GetInputDataAddrs(rts_param, op_desc); + const vector output_data_addrs = ModelUtils::GetOutputDataAddrs(rts_param, op_desc); + const vector workspace_data_addrs = ModelUtils::GetWorkspaceDataAddrs(rts_param, op_desc); + vector tensor_device_addrs; + tensor_device_addrs.insert(tensor_device_addrs.end(), input_data_addrs.begin(), input_data_addrs.end()); + tensor_device_addrs.insert(tensor_device_addrs.end(), output_data_addrs.begin(), output_data_addrs.end()); + tensor_device_addrs.insert(tensor_device_addrs.end(), workspace_data_addrs.begin(), workspace_data_addrs.end()); + void *addr = nullptr; + auto size = kAddrLen * tensor_device_addrs.size(); + GE_CHK_RT_RET(rtMalloc(&addr, size, RT_MEMORY_HBM)); + + rtError_t rt_ret = rtMemcpy(addr, size, tensor_device_addrs.data(), size, RT_MEMCPY_HOST_TO_DEVICE); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(rt_ret, "rtMemcpy error"); + GE_CHK_RT(rtFree(addr)); + return FAILED; + } + saved_task_addrs_.emplace(op_desc, addr); + } + GE_TIMESTAMP_RESTART(InitTbeHandle); uint32_t run_mode = static_cast(domi::ImplyType::INVALID); if (AttrUtils::GetInt(op_desc, ATTR_NAME_IMPLY_TYPE, run_mode) && @@ -773,7 +848,6 @@ Status DavinciModel::InitNodes(const ComputeGraphPtr &compute_graph) { /// @brief Data Op Initialize. /// @param [in] NodePtr: Data Op. /// @param [in/out] data_op_index: NetOutput addr size info. -/// @param [in/out] input_data_info: Data index and addr info {index, {size, addr}}. /// @return Status Status DavinciModel::InitDataOp(const NodePtr &node, uint32_t &data_op_index) { // op_desc Checked by Init: Data, valid. @@ -801,7 +875,7 @@ Status DavinciModel::InitDataOp(const NodePtr &node, uint32_t &data_op_index) { // Make information for copy input data. const vector output_size_list = ModelUtils::GetOutputSize(op_desc); - const vector virtual_addr_list = ModelUtils::GetOutputDataAddrs(runtime_param_, op_desc, false); + const vector virtual_addr_list = ModelUtils::GetOutputDataAddrs(runtime_param_, op_desc); if (output_size_list.empty() || virtual_addr_list.empty() || (output_size_list.size() != virtual_addr_list.size())) { GELOGE(PARAM_INVALID, "Data[%s] init failed: Output size is %zu, Output addr is %zu", op_desc->GetName().c_str(), output_size_list.size(), virtual_addr_list.size()); @@ -877,7 +951,7 @@ Status DavinciModel::InitNetOutput(const NodePtr &node) { output_op_list_.push_back(op_desc); // Make information for copy output data. const vector input_size_list = ModelUtils::GetInputSize(op_desc); - const vector virtual_addr_list = ModelUtils::GetInputDataAddrs(runtime_param_, op_desc, false); + const vector virtual_addr_list = ModelUtils::GetInputDataAddrs(runtime_param_, op_desc); if (input_size_list.empty() && virtual_addr_list.empty()) { GELOGI("NetOutput[%s] is empty.", op_desc->GetName().c_str()); return SUCCESS; @@ -890,7 +964,15 @@ Status DavinciModel::InitNetOutput(const NodePtr &node) { size_t num = output_data_info_.size(); for (size_t idx = 0; idx < input_size_list.size(); ++idx) { - output_data_info_[num + idx] = {input_size_list[idx], virtual_addr_list[idx]}; + int64_t size = input_size_list[idx]; + auto tensor_desc = op_desc->GetInputDescPtr(idx); + if ((tensor_desc == nullptr) || (TensorUtils::GetTensorSizeInBytes(*tensor_desc, size) != GRAPH_SUCCESS)) { + GELOGE(FAILED, "GetTensorSizeInBytes failed!"); + return FAILED; + } + + GELOGI("Tensor data size: GetSize=%ld, GetTensorSizeInBytes=%ld", input_size_list[idx], size); + output_data_info_[num + idx] = {size, virtual_addr_list[idx]}; } SetOutputOutsideAddr(virtual_addr_list); @@ -1000,7 +1082,7 @@ Status DavinciModel::InitVariable(const OpDescPtr &op_desc) { Status DavinciModel::SetQueIds(const std::vector &input_queue_ids, const std::vector &output_queue_ids) { if (input_queue_ids.empty() && output_queue_ids.empty()) { - GELOGE(PARAM_INVALID, "Para is empty"); + GELOGE(PARAM_INVALID, "Param is empty"); return PARAM_INVALID; } @@ -1033,11 +1115,7 @@ Status DavinciModel::LoadWithQueue() { return PARAM_INVALID; } - // create stream instance which rt_model_handel is running on, this is S0. - GE_CHK_RT_RET(rtStreamCreateWithFlags(&rt_model_stream_, priority_, RT_STREAM_AICPU)); - is_inner_model_stream_ = true; - GE_CHK_RT_RET(rtModelBindStream(rt_model_handle_, rt_model_stream_, RT_HEAD_STREAM)); - + GE_CHK_STATUS_RET(AddHeadStream(), "Add head stream failed."); // Binding input_queue and Data Op. GE_CHK_STATUS_RET(BindInputQueue(), "Launch bind input queue failed."); GE_CHK_STATUS_RET(CpuTaskModelZeroCopy(input_mbuf_list_, input_outside_addrs_), "Launch zero copy failed."); @@ -1046,7 +1124,7 @@ Status DavinciModel::LoadWithQueue() { GE_CHK_STATUS_RET(BindOutputQueue(), "Launch bind output queue failed."); GE_CHK_STATUS_RET(CpuTaskModelZeroCopy(output_mbuf_list_, output_outside_addrs_), "Launch zero copy failed."); - GE_CHK_STATUS_RET(CpuActiveStream(active_stream_list_), "Launch active entry stream failed."); + GE_CHK_STATUS_RET(CpuActiveStream(), "Launch active entry stream failed."); GE_CHK_STATUS_RET(CpuWaitEndGraph(), "Launch wait end graph failed."); GE_CHK_STATUS_RET(BindEnqueue(), "Launch enqueue failed."); GE_CHK_STATUS_RET(CpuModelRepeat(), "Launch model repeat failed."); @@ -1090,7 +1168,7 @@ Status DavinciModel::BindInputQueue() { /// @return: 0 for success / others for failed Status DavinciModel::CpuModelDequeue(uint32_t queue_id) { GELOGI("Set CpuKernel model dequeue task enter."); - std::shared_ptr dequeue_task = MakeShared(rt_model_stream_); + std::shared_ptr dequeue_task = MakeShared(rt_entry_stream_); if (dequeue_task == nullptr) { GELOGE(FAILED, "Make CpuTaskModelDequeue task failed."); return FAILED; @@ -1111,7 +1189,7 @@ Status DavinciModel::CpuModelDequeue(uint32_t queue_id) { Status DavinciModel::CpuTaskModelZeroCopy(std::vector &mbuf_list, std::map> &outside_addrs) { GELOGI("Set CpuKernel model zero_copy task enter."); - std::shared_ptr zero_copy = MakeShared(rt_model_stream_); + std::shared_ptr zero_copy = MakeShared(rt_entry_stream_); if (zero_copy == nullptr) { GELOGE(FAILED, "Make CpuTaskZeroCopy task failed."); return FAILED; @@ -1156,7 +1234,6 @@ Status DavinciModel::BindOutputQueue() { /// @ingroup ge /// @brief definiteness queue schedule, bind output queue to task. -/// @param [in] queue_id: output queue id from user. /// @param [in] addr: NetOutput Op input tensor address. /// @param [in] size: NetOutput Op input tensor size. /// @return: 0 for success / others for failed @@ -1167,7 +1244,7 @@ Status DavinciModel::CpuModelPrepareOutput(uintptr_t addr, uint32_t size) { return FAILED; } - std::shared_ptr prepare_output = MakeShared(rt_model_stream_); + std::shared_ptr prepare_output = MakeShared(rt_entry_stream_); if (prepare_output == nullptr) { GELOGE(FAILED, "Make CpuTaskPrepareOutput task failed."); return FAILED; @@ -1187,25 +1264,21 @@ Status DavinciModel::CpuModelPrepareOutput(uintptr_t addr, uint32_t size) { /// /// @ingroup ge /// @brief definiteness queue schedule, active original model stream. -/// @param [in] streams: streams will active by S0. /// @return: 0 for success / others for failed /// -Status DavinciModel::CpuActiveStream(const std::vector &stream_list) { - GELOGI("Set CpuKernel active stream task:%zu enter.", stream_list.size()); - for (auto s : stream_list) { - std::shared_ptr active_entry = MakeShared(rt_model_stream_); - if (active_entry == nullptr) { - GELOGE(FAILED, "Make CpuTaskActiveEntry task failed."); - return FAILED; - } - - if (active_entry->Init(s) != SUCCESS) { - return FAILED; - } +Status DavinciModel::CpuActiveStream() { + GELOGI("Set CpuKernel active stream task enter."); + std::shared_ptr active_entry = MakeShared(rt_entry_stream_); + if (active_entry == nullptr) { + GELOGE(FAILED, "Make CpuTaskActiveEntry task failed."); + return FAILED; + } - cpu_task_list_.push_back(active_entry); + if (active_entry->Init(rt_head_stream_) != SUCCESS) { + return FAILED; } + cpu_task_list_.push_back(active_entry); GELOGI("Set CpuKernel active stream task success."); return SUCCESS; } @@ -1215,7 +1288,7 @@ Status DavinciModel::CpuActiveStream(const std::vector &stream_list) /// @return: 0 for success / others for failed Status DavinciModel::CpuWaitEndGraph() { GELOGI("Set CpuKernel wait end graph task enter."); - std::shared_ptr wait_endgraph = MakeShared(rt_model_stream_); + std::shared_ptr wait_endgraph = MakeShared(rt_entry_stream_); if (wait_endgraph == nullptr) { GELOGE(FAILED, "Make CpuTaskWaitEndGraph task failed."); return FAILED; @@ -1248,7 +1321,7 @@ Status DavinciModel::BindEnqueue() { Status DavinciModel::CpuModelEnqueue(uint32_t queue_id, uintptr_t out_mbuf) { GELOGI("Set CpuKernel model enqueue task enter."); - std::shared_ptr model_enqueue = MakeShared(rt_model_stream_); + std::shared_ptr model_enqueue = MakeShared(rt_entry_stream_); if (model_enqueue == nullptr) { GELOGE(FAILED, "Make CpuTaskModelEnqueue task failed."); return FAILED; @@ -1267,7 +1340,7 @@ Status DavinciModel::CpuModelEnqueue(uint32_t queue_id, uintptr_t out_mbuf) { /// @return: 0 for success / others for failed Status DavinciModel::CpuModelRepeat() { GELOGI("Set CpuKernel repeat task enter."); - std::shared_ptr model_repeat = MakeShared(rt_model_stream_); + std::shared_ptr model_repeat = MakeShared(rt_entry_stream_); if (model_repeat == nullptr) { GELOGE(FAILED, "Make CpuTaskModelRepeat task failed."); return FAILED; @@ -1319,36 +1392,8 @@ Status DavinciModel::GetInputOutputDescInfo(vector &input_d /// @param [out] batch_info /// @return execute result /// -Status DavinciModel::GetDynamicBatchInfo(std::vector> &batch_info) { - for (auto &iter : op_list_) { - OpDescPtr op_desc = iter.second; - if (op_desc == nullptr) { - GELOGE(FAILED, "op_desc is null, index=%u.", iter.first); - return FAILED; - } - - if (op_desc->GetType() != STREAMSWITCHN) { - continue; - } - - batch_info.clear(); - uint32_t batch_num = 0; - if (!AttrUtils::GetInt(op_desc, ATTR_NAME_BATCH_NUM, batch_num)) { - GELOGE(FAILED, "Failed to get attr ATTR_NAME_BATCH_NUM, StreamSwitchN: %s.", op_desc->GetName().c_str()); - return FAILED; - } - std::vector batch_shape; - for (uint32_t i = 0; i < batch_num; i++) { - batch_shape.clear(); - const std::string attr_name = ATTR_NAME_PRED_VALUE + "_" + std::to_string(i); - if (!AttrUtils::GetListInt(op_desc, attr_name, batch_shape)) { - GELOGE(FAILED, "Failed to get attr ATTR_NAME_PRED_VALUE, StreamSwitchN: %s.", op_desc->GetName().c_str()); - return FAILED; - } - batch_info.emplace_back(batch_shape); - } - break; - } +Status DavinciModel::GetDynamicBatchInfo(std::vector> &batch_info) const { + batch_info = batch_info_; return SUCCESS; } @@ -1447,6 +1492,55 @@ Status DavinciModel::GetInputOutputDescInfoForZeroCopy(vectorHasAttr(ATTR_NAME_INPUT_DIMS)) { + // When static aipp is set, need to get the model input dims which processed by aipp + vector model_input_dims; + (void)AttrUtils::GetListInt(op_desc, ATTR_NAME_INPUT_DIMS, model_input_dims); + if (model_input_dims.size() == static_cast(NORMAL_TENSOR_SIZE)) { + input.shape_info.num = model_input_dims[n]; + input.shape_info.height = model_input_dims[h]; + input.shape_info.width = model_input_dims[w]; + input.shape_info.channel = model_input_dims[c]; + } + for (size_t k = 0; k < model_input_dims.size(); ++k) { + input.shape_info.dims.push_back(model_input_dims[k]); + } + is_new_model_desc_ = false; + return; + } + + if (!op_desc->HasAttr(ATTR_MBATCH_ORIGIN_INPUT_DIMS)) { + if (op_desc->GetInputDescPtr(0)->GetShape().GetDimNum() == static_cast(NORMAL_TENSOR_SIZE)) { + input.shape_info.num = op_desc->GetInputDescPtr(0)->GetShape().GetDim(n); + input.shape_info.height = op_desc->GetInputDescPtr(0)->GetShape().GetDim(h); + input.shape_info.width = op_desc->GetInputDescPtr(0)->GetShape().GetDim(w); + input.shape_info.channel = op_desc->GetInputDescPtr(0)->GetShape().GetDim(c); + } + for (size_t k = 0; k < op_desc->GetInputDescPtr(0)->GetShape().GetDimNum(); k++) { + input.shape_info.dims.push_back(op_desc->GetInputDescPtr(0)->GetShape().GetDim(k)); + } + } else { + vector origin_input_dims; + (void)AttrUtils::GetListInt(op_desc, ATTR_MBATCH_ORIGIN_INPUT_DIMS, origin_input_dims); + if (origin_input_dims.size() == static_cast(NORMAL_TENSOR_SIZE)) { + input.shape_info.num = origin_input_dims[n]; + input.shape_info.height = origin_input_dims[h]; + input.shape_info.width = origin_input_dims[w]; + input.shape_info.channel = origin_input_dims[c]; + } + for (size_t k = 0; k < origin_input_dims.size(); ++k) { + input.shape_info.dims.push_back(origin_input_dims[k]); + } + } +} + Status DavinciModel::GetInputDescInfo(vector &input_desc, std::vector &formats) { for (size_t index = 0; index < data_op_list_.size(); ++index) { InputOutputDescInfo input; @@ -1455,6 +1549,7 @@ Status DavinciModel::GetInputDescInfo(vector &input_desc, s Format format = data_op_list_[index]->GetInputDescPtr(0)->GetFormat(); CreateInputDimsInfo(data_op_list_[index], format, input); + input.data_type = data_op_list_[index]->GetInputDescPtr(0)->GetDataType(); input.name = data_op_list_[index]->GetName(); int64_t input_size = 0; @@ -1511,7 +1606,7 @@ void DavinciModel::CreateOutput(uint32_t index, OpDescPtr &op_desc, InputOutputD int64_t tensor_size = 0; (void)TensorUtils::CalcTensorMemSize(shape, format, data_type, tensor_size); // no need to check value - output.size = static_cast(tensor_size); + output.size = static_cast(tensor_size); output.data_type = op_desc->GetInputDescPtr(index)->GetDataType(); } @@ -1520,9 +1615,6 @@ Status DavinciModel::GetOutputDescInfo(vector &output_desc, for (size_t i = 0; i < output_op_list_.size(); i++) { auto &op_desc = output_op_list_[i]; uint32_t out_size = static_cast(op_desc->GetInputsSize()); - // get real out nodes from model - vector out_node_name; - (void)ge::AttrUtils::GetListStr(ge_model_, ATTR_MODEL_OUT_NODES_NAME, out_node_name); for (uint32_t index = 0; index < out_size; index++) { string output_name; InputOutputDescInfo output; @@ -1534,11 +1626,11 @@ Status DavinciModel::GetOutputDescInfo(vector &output_desc, GE_CHK_BOOL_RET_STATUS(src_name.size() > index && src_index.size() > index, INTERNAL_ERROR, "construct output_name failed."); // forward compatbility, if old om has no out_node_name, need to return output follow origin way - if (out_size == out_node_name.size()) { + if (out_size == out_node_name_.size()) { // neweast plan, the index will add to name during generate model. - bool contains_colon = out_node_name[index].find(":") != std::string::npos; + bool contains_colon = out_node_name_[index].find(":") != std::string::npos; output_name = - contains_colon ? out_node_name[index] : out_node_name[index] + ":" + std::to_string(src_index[index]); + contains_colon ? out_node_name_[index] : out_node_name_[index] + ":" + std::to_string(src_index[index]); } else { output_name = std::string("output_") + std::to_string(index) + "_" + src_name[index] + "_" + std::to_string(src_index[index]); @@ -1572,12 +1664,12 @@ Status DavinciModel::CopyInputData(const InputData &input_data, bool device_data const DataBuffer &data_buf = blobs[data.first]; void *mem_addr = data.second.second; - uint32_t mem_size = static_cast(data.second.first); + uint64_t mem_size = static_cast(data.second.first); GE_CHK_BOOL_RET_STATUS(mem_size >= data_buf.length, PARAM_INVALID, - "input data size(%u) does not match model required size(%u), ret failed.", data_buf.length, + "input data size(%lu) does not match model required size(%lu), ret failed.", data_buf.length, mem_size); - GELOGI("[IMAS]CopyPlainData memcpy graph_%u type[F] input[%u] dst[%p] src[%p] mem_size[%u] datasize[%u]", + GELOGI("[IMAS]CopyPlainData memcpy graph_%lu type[F] input[%lu] dst[%p] src[%p] mem_size[%lu] datasize[%lu]", runtime_param_.graph_id, data.first, mem_addr, data_buf.data, mem_size, data_buf.length); if (data_buf.length == 0) { GELOGW("No data need to memcpy!"); @@ -1625,15 +1717,9 @@ inline int64_t SumSize(const vector &size_list) { } Status DavinciModel::SinkModelProfile() { - // not support non-sink model - GE_CHK_BOOL_EXEC(this->model_task_def_ != nullptr, return SUCCESS); - // profiling plugin must be registered Msprof::Engine::Reporter *reporter = PluginImpl::GetPluginReporter(); - if (reporter == nullptr) { - GELOGI("Profiling report is nullptr!"); - return SUCCESS; - } + GE_IF_BOOL_EXEC(reporter == nullptr, GELOGI("Profiling report is nullptr!"); return SUCCESS); GELOGI("Start collect model load profiling data."); @@ -1645,15 +1731,19 @@ Status DavinciModel::SinkModelProfile() { return FAILED, "Sink model tag memcpy error."); // Model Header - string name = this->Name(); - int32_t name_len = name.size(); + string name; + if (!om_name_.empty()) { + name = om_name_; + } else { + name = name_; + } + size_t name_len = name.size(); // phy device id uint32_t phy_device_id = 0; rtError_t rt_ret = rtGetDevicePhyIdByIndex(device_id_, &phy_device_id); - if (rt_ret != RT_ERROR_NONE) { - GELOGE(rt_ret, "runtime get phy_device_id failed, current phy_device_id:%d", phy_device_id); - return FAILED; - } + GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, + GELOGE(rt_ret, "runtime get phy_device_id failed, current phy_device_id:%u", phy_device_id); + return FAILED); reporter_data.deviceId = phy_device_id; reporter_data.data = (unsigned char *)&name_len; reporter_data.dataLen = sizeof(int32_t); @@ -1690,7 +1780,6 @@ Status DavinciModel::SinkModelProfile() { for (int32_t i = 0; i < task_num; i++) { auto task = task_list_[i]; auto fusion_op_info = task->GetFusionOpInfo(); - // when type is RT_MODEL_TASK_KERNEL, ctx is not null if (fusion_op_info != nullptr) { uint32_t op_num = fusion_op_info->original_op_names.size(); @@ -1809,15 +1898,9 @@ Status DavinciModel::SinkModelProfile() { } Status DavinciModel::SinkTimeProfile(const InputData ¤t_data) { - // not support non-sink model - GE_CHK_BOOL_EXEC(this->model_task_def_ != nullptr, return SUCCESS); - // profiling plugin must be registered Msprof::Engine::Reporter *reporter = PluginImpl::GetPluginReporter(); - if (reporter == nullptr) { - GELOGI("Profiling report is nullptr!"); - return SUCCESS; - } + GE_IF_BOOL_EXEC(reporter == nullptr, GELOGI("Profiling report is nullptr!"); return SUCCESS); Msprof::Engine::ReporterData reporter_data{}; // report model data tag name @@ -1832,15 +1915,19 @@ Status DavinciModel::SinkTimeProfile(const InputData ¤t_data) { // device id uint32_t phy_device_id = 0; rtError_t rt_ret = rtGetDevicePhyIdByIndex(device_id_, &phy_device_id); - if (rt_ret != RT_ERROR_NONE) { - GELOGE(rt_ret, "runtime get phy_device_id failed, current phy_device_id:%d", phy_device_id); - return FAILED; - } + GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, + GELOGE(rt_ret, "runtime get phy_device_id failed, current phy_device_id:%u", phy_device_id); + return FAILED); reporter_data.deviceId = phy_device_id; // Model Header - string name = this->Name(); - int32_t name_len = name.size(); + string name; + if (!om_name_.empty()) { + name = om_name_; + } else { + name = name_; + } + size_t name_len = name.size(); reporter_data.data = (unsigned char *)&name_len; reporter_data.dataLen = sizeof(int32_t); GE_CHK_BOOL_EXEC(reporter->Report(&reporter_data) == SUCCESS, return FAILED, "Reporter data fail, model id:%u.", @@ -1918,77 +2005,62 @@ void DavinciModel::SetProfileTime(ModelProcStage stage, int64_t endTime) { /// @ingroup ge /// @brief send Output Op result to upper layer /// @already malloced in ModelLoad, no need to malloc again -/// @param [in] sink_op Sink Op +/// @param [in] data_id: the index of output_data +/// @param [in/out] output_data: real user output_data +/// @param [in] kind: the kind of rtMemcpy /// @return Status result /// @author /// -Status DavinciModel::CopyOutputData(uint32_t data_id, OutputData &output_data) { - Status ret = SUCCESS; +Status DavinciModel::CopyOutputData(uint32_t data_id, OutputData &output_data, rtMemcpyKind_t kind) { if (output_op_list_.empty()) { - ret = SyncVarData(); - } else { - output_data.index = data_id; - output_data.model_id = model_id_; - GE_CHK_BOOL_RET_STATUS(output_data.blobs.size() == output_data_info_.size(), INTERNAL_ERROR, - "output buffer size[%zu] not equal output_size_list[%zu] size!", output_data.blobs.size(), - output_data_info_.size()); - - // index of data in output_data - uint32_t output_data_index = 0; - for (auto &op_desc : output_op_list_) { - ret = CopyOutputDataToUser(op_desc, output_data.blobs, output_data_index); - GE_CHK_BOOL_EXEC(ret == SUCCESS, break, "Copy output data to model ret failed, index:%u, model id:%u", - output_data.index, output_data.model_id); - } + Status ret = SyncVarData(); + DumpOpInputOutput(); + return ret; } - (void)DumpOpInputOutput(); // dump, not care result. - return ret; -} - -Status DavinciModel::CopyOutputDataToUser(OpDescPtr &op_desc, std::vector &blobs, uint32_t &data_index) { - Output model_output(op_desc, this); - - GE_CHK_BOOL_RET_STATUS(model_output.Init() == SUCCESS, PARAM_INVALID, "make shared model_output failed"); + output_data.index = data_id; + output_data.model_id = model_id_; + if (output_data.blobs.size() != output_data_info_.size()) { + GELOGE(FAILED, "Output data buffer num=%zu not equal model data num=%zu", output_data.blobs.size(), + output_data_info_.size()); + return FAILED; + } - vector v_output_size; - vector v_output_data_addr; - model_output.GetOutputData(v_output_data_addr, v_output_size); + std::vector &blobs = output_data.blobs; + for (const auto &output : output_data_info_) { + if (output.first >= blobs.size()) { + GELOGE(FAILED, "Blobs not match: blobs=%zu, tensor=%zu, index=%u, size=%ld", blobs.size(), + input_data_info_.size(), output.first, output.second.first); + return FAILED; + } - // for all output tensor, copy output data from op to designated position - for (size_t i = 0; i < v_output_size.size(); ++i) { - GE_CHK_BOOL_RET_STATUS(data_index < blobs.size(), PARAM_INVALID, - "The blobs size:%zu, data_op size:%zu, curr output size:%zu", blobs.size(), - data_op_list_.size(), v_output_size.size()); + if ((kind == RT_MEMCPY_DEVICE_TO_DEVICE) && (copy_only_addrs_.count(output.second.second) == 0)) { + continue; // Skip: Feed by zero copy. + } - DataBuffer &data_buf = blobs[data_index]; - data_index++; + DataBuffer &buffer = blobs[output.first]; + uint64_t mem_size = static_cast(output.second.first); + if ((buffer.length == 0) || (mem_size == 0)) { + GELOGI("Length of data is zero, No need copy. output tensor index=%u", output.first); + continue; + } - uint32_t size = data_buf.length; - GE_CHK_BOOL_RET_STATUS(size <= v_output_size[i], PARAM_INVALID, - "Model output data size(%u) does not match required size(%u).", v_output_size[i], - data_buf.length); + if (buffer.length < mem_size) { + GELOGE(FAILED, "Tensor data size=%lu, buffer size=%u", mem_size, buffer.length); + return FAILED; + } else if (buffer.length > mem_size) { + GELOGW("Tensor data size=%lu, buffer size=%u", mem_size, buffer.length); + } - GELOGI( - "CopyOutputDataToUser memcpy graph_%u type[F] name[%s] output[%lu] dst[%p] src[%p] mem_size[%u] datasize[%u]", - runtime_param_.graph_id, op_desc->GetName().c_str(), i, data_buf.data, v_output_data_addr[i], data_buf.length, - v_output_size[i]); - GE_CHK_RT_RET(rtMemcpy(data_buf.data, size, v_output_data_addr[i], size, RT_MEMCPY_DEVICE_TO_DEVICE)); + GELOGI("[IMAS]CopyPlainData memcpy graph_%u type[F] output[%u] memaddr[%p] mem_size[%lu] datasize[%u]", + runtime_param_.graph_id, output.first, output.second.second, mem_size, buffer.length); + GE_CHK_RT_RET(rtMemcpy(buffer.data, buffer.length, output.second.second, mem_size, kind)); } + DumpOpInputOutput(); return SUCCESS; } -Status DavinciModel::SyncDataAndDump() { - Status ret = SUCCESS; - if (output_op_list_.empty()) { - ret = SyncVarData(); - } - - (void)DumpOpInputOutput(); // dump, not care result. - return ret; -} - Status DavinciModel::GenOutputTensorInfo(const OpDescPtr &op_desc, uint32_t data_index, OutputData *output_data, std::vector &outputs) { GE_CHECK_NOTNULL(op_desc); @@ -2020,13 +2092,13 @@ Status DavinciModel::GenOutputTensorInfo(const OpDescPtr &op_desc, uint32_t data GELOGE(GE_GRAPH_MALLOC_FAILED, "Malloc buffer failed."); return GE_GRAPH_MALLOC_FAILED; } - output_data->blobs.push_back({data_buf.get(), static_cast(out_buffer_size_vec[i]), false}); + output_data->blobs.push_back({data_buf.get(), static_cast(out_buffer_size_vec[i]), false}); ge::OutputTensorInfo output; output.dims = shape_info_vec[i]; output.data = std::move(data_buf); output.length = out_buffer_size_vec[i]; outputs.emplace_back(std::move(output)); - GELOGI("Output index:%zu, data_length:%u.", i, output.length); + GELOGI("Output index:%zu, data_length:%lu.", i, output.length); } return SUCCESS; } @@ -2035,7 +2107,10 @@ Status DavinciModel::GenOutputTensorInfo(const OpDescPtr &op_desc, uint32_t data /// @ingroup ge /// @brief send Output Op result to upper layer /// @already malloced in ModelLoad, no need to malloc again -/// @param [in] sink_op Sink Op +/// @param [in] data_id: the index of output_data +/// @param [in] rslt_flg: result flag +/// @param [in] seq_end_flag: sequence end flag +/// @param [out] output_data: real user output_data /// @return Status result /// @author /// @@ -2066,20 +2141,17 @@ Status DavinciModel::ReturnResult(uint32_t data_id, const bool rslt_flg, const b // copy output data from op to designated position for (auto &op_desc : output_op_list_) { - Output model_output(op_desc, this); - if (model_output.Init() != SUCCESS || GenOutputTensorInfo(op_desc, data_index, output_data, outputs) != SUCCESS) { + if (GenOutputTensorInfo(op_desc, data_index, output_data, outputs) != SUCCESS) { return INTERNAL_ERROR; } + data_index += op_desc->GetInputsSize(); + } - Status ret = model_output.CopyResult(*output_data, data_index, data_index, false); - if (ret != SUCCESS) { - GELOGE(INTERNAL_ERROR, "CopyResult failed, op name: %s", op_desc->GetName().c_str()); - GE_CHK_STATUS(listener_->OnComputeDone(model_id_, data_id, INTERNAL_ERROR, outputs), "OnComputeDone failed"); - return INTERNAL_ERROR; - } + if (CopyOutputData(data_id, *output_data, RT_MEMCPY_DEVICE_TO_HOST) != SUCCESS) { + GE_CHK_STATUS(listener_->OnComputeDone(model_id_, data_id, INTERNAL_ERROR, outputs), "OnComputeDone failed"); + return INTERNAL_ERROR; } - GE_IF_BOOL_EXEC((DumpOpInputOutput() != SUCCESS), GELOGW("dump op failed, model_id: %u", model_id_);); if (seq_end_flag) { GELOGW("End of sequence, model id: %u", model_id_); GE_CHK_STATUS(listener_->OnComputeDone(model_id_, data_id, END_OF_SEQUENCE, outputs), "OnCompute Done failed."); @@ -2092,6 +2164,7 @@ Status DavinciModel::ReturnResult(uint32_t data_id, const bool rslt_flg, const b /// /// @ingroup ge /// @brief return not output to upper layer for cloud case +/// @param [in] data_id /// @return Status result /// Status DavinciModel::ReturnNoOutput(uint32_t data_id) { @@ -2103,7 +2176,7 @@ Status DavinciModel::ReturnNoOutput(uint32_t data_id) { op_desc->GetName().c_str()); } - GE_IF_BOOL_EXEC((DumpOpInputOutput() != SUCCESS), GELOGW("dump op failed, model_id: %u", model_id_);); + DumpOpInputOutput(); GE_CHK_BOOL_EXEC(listener_ != nullptr, return PARAM_INVALID, "listener_ is null!"); std::vector outputs; GE_CHK_STATUS(listener_->OnComputeDone(model_id_, data_id, SUCCESS, outputs), "OnComputeDone failed."); @@ -2113,41 +2186,40 @@ Status DavinciModel::ReturnNoOutput(uint32_t data_id) { /// /// @ingroup ge /// @brief dump all op input and output information -/// @param [in] op_list model_id -/// @return Status result +/// @return void /// -Status DavinciModel::DumpOpInputOutput() { +void DavinciModel::DumpOpInputOutput() { + char *ge_dump_env = std::getenv("DUMP_OP"); + int dump_op_switch = (ge_dump_env != nullptr) ? std::strtol(ge_dump_env, nullptr, kDecimal) : 0; + if (dump_op_switch == 0) { + GELOGI("need to set DUMP_OP for dump op input and output"); + return; + } + if (op_list_.empty()) { - GELOGW("op_list is empty."); - return FAILED; + GELOGW("op list is empty"); + return; } - char *ge_dump_env = getenv("DUMP_OP"); - int dump_op_switch = - (ge_dump_env != nullptr) ? std::strtol(ge_dump_env, nullptr, kDecimal) : 0; // 10 for decimal number - if (dump_op_switch != 0) { - int64_t cnt = 1; - for (auto it : op_list_) { - if (maxDumpOpNum_ != 0 && cnt > maxDumpOpNum_) { - GELOGW("dump op cnt > maxDumpOpNum, maxDumpOpNum: %ld.", maxDumpOpNum_); - return SUCCESS; - } - Status ret = DumpSingleOpInputOutput(it.second); - cnt++; - if (ret != SUCCESS) { - GELOGE(FAILED, "dump single op failed, model_id: %u", model_id_); - return FAILED; - } + + int64_t cnt = 1; + for (auto it : op_list_) { + if (maxDumpOpNum_ != 0 && cnt > maxDumpOpNum_) { + GELOGW("dump op cnt > maxDumpOpNum, maxDumpOpNum: %ld", maxDumpOpNum_); + return; + } + + cnt++; + if (DumpSingleOpInputOutput(it.second) != SUCCESS) { + GELOGW("dump single op failed, model_id: %u", model_id_); + return; } - } else { - GELOGW("need to set DUMP_OP for dump op input and output."); } - return SUCCESS; } /// /// @ingroup ge /// @brief dump single op input and output information -/// @param [in] dump_op model_id +/// @param [in] op_def: the op_desc which will be dump /// @return Status result /// Status DavinciModel::DumpSingleOpInputOutput(const OpDescPtr &op_def) { @@ -2163,7 +2235,7 @@ Status DavinciModel::DumpSingleOpInputOutput(const OpDescPtr &op_def) { } } const vector input_size_vec = ModelUtils::GetInputSize(op_def); - const vector input_addr_vec = ModelUtils::GetInputDataAddrs(runtime_param_, op_def, false); + const vector input_addr_vec = ModelUtils::GetInputDataAddrs(runtime_param_, op_def); vector v_memory_type; bool has_mem_type_attr = ge::AttrUtils::GetListInt(op_def, ATTR_NAME_INPUT_MEM_TYPE_LIST, v_memory_type); GELOGD("DumpSingleOp[%s], input size[%zu], input memory type size[%zu]", op_def->GetName().c_str(), @@ -2186,7 +2258,7 @@ Status DavinciModel::DumpSingleOpInputOutput(const OpDescPtr &op_def) { } const vector output_size_vec = ModelUtils::GetOutputSize(op_def); - const vector output_addr_vec = ModelUtils::GetOutputDataAddrs(runtime_param_, op_def, false); + const vector output_addr_vec = ModelUtils::GetOutputDataAddrs(runtime_param_, op_def); v_memory_type.clear(); has_mem_type_attr = ge::AttrUtils::GetListInt(op_def, ATTR_NAME_OUTPUT_MEM_TYPE_LIST, v_memory_type); GELOGD("DumpSingleOp[%s], output size[%zu], output memory type size[%zu]", op_def->GetName().c_str(), @@ -2256,7 +2328,7 @@ void *DavinciModel::Run(DavinciModel *model) { ret != SUCCESS, (void)model->ReturnResult(current_data.index, false, false, data_wrapper->GetOutput()); CsaInteract::GetInstance().StoreInternalErrorCode(ret, ERROR_MODULE_FMK, JOBSUBSTATE_GRAPH_EXEC); continue, "Copy input data to model failed."); // [No need to check value] - GE_TIMESTAMP_END(Model_SyncVarData, "Model Run SyncVarData"); + GE_IF_BOOL_EXEC(model->is_first_execute_, GE_TIMESTAMP_EVENT_END(Model_SyncVarData, "Model Run SyncVarData")); GELOGI("Copy input data, model id:%u", model_id); GE_IF_BOOL_EXEC(ProfilingManager::Instance().ProfilingOn(), model->SetProfileTime(MODEL_PRE_PROC_START)); @@ -2302,7 +2374,7 @@ void *DavinciModel::Run(DavinciModel *model) { CsaInteract::GetInstance().WriteErrorCode(rt_ret, ERROR_MODULE_RUNTIME, JOBSUBSTATE_GRAPH_EXEC); continue); GELOGI("rtModelExecute end"); - GE_TIMESTAMP_END(rtModelExecute, "GraphExcute::rtModelExecute"); + GE_IF_BOOL_EXEC(model->is_first_execute_, GE_TIMESTAMP_EVENT_END(rtModelExecute, "GraphExcute::rtModelExecute")); GE_TIMESTAMP_START(rtStreamSynchronize); GELOGI("rtStreamSynchronize start."); @@ -2317,7 +2389,8 @@ void *DavinciModel::Run(DavinciModel *model) { CsaInteract::GetInstance().StoreInternalErrorCode(rt_ret, ERROR_MODULE_RUNTIME, JOBSUBSTATE_GRAPH_EXEC); continue); GELOGI("rtStreamSynchronize end."); - GE_TIMESTAMP_END(rtStreamSynchronize, "GraphExcute::Wait for rtStreamSynchronize"); + GE_IF_BOOL_EXEC(model->is_first_execute_, + GE_TIMESTAMP_EVENT_END(rtStreamSynchronize, "GraphExcute::Wait for rtStreamSynchronize")); GE_IF_BOOL_EXEC(ProfilingManager::Instance().ProfilingOn(), model->SetProfileTime(MODEL_INFER_END)); } @@ -2328,11 +2401,13 @@ void *DavinciModel::Run(DavinciModel *model) { (void)model->ReturnResult(current_data.index, rslt_flg, false, data_wrapper->GetOutput())) // copy output data from device to host for variable graph GE_IF_BOOL_EXEC(model->output_op_list_.empty(), (void)model->ReturnNoOutput(current_data.index)); - GE_TIMESTAMP_END(ReturnResult3, "GraphExcute::CopyDataFromDeviceToHost"); + GE_IF_BOOL_EXEC(model->is_first_execute_, + GE_TIMESTAMP_EVENT_END(ReturnResult3, "GraphExcute::CopyDataFromDeviceToHost")); GE_IF_BOOL_EXEC(ProfilingManager::Instance().ProfilingOn(), model->SetProfileTime(MODEL_AFTER_PROC_END)); GE_IF_BOOL_EXEC(ProfilingManager::Instance().ProfilingOn(), (void)model->SinkTimeProfile(current_data)); model->iterator_count_++; + model->is_first_execute_ = false; GELOGI("run iterator count is %lu", model->iterator_count_); } @@ -2385,7 +2460,7 @@ Status DavinciModel::ModelRunStart() { is_inner_model_stream_ = true; string opt = "0"; - (void)ge::GetContext().GetOption("ge.maxDumpOpNum", opt); // option may not be set up, no need to check value + (void)ge::GetContext().GetOption(OPTION_GE_MAX_DUMP_OP_NUM, opt); // option may not be set up, no need to check value int64_t maxDumpOpNum = std::strtol(opt.c_str(), nullptr, kDecimal); maxDumpOpNum_ = maxDumpOpNum; @@ -2428,7 +2503,18 @@ void DavinciModel::UnbindTaskSinkStream() { // destroy stream that is bound with rt_model GE_LOGW_IF(rtStreamDestroy(rt_model_stream_) != RT_ERROR_NONE, "Destroy stream for rt_model failed.") } - return; + + if (is_pure_head_stream_ && rt_head_stream_ != nullptr) { + GE_LOGW_IF(rtModelUnbindStream(rt_model_handle_, rt_head_stream_) != RT_ERROR_NONE, "Unbind stream failed!"); + GE_LOGW_IF(rtStreamDestroy(rt_head_stream_) != RT_ERROR_NONE, "Destroy stream for rt_model failed."); + rt_head_stream_ = nullptr; + } + + if (rt_entry_stream_ != nullptr) { + GE_LOGW_IF(rtModelUnbindStream(rt_model_handle_, rt_entry_stream_) != RT_ERROR_NONE, "Unbind stream failed!"); + GE_LOGW_IF(rtStreamDestroy(rt_entry_stream_) != RT_ERROR_NONE, "Destroy stream for rt_model failed."); + rt_entry_stream_ = nullptr; + } } Status DavinciModel::CreateKnownZeroCopyMap(const vector &inputs, const vector &outputs) { @@ -2437,6 +2523,9 @@ Status DavinciModel::CreateKnownZeroCopyMap(const vector &inputs, const GELOGE(FAILED, "input data addr %u is not equal to input op number %u.", inputs.size(), data_op_list_.size()); return FAILED; } + // remove zero copy addr in last iteration + knonw_input_data_info_.clear(); + knonw_output_data_info_.clear(); for (size_t i = 0; i < data_op_list_.size(); ++i) { const vector addr_list = ModelUtils::GetOutputDataAddrs(runtime_param_, data_op_list_[i]); knonw_input_data_info_[addr_list[kDataIndex]] = inputs[i]; @@ -2518,7 +2607,9 @@ Status DavinciModel::InitTaskInfo(domi::ModelTaskDef &model_task_def) { for (int i = 0; i < model_task_def.task_size(); ++i) { // dynamic shape will create task_list_ before const domi::TaskDef &task = model_task_def.task(i); - task_list_[i] = TaskInfoFactory::Instance().Create(static_cast(task.type())); + if (this->task_list_[i] == nullptr) { + task_list_[i] = TaskInfoFactory::Instance().Create(static_cast(task.type())); + } GE_CHECK_NOTNULL(task_list_[i]); Status ret = task_list_[i]->Init(task, this); if (ret != SUCCESS) { @@ -2532,13 +2623,14 @@ Status DavinciModel::InitTaskInfo(domi::ModelTaskDef &model_task_def) { Status DavinciModel::MallocKnownArgs() { GELOGI("DavinciModel::MallocKnownArgs in"); - if (model_task_def_->task_size() == 0) { + const auto &model_task_def = ge_model_->GetModelTaskDefPtr(); + if (model_task_def->task_size() == 0) { GELOGW("DavinciModel::MallocKnownArgs davincimodel has no task info."); return SUCCESS; } - task_list_.resize(model_task_def_->task_size()); - for (int32_t i = 0; i < model_task_def_->task_size(); ++i) { - const domi::TaskDef &taskdef = model_task_def_->task(i); + task_list_.resize(model_task_def->task_size()); + for (int32_t i = 0; i < model_task_def->task_size(); ++i) { + const domi::TaskDef &taskdef = model_task_def->task(i); task_list_[i] = TaskInfoFactory::Instance().Create(static_cast(taskdef.type())); GE_CHECK_NOTNULL(task_list_[i]); Status ret = task_list_[i]->CalculateArgs(taskdef, this); @@ -2559,7 +2651,19 @@ Status DavinciModel::MallocKnownArgs() { GELOGE(RT_FAILED, "Call rtMallocHost failed, ret: 0x%X", rt_ret); return RT_FAILED; } - GELOGI("DavinciModel::MallocKnownArgs success, total args size %u.", total_args_size_); + + // malloc fixed addr memory, eg: rts op + if (total_fixed_addr_size_ != 0) { + GELOGI("Begin to allocate fixed addr."); + rt_ret = rtMalloc(&fixed_addrs_, total_fixed_addr_size_, RT_MEMORY_HBM); + if (rt_ret != RT_ERROR_NONE) { + GELOGE(RT_FAILED, "Call rtMalloc failed, ret: 0x%X", rt_ret); + return RT_FAILED; + } + } + + GELOGI("DavinciModel::MallocKnownArgs success, total args size %u. total fixed addr size %ld", total_args_size_, + total_fixed_addr_size_); return SUCCESS; } @@ -2575,26 +2679,28 @@ Status DavinciModel::DistributeTask() { task_desc_info_.clear(); bool flag = GetL1FusionEnableOption(); - char *skt_enable_env = getenv("SKT_ENABLE"); - int64_t env_flag = (skt_enable_env != nullptr) ? strtol(skt_enable_env, nullptr, 10) : 0; + char *skt_enable_env = std::getenv("SKT_ENABLE"); + int64_t env_flag = (skt_enable_env != nullptr) ? std::strtol(skt_enable_env, nullptr, kDecimal) : 0; if (env_flag != 0) { flag = true; } + const auto &model_task_def = ge_model_->GetModelTaskDefPtr(); for (size_t task_index = 0; task_index < task_list_.size(); ++task_index) { auto &task = task_list_.at(task_index); GE_CHK_STATUS_RET(task->Distribute(), "Task[%zu] distribute fail", task_index); // for data dump if (reinterpret_cast(task->GetDumpArgs()) != nullptr) { - auto op_index = std::max(model_task_def_->task(task_index).kernel().context().op_index(), - model_task_def_->task(task_index).kernel_ex().op_index()); + auto op_index = std::max(model_task_def->task(task_index).kernel().context().op_index(), + model_task_def->task(task_index).kernel_ex().op_index()); OpDescPtr op = GetOpByIndex(op_index); if (op == nullptr) { GELOGE(PARAM_INVALID, "Op index %u is null, op list size %zu.", op_index, op_list_.size()); return PARAM_INVALID; } - if (PropertiesManager::Instance().IsLayerNeedDump(name_, om_name_, op->GetName())) { + bool call_dump = GetDumpProperties().IsLayerNeedDump(name_, om_name_, op->GetName()) && task->CallSaveDumpInfo(); + if (call_dump) { SaveDumpTask(task->GetTaskID(), task->GetStreamId(), op, task->GetDumpArgs()); } } @@ -2609,8 +2715,13 @@ Status DavinciModel::DistributeTask() { // else task index is found in op_name_map_ TaskDescInfo task_desc_info; string op_name = op_name_map_[task_index]; + if (!om_name_.empty()) { + task_desc_info.model_name = om_name_; + } else { + task_desc_info.model_name = name_; + } task_desc_info.op_name = op_name; - task_desc_info.block_dim = model_task_def_->task(task_index).kernel().block_dim(); + task_desc_info.block_dim = model_task_def->task(task_index).kernel().block_dim(); task_desc_info.task_id = task->GetTaskID(); task_desc_info.stream_id = task->GetStreamId(); task_desc_info_.emplace_back(task_desc_info); @@ -2631,7 +2742,7 @@ Status DavinciModel::DistributeTask() { } void DavinciModel::SetEndGraphId(uint32_t task_id, uint32_t stream_id) { - auto all_dump_model = PropertiesManager::Instance().GetAllDumpModel(); + auto all_dump_model = GetDumpProperties().GetAllDumpModel(); bool findByOmName = all_dump_model.find(om_name_) != all_dump_model.end(); bool findByModelName = all_dump_model.find(name_) != all_dump_model.end(); if (all_dump_model.find(ge::DUMP_ALL_MODEL) != all_dump_model.end() || findByOmName || findByModelName) { @@ -2669,11 +2780,26 @@ void DavinciModel::SetOutputOutsideAddr(const std::vector &outside_addrs continue; } - (void)output_outside_addrs_.emplace(std::pair>(addr, {})); + DisableZeroCopy(addr); // Data to NetOutput directly. + output_outside_addrs_.emplace(std::pair>(addr, {})); GELOGI("SetOutputOutsideAddr success."); } } +/// +/// @ingroup ge +/// @brief Set copy only for No task feed NetOutput address. +/// @return None. +/// +void DavinciModel::SetCopyOnlyOutput() { + for (const auto &addrs : output_outside_addrs_) { + const auto &used_list = addrs.second; + if (used_list.empty()) { // No task feed Output addr, Need copy directly. + copy_only_addrs_.insert(addrs.first); + } + } +} + /// /// @ingroup ge /// @brief Set disabled input zero copy addr. @@ -2681,8 +2807,8 @@ void DavinciModel::SetOutputOutsideAddr(const std::vector &outside_addrs /// @return None. /// void DavinciModel::DisableZeroCopy(const void *addr) { - auto it = input_outside_addrs_.find(addr); - if (it == input_outside_addrs_.end()) { + if ((input_outside_addrs_.find(addr) == input_outside_addrs_.end()) && + (output_outside_addrs_.find(addr) == output_outside_addrs_.end())) { return; } @@ -2696,7 +2822,10 @@ void DavinciModel::DisableZeroCopy(const void *addr) { /// @brief Save outside address used info for ZeroCopy. /// @param [in] const OpDescPtr &op_desc: current op desc /// @param [in] const std::vector &outside_addrs: address of task -/// @param [in] const char *args_offset: arguments address save the address. +/// @param [in] const void *info: task args +/// @param [in] const char *args: task args +/// @param [in] size_t size: size of task args +/// @param [in] size_t offset: offset of task args /// @return None. /// void DavinciModel::SetZeroCopyAddr(const OpDescPtr &op_desc, const std::vector &outside_addrs, const void *info, @@ -2772,7 +2901,7 @@ bool DavinciModel::CheckInputAndModelSize(const int64_t &input_size, const int64 if (input_size > op_size) { GELOGW( - "Input size [%u] is bigger than om size need [%u]," + "Input size [%u] is bigger than om size need [%u], " "MAY cause inference result ERROR, please check model input", input_size, op_size); } @@ -2866,7 +2995,7 @@ Status DavinciModel::UpdateIoTaskArgs(const map> return FAILED; } - GELOGI("[ZCPY] Copy Blobs: %u, addr: %p, size: %ld, data: %p, length: %u.", data.first, data.second.second, + GELOGI("[ZCPY] Copy Blobs: %u, addr: %p, size: %ld, data: %p, length: %lu.", data.first, data.second.second, data.second.first, buffer.data, buffer.length); if (!CheckInputAndModelSize(buffer.length, size, is_dynamic)) { GELOGE(FAILED, "Check input size and model size failed"); @@ -3134,6 +3263,24 @@ Status DavinciModel::InitStreamSwitchN(const OpDescPtr &op_desc) { GELOGI("StreamSwitchNOp node:%s, active_stream_id=%u.", op_desc->GetName().c_str(), active_stream_list[j]); } + batch_info_.clear(); + uint32_t batch_num = 0; + if (!AttrUtils::GetInt(op_desc, ATTR_NAME_BATCH_NUM, batch_num)) { + GELOGE(FAILED, "Failed to get attr ATTR_NAME_BATCH_NUM, StreamSwitchN: %s.", op_desc->GetName().c_str()); + return FAILED; + } + + for (uint32_t i = 0; i < batch_num; i++) { + std::vector batch_shape; + const std::string attr_name = ATTR_NAME_PRED_VALUE + "_" + std::to_string(i); + if (!AttrUtils::GetListInt(op_desc, attr_name, batch_shape)) { + GELOGE(FAILED, "Failed to get attr ATTR_NAME_PRED_VALUE, StreamSwitchN: %s.", op_desc->GetName().c_str()); + batch_info_.clear(); + return FAILED; + } + batch_info_.emplace_back(batch_shape); + } + return SUCCESS; } @@ -3152,20 +3299,6 @@ bool DavinciModel::IsBroadCastOpData(const ge::NodePtr &var_node) { return false; } -void DavinciModel::InitZeroCopyUtil(bool is_dynamic_batch, bool &input_zero_copy, bool &output_zero_copy) { - if (!is_dynamic_batch) { - zero_copy_batch_label_addrs_.clear(); - } - - for (const auto &addrs : output_outside_addrs_) { - const auto &used_list = addrs.second; - if (used_list.empty()) { - output_zero_copy = false; - break; - } - } -} - /// /// @ingroup ge /// @brief Init model stream for NN model. @@ -3213,20 +3346,12 @@ Status DavinciModel::NnExecute(rtStream_t stream, bool async_mode, const InputDa GELOGI("Model Run begin, model id:%u, data index:%u, flag:%d.", model_id_, input_data.index, is_async_mode_); GE_CHK_STATUS_RET(InitModelStream(stream), "Init model stream failed."); - bool input_use_zero_copy = true; - bool output_use_zero_copy = true; - bool is_dynamic_batch = input_data.is_dynamic_batch; - InitZeroCopyUtil(is_dynamic_batch, input_use_zero_copy, output_use_zero_copy); - - // Empty task, Just copy input to output, need direct copy. - if (task_list_.empty() && (input_use_zero_copy || output_use_zero_copy)) { - GELOGE(FAILED, "Empty task, Just copy input to output, need direct copy."); - return FAILED; + if (!input_data.is_dynamic_batch) { + zero_copy_batch_label_addrs_.clear(); } GE_IF_BOOL_EXEC(ProfilingManager::Instance().ProfilingOn(), SetProfileTime(MODEL_PRE_PROC_START)); - Status ret = - input_use_zero_copy ? CopyModelData(input_data, output_data, is_dynamic_batch) : CopyInputData(input_data, true); + Status ret = CopyModelData(input_data, output_data, input_data.is_dynamic_batch); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(ret != SUCCESS, return INTERNAL_ERROR, "Copy input data to model failed."); GELOGI("current_data.index=%u", input_data.index); @@ -3243,7 +3368,7 @@ Status DavinciModel::NnExecute(rtStream_t stream, bool async_mode, const InputDa if (!is_async_mode_) { GE_IF_BOOL_EXEC(ProfilingManager::Instance().ProfilingOn(), SetProfileTime(MODEL_AFTER_PROC_START)); - ret = output_use_zero_copy ? SyncDataAndDump() : CopyOutputData(input_data.index, output_data); + ret = CopyOutputData(input_data.index, output_data, RT_MEMCPY_DEVICE_TO_DEVICE); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(ret != SUCCESS, return INTERNAL_ERROR, "Copy Output data to user failed."); GE_IF_BOOL_EXEC(ProfilingManager::Instance().ProfilingOn(), SetProfileTime(MODEL_AFTER_PROC_END)); } @@ -3254,11 +3379,60 @@ Status DavinciModel::NnExecute(rtStream_t stream, bool async_mode, const InputDa return SUCCESS; } +// Add active entry stream for special env. +Status DavinciModel::AddHeadStream() { + if (active_stream_list_.empty()) { + GELOGE(INTERNAL_ERROR, "Active stream is empty, stream list size: %zu, stream indication size: %zu.", + stream_list_.size(), active_stream_indication_.size()); + return INTERNAL_ERROR; + } + + if (active_stream_list_.size() == 1) { + GELOGI("Just one active stream, take as head stream."); + rt_head_stream_ = active_stream_list_[0]; + is_pure_head_stream_ = false; + } else { + // Create stream which rt_model_handel running on, this is S0, TS stream. + GELOGI("Multiple active stream: %zu, create head stream.", active_stream_list_.size()); + GE_CHK_RT_RET(rtStreamCreateWithFlags(&rt_head_stream_, priority_, RT_STREAM_PERSISTENT)); + GE_CHK_RT_RET(rtModelBindStream(rt_model_handle_, rt_head_stream_, RT_INVALID_FLAG)); // Not active. + is_pure_head_stream_ = true; + + for (auto s : active_stream_list_) { + std::shared_ptr active_entry = MakeShared(rt_head_stream_); + if (active_entry == nullptr) { + GELOGE(FAILED, "Make CpuTaskActiveEntry task failed."); + return FAILED; + } + + if (active_entry->Init(s) != SUCCESS) { + return FAILED; + } + + cpu_task_list_.emplace_back(active_entry); + } + } + + // Create entry stream active head stream. AICPU stream. + GE_CHK_RT_RET(rtStreamCreateWithFlags(&rt_entry_stream_, priority_, RT_STREAM_AICPU)); + GE_CHK_RT_RET(rtModelBindStream(rt_model_handle_, rt_entry_stream_, RT_HEAD_STREAM)); + return SUCCESS; +} + +Status DavinciModel::InitEntryTask() { + if (deploy_type_ == AICPU_DEPLOY_CROSS_THREAD) { + GE_CHK_STATUS_RET(AddHeadStream(), "Add head stream failed."); + return CpuActiveStream(); + } else { + return LoadWithQueue(); + } +} + uint8_t *DavinciModel::MallocFeatureMapMem(size_t data_size) { uint8_t *mem_base = nullptr; const string purpose("feature map,used for op input and output."); if (std::getenv(kEnvGeuseStaticMemory) != nullptr) { - data_size = static_cast(VarManager::Instance(0)->GetGraphMemoryMaxSize()); + data_size = static_cast(VarManager::Instance(session_id_)->GetGraphMemoryMaxSize()); string memory_key = std::to_string(0) + "_f"; mem_base = MemManager::Instance(RT_MEMORY_HBM)->MallocMemory(purpose, memory_key, data_size, GetDeviceId()); } else { @@ -3343,12 +3517,14 @@ Status DavinciModel::TransAllVarData(ComputeGraphPtr &graph, uint32_t graph_id) return SUCCESS; } -void DavinciModel::SetDataDumperArgs() { +void DavinciModel::SetDataDumperArgs(const ComputeGraphPtr &compute_graph) { GELOGI("set data dumper args, name: %s, id: %u.", name_.c_str(), model_id_); data_dumper_.SetModelName(name_); data_dumper_.SetModelId(model_id_); data_dumper_.SetMemory(runtime_param_); data_dumper_.SetOmName(om_name_); + data_dumper_.SetComputeGraph(compute_graph); + data_dumper_.SetRefInfo(saved_task_addrs_); int32_t device_id = 0; rtError_t rt_ret = rtGetDevice(&device_id); @@ -3404,18 +3580,9 @@ void DavinciModel::ReuseHcclFollowStream(int64_t remain_cap, int64_t &index) { } } -Status DavinciModel::CopyVarData(ComputeGraphPtr &compute_graph) { - return TransVarDataUtils::CopyVarData(compute_graph, session_id_, device_id_); -} - -Status DavinciModel::GetComputeGraphInfo(std::vector &compute_graph_desc_info) { +Status DavinciModel::GetComputeGraphInfo(const ComputeGraphPtr &graph, vector &graph_desc_info) { GELOGI("GetComputeGraphInfo start."); - if (compute_graph_ == nullptr) { - GELOGE(FAILED, "compute_graph_ is nullptr"); - return FAILED; - } - - for (auto &node : compute_graph_->GetAllNodes()) { + for (auto &node : graph->GetAllNodes()) { ComputeGraphDescInfo compute_graph_info; auto op_desc = node->GetOpDesc(); if (op_desc == nullptr) { @@ -3426,6 +3593,11 @@ Status DavinciModel::GetComputeGraphInfo(std::vector &comp auto op_mode = static_cast(domi::ImplyType::INVALID); if (AttrUtils::GetInt(op_desc, ATTR_NAME_IMPLY_TYPE, op_mode) && op_mode == static_cast(domi::ImplyType::TVM)) { + if (!om_name_.empty()) { + compute_graph_info.model_name = om_name_; + } else { + compute_graph_info.model_name = name_; + } compute_graph_info.op_name = op_desc->GetName(); compute_graph_info.op_type = op_desc->GetType(); @@ -3443,12 +3615,18 @@ Status DavinciModel::GetComputeGraphInfo(std::vector &comp compute_graph_info.output_data_type.emplace_back(output_desc.GetDataType()); } - compute_graph_desc_info.emplace_back(compute_graph_info); + graph_desc_info.emplace_back(compute_graph_info); } } GELOGI("GetComputeGraphInfo end."); return SUCCESS; } +void DavinciModel::SetTotalFixedAddrsSize(string tensor_name, int64_t fix_addr_size) { + if (tensor_name_to_fixed_addr_size_.find(tensor_name) == tensor_name_to_fixed_addr_size_.end()) { + tensor_name_to_fixed_addr_size_[tensor_name] = total_fixed_addr_size_; + total_fixed_addr_size_ += fix_addr_size; + } +} Status DavinciModel::GetOrigInputInfo(uint32_t index, OriginInputInfo &orig_input_info) { GE_CHK_BOOL_RET_STATUS(index < data_op_list_.size(), PARAM_INVALID, "Index %u is invalid.", index); @@ -3537,4 +3715,23 @@ Status DavinciModel::GetAllAippInputOutputDims(uint32_t index, std::vectorHasAttr(ATTR_DYNAMIC_SHAPE_FIXED_ADDR) && op_desc->HasAttr(ATTR_DYNAMIC_SHAPE_FIXED_ADDR_INDEX)) { + string tensor_name; + (void)AttrUtils::GetStr(op_desc, ATTR_DYNAMIC_SHAPE_FIXED_ADDR, tensor_name); + int64_t index = -1; + (void)AttrUtils::GetInt(op_desc, ATTR_DYNAMIC_SHAPE_FIXED_ADDR_INDEX, index); + if (index >= 0) { + tensor_name_to_peer_output_index_[tensor_name] = index; + } + } +} } // namespace ge diff --git a/src/ge/graph/load/new_model_manager/davinci_model.h b/src/ge/graph/load/new_model_manager/davinci_model.h index 3254a23b..0f0b1e5c 100644 --- a/src/ge/graph/load/new_model_manager/davinci_model.h +++ b/src/ge/graph/load/new_model_manager/davinci_model.h @@ -29,6 +29,7 @@ #include "common/helper/om_file_helper.h" #include "common/opskernel/ge_task_info.h" #include "common/types.h" +#include "common/properties_manager.h" #include "framework/common/util.h" #include "graph/debug/ge_attr_define.h" #include "graph/load/new_model_manager/data_dumper.h" @@ -47,6 +48,10 @@ #include "task_info/task_info.h" namespace ge { +// op debug need 2048 bits buffer +const size_t kOpDebugMemorySize = 2048UL; +const size_t kDebugP2pSize = 8UL; + typedef enum tagModelProcStage { MODEL_LOAD_START = 1, MODEL_LOAD_END, @@ -171,13 +176,6 @@ class DavinciModel { // get session id uint64_t SessionId() const { return runtime_param_.session_id; } - vector GetOpDesc() { - vector opDescVector; - GE_IF_BOOL_EXEC(AttrUtils::GetListOpDesc(GetGeModel(), MODEL_ATTR_FUSION_MODEL_DEF, opDescVector), - GELOGI("get opDesc of opDescVector")); - return opDescVector; - } - // get model priority int32_t Priority() const { return priority_; } @@ -248,15 +246,9 @@ class DavinciModel { /// Format GetFormat(); - rtModel_t GetRtModelHandle() { - rtModel_t res = rt_model_handle_; - return res; - } + rtModel_t GetRtModelHandle() const { return rt_model_handle_; } - rtStream_t GetRtModelStream() { - rtModel_t res = rt_model_stream_; - return res; - } + rtStream_t GetRtModelStream() const { return rt_model_stream_; } uint64_t GetRtBaseAddr() const { return runtime_param_.logic_mem_base; } @@ -295,7 +287,7 @@ class DavinciModel { /// @param [out] batch_info /// @return execute result /// - Status GetDynamicBatchInfo(std::vector> &batch_info); + Status GetDynamicBatchInfo(std::vector> &batch_info) const; void GetCurShape(std::vector &batch_info); @@ -344,10 +336,9 @@ class DavinciModel { /// /// @ingroup ge /// @brief dump all op input and output information - /// @param [in] op_list model_id - /// @return Status + /// @return void /// - Status DumpOpInputOutput(); + void DumpOpInputOutput(); /// /// @ingroup ge @@ -403,7 +394,9 @@ class DavinciModel { /// uint32_t GetDeviceId() const { return device_id_; } - GeModelPtr GetGeModel() { return ge_model_; } + bool NeedDestroyAicpuKernel() const { return need_destroy_aicpu_kernel_; } + + Status UpdateSessionId(uint64_t session_id); const RuntimeParam &GetRuntimeParam() { return runtime_param_; } @@ -463,6 +456,19 @@ class DavinciModel { void *cur_args = static_cast(args_) + offset; return cur_args; } + void SetTotalFixedAddrsSize(string tensor_name, int64_t fix_addr_size); + int64_t GetFixedAddrsSize(string tensor_name); + void *GetCurrentFixedAddr(int64_t offset) const { + void *cur_addr = static_cast(fixed_addrs_) + offset; + return cur_addr; + } + + uint32_t GetFixedAddrOutputIndex(string tensor_name) { + if (tensor_name_to_peer_output_index_.find(tensor_name) != tensor_name_to_peer_output_index_.end()) { + return tensor_name_to_peer_output_index_[tensor_name]; + } + return UINT32_MAX; + } void SetKnownNode(bool known_node) { known_node_ = known_node; } bool IsKnownNode() { return known_node_; } Status MallocKnownArgs(); @@ -473,9 +479,13 @@ class DavinciModel { Status GetOrigInputInfo(uint32_t index, OriginInputInfo &orig_input_info); Status GetAllAippInputOutputDims(uint32_t index, std::vector &input_dims, std::vector &output_dims); + void SetModelDescVersion(bool is_new_model_desc) { is_new_model_desc_ = is_new_model_desc; } // om file name void SetOmName(string om_name) { om_name_ = om_name; } + void SetDumpProperties(const DumpProperties &dump_properties) { data_dumper_.SetDumpProperties(dump_properties); } + const DumpProperties &GetDumpProperties() const { return data_dumper_.GetDumpProperties(); } + private: // memory address of weights uint8_t *weights_mem_base_; @@ -492,8 +502,6 @@ class DavinciModel { struct timeInfo time_info_; int32_t dataInputTid; - void InitZeroCopyUtil(bool is_dynamic_batch, bool &input_zero_copy, bool &output_zero_copy); - /// /// @ingroup ge /// @brief Save Batch label Info. @@ -529,6 +537,13 @@ class DavinciModel { /// bool CheckInputAndModelSize(const int64_t &input_size, const int64_t &op_size, bool is_dynamic); + /// + /// @ingroup ge + /// @brief Set copy only for No task feed NetOutput address. + /// @return None. + /// + void SetCopyOnlyOutput(); + /// /// @ingroup ge /// @brief Copy Input/Output to model for direct use. @@ -554,16 +569,14 @@ class DavinciModel { Status CopyInputData(const InputData &input_data, bool device_data = false); - Status CopyOutputData(uint32_t data_id, OutputData &output_data); - - Status CopyOutputDataToUser(OpDescPtr &op_desc, std::vector &blobs, uint32_t &data_index); + Status CopyOutputData(uint32_t data_id, OutputData &output_data, rtMemcpyKind_t kind); Status SyncVarData(); - Status SyncDataAndDump(); - Status InitModelMem(void *dev_ptr, size_t memsize, void *weight_ptr, size_t weightsize); + void CreateInputDimsInfo(const OpDescPtr &op_desc, Format format, InputOutputDescInfo &input); + Status GetInputDescInfo(vector &input_desc, std::vector &formats); Status InitTaskInfo(domi::ModelTaskDef &modelTaskInfo); @@ -586,7 +599,12 @@ class DavinciModel { bool IsAicpuKernelConnectSpecifiedLayer(); - Status MarkSpecifiedAicpuKernel(); + /// + /// @ingroup ge + /// @brief Reduce memory usage after task sink. + /// @return: void + /// + void Shrink(); /// /// @ingroup ge @@ -722,10 +740,9 @@ class DavinciModel { /// /// @ingroup ge /// @brief definiteness queue schedule, active original model stream. - /// @param [in] streams: streams will active by S0. /// @return: 0 for success / others for fail /// - Status CpuActiveStream(const std::vector &stream_list); + Status CpuActiveStream(); /// /// @ingroup ge @@ -743,6 +760,9 @@ class DavinciModel { /// Status CpuModelRepeat(); + Status InitEntryTask(); + Status AddHeadStream(); + /// /// @ingroup ge /// @brief set ts device. @@ -750,6 +770,10 @@ class DavinciModel { /// Status SetTSDevice(); + Status OpDebugRegister(); + + void OpDebugUnRegister(); + void CheckHasHcomOp(); Status DoTaskSink(); @@ -757,17 +781,17 @@ class DavinciModel { void CreateOutput(uint32_t index, OpDescPtr &op_desc, InputOutputDescInfo &output, uint32_t &format_result); Status TransAllVarData(ComputeGraphPtr &graph, uint32_t graph_id); - Status CopyVarData(ComputeGraphPtr &graph); // get desc info of graph for profiling - Status GetComputeGraphInfo(vector &compute_graph_desc_info); + Status GetComputeGraphInfo(const ComputeGraphPtr &graph, vector &graph_desc_info); - void SetDataDumperArgs(); + void SetDataDumperArgs(const ComputeGraphPtr &compute_graph); Status GenOutputTensorInfo(const OpDescPtr &op_desc, uint32_t data_index, OutputData *output_data, std::vector &outputs); void ParseAIPPInfo(std::string in_out_info, InputOutputDims &dims_info); + void GetFixedAddrAttr(const OpDescPtr &op_desc); bool is_model_has_inited_; uint32_t model_id_; @@ -780,6 +804,9 @@ class DavinciModel { uint32_t version_; GeModelPtr ge_model_; + bool need_destroy_aicpu_kernel_{false}; + vector out_node_name_; + map op_list_; // data op_desc @@ -840,6 +867,11 @@ class DavinciModel { bool is_async_mode_; // For NN execute, Async mode use rtMemcpyAsync on rt_model_stream_. + bool is_pure_head_stream_{false}; + rtStream_t rt_head_stream_{nullptr}; + rtStream_t rt_entry_stream_{nullptr}; + rtAicpuDeployType_t deploy_type_{AICPU_DEPLOY_RESERVED}; + // ACL queue schedule, save queue ids for Init. std::vector cpu_task_list_; std::vector input_queue_ids_; // input queue ids created by caller. @@ -861,8 +893,6 @@ class DavinciModel { std::vector active_stream_list_; std::set active_stream_indication_; - std::shared_ptr model_task_def_; - std::set aicpu_streams_; std::set hcom_streams_; RuntimeParam runtime_param_; @@ -874,22 +904,40 @@ class DavinciModel { // for profiling task and graph info std::map op_name_map_; std::vector task_desc_info_; - ComputeGraphPtr compute_graph_; int64_t maxDumpOpNum_; // for data dump DataDumper data_dumper_; uint64_t iterator_count_; bool is_l1_fusion_enable_; + std::map saved_task_addrs_; bool known_node_ = false; uint32_t total_args_size_ = 0; void *args_ = nullptr; void *args_host_ = nullptr; + void *fixed_addrs_ = nullptr; + int64_t total_fixed_addr_size_ = 0; std::map knonw_input_data_info_; std::map knonw_output_data_info_; + vector> batch_info_; + vector batch_size_; + // key: input tensor name, generally rts op; + // value: the fixed addr of input anchor, same as the peer output anchor addr of the peer op + std::map tensor_name_to_fixed_addr_size_; + + // key: input tensor name, generally rts op; value: the peer output anchor of the peer op + std::map tensor_name_to_peer_output_index_; + // if model is first execute + bool is_first_execute_; + // for op debug + std::mutex debug_reg_mutex_; + bool is_op_debug_reg_ = false; + void *op_debug_addr_ = nullptr; + void *p2p_debug_addr_ = nullptr; + bool is_new_model_desc_{false}; }; } // namespace ge #endif // GE_GRAPH_LOAD_NEW_MODEL_MANAGER_DAVINCI_MODEL_H_ diff --git a/src/ge/graph/load/new_model_manager/model_manager.cc b/src/ge/graph/load/new_model_manager/model_manager.cc index 384e203b..04c836dd 100644 --- a/src/ge/graph/load/new_model_manager/model_manager.cc +++ b/src/ge/graph/load/new_model_manager/model_manager.cc @@ -22,8 +22,9 @@ #include "common/profiling/profiling_manager.h" #include "common/properties_manager.h" #include "framework/common/debug/ge_log.h" -#include "graph/debug/ge_attr_define.h" #include "framework/common/util.h" +#include "graph/common/ge_call_wrapper.h" +#include "graph/debug/ge_attr_define.h" #include "graph/load/new_model_manager/davinci_model.h" #include "graph/load/new_model_manager/davinci_model_parser.h" #include "model/ge_root_model.h" @@ -33,9 +34,10 @@ thread_local uint32_t device_count = 0; namespace { const int kCmdParSize = 2; const int kDumpCmdPairSize = 2; -const char *const kNeedDestroySpecifiedAicpuKernel = "need_destroy_specified_aicpu_kernel"; } // namespace +DumpProperties ModelManager::dump_properties_; + std::shared_ptr ModelManager::GetInstance() { static const std::shared_ptr instance_ptr = shared_ptr(new (std::nothrow) ModelManager(), ModelManager::FinalizeForPtr); @@ -272,6 +274,10 @@ Status ModelManager::LoadModelOnline(uint32_t &model_id, const shared_ptrSetId(model_id); davinci_model->SetDeviceId(GetContext().DeviceId()); + const DumpProperties &dump_properties = PropertiesManager::Instance().GetDumpProperties(GetContext().SessionId()); + davinci_model->SetDumpProperties(dump_properties); + dump_properties_ = dump_properties; + auto root_graph = ge_root_model->GetRootGraph(); GE_CHECK_NOTNULL(root_graph); string root_model_name = root_graph->GetName(); @@ -296,9 +302,6 @@ Status ModelManager::LoadModelOnline(uint32_t &model_id, const shared_ptrSetProfileTime(MODEL_LOAD_START, (timespec.tv_sec * 1000 * 1000 * 1000 + timespec.tv_nsec)); // 1000 ^ 3 converts second to nanosecond davinci_model->SetProfileTime(MODEL_LOAD_END); - if (davinci_model->SinkModelProfile() != SUCCESS) { - GELOGW("Sink model profile failed."); - } } } while (0); @@ -611,10 +614,10 @@ Status ModelManager::HandleDumpCommand(const Command &command) { GELOGE(PARAM_INVALID, "parser dump model failed"); return FAILED; } - GELOGI("dump status = %s.", dump_model.c_str()); + GELOGI("dump model = %s.", dump_model.c_str()); if (dump_status == "off" || dump_status == "OFF") { - PropertiesManager::Instance().DeleteDumpPropertyValue(dump_model); + dump_properties_.DeletePropertyValue(dump_model); return SUCCESS; } @@ -631,9 +634,10 @@ Status ModelManager::HandleDumpCommand(const Command &command) { return FAILED; } if (!dump_path.empty() && dump_path[dump_path.size() - 1] != '/') { - dump_path = dump_path + "/" + CurrentTimeInStr() + "/"; + dump_path = dump_path + "/"; } - GELOGI("dump status = %s.", dump_path.c_str()); + dump_path = dump_path + CurrentTimeInStr() + "/"; + GELOGI("dump path = %s.", dump_path.c_str()); ret = ParserPara(command, DUMP_MODE, dump_mode); if (ret != SUCCESS) { @@ -642,20 +646,10 @@ Status ModelManager::HandleDumpCommand(const Command &command) { } GELOGI("dump mode = %s", dump_mode.c_str()); - auto iter_dump_mode = std::find(command.cmd_params.begin(), command.cmd_params.end(), DUMP_MODE); - if (iter_dump_mode != command.cmd_params.end()) { - ++iter_dump_mode; - if (iter_dump_mode == command.cmd_params.end()) { - GELOGE(PARAM_INVALID, "Invalid access."); - return PARAM_INVALID; - } - dump_mode = *iter_dump_mode; - GELOGI("dump mode = %s", dump_mode.c_str()); - } + dump_properties_.AddPropertyValue(dump_model, dump_layers); + dump_properties_.SetDumpPath(dump_path); + dump_properties_.SetDumpMode(dump_mode); - PropertiesManager::Instance().AddDumpPropertyValue(dump_model, dump_layers); - PropertiesManager::Instance().SetDumpOutputPath(dump_path); - PropertiesManager::Instance().SetDumpMode(dump_mode); return SUCCESS; } @@ -685,11 +679,14 @@ Status ModelManager::GetInputOutputDescInfo(const uint32_t model_id, vector &input_desc, vector &output_desc, - std::vector &inputFormats, std::vector &outputFormats) { + std::vector &inputFormats, std::vector &outputFormats, + bool new_model_desc) { std::shared_ptr davinci_model = GetModel(model_id); GE_CHK_BOOL_RET_STATUS(davinci_model != nullptr, PARAM_INVALID, "GetInputOutputDescInfo Failed, Invalid Model ID %u !", model_id); + davinci_model->SetModelDescVersion(new_model_desc); + return davinci_model->GetInputOutputDescInfo(input_desc, output_desc, inputFormats, outputFormats); } @@ -768,17 +765,6 @@ Status ModelManager::GenSessionId(uint64_t &session_id) { return SUCCESS; } -Status ModelManager::UpdateSessionId(std::shared_ptr &davinci_model, uint64_t session_id) { - GeModelPtr ge_model_current = davinci_model->GetGeModel(); - GE_CHECK_NOTNULL(ge_model_current); - if (!ge::AttrUtils::SetInt(ge_model_current, ge::MODEL_ATTR_SESSION_ID, static_cast(session_id))) { - GELOGW("Set attr[%s] failed in updating session_id.", MODEL_ATTR_SESSION_ID.c_str()); - } - - GELOGD("Update session id: %lu.", session_id); - return SUCCESS; -} - Status ModelManager::LoadModelOffline(uint32_t &model_id, const ModelData &model, shared_ptr listener, void *dev_ptr, size_t mem_size, void *weight_ptr, size_t weight_size) { GE_CHK_BOOL_RET_STATUS(model.key.empty() || access(model.key.c_str(), F_OK) == 0, PARAM_INVALID, @@ -821,6 +807,7 @@ Status ModelManager::LoadModelOffline(uint32_t &model_id, const ModelData &model } davinci_model->SetDeviceId(device_id); davinci_model->SetOmName(model.om_name); + davinci_model->SetDumpProperties(dump_properties_); /// In multi-threaded inference, using the same session_id among multiple threads may cause some threads to fail. /// These session_ids come from the same model, so the values of session_id are the same. @@ -828,7 +815,7 @@ Status ModelManager::LoadModelOffline(uint32_t &model_id, const ModelData &model uint64_t new_session_id; ret = GenSessionId(new_session_id); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(ret != SUCCESS, break, "Generate session_id for infer failed."); - ret = UpdateSessionId(davinci_model, new_session_id); + ret = davinci_model->UpdateSessionId(new_session_id); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(ret != SUCCESS, break, "Update session_id for infer failed."); ret = davinci_model->Init(dev_ptr, mem_size, weight_ptr, weight_size); @@ -843,9 +830,6 @@ Status ModelManager::LoadModelOffline(uint32_t &model_id, const ModelData &model davinci_model->SetProfileTime(MODEL_LOAD_START, (timespec.tv_sec * 1000 * 1000 * 1000 + timespec.tv_nsec)); // 1000 ^ 3 converts second to nanosecond davinci_model->SetProfileTime(MODEL_LOAD_END); - if (davinci_model->SinkModelProfile() != SUCCESS) { - GELOGW("Sink model profile failed."); - } } GE_IF_BOOL_EXEC(ret == SUCCESS, device_count++); @@ -895,7 +879,7 @@ Status ModelManager::LoadModelWithQ(uint32_t &model_id, const ModelData &model_d uint64_t new_session_id; ret = GenSessionId(new_session_id); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(ret != SUCCESS, return ret, "Generate session_id for infer failed."); - ret = UpdateSessionId(davinci_model, new_session_id); + ret = davinci_model->UpdateSessionId(new_session_id); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(ret != SUCCESS, return ret, "Update session_id for infer failed."); GenModelId(&model_id); @@ -906,6 +890,8 @@ Status ModelManager::LoadModelWithQ(uint32_t &model_id, const ModelData &model_d return ret; } + davinci_model->SetDumpProperties(dump_properties_); + ret = davinci_model->Init(); if (ret != SUCCESS) { GELOGE(ret, "init model failed."); @@ -932,12 +918,8 @@ Status ModelManager::ExecuteModel(uint32_t model_id, rtStream_t stream, bool asy std::shared_ptr davinci_model = GetModel(model_id); GE_CHK_BOOL_RET_STATUS(davinci_model != nullptr, PARAM_INVALID, "Invalid Model ID %u to start! ", model_id); - GeModelPtr ge_model_current = davinci_model->GetGeModel(); - bool need_destroy_aicpu_kernel = false; - bool result = ge::AttrUtils::GetBool(ge_model_current, kNeedDestroySpecifiedAicpuKernel, need_destroy_aicpu_kernel); - if (result && need_destroy_aicpu_kernel) { - GELOGI("Get attr %s successfully, start to destroy specified aicpu kernel.", kNeedDestroySpecifiedAicpuKernel); - + if (davinci_model->NeedDestroyAicpuKernel()) { + GELOGI("Start to destroy specified aicpu kernel."); // Zero copy is enabled by default, no need to judge. uint64_t session_id_davinci = davinci_model->GetSessionId(); uint32_t model_id_davinci = davinci_model->GetModelId(); @@ -1047,4 +1029,19 @@ Status ModelManager::GetAllAippInputOutputDims(uint32_t model_id, uint32_t index return davinci_model->GetAllAippInputOutputDims(index, input_dims, output_dims); } +bool ModelManager::IsDynamicShape(uint32_t model_id) { + auto model = GetHybridModel(model_id); + return model != nullptr; +} + +ge::Status ModelManager::SyncExecuteModel(uint32_t model_id, const vector &inputs, + vector &outputs) { + auto model = GetHybridModel(model_id); + if (model == nullptr) { + GELOGE(FAILED, "Hybrid model not found. model id = %u.", model_id); + return FAILED; + } + + return model->Execute(inputs, outputs); +} } // namespace ge diff --git a/src/ge/graph/load/new_model_manager/model_manager.h b/src/ge/graph/load/new_model_manager/model_manager.h index 9a94e5c9..2ba23d7c 100644 --- a/src/ge/graph/load/new_model_manager/model_manager.h +++ b/src/ge/graph/load/new_model_manager/model_manager.h @@ -31,6 +31,7 @@ #include "common/ge_types.h" #include "common/helper/model_helper.h" #include "common/helper/om_file_helper.h" +#include "common/properties_manager.h" #include "common/types.h" #include "ge/ge_api_types.h" #include "graph/ge_context.h" @@ -141,6 +142,8 @@ class FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY ModelManager { ge::Status ExecuteModel(uint32_t model_id, rtStream_t stream, bool async_mode, const InputData &input_data, OutputData &output_data); + ge::Status SyncExecuteModel(uint32_t model_id, const std::vector &inputs, std::vector &outputs); + /// /// @ingroup domi_ome /// @brief model stop @@ -178,7 +181,7 @@ class FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY ModelManager { ge::Status GetInputOutputDescInfo(const uint32_t model_id, std::vector &input_desc, std::vector &output_desc, std::vector &inputFormats, - std::vector &outputFormats); + std::vector &outputFormats, bool new_model_desc = false); /// /// @ingroup ge /// @brief Get dynamic batch_info @@ -249,6 +252,8 @@ class FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY ModelManager { ge::Status GetAllAippInputOutputDims(uint32_t model_id, uint32_t index, std::vector &input_dims, std::vector &output_dims); + bool IsDynamicShape(uint32_t model_id); + private: /// /// @ingroup domi_ome @@ -276,7 +281,6 @@ class FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY ModelManager { ge::Status DeleteModel(uint32_t id); void GenModelId(uint32_t *id); - ge::Status UpdateSessionId(std::shared_ptr &davinci_model, uint64_t session_id); std::map> model_map_; std::map> hybrid_model_map_; @@ -287,6 +291,8 @@ class FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY ModelManager { std::mutex session_id_create_mutex_; uint64_t session_id_bias_; std::set sess_ids_; + + static DumpProperties dump_properties_; }; } // namespace ge diff --git a/src/ge/graph/load/new_model_manager/model_utils.cc b/src/ge/graph/load/new_model_manager/model_utils.cc index a807f2a3..bd684b9d 100644 --- a/src/ge/graph/load/new_model_manager/model_utils.cc +++ b/src/ge/graph/load/new_model_manager/model_utils.cc @@ -31,7 +31,7 @@ namespace ge { /// -/// @ingroup domi_ome +/// @ingroup ge /// @brief Get input size. /// @return vector /// @@ -43,22 +43,26 @@ vector ModelUtils::GetInputSize(ConstOpDescPtr op_desc) { const vector v_is_input_const = op_desc->GetIsInputConst(); for (size_t i = 0; i < inputs_size; ++i) { + const GeTensorDescPtr tensor_desc = op_desc->MutableInputDesc(i); + if (tensor_desc == nullptr) { + GELOGW("Op: %s, Index: %zu, Tensor Desc is null", op_desc->GetName().c_str(), i); + continue; + } + + int64_t tensor_size = 0; if ((i < v_is_input_const.size()) && v_is_input_const[i] && (op_type != NETOUTPUT)) { // TBE: add weights size to input - GeTensorDesc tensor_desc = op_desc->GetInputDesc(i); - int64_t tensor_size = 0; - GE_CHK_STATUS(TensorUtils::GetSize(tensor_desc, tensor_size)); + GE_CHK_STATUS(TensorUtils::GetSize(*tensor_desc, tensor_size)); if (tensor_size) { v_input_size.push_back(tensor_size); } continue; } - int64_t tensor_size = 0; GE_IF_BOOL_EXEC( - TensorUtils::GetSize(op_desc->GetInputDesc(i), tensor_size) != GRAPH_SUCCESS, + TensorUtils::GetSize(*tensor_desc, tensor_size) != GRAPH_SUCCESS, GELOGI("Get size from TensorDesc failed, op : %s, input index : %zu", op_desc->GetName().c_str(), i); - continue;); + continue); v_input_size.push_back(tensor_size); } @@ -67,7 +71,7 @@ vector ModelUtils::GetInputSize(ConstOpDescPtr op_desc) { } /// -/// @ingroup domi_ome +/// @ingroup ge /// @brief Get output size. /// @return vector /// @@ -82,11 +86,17 @@ vector ModelUtils::GetOutputSize(ConstOpDescPtr op_desc) { return v_output_size;); for (size_t i = 0; i < outputs_size; ++i) { + const GeTensorDescPtr tensor_desc = op_desc->MutableOutputDesc(i); + if (tensor_desc == nullptr) { + GELOGW("Op: %s, Index: %zu, Tensor Desc is null", op_desc->GetName().c_str(), i); + continue; + } + int64_t tensor_size = 0; GE_IF_BOOL_EXEC( - TensorUtils::GetSize(op_desc->GetOutputDesc(i), tensor_size) != GRAPH_SUCCESS, + TensorUtils::GetSize(*tensor_desc, tensor_size) != GRAPH_SUCCESS, GELOGI("Get size from TensorDesc failed, op : %s, output index : %zu", op_desc->GetName().c_str(), i); - continue;); + continue); v_output_size.push_back(tensor_size); } @@ -95,7 +105,7 @@ vector ModelUtils::GetOutputSize(ConstOpDescPtr op_desc) { } /// -/// @ingroup domi_ome +/// @ingroup ge /// @brief Get workspace size. /// @return vector /// @@ -118,7 +128,7 @@ vector ModelUtils::GetWorkspaceSize(ConstOpDescPtr op_desc) { } /// -/// @ingroup domi_ome +/// @ingroup ge /// @brief Get weight size. /// @return vector /// @@ -142,8 +152,14 @@ vector ModelUtils::GetWeightSize(ConstOpDescPtr op_desc) { const vector v_is_input_const = op_desc->GetIsInputConst(); for (size_t i = 0; i < inputs_size; ++i) { if ((i < v_is_input_const.size()) && v_is_input_const[i]) { + const GeTensorDescPtr tensor_desc = op_desc->MutableInputDesc(i); + if (tensor_desc == nullptr) { + GELOGW("Op: %s, Index: %zu, Tensor Desc is null", op_desc->GetName().c_str(), i); + continue; + } + int64_t tensor_size = 0; - (void)TensorUtils::GetSize(op_desc->GetInputDesc(i), tensor_size); + (void)TensorUtils::GetSize(*tensor_desc, tensor_size); v_weight_size.push_back(tensor_size); } } @@ -152,7 +168,7 @@ vector ModelUtils::GetWeightSize(ConstOpDescPtr op_desc) { } /// -/// @ingroup domi_ome +/// @ingroup ge /// @brief Get weights. /// @return vector /// @@ -176,9 +192,14 @@ vector ModelUtils::GetWeights(ConstOpDescPtr op_desc) { const vector v_is_input_const = op_desc->GetIsInputConst(); for (size_t i = 0; i < inputs_size; ++i) { if ((i < v_is_input_const.size()) && v_is_input_const[i]) { + const GeTensorDescPtr tensor_desc = op_desc->MutableInputDesc(i); + if (tensor_desc == nullptr) { + GELOGW("Op: %s, Index: %zu, Tensor Desc is null", op_desc->GetName().c_str(), i); + continue; + } + ConstGeTensorPtr weight = nullptr; - GeTensorDesc tensor_desc = op_desc->GetInputDesc(i); - if (AttrUtils::GetTensor(tensor_desc, ATTR_NAME_WEIGHTS, weight)) { + if (AttrUtils::GetTensor(*tensor_desc, ATTR_NAME_WEIGHTS, weight)) { v_weights.push_back(weight); } } @@ -188,7 +209,7 @@ vector ModelUtils::GetWeights(ConstOpDescPtr op_desc) { } /// -/// @ingroup domi_ome +/// @ingroup ge /// @brief Get AiCpuOp Input descriptor. /// @return vector<::tagCcAICPUTensor> /// @@ -205,20 +226,25 @@ vector<::tagCcAICPUTensor> ModelUtils::GetInputDescs(ConstOpDescPtr op_desc) { continue; } + const GeTensorDescPtr tensor_desc = op_desc->MutableInputDesc(i); + if (tensor_desc == nullptr) { + GELOGW("Op: %s, Index: %zu, Tensor Desc is null", op_desc->GetName().c_str(), i); + continue; + } + uint32_t dim_cnt = 0; - const auto &descriptor = op_desc->GetInputDesc(i); - GE_CHK_BOOL_EXEC_WARN(TensorUtils::GetRealDimCnt(descriptor, dim_cnt) == GRAPH_SUCCESS, continue, + GE_CHK_BOOL_EXEC_WARN(TensorUtils::GetRealDimCnt(*tensor_desc, dim_cnt) == GRAPH_SUCCESS, continue, "Get dim_cnt failed"); opTensor_t tmp; - uint32_t tmp_fmt = descriptor.GetFormat(); + uint32_t tmp_fmt = tensor_desc->GetFormat(); tmp.format = tagOpTensorFormat(tmp_fmt); tmp.dim_cnt = static_cast(dim_cnt); - uint32_t tmp_type = descriptor.GetDataType(); + uint32_t tmp_type = tensor_desc->GetDataType(); tmp.data_type = tagOpDataType(tmp_type); for (int32_t j = 0; j < 4; j++) { // 4 dims - tmp.dim[j] = (j < tmp.dim_cnt ? descriptor.GetShape().GetDim(j) : 1); + tmp.dim[j] = (j < tmp.dim_cnt ? tensor_desc->GetShape().GetDim(j) : 1); } v_input_descs.push_back(tmp); @@ -228,7 +254,7 @@ vector<::tagCcAICPUTensor> ModelUtils::GetInputDescs(ConstOpDescPtr op_desc) { } /// -/// @ingroup domi_ome +/// @ingroup ge /// @brief Get AiCpuOp Output descriptor. /// @return vector<::tagCcAICPUTensor> /// @@ -240,20 +266,25 @@ vector<::tagCcAICPUTensor> ModelUtils::GetOutputDescs(ConstOpDescPtr op_desc) { // init op output opTensor_t struct const size_t output_num = op_desc->GetOutputsSize(); for (size_t i = 0; i < output_num; ++i) { + const GeTensorDescPtr tensor_desc = op_desc->MutableOutputDesc(i); + if (tensor_desc == nullptr) { + GELOGW("Op: %s, Index: %zu, Tensor Desc is null", op_desc->GetName().c_str(), i); + continue; + } + uint32_t dim_cnt = 0; - const auto &descriptor = op_desc->GetOutputDesc(i); - GE_CHK_BOOL_EXEC_WARN(TensorUtils::GetRealDimCnt(descriptor, dim_cnt) == GRAPH_SUCCESS, continue, + GE_CHK_BOOL_EXEC_WARN(TensorUtils::GetRealDimCnt(*tensor_desc, dim_cnt) == GRAPH_SUCCESS, continue, "Get dim_cnt failed"); opTensor_t tmp; - uint32_t tmp_fmt = descriptor.GetFormat(); + uint32_t tmp_fmt = tensor_desc->GetFormat(); tmp.format = tagOpTensorFormat(tmp_fmt); tmp.dim_cnt = static_cast(dim_cnt); - uint32_t tmp_type = descriptor.GetDataType(); + uint32_t tmp_type = tensor_desc->GetDataType(); tmp.data_type = tagOpDataType(tmp_type); for (int32_t j = 0; j < 4; j++) { // 4 dims - tmp.dim[j] = (j < tmp.dim_cnt ? descriptor.GetShape().GetDim(j) : 1); + tmp.dim[j] = (j < tmp.dim_cnt ? tensor_desc->GetShape().GetDim(j) : 1); } v_output_descs.push_back(tmp); @@ -263,44 +294,14 @@ vector<::tagCcAICPUTensor> ModelUtils::GetOutputDescs(ConstOpDescPtr op_desc) { } /// -/// @ingroup domi_ome +/// @ingroup ge /// @brief Get input data address. /// @return vector /// -vector ModelUtils::GetInputDataAddrs(const RuntimeParam &model_param, ConstOpDescPtr op_desc, - bool need_convert) { +vector ModelUtils::GetInputDataAddrs(const RuntimeParam &model_param, ConstOpDescPtr op_desc) { vector v_input_data_addr; // init as:buf_base + op_def_->input(i)); GE_CHECK_NOTNULL_EXEC(op_desc, return v_input_data_addr); uint64_t session_id = model_param.session_id; - uint8_t *mem_base = model_param.mem_base; - uint8_t *var_base = model_param.var_base; - uint8_t *weight_base = model_param.weight_base; - const uint64_t logic_mem_base = 0; - uint64_t logic_weight_base = 0; - uint64_t logic_var_base = model_param.logic_var_base; - uint64_t mem_size = model_param.mem_size; - uint64_t weight_size = model_param.weight_size; - uint64_t var_size = model_param.var_size; - - if (need_convert) { - Status status = ConvertVirtualAddressToPhysical(mem_base, mem_size, mem_base); - if (status != SUCCESS) { - GELOGE(RT_FAILED, "Convert virtual address to physical for mem_base failed."); - return v_input_data_addr; - } - - status = ConvertVirtualAddressToPhysical(weight_base, weight_size, weight_base); - if (status != SUCCESS) { - GELOGE(RT_FAILED, "Convert virtual address to physical for weight_base failed."); - return v_input_data_addr; - } - - status = ConvertVirtualAddressToPhysical(var_base, var_size, var_base); - if (status != SUCCESS) { - GELOGE(RT_FAILED, "Convert virtual address to physical for var_base failed."); - return v_input_data_addr; - } - } const size_t inputs_size = op_desc->GetInputsSize(); const vector v_input_offset = op_desc->GetInputOffset(); @@ -319,13 +320,18 @@ vector ModelUtils::GetInputDataAddrs(const RuntimeParam &model_param, Co for (size_t i = 0; i < inputs_size; ++i) { if ((i < v_is_input_const.size()) && v_is_input_const[i] && (op_type != NETOUTPUT)) { // TBE: add weights address to input - GeTensorDesc tensor_desc = op_desc->GetInputDesc(i); + const GeTensorDescPtr tensor_desc = op_desc->MutableInputDesc(i); + if (tensor_desc == nullptr) { + GELOGW("Op: %s, Index: %zu, Tensor Desc is null", op_desc->GetName().c_str(), i); + continue; + } + int64_t tensor_size = 0; - GE_CHK_STATUS(TensorUtils::GetSize(tensor_desc, tensor_size)); + GE_CHK_STATUS(TensorUtils::GetSize(*tensor_desc, tensor_size)); if (tensor_size) { int64_t data_offset = 0; - GE_CHK_STATUS(TensorUtils::GetDataOffset(tensor_desc, data_offset)); - uint8_t *weight_addr = static_cast(weight_base + data_offset - logic_weight_base); + GE_CHK_STATUS(TensorUtils::GetDataOffset(*tensor_desc, data_offset)); + uint8_t *weight_addr = model_param.weight_base + data_offset; v_input_data_addr.push_back(weight_addr); GELOGI("[IMAS]GetInputDataAddrs graph_%u type[C] name[%s] input[%zu] memaddr[%p]", model_param.graph_id, op_desc->GetName().c_str(), i, weight_addr); @@ -340,17 +346,13 @@ vector ModelUtils::GetInputDataAddrs(const RuntimeParam &model_param, Co int64_t input_offset = v_input_offset[non_const_index]; non_const_index++; - GE_IF_BOOL_EXEC(var_size != 0 && ge::VarManager::Instance(session_id)->IsVarAddr(input_offset), - uint8_t *variable_addr = var_base + input_offset - logic_var_base; + GE_IF_BOOL_EXEC(model_param.var_size != 0 && ge::VarManager::Instance(session_id)->IsVarAddr(input_offset), + uint8_t *variable_addr = model_param.var_base + input_offset - model_param.logic_var_base; v_input_data_addr.push_back(variable_addr); GELOGI("[IMAS]GetInputDataAddrs graph_%u type[V] name[%s] input[%lu] memaddr[%p]", model_param.graph_id, op_desc->GetName().c_str(), i, variable_addr); - continue;); + continue); - bool input_tensor = false; - GE_IF_BOOL_EXEC(TensorUtils::GetInputTensor(op_desc->GetOutputDesc(i), input_tensor) != GRAPH_SUCCESS, - GELOGW("get size from TensorDesc failed, op: %s, input index: %zu", op_desc->GetName().c_str(), i); - continue;); // feature maps uint8_t *mem_addr = nullptr; // fusion @@ -358,7 +360,7 @@ vector ModelUtils::GetInputDataAddrs(const RuntimeParam &model_param, Co mem_addr = reinterpret_cast(reinterpret_cast(input_offset)); v_input_data_addr.push_back(mem_addr); } else { - mem_addr = static_cast(mem_base + input_offset - logic_mem_base); + mem_addr = model_param.mem_base + input_offset; v_input_data_addr.push_back(mem_addr); } GELOGI("[IMAS]GetInputDataAddrs graph_%u type[F] name[%s] input[%zu] memaddr[%p]", model_param.graph_id, @@ -369,41 +371,20 @@ vector ModelUtils::GetInputDataAddrs(const RuntimeParam &model_param, Co } /// -/// @ingroup domi_ome +/// @ingroup ge /// @brief Get output data address. /// @return vector /// -vector ModelUtils::GetOutputDataAddrs(const RuntimeParam &model_param, ConstOpDescPtr op_desc, - bool need_convert) { +vector ModelUtils::GetOutputDataAddrs(const RuntimeParam &model_param, ConstOpDescPtr op_desc) { vector v_output_data_addr; // init as:buf_base + op_def_->output(i) GE_CHECK_NOTNULL_EXEC(op_desc, return v_output_data_addr); uint64_t session_id = model_param.session_id; - uint8_t *mem_base = model_param.mem_base; - uint8_t *var_base = model_param.var_base; - const uint64_t logic_mem_base = 0; - uint64_t logic_var_base = model_param.logic_var_base; - uint64_t mem_size = model_param.mem_size; - uint64_t var_size = model_param.var_size; - - if (need_convert) { - Status status = ConvertVirtualAddressToPhysical(mem_base, mem_size, mem_base); - if (status != SUCCESS) { - GELOGE(RT_FAILED, "Convert virtual address to physical for mem_base failed."); - return v_output_data_addr; - } - - status = ConvertVirtualAddressToPhysical(var_base, var_size, var_base); - if (status != SUCCESS) { - GELOGE(RT_FAILED, "Convert virtual address to physical for var_base failed."); - return v_output_data_addr; - } - } const size_t outputs_size = op_desc->GetOutputsSize(); const vector v_output_offset = op_desc->GetOutputOffset(); GE_IF_BOOL_EXEC(v_output_offset.size() != outputs_size, GELOGW("Output param invalid: output_offset=%zu, outputs=%zu.", v_output_offset.size(), outputs_size); - return v_output_data_addr;); + return v_output_data_addr); vector v_memory_type; bool has_mem_type_attr = ge::AttrUtils::GetListInt(op_desc, ATTR_NAME_OUTPUT_MEM_TYPE_LIST, v_memory_type); if (has_mem_type_attr && (v_memory_type.size() != outputs_size)) { @@ -413,12 +394,12 @@ vector ModelUtils::GetOutputDataAddrs(const RuntimeParam &model_param, C return v_output_data_addr; } for (size_t i = 0; i < outputs_size; ++i) { - GE_IF_BOOL_EXEC(var_size != 0 && ge::VarManager::Instance(session_id)->IsVarAddr(v_output_offset[i]), - uint8_t *variable_addr = static_cast(var_base + v_output_offset[i] - logic_var_base); + GE_IF_BOOL_EXEC(model_param.var_size != 0 && ge::VarManager::Instance(session_id)->IsVarAddr(v_output_offset[i]), + uint8_t *variable_addr = model_param.var_base + v_output_offset[i] - model_param.logic_var_base; v_output_data_addr.push_back(variable_addr); GELOGI("[IMAS]GetOutputDataAddrs graph_%u type[V] name[%s] output[%zu] memaddr[%p]", model_param.graph_id, op_desc->GetName().c_str(), i, variable_addr); - continue;); + continue); // feature maps uint8_t *mem_addr = nullptr; // fusion @@ -426,7 +407,7 @@ vector ModelUtils::GetOutputDataAddrs(const RuntimeParam &model_param, C mem_addr = reinterpret_cast(reinterpret_cast(v_output_offset[i])); v_output_data_addr.push_back(mem_addr); } else { - mem_addr = static_cast(mem_base + v_output_offset[i] - logic_mem_base); + mem_addr = static_cast(model_param.mem_base + v_output_offset[i]); v_output_data_addr.push_back(mem_addr); } GELOGI("[IMAS]GetOutputDataAddrs graph_%u type[F] name[%s] output[%zu] memaddr[%p]", model_param.graph_id, @@ -436,24 +417,13 @@ vector ModelUtils::GetOutputDataAddrs(const RuntimeParam &model_param, C } /// -/// @ingroup domi_ome +/// @ingroup ge /// @brief Get workspace data address. /// @return vector /// -vector ModelUtils::GetWorkspaceDataAddrs(const RuntimeParam &model_param, ConstOpDescPtr op_desc, - bool need_convert) { +vector ModelUtils::GetWorkspaceDataAddrs(const RuntimeParam &model_param, ConstOpDescPtr op_desc) { vector v_workspace_data_addr; GE_CHECK_NOTNULL_EXEC(op_desc, return v_workspace_data_addr); - uint8_t *mem_base = model_param.mem_base; - uint64_t mem_size = model_param.mem_size; - - if (need_convert) { - Status status = ConvertVirtualAddressToPhysical(mem_base, mem_size, mem_base); - if (status != SUCCESS) { - GELOGE(RT_FAILED, "Convert virtual address to physical for mem_base failed."); - return v_workspace_data_addr; - } - } const vector v_workspace_offset = op_desc->GetWorkspace(); const vector v_workspace_bytes = op_desc->GetWorkspaceBytes(); @@ -466,13 +436,13 @@ vector ModelUtils::GetWorkspaceDataAddrs(const RuntimeParam &model_param bool has_mem_type_attr = ge::AttrUtils::GetListInt(op_desc, TVM_ATTR_NAME_WORKSPACE_TYPE, v_memory_type); for (size_t i = 0; i < v_workspace_bytes.size(); ++i) { if (has_mem_type_attr && v_memory_type[i] == RT_MEMORY_L1) { - v_workspace_data_addr.push_back(reinterpret_cast(v_workspace_offset[i])); + v_workspace_data_addr.push_back(reinterpret_cast(reinterpret_cast(v_workspace_offset[i]))); GELOGI("Fusion: op: %s, GetWorkspaceDataAddrs mem_addr[workspace index %zu]:%p", op_desc->GetName().c_str(), i, reinterpret_cast(reinterpret_cast(v_workspace_offset[i]))); } else { int64_t workspace_offset = v_workspace_offset[i]; int64_t workspace_bytes = v_workspace_bytes[i]; - uint8_t *mem_addr = workspace_bytes == 0 ? nullptr : mem_base + workspace_offset; + uint8_t *mem_addr = workspace_bytes == 0 ? nullptr : model_param.mem_base + workspace_offset; v_workspace_data_addr.push_back(mem_addr); GELOGI("[IMAS]GetWorkspaceDataAddrs graph_%u type[F] name[%s] workspace[%zu] offset[%ld] bytes[%ld] memaddr[%p]", model_param.graph_id, op_desc->GetName().c_str(), i, workspace_offset, workspace_bytes, mem_addr); @@ -482,21 +452,32 @@ vector ModelUtils::GetWorkspaceDataAddrs(const RuntimeParam &model_param return v_workspace_data_addr; } -Status ModelUtils::ConvertVirtualAddressToPhysical(uint8_t *virtual_address, uint64_t size, - uint8_t *&physical_address) { - // Indicates whether use physical address. - const char *use_physical_address = std::getenv("GE_USE_PHYSICAL_ADDRESS"); - if (use_physical_address == nullptr || virtual_address == 0 || size == 0) { - return SUCCESS; - } - - rtError_t ret = rtKernelConfigTransArg(virtual_address, size, 0, reinterpret_cast(&physical_address)); - if (ret != RT_ERROR_NONE) { - GELOGE(RT_FAILED, "Call rtKernelConfigTransArg failed, ret: 0x%X", ret); - return RT_FAILED; +/// +/// @ingroup ge +/// @brief Get runtime memory address. +/// @return Status +/// +Status ModelUtils::GetRtAddress(const RuntimeParam ¶m, uintptr_t logic_addr, uint8_t *&mem_addr) { + uint8_t *runtime_base_addr = nullptr; + if ((param.logic_mem_base <= logic_addr) && (logic_addr < param.logic_mem_base + param.mem_size)) { + runtime_base_addr = param.mem_base - param.logic_mem_base; + GELOGI("The logic addr:0x%lx is data address, base:0x%lx, size:%lu", logic_addr, param.logic_mem_base, + param.mem_size); + } else if ((param.logic_weight_base <= logic_addr) && (logic_addr < param.logic_weight_base + param.weight_size)) { + runtime_base_addr = param.weight_base - param.logic_weight_base; + GELOGI("The logic addr:0x%lx is weight address, base:0x%lx, size:%lu", logic_addr, param.logic_weight_base, + param.weight_size); + } else if ((param.logic_var_base <= logic_addr) && (logic_addr < param.logic_var_base + param.var_size)) { + runtime_base_addr = param.var_base - param.logic_var_base; + GELOGI("The logic addr:0x%lx is variable address, base:0x%lx, size:%lu", logic_addr, param.logic_var_base, + param.var_size); + } else if (logic_addr != 0) { + mem_addr = nullptr; + GELOGE(PARAM_INVALID, "The logic addr:0x%lx is abnormal", logic_addr); + return PARAM_INVALID; } - GELOGD("virtual_address=%p, physical_address=%p", virtual_address, physical_address); + mem_addr = runtime_base_addr + logic_addr; return SUCCESS; } } // namespace ge diff --git a/src/ge/graph/load/new_model_manager/model_utils.h b/src/ge/graph/load/new_model_manager/model_utils.h index d6afd5c8..8474a987 100644 --- a/src/ge/graph/load/new_model_manager/model_utils.h +++ b/src/ge/graph/load/new_model_manager/model_utils.h @@ -34,78 +34,79 @@ class ModelUtils { ~ModelUtils() = default; /// - /// @ingroup domi_ome + /// @ingroup ge /// @brief Get input size. /// @return vector /// static vector GetInputSize(ConstOpDescPtr op_desc); /// - /// @ingroup domi_ome + /// @ingroup ge /// @brief Get output size. /// @return vector /// static vector GetOutputSize(ConstOpDescPtr op_desc); /// - /// @ingroup domi_ome + /// @ingroup ge /// @brief Get workspace size. /// @return vector /// static vector GetWorkspaceSize(ConstOpDescPtr op_desc); /// - /// @ingroup domi_ome + /// @ingroup ge /// @brief Get weight size. /// @return vector /// static vector GetWeightSize(ConstOpDescPtr op_desc); /// - /// @ingroup domi_ome + /// @ingroup ge /// @brief Get weights. /// @return vector /// static vector GetWeights(ConstOpDescPtr op_desc); /// - /// @ingroup domi_ome + /// @ingroup ge /// @brief Get AiCpuOp Input descriptor. /// @return vector<::tagCcAICPUTensor> /// static vector<::tagCcAICPUTensor> GetInputDescs(ConstOpDescPtr op_desc); /// - /// @ingroup domi_ome + /// @ingroup ge /// @brief Get AiCpuOp Output descriptor. /// @return vector<::tagCcAICPUTensor> /// static vector<::tagCcAICPUTensor> GetOutputDescs(ConstOpDescPtr op_desc); /// - /// @ingroup domi_ome + /// @ingroup ge /// @brief Get input data address. /// @return vector /// - static vector GetInputDataAddrs(const RuntimeParam &model_param, ConstOpDescPtr op_desc, - bool need_convert = true); + static vector GetInputDataAddrs(const RuntimeParam &model_param, ConstOpDescPtr op_desc); /// - /// @ingroup domi_ome + /// @ingroup ge /// @brief Get output data address. /// @return vector /// - static vector GetOutputDataAddrs(const RuntimeParam &model_param, ConstOpDescPtr op_desc, - bool need_convert = true); + static vector GetOutputDataAddrs(const RuntimeParam &model_param, ConstOpDescPtr op_desc); /// - /// @ingroup domi_ome + /// @ingroup ge /// @brief Get workspace data address. /// @return vector /// - static vector GetWorkspaceDataAddrs(const RuntimeParam &model_param, ConstOpDescPtr op_desc, - bool need_convert = true); + static vector GetWorkspaceDataAddrs(const RuntimeParam &model_param, ConstOpDescPtr op_desc); - static ge::Status ConvertVirtualAddressToPhysical(uint8_t *virtual_address, uint64_t size, - uint8_t *&physical_address); + /// + /// @ingroup ge + /// @brief Get memory runtime base. + /// @return Status + /// + static Status GetRtAddress(const RuntimeParam &model_param, uintptr_t logic_addr, uint8_t *&mem_addr); }; } // namespace ge diff --git a/src/ge/graph/load/new_model_manager/task_info/end_graph_task_info.cc b/src/ge/graph/load/new_model_manager/task_info/end_graph_task_info.cc index 077ae827..920b52e6 100644 --- a/src/ge/graph/load/new_model_manager/task_info/end_graph_task_info.cc +++ b/src/ge/graph/load/new_model_manager/task_info/end_graph_task_info.cc @@ -45,7 +45,7 @@ Status EndGraphTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *davin Status EndGraphTaskInfo::Distribute() { GELOGI("EndGraphTaskInfo Distribute Start."); GE_CHECK_NOTNULL(davinci_model_); - auto all_dump_model = PropertiesManager::Instance().GetAllDumpModel(); + auto all_dump_model = davinci_model_->GetDumpProperties().GetAllDumpModel(); if (all_dump_model.find(ge::DUMP_ALL_MODEL) != all_dump_model.end() || all_dump_model.find(davinci_model_->Name()) != all_dump_model.end() || all_dump_model.find(davinci_model_->OmName()) != all_dump_model.end()) { @@ -80,5 +80,4 @@ Status EndGraphTaskInfo::Distribute() { } REGISTER_TASK_INFO(RT_MODEL_TASK_MODEL_END_GRAPH, EndGraphTaskInfo); - } // namespace ge diff --git a/src/ge/graph/load/new_model_manager/task_info/end_graph_task_info.h b/src/ge/graph/load/new_model_manager/task_info/end_graph_task_info.h index 49bef082..82e228e6 100644 --- a/src/ge/graph/load/new_model_manager/task_info/end_graph_task_info.h +++ b/src/ge/graph/load/new_model_manager/task_info/end_graph_task_info.h @@ -22,7 +22,7 @@ namespace ge { class EndGraphTaskInfo : public TaskInfo { public: - EndGraphTaskInfo() : model_(0) {} + EndGraphTaskInfo() {} ~EndGraphTaskInfo() override { model_ = nullptr; } @@ -35,10 +35,10 @@ class EndGraphTaskInfo : public TaskInfo { uint32_t GetStreamId() override { return stream_id_; } private: - rtModel_t model_; - DavinciModel *davinci_model_; - uint32_t task_id_; - uint32_t stream_id_; + rtModel_t model_{nullptr}; + DavinciModel *davinci_model_{nullptr}; + uint32_t task_id_{0}; + uint32_t stream_id_{0}; }; } // namespace ge #endif // GE_GRAPH_LOAD_NEW_MODEL_MANAGER_TASK_INFO_END_GRAPH_TASK_INFO_H_ diff --git a/src/ge/graph/load/new_model_manager/task_info/hccl_task_info.cc b/src/ge/graph/load/new_model_manager/task_info/hccl_task_info.cc index 0ee9727a..2a79997f 100644 --- a/src/ge/graph/load/new_model_manager/task_info/hccl_task_info.cc +++ b/src/ge/graph/load/new_model_manager/task_info/hccl_task_info.cc @@ -42,6 +42,7 @@ HcclTaskInfo::~HcclTaskInfo() { davinci_model_ = nullptr; ops_kernel_store_ = nullptr; max_node_of_hccl_stream_ = 0; + args_ = nullptr; } Status HcclTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *davinci_model) { GELOGI("HcclTaskInfo Init Start."); @@ -60,52 +61,59 @@ Status HcclTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *davinci_m GELOGI("HcclTaskInfo Init, op_index is: %u", op_index); // Get HCCL op - OpDescPtr op_desc = davinci_model->GetOpByIndex(op_index); - GE_CHECK_NOTNULL(op_desc); + op_desc_ = davinci_model->GetOpByIndex(op_index); + GE_CHECK_NOTNULL(op_desc_); // Create the kernel hccl infos - CreateKernelHcclInfo(op_desc); + CreateKernelHcclInfo(op_desc_); // Initialize the hccl_type of all kernel hccl info HcomOmeUtil::GetHcclType(task_def, kernel_hccl_infos_); // Only in Horovod scenario should get the inputName and GeShape - ret = HcomOmeUtil::GetHorovodInputs(op_desc, kernel_hccl_infos_); + ret = HcomOmeUtil::GetHorovodInputs(op_desc_, kernel_hccl_infos_); if (ret != SUCCESS) { GELOGE(FAILED, "davinci_model: GetHorovodInputs fail! domi error: %u", ret); return FAILED; } - Status dmrt = HcomOmeUtil::GetHcclDataType(op_desc, kernel_hccl_infos_); + Status dmrt = HcomOmeUtil::GetHcclDataType(op_desc_, kernel_hccl_infos_); if (dmrt != SUCCESS) { GELOGE(FAILED, "davinci_model: GetHcomDataType fail! domi error: %u", dmrt); return FAILED; } - dmrt = HcomOmeUtil::GetHcclCount(op_desc, kernel_hccl_infos_); + dmrt = HcomOmeUtil::GetHcclCount(op_desc_, kernel_hccl_infos_); if (dmrt != SUCCESS) { GELOGE(FAILED, "davinci_model: GetHcomCount fail! domi error: %u", dmrt); return FAILED; } // Only HCOMBROADCAST and HVDCALLBACKBROADCAST need to get the rootId - dmrt = HcomOmeUtil::GetAllRootId(op_desc, kernel_hccl_infos_); + dmrt = HcomOmeUtil::GetAllRootId(op_desc_, kernel_hccl_infos_); if (dmrt != SUCCESS) { GELOGE(FAILED, "davinci_model: Get rootId fail! domi error: %u", dmrt); return FAILED; } - ret = SetAddrs(op_desc, kernel_hccl_infos_); + + // GE's new process: hccl declares the number of streams required, creates a stream by GE, and sends it to hccl + ret = SetFollowStream(op_desc_, davinci_model); if (ret != SUCCESS) { - GELOGE(ret, "Setaddrs Fail."); + GELOGE(ret, "SetStream Fail."); return ret; } - // GE's new process: hccl declares the need for Workspace size, and GE allocates Workspace - ret = SetWorkspace(op_desc, kernel_hccl_infos_); + + if (davinci_model_->IsKnownNode()) { + args_ = davinci_model_->GetCurrentArgsAddr(args_offset_); + GELOGI("Known node %s args addr %p, offset %u.", op_desc_->GetName().c_str(), args_, args_offset_); + } + + ret = SetAddrs(op_desc_, kernel_hccl_infos_); if (ret != SUCCESS) { - GELOGE(ret, "SetWorkspace Fail."); + GELOGE(ret, "Setaddrs Fail."); return ret; } - // GE's new process: hccl declares the number of streams required, creates a stream by GE, and sends it to hccl - ret = SetFollowStream(op_desc, davinci_model); + // GE's new process: hccl declares the need for Workspace size, and GE allocates Workspace + ret = SetWorkspace(op_desc_, kernel_hccl_infos_); if (ret != SUCCESS) { - GELOGE(ret, "SetStream Fail."); + GELOGE(ret, "SetWorkspace Fail."); return ret; } @@ -209,40 +217,83 @@ Status HcclTaskInfo::Distribute() { GELOGI("HcclTaskInfo Distribute Success."); return SUCCESS; } + +Status HcclTaskInfo::CalculateArgs(const domi::TaskDef &task_def, DavinciModel *davinci_model) { + GE_CHECK_NOTNULL(davinci_model); + auto hccl_def = task_def.kernel_hccl(); + uint32_t op_index = hccl_def.op_index(); + GELOGI("HcclTaskInfo Init, op_index is: %u", op_index); + // Get HCCL op + auto op_desc = davinci_model->GetOpByIndex(op_index); + GE_CHECK_NOTNULL(op_desc); + GELOGI("Calc opType[%s] args size. Node name is [%s]", op_desc->GetType().c_str(), op_desc->GetName().c_str()); + // Only need the number of addr to allocate args memory + auto input_size = op_desc->GetInputsSize(); + auto output_size = op_desc->GetOutputsSize(); + auto workspace_size = op_desc->GetWorkspaceBytes().size(); + uint32_t args_size = sizeof(void *) * (input_size + output_size + workspace_size); + args_offset_ = davinci_model->GetTotalArgsSize(); + davinci_model->SetTotalArgsSize(args_size); + GELOGI("Calculate hccl task args , args_size %u, args_offset %u", args_size, args_offset_); + return SUCCESS; +} + +Status HcclTaskInfo::UpdateArgs() { + GELOGI("HcclTaskInfo::UpdateArgs in."); + const RuntimeParam &rts_param = davinci_model_->GetRuntimeParam(); + input_data_addrs_ = ModelUtils::GetInputDataAddrs(rts_param, op_desc_); + output_data_addrs_ = ModelUtils::GetOutputDataAddrs(rts_param, op_desc_); + workspace_data_addrs_ = ModelUtils::GetWorkspaceDataAddrs(rts_param, op_desc_); + + vector io_addrs; + io_addrs.insert(io_addrs.end(), input_data_addrs_.begin(), input_data_addrs_.end()); + io_addrs.insert(io_addrs.end(), output_data_addrs_.begin(), output_data_addrs_.end()); + io_addrs.insert(io_addrs.end(), workspace_data_addrs_.begin(), workspace_data_addrs_.end()); + + GE_CHK_STATUS_RET(davinci_model_->UpdateKnownZeroCopyAddr(io_addrs, args_offset_), + "update known node %s zero copy addr failed.", op_desc_->GetName().c_str()); + + GELOGI("HcclTaskInfo::UpdateArgs success."); + return SUCCESS; +} + Status HcclTaskInfo::SetAddrs(const std::shared_ptr &op_desc, std::vector &kernel_hccl_infos) { GE_CHECK_NOTNULL(op_desc); - if (HcomOmeUtil::CheckKernelHcclInfo(op_desc, kernel_hccl_infos) != SUCCESS) { - GELOGE(PARAM_INVALID, "HcomOmeUtil:: the number of GETaskKernelHcclInfo is invalid."); - return PARAM_INVALID; - } + GE_CHK_STATUS_RET(HcomOmeUtil::CheckKernelHcclInfo(op_desc, kernel_hccl_infos), + "HcomOmeUtil:: the number of GETaskKernelHcclInfo is invalid."); GELOGI("Set hccl task input output address, node[%s}, type[%s] kernel_hccl_infos.size[%zu].", op_desc->GetName().c_str(), op_desc->GetType().c_str(), kernel_hccl_infos.size()); if (op_desc->GetType() == HVDWAIT) { return SUCCESS; } - domi::Status dmrt; + hcclRedOp_t op_type = HCCL_REP_OP_SUM; GE_CHECK_NOTNULL(davinci_model_); GELOGI("Calc opType[%s] input address before. Node name[%s]", op_desc->GetType().c_str(), op_desc->GetName().c_str()); - auto input_data_addr_list = ModelUtils::GetInputDataAddrs(davinci_model_->GetRuntimeParam(), op_desc); - - auto output_data_addr_list = ModelUtils::GetOutputDataAddrs(davinci_model_->GetRuntimeParam(), op_desc); + if (!davinci_model_->IsKnownNode()) { + input_data_addrs_ = ModelUtils::GetInputDataAddrs(davinci_model_->GetRuntimeParam(), op_desc); + output_data_addrs_ = ModelUtils::GetOutputDataAddrs(davinci_model_->GetRuntimeParam(), op_desc); + } + void *input_data_addr = nullptr; + void *output_data_addr = nullptr; // initialize every kernel_hccl_info inputDataAddr for (size_t i = 0; i < kernel_hccl_infos.size(); i++) { std::string hccl_type = kernel_hccl_infos[i].hccl_type; - void *input_data_addr = input_data_addr_list.empty() ? nullptr : input_data_addr_list[i]; + if (davinci_model_->IsKnownNode()) { + input_data_addr = reinterpret_cast(reinterpret_cast(args_) + i); + output_data_addr = reinterpret_cast(reinterpret_cast(args_) + op_desc->GetInputsSize() + i); + GELOGI("Hccl task info known input addr %p, output addr %p.", input_data_addr, output_data_addr); + } else { + input_data_addr = input_data_addrs_.empty() ? nullptr : input_data_addrs_[i]; + output_data_addr = output_data_addrs_.empty() ? nullptr : output_data_addrs_[i]; + } kernel_hccl_infos[i].inputDataAddr = input_data_addr; - - void *output_data_addr = output_data_addr_list.empty() ? nullptr : output_data_addr_list[i]; if (hccl_type == HCOMALLGATHER || hccl_type == HCOMRECEIVE || hccl_type == HVDCALLBACKALLGATHER) { kernel_hccl_infos[i].outputDataAddr = output_data_addr; } else if (hccl_type == HCOMALLREDUCE || hccl_type == HCOMREDUCESCATTER || hccl_type == HVDCALLBACKALLREDUCE) { - dmrt = HcomOmeUtil::GetHcclOperationType(op_desc, op_type); - if (dmrt != SUCCESS) { - GELOGE(FAILED, "davinci_model: GetHcomOperationType fail! domi error: %u", dmrt); - return FAILED; - } + GE_CHK_STATUS_RET(HcomOmeUtil::GetHcclOperationType(op_desc, op_type), + "davinci_model: GetHcomOperationType fail!"); kernel_hccl_infos[i].outputDataAddr = output_data_addr; kernel_hccl_infos[i].opType = op_type; } @@ -310,6 +361,7 @@ void HcclTaskInfo::CreateKernelHcclInfo(const ge::ConstOpDescPtr &op_desc) { Status HcclTaskInfo::SetWorkspace(const std::shared_ptr &op_desc, std::vector &kernel_hccl_infos) { GE_CHECK_NOTNULL(op_desc); + GE_CHECK_NOTNULL(davinci_model_); GELOGI("SetWorkspace Node[%s] opType[%s] set workspace.", op_desc->GetName().c_str(), op_desc->GetType().c_str()); uint64_t workspace_mem_size = 0; void *workspace_addr = nullptr; @@ -319,11 +371,12 @@ Status HcclTaskInfo::SetWorkspace(const std::shared_ptr &op_desc, GELOGI("hccl need workSpaceMemSize=%lu", workspace_mem_size_tmp); if (workspace_mem_size_tmp != 0) { workspace_mem_size = workspace_mem_size_tmp; - vector workspace_data_addrs = - ModelUtils::GetWorkspaceDataAddrs(davinci_model_->GetRuntimeParam(), op_desc); - if (!workspace_data_addrs.empty()) { - GELOGI("Get workSpaceAddr"); - workspace_addr = workspace_data_addrs[0]; + if (davinci_model_->IsKnownNode()) { + workspace_addr = reinterpret_cast(reinterpret_cast(args_) + op_desc->GetInputsSize() + + op_desc->GetOutputsSize()); + } else { + workspace_data_addrs_ = ModelUtils::GetWorkspaceDataAddrs(davinci_model_->GetRuntimeParam(), op_desc); + workspace_addr = workspace_data_addrs_.empty() ? nullptr : workspace_data_addrs_[0]; } } } diff --git a/src/ge/graph/load/new_model_manager/task_info/hccl_task_info.h b/src/ge/graph/load/new_model_manager/task_info/hccl_task_info.h index bb0a88de..cc3109f4 100644 --- a/src/ge/graph/load/new_model_manager/task_info/hccl_task_info.h +++ b/src/ge/graph/load/new_model_manager/task_info/hccl_task_info.h @@ -34,7 +34,10 @@ class HcclTaskInfo : public TaskInfo { hccl_stream_list_(), ops_kernel_store_(nullptr), private_def_(nullptr), - private_def_len_(0) {} + private_def_len_(0), + op_desc_(nullptr), + args_(nullptr), + args_offset_(0) {} ~HcclTaskInfo() override; @@ -44,6 +47,10 @@ class HcclTaskInfo : public TaskInfo { uint32_t GetTaskID() override { return id_; } + Status CalculateArgs(const domi::TaskDef &task_def, DavinciModel *davinci_model) override; + + Status UpdateArgs() override; + private: ge::Status SetAddrs(const std::string &hccl_type, const std::shared_ptr &op); @@ -72,6 +79,12 @@ class HcclTaskInfo : public TaskInfo { static std::mutex hccl_follow_stream_mutex_; static uint32_t max_node_of_hccl_stream_; vector kernel_hccl_infos_; + vector input_data_addrs_; + vector output_data_addrs_; + vector workspace_data_addrs_; + OpDescPtr op_desc_; + void *args_; + uint32_t args_offset_; }; } // namespace ge #endif // GE_GRAPH_LOAD_NEW_MODEL_MANAGER_TASK_INFO_HCCL_TASK_INFO_H_ diff --git a/src/ge/graph/load/new_model_manager/task_info/kernel_ex_task_info.cc b/src/ge/graph/load/new_model_manager/task_info/kernel_ex_task_info.cc index 79971529..a241e129 100644 --- a/src/ge/graph/load/new_model_manager/task_info/kernel_ex_task_info.cc +++ b/src/ge/graph/load/new_model_manager/task_info/kernel_ex_task_info.cc @@ -79,6 +79,9 @@ Status KernelExTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *davin return FAILED;) } + GELOGI("Node[%s] type[%s] kernel_ext_info size=%zu, ext_info_addr_=%p", op_desc_->GetName().c_str(), + op_desc_->GetType().c_str(), ext_info.size(), ext_info_addr_); + // 2.1 get loop cond variable for tensor array write uint64_t step_id_addr = 0; OpDescPtr step_id_node = davinci_model_->GetVariableOp(NODE_NAME_GLOBAL_STEP); @@ -97,6 +100,11 @@ Status KernelExTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *davin GE_IF_BOOL_EXEC(ModelManager::GetInstance()->CreateAicpuKernel(session_id, davinci_model->Id(), kernel_id) != SUCCESS, GELOGE(FAILED, "CreateAicpuKernel error."); return FAILED;) + // 2.3 Create session + GE_CHECK_NOTNULL(ModelManager::GetInstance()); + GE_IF_BOOL_EXEC(ModelManager::GetInstance()->CreateAicpuSession(session_id) != SUCCESS, + GELOGE(FAILED, "CreateAicpuSession error. session id: %lu", session_id); + return FAILED;) kernel_buf_size_ = sizeof(STR_FWK_OP_KERNEL); if (davinci_model_->IsKnownNode()) { @@ -153,8 +161,8 @@ Status KernelExTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *davin GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtMemcpy to input_output_addr_ error: 0x%X", rt_ret); return FAILED;) - if (PropertiesManager::Instance().IsLayerNeedDump(davinci_model_->Name(), davinci_model_->OmName(), - op_desc->GetName())) { + if (davinci_model_->GetDumpProperties().IsLayerNeedDump(davinci_model_->Name(), davinci_model_->OmName(), + op_desc->GetName())) { dump_flag_ = RT_KERNEL_DUMPFLAG; dump_args_ = input_output_addr_; } @@ -167,12 +175,7 @@ Status KernelExTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *davin fwk_op_kernel.fwkKernelBase.fwk_kernel.extInfoLen = ext_info.size(); fwk_op_kernel.fwkKernelBase.fwk_kernel.extInfoAddr = reinterpret_cast(ext_info_addr_); - // 4. Create session - GE_CHECK_NOTNULL(ModelManager::GetInstance()); - GE_IF_BOOL_EXEC(ModelManager::GetInstance()->CreateAicpuSession(session_id) != SUCCESS, - GELOGE(FAILED, "CreateAicpuSession error. session id: %lu", session_id); - return FAILED;) - // 5. Return result + // 4. Return result rtError_t rt_ret = rtMalloc(&kernel_buf_, sizeof(STR_FWK_OP_KERNEL), RT_MEMORY_HBM); GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtMalloc error: 0x%X", rt_ret); return FAILED;) @@ -180,12 +183,7 @@ Status KernelExTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *davin sizeof(STR_FWK_OP_KERNEL), RT_MEMCPY_HOST_TO_DEVICE); GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtMemcpy error, ret: Ox%X", rt_ret); return FAILED;) - vector virtual_io_addrs; // use virtual address for zero copy key. - const vector virtual_in_addrs = ModelUtils::GetInputDataAddrs(rts_param, op_desc, false); - const vector virtual_out_addrs = ModelUtils::GetOutputDataAddrs(rts_param, op_desc, false); - virtual_io_addrs.insert(virtual_io_addrs.end(), virtual_in_addrs.begin(), virtual_in_addrs.end()); - virtual_io_addrs.insert(virtual_io_addrs.end(), virtual_out_addrs.begin(), virtual_out_addrs.end()); - davinci_model_->SetZeroCopyAddr(op_desc, virtual_io_addrs, io_addrs.data(), input_output_addr_, addrs_size, 0); + davinci_model_->SetZeroCopyAddr(op_desc, io_addrs, io_addrs.data(), input_output_addr_, addrs_size, 0); GELOGI("KernelExTaskInfo Init Success. session id: %lu", session_id); return SUCCESS; @@ -207,19 +205,55 @@ Status KernelExTaskInfo::CalculateArgs(const domi::TaskDef &task_def, DavinciMod uint32_t mem_size = sizeof(uint64_t) * mem_length; davinci_model->SetTotalArgsSize(mem_size); GELOGI("kernel task name %s, args_size %u, args_offset %u", op_desc->GetName().c_str(), mem_size, args_offset_); + + // alloc fixed addr + string peer_input_name; + if (AttrUtils::GetStr(op_desc, ATTR_DYNAMIC_SHAPE_FIXED_ADDR, peer_input_name) && !peer_input_name.empty()) { + uint32_t output_index = davinci_model->GetFixedAddrOutputIndex(peer_input_name); + if (output_index > outputs_size) { + GELOGE(FAILED, "The output size[%zu] and output index[%u] are inconsistent.", outputs_size, output_index); + return FAILED; + } + fixed_addr_offset_ = davinci_model->GetFixedAddrsSize(peer_input_name); + auto tensor_desc = op_desc->GetOutputDesc(output_index); + int64_t tensor_size = 0; + GE_CHK_STATUS(TensorUtils::GetSize(tensor_desc, tensor_size)); + davinci_model->SetTotalFixedAddrsSize(peer_input_name, tensor_size); + GELOGI("Calculate stream switch task args , tensor size is %ld, fixed addr offset %ld", tensor_size, + fixed_addr_offset_); + } return SUCCESS; } Status KernelExTaskInfo::UpdateArgs() { GELOGI("KernelExTaskInfo::UpdateArgs in."); const RuntimeParam &rts_param = davinci_model_->GetRuntimeParam(); - vector io_addrs; vector input_data_addrs = ModelUtils::GetInputDataAddrs(rts_param, op_desc_); vector output_data_addrs = ModelUtils::GetOutputDataAddrs(rts_param, op_desc_); - - io_addrs.insert(io_addrs.end(), input_data_addrs.begin(), input_data_addrs.end()); - io_addrs.insert(io_addrs.end(), output_data_addrs.begin(), output_data_addrs.end()); - + vector io_addrs; + if (!op_desc_->HasAttr(ATTR_DYNAMIC_SHAPE_FIXED_ADDR)) { + io_addrs.insert(io_addrs.end(), input_data_addrs.begin(), input_data_addrs.end()); + io_addrs.insert(io_addrs.end(), output_data_addrs.begin(), output_data_addrs.end()); + } else { + string peer_input_name; + if (AttrUtils::GetStr(op_desc_, ATTR_DYNAMIC_SHAPE_FIXED_ADDR, peer_input_name)) { + uint32_t output_index = davinci_model_->GetFixedAddrOutputIndex(peer_input_name); + if (output_index > output_data_addrs.size()) { + GELOGE(FAILED, "The output data addr size[%zu] and output index[%u] are inconsistent.", + output_data_addrs.size(), output_index); + return FAILED; + } + io_addrs.insert(io_addrs.end(), input_data_addrs.begin(), input_data_addrs.end()); + for (size_t i = 0; i < output_data_addrs.size(); ++i) { + if (i == output_index) { + void *fixed_addr = davinci_model_->GetCurrentFixedAddr(fixed_addr_offset_); + io_addrs.emplace_back(fixed_addr); + continue; + } + io_addrs.emplace_back(output_data_addrs[i]); + } + } + } GE_CHK_STATUS_RET(davinci_model_->UpdateKnownZeroCopyAddr(io_addrs, args_offset_), "update known node %s zero copy addr failed.", op_desc_->GetName().c_str()); @@ -231,7 +265,7 @@ Status KernelExTaskInfo::CopyTaskInfo(const domi::KernelExDef &kernel_def, const const OpDescPtr &op_desc) { // Userspace copy need virtual address. const vector workspace_data_sizes = ModelUtils::GetWorkspaceSize(op_desc); - const vector workspace_data_addrs = ModelUtils::GetWorkspaceDataAddrs(rts_param, op_desc, false); + const vector workspace_data_addrs = ModelUtils::GetWorkspaceDataAddrs(rts_param, op_desc); if (workspace_data_addrs.empty() || workspace_data_sizes.empty()) { GELOGE(FAILED, "Node:%s invalid workspace, addrs is %zu, size is %zu.", op_desc->GetName().c_str(), workspace_data_addrs.size(), workspace_data_sizes.size()); diff --git a/src/ge/graph/load/new_model_manager/task_info/kernel_ex_task_info.h b/src/ge/graph/load/new_model_manager/task_info/kernel_ex_task_info.h index ff8f3119..b26a95ac 100644 --- a/src/ge/graph/load/new_model_manager/task_info/kernel_ex_task_info.h +++ b/src/ge/graph/load/new_model_manager/task_info/kernel_ex_task_info.h @@ -54,6 +54,7 @@ class KernelExTaskInfo : public TaskInfo { auto ret = reinterpret_cast(dump_args_); return ret; } + bool CallSaveDumpInfo() override { return true; }; private: Status CopyTaskInfo(const domi::KernelExDef &kernel_def, const RuntimeParam &rts_param, const OpDescPtr &op_desc); @@ -69,6 +70,7 @@ class KernelExTaskInfo : public TaskInfo { void *dump_args_; OpDescPtr op_desc_ = nullptr; uint32_t args_offset_ = 0; + int64_t fixed_addr_offset_ = 0; }; } // namespace ge #endif // GE_GRAPH_LOAD_NEW_MODEL_MANAGER_TASK_INFO_KERNEL_EX_TASK_INFO_H_ diff --git a/src/ge/graph/load/new_model_manager/task_info/kernel_task_info.cc b/src/ge/graph/load/new_model_manager/task_info/kernel_task_info.cc index 7ef65555..12fe0206 100644 --- a/src/ge/graph/load/new_model_manager/task_info/kernel_task_info.cc +++ b/src/ge/graph/load/new_model_manager/task_info/kernel_task_info.cc @@ -47,16 +47,16 @@ const uint32_t kAddrLen = sizeof(void *); namespace ge { KernelTaskInfo::SuperKernelTaskInfo KernelTaskInfo::skt_info_ = { - 0, 0, 0, 0, nullptr, nullptr, {}, {}, RT_KERNEL_DEFAULT, kInvalidGroupKey, 0, nullptr}; + 0, 0, 0, 0, nullptr, nullptr, {}, {}, {}, {}, {}, RT_KERNEL_DEFAULT, kInvalidGroupKey, 0, nullptr}; Status KernelTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *davinci_model) { if (davinci_model == nullptr) { - GELOGE(PARAM_INVALID, "davinci_model is null!"); + GELOGE(PARAM_INVALID, "davinci model is null!"); return PARAM_INVALID; } davinci_model_ = davinci_model; is_l1_fusion_enable_ = davinci_model_->GetL1FusionEnableOption(); - GELOGD("KernelTaskInfo Init Start, ge.enableL1Fusion in davinci model is %d.", is_l1_fusion_enable_); + GELOGD("KernelTaskInfo init start, ge.enableL1Fusion in davinci model is %d.", is_l1_fusion_enable_); Status ret = SetStream(task_def.stream_id(), davinci_model_->GetStreamList()); if (ret != SUCCESS) { @@ -73,7 +73,7 @@ Status KernelTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *davinci // get opdesc op_desc_ = davinci_model_->GetOpByIndex(context.op_index()); if (op_desc_ == nullptr) { - GELOGE(INTERNAL_ERROR, "Get op_desc failed, index is out of range!"); + GELOGE(INTERNAL_ERROR, "Get op desc failed, index is out of range!"); return INTERNAL_ERROR; } (void)AttrUtils::GetBool(*op_desc_, ATTR_N_BATCH_SPILT, is_n_batch_spilt_); @@ -138,14 +138,21 @@ Status KernelTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *davinci ret = InitCceTask(kernel_def); } - GELOGD("KernelTaskInfo Init finish, result=%u.", ret); + GELOGD("KernelTaskInfo init finish, result=%u.", ret); return ret; } Status KernelTaskInfo::SaveSKTDumpInfo() { GE_CHECK_NOTNULL(davinci_model_); - davinci_model_->SaveDumpTask(skt_info_.last_task_id, skt_info_.last_stream_id, skt_info_.last_op, - skt_info_.last_dump_args); + if (skt_dump_flag_ == RT_KERNEL_DEFAULT) { + GELOGD("no need save skt dump info"); + return SUCCESS; + } + // all op in super kernel share one taskid and streamid + for (size_t i = 0; i < skt_info_.op_desc_list.size(); i++) { + davinci_model_->SaveDumpTask(skt_info_.last_task_id, skt_info_.last_stream_id, skt_info_.op_desc_list[i], + skt_info_.dump_args_list[i]); + } return SUCCESS; } @@ -187,6 +194,9 @@ Status KernelTaskInfo::SKTFinalize() { GELOGI("SuperKernel Distribute [skt_id:%u]", skt_id_); skt_info_.kernel_list.clear(); skt_info_.arg_list.clear(); + skt_info_.dump_flag_list.clear(); + skt_info_.op_desc_list.clear(); + skt_info_.dump_args_list.clear(); skt_info_.last_stream = nullptr; skt_info_.last_block_dim = 0; skt_info_.last_sm_desc = sm_desc_; @@ -197,6 +207,15 @@ Status KernelTaskInfo::SKTFinalize() { return SUCCESS; } +uint32_t KernelTaskInfo::GetDumpFlag() { + for (auto flag : skt_info_.dump_flag_list) { + if (flag == RT_KERNEL_DUMPFLAG) { + return RT_KERNEL_DUMPFLAG; + } + } + return RT_KERNEL_DEFAULT; +} + Status KernelTaskInfo::SuperKernelLaunch() { if (skt_info_.kernel_list.empty()) { GELOGI("SuperKernelLaunch: Skt_kernel_list has no task, just return"); @@ -206,7 +225,7 @@ Status KernelTaskInfo::SuperKernelLaunch() { auto &skt_kernel_list = skt_info_.kernel_list; auto &skt_arg_list = skt_info_.arg_list; GELOGI("SuperKernelLaunch: Skt_kernel_list size[%d] skt_arg_list[%d]", skt_kernel_list.size(), skt_arg_list.size()); - if (skt_kernel_list.size() == kSKTSingleSize) { + if (skt_kernel_list.size() == kSKTSingleSize && skt_arg_list.size() == kSKTSingleSize) { rt_ret = rtKernelLaunchWithFlag(skt_info_.kernel_list[0], static_cast(skt_info_.last_block_dim), skt_info_.arg_list[0], skt_info_.last_args_size, static_cast(skt_info_.last_sm_desc), skt_info_.last_stream, @@ -215,6 +234,7 @@ Status KernelTaskInfo::SuperKernelLaunch() { GELOGE(RT_FAILED, "SuperKernelLaunch: Call rt api failed, ret: 0x%X", rt_ret); return RT_FAILED; } + call_save_dump_ = true; GE_CHK_STATUS_RET(SKTFinalize(), "Skt finalize failed"); return SUCCESS; } @@ -226,18 +246,22 @@ Status KernelTaskInfo::SuperKernelLaunch() { return RT_FAILED; } // Call the fuse API - skt::SuperKernel *superKernel = nullptr; + std::unique_ptr superKernel = nullptr; if (factory->FuseKernels(skt_kernel_list, skt_arg_list, skt_info_.last_block_dim, superKernel) != SUCCESS) { GELOGE(RT_FAILED, "SuperKernelLaunch: fuse call failed"); return RT_FAILED; } // Launch a super kernel - if (superKernel->Launch(skt_info_.last_stream, RT_KERNEL_DUMPFLAG) != SUCCESS) { + skt_dump_flag_ = GetDumpFlag(); + if (superKernel->Launch(skt_info_.last_stream, skt_dump_flag_) != SUCCESS) { GELOGE(RT_FAILED, "SuperKernelLaunch: launch failed"); return RT_FAILED; } GELOGI("SuperKernelLaunch: success[skt_kernel_list size[%zu] skt_arg_list[%zu]]", skt_kernel_list.size(), skt_arg_list.size()); + // record skt addr for release + superkernel_dev_nav_table_ = superKernel->GetNavTablePtr(); + superkernel_device_args_addr_ = superKernel->GetDeviceArgsPtr(); GE_CHK_STATUS_RET(SKTFinalize(), "Skt finalize failed"); return SUCCESS; } @@ -250,6 +274,9 @@ Status KernelTaskInfo::SaveSuperKernelInfo() { skt_info_.last_args_size = args_size_; skt_info_.last_sm_desc = sm_desc_; skt_info_.last_dump_flag = dump_flag_; + skt_info_.dump_flag_list.push_back(dump_flag_); + skt_info_.op_desc_list.push_back(op_desc_); + skt_info_.dump_args_list.push_back(reinterpret_cast(dump_args_)); skt_info_.last_group_key = group_key_; skt_info_.last_dump_args = reinterpret_cast(dump_args_); skt_info_.last_op = op_desc_; @@ -328,6 +355,7 @@ Status KernelTaskInfo::SuperKernelDistribute() { GELOGE(RT_FAILED, "Call rt api failed, ret: 0x%X", rt_ret); return FAILED; } + call_save_dump_ = true; UpdateTaskId(); GELOGI("Current Common Task Distribute [taskid:%u]", task_id_); } else { @@ -356,6 +384,7 @@ Status KernelTaskInfo::Distribute() { rt_ret = rtCpuKernelLaunchWithFlag(reinterpret_cast(so_name_.c_str()), reinterpret_cast(kernel_name_.c_str()), 1, args_, args_size_, nullptr, stream_, dump_flag_); + call_save_dump_ = true; } else { /* default: not skt launch */ GELOGI( @@ -369,6 +398,7 @@ Status KernelTaskInfo::Distribute() { // call rtKernelLaunch for current task rt_ret = rtKernelLaunchWithFlag(stub_func_, block_dim_, args_, args_size_, static_cast(sm_desc_), stream_, dump_flag_); + call_save_dump_ = true; } } if (rt_ret != RT_ERROR_NONE) { @@ -392,9 +422,31 @@ Status KernelTaskInfo::UpdateArgs() { vector workspace_data_addrs = ModelUtils::GetWorkspaceDataAddrs(rts_param, op_desc_); vector io_addrs; - io_addrs.insert(io_addrs.end(), input_data_addrs.begin(), input_data_addrs.end()); - io_addrs.insert(io_addrs.end(), output_data_addrs.begin(), output_data_addrs.end()); - io_addrs.insert(io_addrs.end(), workspace_data_addrs.begin(), workspace_data_addrs.end()); + if (!op_desc_->HasAttr(ATTR_DYNAMIC_SHAPE_FIXED_ADDR)) { + io_addrs.insert(io_addrs.end(), input_data_addrs.begin(), input_data_addrs.end()); + io_addrs.insert(io_addrs.end(), output_data_addrs.begin(), output_data_addrs.end()); + io_addrs.insert(io_addrs.end(), workspace_data_addrs.begin(), workspace_data_addrs.end()); + } else { + string peer_input_name; + if (AttrUtils::GetStr(op_desc_, ATTR_DYNAMIC_SHAPE_FIXED_ADDR, peer_input_name)) { + uint32_t output_index = davinci_model_->GetFixedAddrOutputIndex(peer_input_name); + if (output_index > output_data_addrs.size()) { + GELOGE(FAILED, "The output data addr size[%zu] and output index[%u] are inconsistent.", + output_data_addrs.size(), output_index); + return FAILED; + } + io_addrs.insert(io_addrs.end(), input_data_addrs.begin(), input_data_addrs.end()); + for (size_t i = 0; i < output_data_addrs.size(); ++i) { + if (i == output_index) { + void *fixed_addr = davinci_model_->GetCurrentFixedAddr(fixed_addr_offset_); + io_addrs.emplace_back(fixed_addr); + continue; + } + io_addrs.emplace_back(output_data_addrs[i]); + } + io_addrs.insert(io_addrs.end(), workspace_data_addrs.begin(), workspace_data_addrs.end()); + } + } GE_CHK_STATUS_RET(davinci_model_->UpdateKnownZeroCopyAddr(io_addrs, args_offset_), "update known node %s zero copy addr failed.", op_desc_->GetName().c_str()); @@ -408,6 +460,8 @@ Status KernelTaskInfo::Release() { return SUCCESS; } FreeRtMem(&args_); + FreeRtMem(&superkernel_device_args_addr_); + FreeRtMem(&superkernel_dev_nav_table_); FreeRtMem(&flowtable_); FreeRtMem(&custom_info_.input_descs); FreeRtMem(&custom_info_.input_addrs); @@ -472,6 +526,29 @@ Status KernelTaskInfo::CalculateArgs(const domi::TaskDef &task_def, DavinciModel args_offset_ = davinci_model->GetTotalArgsSize(); davinci_model->SetTotalArgsSize(args_size); GELOGI("kernel task name , args_size %u, args_offset %u", args_size, args_offset_); + + // get opcontext stored in model + const domi::KernelContext &context = kernel_def.context(); + // get opdesc + op_desc_ = davinci_model->GetOpByIndex(context.op_index()); + GE_CHECK_NOTNULL(op_desc_); + // alloc fixed addr + string peer_input_name; + if (AttrUtils::GetStr(op_desc_, ATTR_DYNAMIC_SHAPE_FIXED_ADDR, peer_input_name) && !peer_input_name.empty()) { + uint32_t output_index = davinci_model->GetFixedAddrOutputIndex(peer_input_name); + if (output_index > op_desc_->GetOutputsSize()) { + GELOGE(FAILED, "The output size[%zu] and output index[%u] are inconsistent.", op_desc_->GetOutputsSize(), + output_index); + return FAILED; + } + fixed_addr_offset_ = davinci_model->GetFixedAddrsSize(peer_input_name); + auto tensor_desc = op_desc_->GetOutputDesc(output_index); + int64_t tensor_size = 0; + GE_CHK_STATUS(TensorUtils::GetSize(tensor_desc, tensor_size)); + davinci_model->SetTotalFixedAddrsSize(peer_input_name, tensor_size); + GELOGI("Calculate stream switch task args , tensor size is %ld, fixed addr offset %ld", tensor_size, + fixed_addr_offset_); + } return SUCCESS; } @@ -549,8 +626,8 @@ Status KernelTaskInfo::InitTVMTask(uint16_t offset, const domi::KernelDef &kerne return FAILED; } - if (PropertiesManager::Instance().IsLayerNeedDump(davinci_model_->Name(), davinci_model_->OmName(), - op_desc->GetName())) { + if (davinci_model_->GetDumpProperties().IsLayerNeedDump(davinci_model_->Name(), davinci_model_->OmName(), + op_desc->GetName())) { dump_flag_ = RT_KERNEL_DUMPFLAG; dump_args_ = static_cast(args_) + offset; } @@ -561,10 +638,8 @@ Status KernelTaskInfo::InitTVMTask(uint16_t offset, const domi::KernelDef &kerne } vector virtual_io_addrs; // use virtual address for zero copy key. - const vector virtual_in_addrs = ModelUtils::GetInputDataAddrs(rts_param, op_desc, false); - const vector virtual_out_addrs = ModelUtils::GetOutputDataAddrs(rts_param, op_desc, false); - virtual_io_addrs.insert(virtual_io_addrs.end(), virtual_in_addrs.begin(), virtual_in_addrs.end()); - virtual_io_addrs.insert(virtual_io_addrs.end(), virtual_out_addrs.begin(), virtual_out_addrs.end()); + virtual_io_addrs.insert(virtual_io_addrs.end(), input_data_addrs.begin(), input_data_addrs.end()); + virtual_io_addrs.insert(virtual_io_addrs.end(), output_data_addrs.begin(), output_data_addrs.end()); davinci_model_->SetZeroCopyAddr(op_desc, virtual_io_addrs, args_info.data(), args_, args_size_, offset); GELOGD("Do InitTVMTask end"); @@ -602,7 +677,6 @@ Status KernelTaskInfo::InitAICPUCustomTask(uint32_t op_index, const domi::Kernel const std::vector output_data_addrs = ModelUtils::GetOutputDataAddrs(rts_param, op_desc); Status ret = StoreInputOutputTensor(input_data_addrs, output_data_addrs, ModelUtils::GetInputDescs(op_desc), ModelUtils::GetOutputDescs(op_desc)); - if (ret != SUCCESS) { GELOGE(ret, "StoreInputOutputTensor Failed"); return ret; @@ -667,11 +741,9 @@ Status KernelTaskInfo::InitAICPUCustomTask(uint32_t op_index, const domi::Kernel return RT_FAILED; } - const vector virtual_in_addrs = ModelUtils::GetInputDataAddrs(rts_param, op_desc, false); - const vector virtual_out_addrs = ModelUtils::GetOutputDataAddrs(rts_param, op_desc, false); - davinci_model_->SetZeroCopyAddr(op_desc, virtual_in_addrs, input_data_addrs.data(), custom_info_.input_addrs, - virtual_in_addrs.size() * kAddrLen, 0); - davinci_model_->SetZeroCopyAddr(op_desc, virtual_out_addrs, output_data_addrs.data(), custom_info_.output_addrs, + davinci_model_->SetZeroCopyAddr(op_desc, input_data_addrs, input_data_addrs.data(), custom_info_.input_addrs, + input_data_addrs.size() * kAddrLen, 0); + davinci_model_->SetZeroCopyAddr(op_desc, output_data_addrs, output_data_addrs.data(), custom_info_.output_addrs, output_data_addrs.size() * kAddrLen, 0); return SUCCESS; } @@ -801,6 +873,9 @@ Status KernelTaskInfo::InitAicpuTask(uint32_t op_index, const domi::KernelDef &k GELOGE(init_ret, "Init aicpu task ext info failed, ext_info size=%zu", ext_info.size()); return init_ret; } + GELOGI("Node[%s] type[%s] kernel_ext_info size=%zu, aicpu_ext_info_addr_=%p", op_desc_->GetName().c_str(), + op_desc_->GetType().c_str(), ext_info.size(), aicpu_ext_info_addr_); + aicpu_param_head->extInfoAddr = reinterpret_cast(aicpu_ext_info_addr_); aicpu_param_head->extInfoLength = reinterpret_cast(ext_info.size()); @@ -819,19 +894,13 @@ Status KernelTaskInfo::InitAicpuTask(uint32_t op_index, const domi::KernelDef &k return RT_FAILED; } - if (PropertiesManager::Instance().IsLayerNeedDump(davinci_model_->Name(), davinci_model_->OmName(), - op_desc->GetName())) { + if (davinci_model_->GetDumpProperties().IsLayerNeedDump(davinci_model_->Name(), davinci_model_->OmName(), + op_desc->GetName())) { dump_flag_ = RT_KERNEL_DUMPFLAG; dump_args_ = static_cast(args_) + sizeof(aicpu::AicpuParamHead); } - vector virtual_io_addrs; // use virtual address for zero copy key. - const vector virtual_in_addrs = ModelUtils::GetInputDataAddrs(rts_param, op_desc, false); - const vector virtual_out_addrs = ModelUtils::GetOutputDataAddrs(rts_param, op_desc, false); - virtual_io_addrs.insert(virtual_io_addrs.end(), virtual_in_addrs.begin(), virtual_in_addrs.end()); - virtual_io_addrs.insert(virtual_io_addrs.end(), virtual_out_addrs.begin(), virtual_out_addrs.end()); - davinci_model_->SetZeroCopyAddr(op_desc, virtual_io_addrs, args_addr.get(), args_, args_size_, - sizeof(aicpu::AicpuParamHead)); + davinci_model_->SetZeroCopyAddr(op_desc, io_addrs, args_addr.get(), args_, args_size_, sizeof(aicpu::AicpuParamHead)); return SUCCESS; } diff --git a/src/ge/graph/load/new_model_manager/task_info/kernel_task_info.h b/src/ge/graph/load/new_model_manager/task_info/kernel_task_info.h index 41ed5728..04cd6312 100644 --- a/src/ge/graph/load/new_model_manager/task_info/kernel_task_info.h +++ b/src/ge/graph/load/new_model_manager/task_info/kernel_task_info.h @@ -61,6 +61,8 @@ class KernelTaskInfo : public TaskInfo { sm_desc_ = nullptr; flowtable_ = nullptr; args_ = nullptr; + superkernel_device_args_addr_ = nullptr; + superkernel_dev_nav_table_ = nullptr; } Status Init(const domi::TaskDef &task_def, DavinciModel *davinci_model) override; @@ -88,6 +90,8 @@ class KernelTaskInfo : public TaskInfo { uint32_t GetSktTaskID() override { return skt_id_; } + bool CallSaveDumpInfo() override { return call_save_dump_; }; + cce::ccOpContext ctx_; FusionOpInfo fusion_op_info_; @@ -130,6 +134,7 @@ class KernelTaskInfo : public TaskInfo { void UpdateSKTTaskId(); Status SKTFinalize(); Status SuperKernelLaunch(); + uint32_t GetDumpFlag(); Status SaveSuperKernelInfo(); bool IsMarkedLastNode(); bool IsMarkedFirstNode(); @@ -153,6 +158,8 @@ class KernelTaskInfo : public TaskInfo { OpDescPtr op_desc_; DavinciModel *davinci_model_; uint32_t args_offset_ = 0; + int64_t fixed_addr_offset_ = 0; + bool call_save_dump_ = false; // aicpu ext_info device mem void *aicpu_ext_info_addr_ = nullptr; @@ -164,6 +171,9 @@ class KernelTaskInfo : public TaskInfo { bool is_n_batch_spilt_; int64_t group_key_; bool has_group_key_; + uint32_t skt_dump_flag_ = RT_KERNEL_DEFAULT; + void *superkernel_device_args_addr_ = nullptr; + void *superkernel_dev_nav_table_ = nullptr; struct AICPUCustomInfo { void *input_descs = nullptr; @@ -183,6 +193,9 @@ class KernelTaskInfo : public TaskInfo { void *last_sm_desc; std::vector kernel_list; std::vector arg_list; + std::vector dump_flag_list; + std::vector op_desc_list; + std::vector dump_args_list; uint32_t last_dump_flag; int64_t last_group_key; uintptr_t last_dump_args; diff --git a/src/ge/graph/load/new_model_manager/task_info/label_switch_by_index_task_info.cc b/src/ge/graph/load/new_model_manager/task_info/label_switch_by_index_task_info.cc index 818307eb..162cf00d 100644 --- a/src/ge/graph/load/new_model_manager/task_info/label_switch_by_index_task_info.cc +++ b/src/ge/graph/load/new_model_manager/task_info/label_switch_by_index_task_info.cc @@ -16,8 +16,8 @@ #include "graph/load/new_model_manager/task_info/label_switch_by_index_task_info.h" -#include "graph/load/new_model_manager/davinci_model.h" #include "graph/debug/ge_attr_define.h" +#include "graph/load/new_model_manager/davinci_model.h" namespace ge { constexpr uint8_t kLabelSwitchIndexNum = 1; @@ -59,7 +59,13 @@ Status LabelSwitchByIndexTaskInfo::Init(const domi::TaskDef &task_def, DavinciMo op_desc->GetName().c_str(), input_data_addr.size(), kLabelSwitchIndexNum); return INTERNAL_ERROR; } - index_value_ = input_data_addr[0]; + + if (davinci_model->IsKnownNode()) { + index_value_ = davinci_model->GetCurrentFixedAddr(fixed_addr_offset_); + } else { + index_value_ = input_data_addr[0]; + } + davinci_model->DisableZeroCopy(index_value_); std::vector label_idx_list; @@ -124,5 +130,28 @@ Status LabelSwitchByIndexTaskInfo::Distribute() { return SUCCESS; } +Status LabelSwitchByIndexTaskInfo::CalculateArgs(const domi::TaskDef &task_def, DavinciModel *davinci_model) { + GE_CHECK_NOTNULL(davinci_model); + auto label_switch = task_def.label_switch_by_index(); + uint32_t op_index = label_switch.op_index(); + GELOGI("Begin to calculate args, op_index is: %u", op_index); + auto op_desc = davinci_model->GetOpByIndex(op_index); + GE_CHECK_NOTNULL(op_desc); + GELOGI("Calc opType[%s] args size. Node name is [%s]", op_desc->GetType().c_str(), op_desc->GetName().c_str()); + if (op_desc->GetInputsSize() != kLabelSwitchIndexNum) { + GELOGE(FAILED, "Label switch op only have one data input. Now input size is %zu", op_desc->GetInputsSize()); + return FAILED; + } + string input_tensor_name = op_desc->GetInputNameByIndex(0); + fixed_addr_offset_ = davinci_model->GetFixedAddrsSize(input_tensor_name); + auto tensor_desc = op_desc->GetInputDesc(0); + int64_t tensor_size = 0; + GE_CHK_STATUS(TensorUtils::GetSize(tensor_desc, tensor_size)); + davinci_model->SetTotalFixedAddrsSize(input_tensor_name, tensor_size); + GELOGI("Calculate stream switchn task args , tensor_size %ld, fixed_addr_offset %ld", tensor_size, + fixed_addr_offset_); + return SUCCESS; +} + REGISTER_TASK_INFO(RT_MODEL_TASK_STREAM_LABEL_SWITCH_BY_INDEX, LabelSwitchByIndexTaskInfo); } // namespace ge diff --git a/src/ge/graph/load/new_model_manager/task_info/label_switch_by_index_task_info.h b/src/ge/graph/load/new_model_manager/task_info/label_switch_by_index_task_info.h index 1a644736..4cb39c95 100644 --- a/src/ge/graph/load/new_model_manager/task_info/label_switch_by_index_task_info.h +++ b/src/ge/graph/load/new_model_manager/task_info/label_switch_by_index_task_info.h @@ -22,7 +22,8 @@ namespace ge { class LabelSwitchByIndexTaskInfo : public TaskInfo { public: - LabelSwitchByIndexTaskInfo() : index_value_(nullptr), branch_max_(0), args_(nullptr), args_size_(0) {} + LabelSwitchByIndexTaskInfo() + : index_value_(nullptr), branch_max_(0), args_(nullptr), args_size_(0), fixed_addr_offset_(0) {} ~LabelSwitchByIndexTaskInfo() override; @@ -30,13 +31,15 @@ class LabelSwitchByIndexTaskInfo : public TaskInfo { Status Distribute() override; + Status CalculateArgs(const domi::TaskDef &task_def, DavinciModel *davinci_model) override; + private: void *index_value_; // switch index input. uint32_t branch_max_; // max branch count. void *args_; // label info memory. uint32_t args_size_; // label info length. - std::vector label_list_; + int64_t fixed_addr_offset_; }; } // namespace ge #endif // GE_GRAPH_LOAD_NEW_MODEL_MANAGER_TASK_INFO_LABEL_SWITCH_BY_INDEX_TASK_INFO_H_ \ No newline at end of file diff --git a/src/ge/graph/load/new_model_manager/task_info/memcpy_addr_async_task_info.cc b/src/ge/graph/load/new_model_manager/task_info/memcpy_addr_async_task_info.cc index e9d99189..af32b44f 100644 --- a/src/ge/graph/load/new_model_manager/task_info/memcpy_addr_async_task_info.cc +++ b/src/ge/graph/load/new_model_manager/task_info/memcpy_addr_async_task_info.cc @@ -21,9 +21,9 @@ namespace ge { Status MemcpyAddrAsyncTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *davinci_model) { - GELOGI("MemcpyAddrAsyncTaskInfo Init Start."); + GELOGI("MemcpyAddrAsyncTaskInfo Init Start"); if (davinci_model == nullptr) { - GELOGE(PARAM_INVALID, "davinci_model is null!"); + GELOGE(PARAM_INVALID, "davinci_model is null"); return PARAM_INVALID; } @@ -32,45 +32,27 @@ Status MemcpyAddrAsyncTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel return ret; } - auto memcpy_async_def = task_def.memcpy_async(); - uint32_t op_index = memcpy_async_def.op_index(); - OpDescPtr op_desc = davinci_model->GetOpByIndex(op_index); + const auto &memcpy_async = task_def.memcpy_async(); + OpDescPtr op_desc = davinci_model->GetOpByIndex(memcpy_async.op_index()); if (op_desc == nullptr) { - GELOGE(INTERNAL_ERROR, "Init MemcpyAddrAsyncTaskInfo error, index is out of range!"); + GELOGE(INTERNAL_ERROR, "Task op index:%u out of range", memcpy_async.op_index()); return INTERNAL_ERROR; } - uint64_t logic_dst = memcpy_async_def.dst(); - uint64_t logic_src = memcpy_async_def.src(); - - dst_max_ = memcpy_async_def.dst_max(); - - uint64_t update_base_addr = 0; - ret = GetUpdateBaseAddr(davinci_model, logic_src, update_base_addr); + ret = ModelUtils::GetRtAddress(davinci_model->GetRuntimeParam(), memcpy_async.src(), src_); if (ret != SUCCESS) { return ret; } - src_ = reinterpret_cast(update_base_addr + logic_src); - if (src_ == nullptr) { - GELOGE(PARAM_INVALID, "src_ is null!"); - return PARAM_INVALID; - } - uint64_t mem_base = reinterpret_cast(davinci_model->MemBase()); - uint64_t logic_mem_base = davinci_model->GetRtBaseAddr(); - dst_ = reinterpret_cast(reinterpret_cast(mem_base + (logic_dst - logic_mem_base))); - if (dst_ == nullptr) { - GELOGE(PARAM_INVALID, "dst_ is null!"); - return PARAM_INVALID; + ret = ModelUtils::GetRtAddress(davinci_model->GetRuntimeParam(), memcpy_async.dst(), dst_); + if (ret != SUCCESS) { + return ret; } vector io_addrs; io_addrs.emplace_back(src_); io_addrs.emplace_back(dst_); - count_ = memcpy_async_def.count(); - kind_ = memcpy_async_def.kind(); - // malloc args memory size_t args_size = sizeof(void *) * io_addrs.size(); rtError_t rt_ret = rtMalloc(&args_, args_size, RT_MEMORY_HBM); @@ -88,20 +70,18 @@ Status MemcpyAddrAsyncTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel return RT_FAILED; } - // Just dest addr need zero copy. - davinci_model->SetZeroCopyAddr(op_desc, {dst_}, io_addrs.data(), args_, args_size, sizeof(void *)); - - GELOGI("InitMemcpyAddrAsyncTaskInfo, logic_src:%p, logic_dst:%p, src:%p, dst:%p, src_args:%p, dst_args:%p", - reinterpret_cast(reinterpret_cast(logic_src)), - reinterpret_cast(reinterpret_cast(logic_dst)), src_, dst_, args_, - reinterpret_cast(reinterpret_cast(args_) + args_size)); + count_ = memcpy_async.count(); + kind_ = memcpy_async.kind(); + dst_max_ = memcpy_async.dst_max(); + GELOGI("InitMemcpyAddrAsyncTaskInfo, logic[0x%lx, 0x%lx], src:%p, dst:%p, max:%lu, count:%lu, args:%p, size:%zu", + memcpy_async.src(), memcpy_async.dst(), src_, dst_, dst_max_, count_, args_, args_size); + davinci_model->SetZeroCopyAddr(op_desc, io_addrs, io_addrs.data(), args_, args_size, 0); return SUCCESS; } Status MemcpyAddrAsyncTaskInfo::Distribute() { - GELOGI("MemcpyAddrAsyncTaskInfo Distribute Start."); - GELOGI("Distribute MemcpyAddrAsync, dst_max:%lu, count:%lu, kind:%u.", dst_max_, count_, kind_); + GELOGI("MemcpyAddrAsyncTaskInfo Distribute Start, dst_max:%lu, count:%lu, kind:%u", dst_max_, count_, kind_); rtError_t rt_ret = rtMemcpyAsync(reinterpret_cast(reinterpret_cast(args_) + sizeof(void *)), dst_max_, args_, count_, static_cast(kind_), stream_); @@ -113,39 +93,5 @@ Status MemcpyAddrAsyncTaskInfo::Distribute() { return SUCCESS; } -Status MemcpyAddrAsyncTaskInfo::GetUpdateBaseAddr(DavinciModel *davinci_model, uint64_t update_addr, - uint64_t &base_addr) { - GE_CHECK_NOTNULL(davinci_model); - uint64_t data_base_addr = - static_cast(reinterpret_cast(davinci_model->MemBase())) - davinci_model->GetRtBaseAddr(); - uint64_t weight_base_addr = static_cast(reinterpret_cast(davinci_model->WeightsMemBase())) - - davinci_model->GetRtWeightAddr(); - uint64_t var_base_addr = - static_cast(reinterpret_cast(davinci_model->VarMemBase())) - davinci_model->GetRtVarAddr(); - - uint64_t data_base_addr_start = davinci_model->GetRtBaseAddr(); - uint64_t data_base_addr_end = davinci_model->GetRtBaseAddr() + davinci_model->TotalMemSize(); - uint64_t wight_base_addr_start = davinci_model->GetRtWeightAddr(); - uint64_t wight_base_addr_end = davinci_model->GetRtWeightAddr() + davinci_model->TotalWeightsMemSize(); - uint64_t varible_base_addr_start = davinci_model->GetRtVarAddr(); - uint64_t varible_base_addr_end = davinci_model->GetRtVarAddr() + davinci_model->TotalVarMemSize(); - - if ((data_base_addr_start <= update_addr) && (update_addr <= data_base_addr_end)) { - base_addr = data_base_addr; - GELOGI("The update_addr is data address."); - } else if ((wight_base_addr_start <= update_addr) && (update_addr <= wight_base_addr_end)) { - base_addr = weight_base_addr; - GELOGI("The update_addr is weight address."); - } else if ((varible_base_addr_start <= update_addr) && (update_addr <= varible_base_addr_end)) { - base_addr = var_base_addr; - GELOGI("The update_addr is variable address."); - } else if (update_addr != 0) { - base_addr = 0; - GELOGE(PARAM_INVALID, "The update_addr is abnormal."); - return PARAM_INVALID; - } - return SUCCESS; -} - REGISTER_TASK_INFO(RT_MODEL_TASK_MEMCPY_ADDR_ASYNC, MemcpyAddrAsyncTaskInfo); } // namespace ge diff --git a/src/ge/graph/load/new_model_manager/task_info/memcpy_addr_async_task_info.h b/src/ge/graph/load/new_model_manager/task_info/memcpy_addr_async_task_info.h index 9252e43a..f8bf8a90 100644 --- a/src/ge/graph/load/new_model_manager/task_info/memcpy_addr_async_task_info.h +++ b/src/ge/graph/load/new_model_manager/task_info/memcpy_addr_async_task_info.h @@ -16,6 +16,7 @@ #ifndef GE_GRAPH_LOAD_NEW_MODEL_MANAGER_TASK_INFO_MEMCPY_ADDR_ASYNC_TASK_INFO_H_ #define GE_GRAPH_LOAD_NEW_MODEL_MANAGER_TASK_INFO_MEMCPY_ADDR_ASYNC_TASK_INFO_H_ + #include "graph/load/new_model_manager/task_info/task_info.h" namespace ge { @@ -32,9 +33,8 @@ class MemcpyAddrAsyncTaskInfo : public TaskInfo { if (ret != RT_ERROR_NONE) { GELOGE(RT_FAILED, "Call rt api failed, ret: 0x%X", ret); } + args_ = nullptr; } - - args_ = nullptr; } Status Init(const domi::TaskDef &task_def, DavinciModel *davinci_model) override; @@ -42,11 +42,9 @@ class MemcpyAddrAsyncTaskInfo : public TaskInfo { Status Distribute() override; private: - Status GetUpdateBaseAddr(DavinciModel *davinci_model, uint64_t update_addr, uint64_t &base_addr); - - void *dst_; + uint8_t *dst_; uint64_t dst_max_; - void *src_; + uint8_t *src_; void *args_; uint64_t count_; uint32_t kind_; diff --git a/src/ge/graph/load/new_model_manager/task_info/memcpy_async_task_info.cc b/src/ge/graph/load/new_model_manager/task_info/memcpy_async_task_info.cc index 82eabe69..c2b56436 100644 --- a/src/ge/graph/load/new_model_manager/task_info/memcpy_async_task_info.cc +++ b/src/ge/graph/load/new_model_manager/task_info/memcpy_async_task_info.cc @@ -21,9 +21,9 @@ namespace ge { Status MemcpyAsyncTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *davinci_model) { - GELOGI("MemcpyAsyncTaskInfo Init Start."); + GELOGI("MemcpyAsyncTaskInfo Init Start"); if (davinci_model == nullptr) { - GELOGE(PARAM_INVALID, "davinci_model is null!"); + GELOGE(PARAM_INVALID, "davinci_model is null"); return PARAM_INVALID; } @@ -32,35 +32,38 @@ Status MemcpyAsyncTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *da return ret; } - auto memcpy_async_def = task_def.memcpy_async(); - uint64_t logic_dst = memcpy_async_def.dst(); - uint64_t logic_src = memcpy_async_def.src(); - - dst_max_ = memcpy_async_def.dst_max(); - - uint64_t update_base_addr = 0; - ret = GetUpdateBaseAddr(davinci_model, logic_src, update_base_addr); + memcpy_async = task_def.memcpy_async(); + count_ = memcpy_async.count(); + kind_ = memcpy_async.kind(); + dst_max_ = memcpy_async.dst_max(); + if (davinci_model->IsKnownNode()) { + src_ = reinterpret_cast(davinci_model_->GetCurrentArgsAddr(args_offset_)); + dst_ = reinterpret_cast(reinterpret_cast(src_) + sizeof(void *)); + // for zero copy + kind_ = RT_MEMCPY_ADDR_DEVICE_TO_DEVICE; + GELOGI("MemcpyAsyncTaskInfo src_ %p, dst_ %p, args_offset %u.", src_, dst_, args_offset_); + return SUCCESS; + } + ret = ModelUtils::GetRtAddress(davinci_model->GetRuntimeParam(), memcpy_async.src(), src_); if (ret != SUCCESS) { return ret; } - src_ = reinterpret_cast(update_base_addr + logic_src); - davinci_model->DisableZeroCopy(src_); - uint64_t mem_base = reinterpret_cast(davinci_model->MemBase()); - uint64_t logic_mem_base = davinci_model->GetRtBaseAddr(); - dst_ = reinterpret_cast(mem_base + (logic_dst - logic_mem_base)); + ret = ModelUtils::GetRtAddress(davinci_model->GetRuntimeParam(), memcpy_async.dst(), dst_); + if (ret != SUCCESS) { + return ret; + } - count_ = memcpy_async_def.count(); - kind_ = memcpy_async_def.kind(); - GELOGI("MemcpyAsyncTaskInfo Init Success, logic_src:%p, logic_dst:%p, src:%p, dst:%p", - reinterpret_cast(reinterpret_cast(logic_src)), - reinterpret_cast(reinterpret_cast(logic_dst)), src_, dst_); + GELOGI("MemcpyAsyncTaskInfo Init Success, logic[0x%lx, 0x%lx], src:%p, dst:%p, max:%lu, count:%lu", + memcpy_async.src(), memcpy_async.dst(), src_, dst_, dst_max_, count_); + davinci_model->DisableZeroCopy(src_); + davinci_model->DisableZeroCopy(dst_); return SUCCESS; } Status MemcpyAsyncTaskInfo::Distribute() { - GELOGI("MemcpyAsyncTaskInfo Distribute Start. dst_max:%lu, count:%lu, kind:%u.", dst_max_, count_, kind_); + GELOGI("MemcpyAsyncTaskInfo Distribute Start. dst_max:%lu, count:%lu, kind:%u", dst_max_, count_, kind_); rtError_t rt_ret = rtMemcpyAsync(dst_, dst_max_, src_, count_, static_cast(kind_), stream_); if (rt_ret != RT_ERROR_NONE) { @@ -68,40 +71,41 @@ Status MemcpyAsyncTaskInfo::Distribute() { return RT_FAILED; } - GELOGI("MemcpyAsyncTaskInfo Distribute Success."); + GELOGI("MemcpyAsyncTaskInfo Distribute Success"); return SUCCESS; } -Status MemcpyAsyncTaskInfo::GetUpdateBaseAddr(DavinciModel *davinci_model, uint64_t update_addr, uint64_t &base_addr) { - GE_CHECK_NOTNULL(davinci_model); - uint64_t data_base_addr = - reinterpret_cast(reinterpret_cast(davinci_model->MemBase())) - davinci_model->GetRtBaseAddr(); - uint64_t weight_base_addr = reinterpret_cast(reinterpret_cast(davinci_model->WeightsMemBase())) - - davinci_model->GetRtWeightAddr(); - uint64_t var_base_addr = reinterpret_cast(reinterpret_cast(davinci_model->VarMemBase())) - - davinci_model->GetRtVarAddr(); - - uint64_t data_base_addr_start = davinci_model->GetRtBaseAddr(); - uint64_t data_base_addr_end = davinci_model->GetRtBaseAddr() + davinci_model->TotalMemSize(); - uint64_t wight_base_addr_start = davinci_model->GetRtWeightAddr(); - uint64_t wight_base_addr_end = davinci_model->GetRtWeightAddr() + davinci_model->TotalWeightsMemSize(); - uint64_t varible_base_addr_start = davinci_model->GetRtVarAddr(); - uint64_t varible_base_addr_end = davinci_model->GetRtVarAddr() + davinci_model->TotalVarMemSize(); - - if ((data_base_addr_start <= update_addr) && (update_addr <= data_base_addr_end)) { - base_addr = data_base_addr; - GELOGI("The update_addr is data address."); - } else if ((wight_base_addr_start <= update_addr) && (update_addr <= wight_base_addr_end)) { - base_addr = weight_base_addr; - GELOGI("The update_addr is weight address."); - } else if ((varible_base_addr_start <= update_addr) && (update_addr <= varible_base_addr_end)) { - base_addr = var_base_addr; - GELOGI("The update_addr is variable address."); - } else if (update_addr != 0) { - base_addr = 0; - GELOGE(PARAM_INVALID, "The update_addr is abnormal."); - return PARAM_INVALID; +Status MemcpyAsyncTaskInfo::CalculateArgs(const domi::TaskDef &task_def, DavinciModel *davinci_model) { + // the num of src and dst size is 2 + uint32_t args_size = sizeof(void *) * 2; + args_offset_ = davinci_model->GetTotalArgsSize(); + davinci_model->SetTotalArgsSize(args_size); + davinci_model_ = davinci_model; + GELOGI("MemcpyAsyncTaskInfo kernel args_size %u, args_offset %u", args_size, args_offset_); + return SUCCESS; +} + +Status MemcpyAsyncTaskInfo::UpdateArgs() { + GELOGI("MemcpyAsyncTaskInfo::UpdateArgs in."); + GE_CHECK_NOTNULL(davinci_model_); + Status ret = ModelUtils::GetRtAddress(davinci_model_->GetRuntimeParam(), memcpy_async.src(), src_); + if (ret != SUCCESS) { + return ret; + } + + ret = ModelUtils::GetRtAddress(davinci_model_->GetRuntimeParam(), memcpy_async.dst(), dst_); + if (ret != SUCCESS) { + return ret; } + + vector io_addrs; + io_addrs.emplace_back(reinterpret_cast(src_)); + io_addrs.emplace_back(reinterpret_cast(dst_)); + + GE_CHK_STATUS_RET(davinci_model_->UpdateKnownZeroCopyAddr(io_addrs, args_offset_), + "update memcpyasync in known node zero copy addr failed."); + + GELOGI("MemcpyAsyncTaskInfo::UpdateArgs success."); return SUCCESS; } diff --git a/src/ge/graph/load/new_model_manager/task_info/memcpy_async_task_info.h b/src/ge/graph/load/new_model_manager/task_info/memcpy_async_task_info.h index 02872f34..c3daa862 100644 --- a/src/ge/graph/load/new_model_manager/task_info/memcpy_async_task_info.h +++ b/src/ge/graph/load/new_model_manager/task_info/memcpy_async_task_info.h @@ -16,6 +16,7 @@ #ifndef GE_GRAPH_LOAD_NEW_MODEL_MANAGER_TASK_INFO_MEMCPY_ASYNC_TASK_INFO_H_ #define GE_GRAPH_LOAD_NEW_MODEL_MANAGER_TASK_INFO_MEMCPY_ASYNC_TASK_INFO_H_ + #include "graph/load/new_model_manager/task_info/task_info.h" namespace ge { @@ -32,14 +33,19 @@ class MemcpyAsyncTaskInfo : public TaskInfo { Status Distribute() override; - private: - Status GetUpdateBaseAddr(DavinciModel *davinci_model, uint64_t update_addr, uint64_t &base_addr); + Status UpdateArgs() override; - void *dst_; + Status CalculateArgs(const domi::TaskDef &task_def, DavinciModel *davinci_model) override; + + private: + uint8_t *dst_; uint64_t dst_max_; - void *src_; + uint8_t *src_; uint64_t count_; uint32_t kind_; + DavinciModel *davinci_model_ = nullptr; + uint32_t args_offset_ = 0; + domi::MemcpyAsyncDef memcpy_async; }; } // namespace ge #endif // GE_GRAPH_LOAD_NEW_MODEL_MANAGER_TASK_INFO_MEMCPY_ASYNC_TASK_INFO_H_ diff --git a/src/ge/graph/load/new_model_manager/task_info/stream_switch_task_info.cc b/src/ge/graph/load/new_model_manager/task_info/stream_switch_task_info.cc index a1d2f143..0ebaf573 100644 --- a/src/ge/graph/load/new_model_manager/task_info/stream_switch_task_info.cc +++ b/src/ge/graph/load/new_model_manager/task_info/stream_switch_task_info.cc @@ -42,16 +42,11 @@ Status StreamSwitchTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *d auto stream_switch_def = task_def.stream_switch(); uint32_t op_index = stream_switch_def.op_index(); - // get StreamSwitch op OpDescPtr op_desc = davinci_model->GetOpByIndex(op_index); GE_CHECK_NOTNULL(op_desc); auto input_data_addr = ModelUtils::GetInputDataAddrs(davinci_model->GetRuntimeParam(), op_desc); - if (!input_data_addr.empty() && input_data_addr.size() >= STREAM_SWITCH_INPUT_NUM) { - input_ptr_ = input_data_addr[0]; - value_ptr_ = input_data_addr[1]; - } - + SetInputAndValuePtr(davinci_model, input_data_addr); uint32_t cond = 0; if (!AttrUtils::GetInt(op_desc, ATTR_NAME_STREAM_SWITCH_COND, cond)) { GELOGE(INTERNAL_ERROR, "StreamSwitchOp get attr STREAM_SWITCH_COND fail."); @@ -115,6 +110,42 @@ Status StreamSwitchTaskInfo::Distribute() { GELOGI("StreamSwitchTaskInfo Distribute Success. cond:%d, stream:%p, datatype:%d.", cond_, true_stream_, data_type_); return SUCCESS; } +Status StreamSwitchTaskInfo::CalculateArgs(const domi::TaskDef &task_def, DavinciModel *davinci_model) { + GE_CHECK_NOTNULL(davinci_model); + auto stream_switch_def = task_def.stream_switch(); + uint32_t op_index = stream_switch_def.op_index(); + GELOGI("Begin to calculate args, op_index is: %u", op_index); + auto op_desc = davinci_model->GetOpByIndex(op_index); + GE_CHECK_NOTNULL(op_desc); + GELOGI("Calc opType[%s] args size. Node name is [%s]", op_desc->GetType().c_str(), op_desc->GetName().c_str()); + if (op_desc->GetInputsSize() != STREAM_SWITCH_INPUT_NUM) { + GELOGE(FAILED, "Stream switch op only have one data input. Now input size is %zu", op_desc->GetInputsSize()); + return FAILED; + } + for (uint32_t i = 0; i < STREAM_SWITCH_INPUT_NUM; ++i) { + string input_tensor_name = op_desc->GetInputNameByIndex(i); + int64_t fixed_addr_offset = davinci_model->GetFixedAddrsSize(input_tensor_name); + fixed_addr_offset_.emplace_back(fixed_addr_offset); + auto tensor_desc = op_desc->GetInputDesc(i); + int64_t tensor_size = 0; + GE_CHK_STATUS(TensorUtils::GetSize(tensor_desc, tensor_size)); + davinci_model->SetTotalFixedAddrsSize(input_tensor_name, tensor_size); + GELOGI("Calculate stream switch task args , tensor size is %ld, fixed addr[%u] offset %ld", tensor_size, i, + fixed_addr_offset); + } + return SUCCESS; +} +void StreamSwitchTaskInfo::SetInputAndValuePtr(DavinciModel *davinci_model, const vector &input_data_addrs) { + if (davinci_model->IsKnownNode() && fixed_addr_offset_.size() == STREAM_SWITCH_INPUT_NUM) { + input_ptr_ = davinci_model->GetCurrentFixedAddr(fixed_addr_offset_[0]); + value_ptr_ = davinci_model->GetCurrentFixedAddr(fixed_addr_offset_[1]); + } else { + if (!input_data_addrs.empty() && input_data_addrs.size() >= STREAM_SWITCH_INPUT_NUM) { + input_ptr_ = input_data_addrs[0]; + value_ptr_ = input_data_addrs[1]; + } + } +} REGISTER_TASK_INFO(RT_MODEL_TASK_STREAM_SWITCH, StreamSwitchTaskInfo); } // namespace ge diff --git a/src/ge/graph/load/new_model_manager/task_info/stream_switch_task_info.h b/src/ge/graph/load/new_model_manager/task_info/stream_switch_task_info.h index 07509c7c..e6e8339a 100644 --- a/src/ge/graph/load/new_model_manager/task_info/stream_switch_task_info.h +++ b/src/ge/graph/load/new_model_manager/task_info/stream_switch_task_info.h @@ -39,13 +39,18 @@ class StreamSwitchTaskInfo : public TaskInfo { Status Distribute() override; + Status CalculateArgs(const domi::TaskDef &task_def, DavinciModel *davinci_model) override; + private: + void SetInputAndValuePtr(DavinciModel *davinci_model, const vector &input_data_addrs); void *input_ptr_; rtCondition_t cond_; void *value_ptr_; rtStream_t true_stream_; uint32_t true_stream_id_; rtSwitchDataType_t data_type_; + static const uint32_t kInputNum = 2; + vector fixed_addr_offset_; }; } // namespace ge #endif // GE_GRAPH_LOAD_NEW_MODEL_MANAGER_TASK_INFO_STREAM_SWITCH_TASK_INFO_H_ diff --git a/src/ge/graph/load/new_model_manager/task_info/stream_switchn_task_info.cc b/src/ge/graph/load/new_model_manager/task_info/stream_switchn_task_info.cc index 29b107bd..01371af7 100644 --- a/src/ge/graph/load/new_model_manager/task_info/stream_switchn_task_info.cc +++ b/src/ge/graph/load/new_model_manager/task_info/stream_switchn_task_info.cc @@ -24,18 +24,15 @@ namespace { const uint32_t kDynamicBtachParamNum = 1; const uint32_t kDynamicResolutionParamNum = 2; +const uint8_t kStreamSwitchnInputNum = 1; } // namespace namespace ge { Status StreamSwitchNTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel *davinci_model) { GELOGI("StreamSwitchNTaskInfo Init Start."); - if (davinci_model == nullptr) { - GELOGE(PARAM_INVALID, "davinci_model is null!"); - return PARAM_INVALID; - } + GE_CHECK_NOTNULL(davinci_model); - Status ret = SetStream(task_def.stream_id(), davinci_model->GetStreamList()); - if (ret != SUCCESS) { + if (SetStream(task_def.stream_id(), davinci_model->GetStreamList()) != SUCCESS) { return FAILED; } @@ -75,14 +72,16 @@ Status StreamSwitchNTaskInfo::Init(const domi::TaskDef &task_def, DavinciModel * GELOGE(FAILED, "Get true stream ptr of switchN op failed."); return FAILED; } - - // set input_ptr_ - auto input_data_addr = ModelUtils::GetInputDataAddrs(davinci_model->GetRuntimeParam(), op_desc); - if (input_data_addr.empty()) { - GELOGE(FAILED, "Input data addr is nullptr."); - return FAILED; + if (davinci_model->IsKnownNode()) { + input_ptr_ = davinci_model->GetCurrentFixedAddr(args_offset_); + } else { + auto input_data_addr = ModelUtils::GetInputDataAddrs(davinci_model->GetRuntimeParam(), op_desc); + if (input_data_addr.empty()) { + GELOGE(FAILED, "Input data addr is nullptr."); + return FAILED; + } + input_ptr_ = input_data_addr[0]; } - input_ptr_ = input_data_addr[0]; davinci_model->DisableZeroCopy(input_ptr_); GELOGI("StreamSwitchNTaskInfo Init Success, inputSize:%u, elementSize:%d, trueStreamID:%ld.", input_size_, element_size_, op_desc->GetStreamId()); @@ -140,5 +139,26 @@ Status StreamSwitchNTaskInfo::GetTrueStreamPtr(const OpDescPtr &op_desc, Davinci return SUCCESS; } +Status StreamSwitchNTaskInfo::CalculateArgs(const domi::TaskDef &task_def, DavinciModel *davinci_model) { + GE_CHECK_NOTNULL(davinci_model); + auto stream_switchn_def = task_def.stream_switch_n(); + uint32_t op_index = stream_switchn_def.op_index(); + GELOGI("Begin to calculate args, op_index is: %u", op_index); + auto op_desc = davinci_model->GetOpByIndex(op_index); + GE_CHECK_NOTNULL(op_desc); + GELOGI("Calc opType[%s] args size. Node name is [%s]", op_desc->GetType().c_str(), op_desc->GetName().c_str()); + if (op_desc->GetInputsSize() != kStreamSwitchnInputNum) { + GELOGE(FAILED, "Stream switchn op only have one data input. Now input size is %zu", op_desc->GetInputsSize()); + return FAILED; + } + string input_tensor_name = op_desc->GetInputNameByIndex(0); + args_offset_ = davinci_model->GetFixedAddrsSize(input_tensor_name); + auto tensor_desc = op_desc->GetInputDesc(0); + int64_t tensor_size = 0; + GE_CHK_STATUS(TensorUtils::GetSize(tensor_desc, tensor_size)); + davinci_model->SetTotalFixedAddrsSize(input_tensor_name, tensor_size); + GELOGI("Calculate stream switchn task args , tensor_size %ld, args_offset %ld", tensor_size, args_offset_); + return SUCCESS; +} REGISTER_TASK_INFO(RT_MODEL_TASK_STREAM_SWITCH_N, StreamSwitchNTaskInfo); } // namespace ge \ No newline at end of file diff --git a/src/ge/graph/load/new_model_manager/task_info/stream_switchn_task_info.h b/src/ge/graph/load/new_model_manager/task_info/stream_switchn_task_info.h index d1002da7..1a96243a 100644 --- a/src/ge/graph/load/new_model_manager/task_info/stream_switchn_task_info.h +++ b/src/ge/graph/load/new_model_manager/task_info/stream_switchn_task_info.h @@ -29,7 +29,8 @@ class StreamSwitchNTaskInfo : public TaskInfo { value_ptr_(nullptr), true_stream_ptr_(nullptr), element_size_(0), - data_type_(RT_SWITCH_INT64) {} + data_type_(RT_SWITCH_INT64), + args_offset_(0) {} ~StreamSwitchNTaskInfo() override {} @@ -37,6 +38,8 @@ class StreamSwitchNTaskInfo : public TaskInfo { Status Distribute() override; + Status CalculateArgs(const domi::TaskDef &task_def, DavinciModel *davinci_model) override; + private: Status GetTrueStreamPtr(const OpDescPtr &op_desc, DavinciModel *davinci_model); void *input_ptr_; @@ -47,6 +50,7 @@ class StreamSwitchNTaskInfo : public TaskInfo { rtSwitchDataType_t data_type_; vector true_stream_list_; vector value_list_; + int64_t args_offset_; }; } // namespace ge #endif // GE_GRAPH_LOAD_NEW_MODEL_MANAGER_TASK_INFO_STREAM_SWITCHN_TASK_INFO_H_ diff --git a/src/ge/graph/load/new_model_manager/task_info/super_kernel/super_kernel.h b/src/ge/graph/load/new_model_manager/task_info/super_kernel/super_kernel.h index 1c31acd1..b7e76af0 100644 --- a/src/ge/graph/load/new_model_manager/task_info/super_kernel/super_kernel.h +++ b/src/ge/graph/load/new_model_manager/task_info/super_kernel/super_kernel.h @@ -34,22 +34,13 @@ class SuperKernel { public: SuperKernel(const void *stub, void *ptr, uint64_t sz, uint32_t dim) : func_stub_(stub), dev_nav_table_(ptr), nav_table_size_(sz), block_dim_(dim) {} - ~SuperKernel() { - // free memory when all releasing - if (device_args_addr_ != nullptr) { - GE_CHK_RT(rtFree(device_args_addr_)); - GELOGI("SKT: super_kernel args addr free."); - } - if (dev_nav_table_ != nullptr) { - GE_CHK_RT(rtFree(dev_nav_table_)); - GELOGI("SKT: super_kernel args addr free."); - } - } + ~SuperKernel() = default; Status Launch(rtStream_t stream, uint32_t dump_flag); const void *GetFuncStub() const { return func_stub_; } - const void *GetNavTablePtr() const { return dev_nav_table_; } uint64_t GetNavTableSize() const { return nav_table_size_; } uint32_t GetBlockDim() const { return block_dim_; } + void *GetNavTablePtr() const { return dev_nav_table_; } + void *GetDeviceArgsPtr() const { return device_args_addr_; } }; } // namespace skt } // namespace ge diff --git a/src/ge/graph/load/new_model_manager/task_info/super_kernel/super_kernel_factory.cc b/src/ge/graph/load/new_model_manager/task_info/super_kernel/super_kernel_factory.cc index d2ad474a..397c7d98 100644 --- a/src/ge/graph/load/new_model_manager/task_info/super_kernel/super_kernel_factory.cc +++ b/src/ge/graph/load/new_model_manager/task_info/super_kernel/super_kernel_factory.cc @@ -42,21 +42,10 @@ Status SuperKernelFactory::Init() { rt_ret = rtGetAddrByFun(this->func_stub_, &this->func_ptr_); GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtGetAddrByFun failed. error: 0x%X", rt_ret); return FAILED;) - if (this->use_physical_address_ != nullptr) { - void *skt_func = nullptr; - rt_ret = rtKernelConfigTransArg(this->func_ptr_, sizeof(uint64_t), 0, &skt_func); - GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtKernelConfigTransArg failed. error: 0x%X", rt_ret); - return FAILED;) - GELOGD( - "SKT: fuseKernels super_kernel_template subFunc %p, device func " - "address %p, device physic PC %p", - this->func_stub_, this->func_ptr_, skt_func); - } else { - GELOGD( - "SKT: fuseKernels super_kernel_template subFunc %p, device func " - "address %p", - this->func_stub_, this->func_ptr_); - } + GELOGD( + "SKT: fuseKernels super_kernel_template subFunc %p, device func " + "address %p", + this->func_stub_, this->func_ptr_); } is_init_ = true; @@ -71,7 +60,8 @@ Status SuperKernelFactory::Uninitialize() { } Status SuperKernelFactory::FuseKernels(const std::vector &stub_func_list, - const std::vector &args_addr_list, uint32_t block_dim, SuperKernel *&h) { + const std::vector &args_addr_list, uint32_t block_dim, + std::unique_ptr &h) { // Iterate through the ops to be fused // Each subkernel to be fused contains 2 fields: fn address offset, args // address. @@ -101,70 +91,28 @@ Status SuperKernelFactory::FuseKernels(const std::vector &stub_func_list rtError_t rt_ret; void *hbm_nav_table_addr = nullptr; - if (this->use_physical_address_ != nullptr) { - for (unsigned i = 0; i < stub_func_list.size(); i++) { - void *sub_device_func = nullptr; - rt_ret = rtGetAddrByFun(stub_func_list[i], &sub_device_func); - GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtGetAddrByFun failed. error: 0x%X", rt_ret); - return FAILED;) - void *sub_device_func_pys = nullptr; - void *args_addr_pys = nullptr; - rt_ret = rtKernelConfigTransArg(sub_device_func, sizeof(uint64_t), 0, &sub_device_func_pys); - GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtKernelConfigTransArg failed. error: 0x%X", rt_ret); - return FAILED;) - rt_ret = rtKernelConfigTransArg(args_addr_list[i], sizeof(uint64_t), 0, &args_addr_pys); - GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtKernelConfigTransArg failed. error: 0x%X", rt_ret); - return FAILED;) - GELOGD( - "SKT: fuseKernels subFunc %p, device func address %p, device " - "physic func address %p", - stub_func_list[i], sub_device_func, sub_device_func_pys); - // store two uint64_t address - // address divided by 4 because of 32bits encoding, call offset will *4 when calculating - nav_table[i * 2] = reinterpret_cast(reinterpret_cast(sub_device_func_pys)) / 4; - GELOGD("SKT: CALL offset %lu", nav_table[i * 2]); - nav_table[i * 2 + 1] = reinterpret_cast(reinterpret_cast(args_addr_pys)); - - GELOGD("SKT: fuseKernels args base address %lu", nav_table[i * 2 + 1]); - } - - void *hbm_nav_table_addr_pys = nullptr; - rt_ret = rtMalloc((void **)&hbm_nav_table_addr, nav_table_size, RT_MEMORY_HBM); - GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtMalloc failed. error: 0x%X", rt_ret); return FAILED;) - rt_ret = - rtMemcpy((void *)hbm_nav_table_addr, nav_table_size, (void *)nav_table, nav_table_size, RT_MEMCPY_HOST_TO_DEVICE); - GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtMemcpy failed. error: 0x%X", rt_ret); - GE_CHK_RT(rtFree(hbm_nav_table_addr)); return FAILED;) - rt_ret = rtKernelConfigTransArg(hbm_nav_table_addr, sizeof(uint64_t), 0, &hbm_nav_table_addr_pys); - GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtKernelConfigTransArg failed. error: 0x%X", rt_ret); - GE_CHK_RT(rtFree(hbm_nav_table_addr)); return FAILED;) - - GELOGD("SKT: hbm_nav_table_addr %p, hbm_nav_table_addr_pys %p", hbm_nav_table_addr, hbm_nav_table_addr_pys); - // Create the necessary metadata for the super kernel - h = new SuperKernel(this->func_stub_, hbm_nav_table_addr_pys, nav_table_size, block_dim); - } else { - for (unsigned i = 0; i < stub_func_list.size(); i++) { - void *sub_device_func = nullptr; - rt_ret = rtGetAddrByFun(stub_func_list[i], &sub_device_func); - GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtGetAddrByFun failed. error: 0x%X", rt_ret); - return FAILED;) - GELOGD("SKT: fuseKernels subFunc %p, device func address %p", stub_func_list[i], sub_device_func); - // store two uint64_t address - // address divided by 4 because of 32bits encoding, call offset will *4 when calculating - nav_table[i * 2] = reinterpret_cast(reinterpret_cast(sub_device_func)) / 4; - GELOGD("SKT: CALL offet %lu", nav_table[i * 2]); - nav_table[i * 2 + 1] = reinterpret_cast(reinterpret_cast(args_addr_list[i])); - GELOGD("SKT: fuseKernels args base address %lu", nav_table[i * 2 + 1]); - } - rt_ret = rtMalloc((void **)&hbm_nav_table_addr, nav_table_size, RT_MEMORY_HBM); - GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtMalloc failed. error: 0x%X", rt_ret); return FAILED;) - rt_ret = - rtMemcpy((void *)hbm_nav_table_addr, nav_table_size, (void *)nav_table, nav_table_size, RT_MEMCPY_HOST_TO_DEVICE); - GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtMemcpy failed. error: 0x%X", rt_ret); - GE_CHK_RT(rtFree(hbm_nav_table_addr)); return FAILED;) - // Create the necessary metadata for the super kernel - h = new SuperKernel(this->func_stub_, hbm_nav_table_addr, nav_table_size, block_dim); + for (unsigned i = 0; i < stub_func_list.size(); i++) { + void *sub_device_func = nullptr; + rt_ret = rtGetAddrByFun(stub_func_list[i], &sub_device_func); + GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtGetAddrByFun failed. error: 0x%X", rt_ret); + return FAILED;) + GELOGD("SKT: fuseKernels subFunc %p, device func address %p", stub_func_list[i], sub_device_func); + // store two uint64_t address + // address divided by 4 because of 32bits encoding, call offset will *4 when calculating + nav_table[i * 2] = reinterpret_cast(reinterpret_cast(sub_device_func)) / 4; + GELOGD("SKT: CALL offet %lu", nav_table[i * 2]); + nav_table[i * 2 + 1] = reinterpret_cast(reinterpret_cast(args_addr_list[i])); + GELOGD("SKT: fuseKernels args base address %lu", nav_table[i * 2 + 1]); } + rt_ret = rtMalloc((void **)&hbm_nav_table_addr, nav_table_size, RT_MEMORY_HBM); + GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtMalloc failed. error: 0x%X", rt_ret); return FAILED;) + rt_ret = + rtMemcpy((void *)hbm_nav_table_addr, nav_table_size, (void *)nav_table, nav_table_size, RT_MEMCPY_HOST_TO_DEVICE); + GE_IF_BOOL_EXEC(rt_ret != RT_ERROR_NONE, GELOGE(rt_ret, "rtMemcpy failed. error: 0x%X", rt_ret); + GE_CHK_RT(rtFree(hbm_nav_table_addr)); return FAILED;) + // Create the necessary metadata for the super kernel + h = + std::unique_ptr(new SuperKernel(this->func_stub_, hbm_nav_table_addr, nav_table_size, block_dim)); return SUCCESS; } } // namespace skt diff --git a/src/ge/graph/load/new_model_manager/task_info/super_kernel/super_kernel_factory.h b/src/ge/graph/load/new_model_manager/task_info/super_kernel/super_kernel_factory.h index d8b7ff26..7db44eec 100644 --- a/src/ge/graph/load/new_model_manager/task_info/super_kernel/super_kernel_factory.h +++ b/src/ge/graph/load/new_model_manager/task_info/super_kernel/super_kernel_factory.h @@ -29,7 +29,6 @@ class SuperKernelFactory { void *func_ptr_ = nullptr; void *handle_ = nullptr; std::string sk_stub_name_ = "_Z21super_kernel_templatePmm"; - const char *use_physical_address_ = getenv("GE_USE_PHYSICAL_ADDRESS"); bool is_init_ = false; SuperKernelFactory(){}; ~SuperKernelFactory() { @@ -48,7 +47,7 @@ class SuperKernelFactory { Status Init(); Status Uninitialize(); Status FuseKernels(const std::vector &stub_func_list, const std::vector &args_addr_list, - uint32_t block_dim, SuperKernel *&h); + uint32_t block_dim, std::unique_ptr &h); }; } // namespace skt } // namespace ge diff --git a/src/ge/graph/load/new_model_manager/task_info/task_info.h b/src/ge/graph/load/new_model_manager/task_info/task_info.h index 5d2c89eb..f69511e6 100644 --- a/src/ge/graph/load/new_model_manager/task_info/task_info.h +++ b/src/ge/graph/load/new_model_manager/task_info/task_info.h @@ -72,6 +72,8 @@ class TaskInfo { virtual uint32_t GetTaskID() { return 0xFFFFFFFF; } + virtual bool CallSaveDumpInfo() { return false; } + virtual uint32_t GetStreamId() { return 0xFFFFFFFF; } virtual uintptr_t GetDumpArgs() { return 0; } diff --git a/src/ge/graph/load/new_model_manager/task_info/task_info_factory.h b/src/ge/graph/load/new_model_manager/task_info/task_info_factory.h index b6954016..5b220960 100644 --- a/src/ge/graph/load/new_model_manager/task_info/task_info_factory.h +++ b/src/ge/graph/load/new_model_manager/task_info/task_info_factory.h @@ -86,5 +86,5 @@ class TaskInfoFactory { return ptr; \ } \ TaskInfoFactory::Registerar g_##type##_Task_Info_Creator(type, Creator_##type##_Task_Info); -}; // namespace ge +} // namespace ge #endif // GE_GRAPH_LOAD_NEW_MODEL_MANAGER_TASK_INFO_TASK_INFO_FACTORY_H_ diff --git a/src/ge/graph/load/new_model_manager/zero_copy_task.cc b/src/ge/graph/load/new_model_manager/zero_copy_task.cc index 42734a87..be75322d 100644 --- a/src/ge/graph/load/new_model_manager/zero_copy_task.cc +++ b/src/ge/graph/load/new_model_manager/zero_copy_task.cc @@ -129,12 +129,6 @@ Status ZeroCopyTask::UpdateTaskParam(uintptr_t addr, const DataBuffer &data, } auto dst_addr = static_cast(data.data); - auto dst_size = static_cast(data.length); - if (ModelUtils::ConvertVirtualAddressToPhysical(dst_addr, dst_size, dst_addr) != SUCCESS) { - GELOGE(FAILED, "[ZCPY] Convert virtual address to physical for dst_addr failed."); - return FAILED; - } - GELOGI("[ZCPY] %s update task, args: %p, size: %zu, offset: %zu, addr: 0x%lx, length: %u", name_.c_str(), args_addr_, args_size_, offset, addr, data.length); *(uintptr_t *)(args_info + offset) = reinterpret_cast(dst_addr); diff --git a/src/ge/graph/load/output/output.cc b/src/ge/graph/load/output/output.cc deleted file mode 100644 index d922ce7c..00000000 --- a/src/ge/graph/load/output/output.cc +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright 2019-2020 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "graph/load/output/output.h" - -#include - -#include "common/properties_manager.h" -#include "graph/load/new_model_manager/davinci_model.h" -#include "graph/manager/graph_var_manager.h" -#include "graph/utils/op_desc_utils.h" -#include "graph/utils/tensor_utils.h" - -namespace ge { -Output::Output(const OpDescPtr &op_desc, DavinciModel *model) - : base_(nullptr), - var_base_(nullptr), - logic_base_(0), - logic_var_base_(0), - model_(model), - op_desc_(op_desc), - input_num_(0) {} - -Output::~Output() { - var_base_ = nullptr; - base_ = nullptr; - model_ = nullptr; -} - -/// -/// @ingroup domi -/// @brief Initialize input/output params -/// @return Status -/// -Status Output::Init() { - if (op_desc_ == nullptr || model_ == nullptr) { - GELOGE(INTERNAL_ERROR, "The op_desc_ or model_ is nullptr."); - return INTERNAL_ERROR; - } - - base_ = model_->MemBase(); - var_base_ = model_->VarMemBase(); - logic_base_ = model_->GetRtBaseAddr(); - logic_var_base_ = model_->GetRtVarAddr(); - - input_num_ = op_desc_->GetInputsSize(); - v_input_size_.clear(); - v_input_data_addr_.clear(); - - auto input_vector = op_desc_->GetInputOffset(); - if (input_num_ != input_vector.size()) { - GELOGE(INTERNAL_ERROR, "input desc size: %zu != input offset size: %zu.", input_num_, input_vector.size()); - return INTERNAL_ERROR; - } - - for (size_t i = 0; i < input_num_; i++) { - int64_t tensor_size = 0; - auto input_desc = op_desc_->GetInputDescPtr(i); - GE_CHECK_NOTNULL(input_desc); - Status ret = TensorUtils::GetSize(*input_desc, tensor_size); - if (ret != GRAPH_SUCCESS) { - GELOGE(ret, "Get size from TensorDesc failed, op : %s, input index : %zu", op_desc_->GetName().c_str(), i); - return ret; - } - v_input_size_.push_back(tensor_size); - - if (VarManager::Instance(model_->SessionId())->IsVarAddr(input_vector[i])) { - v_input_data_addr_.push_back(static_cast(var_base_ + input_vector[i] - logic_var_base_)); - } else { - v_input_data_addr_.push_back(static_cast(base_ + input_vector[i])); - } - } - - GELOGI("Init output:%lu, %lu, %lu", input_num_, v_input_size_.size(), v_input_data_addr_.size()); - - return SUCCESS; -} - -/// -/// @ingroup domi -/// @brief Copy Op Output to user space. -/// @brief when model running, Add one DataOp as input node, Add one Output Op as output node. -/// @return Status -/// -Status Output::CopyResult(OutputData &rslt, uint32_t data_begin, uint32_t &data_index, bool support_mem_share) { - uint32_t data_count = 0; - if (input_num_ > rslt.blobs.size() - data_begin) { - GELOGE(FAILED, "Tensor num %zu, data_buf num: %zu.", input_num_, rslt.blobs.size() - data_begin); - return FAILED; - } else if (input_num_ < rslt.blobs.size() - data_begin) { - GELOGW("Tensor num %zu, data_buf num: %zu.", input_num_, rslt.blobs.size() - data_begin); - } - - for (size_t i = 0; i < input_num_; i++) { - DataBuffer data_buf = rslt.blobs[data_begin + data_count]; - Status ret = SetDataBuf(data_buf, data_count, i, support_mem_share); - if (ret != SUCCESS) { - GELOGE(ret, "Copy data to host error. index: %zu", i); - return ret; - } - data_index = data_begin + data_count; - } - - return SUCCESS; -} - -Status Output::SetDataBuf(DataBuffer &data_buf, uint32_t &data_count, size_t i, bool support_mem_share) { - if (data_buf.length == 0) { - ++data_count; - GELOGD("Length of data_buffer is zero, No need to copy. output op : %s, output tensor index : %zu!", - op_desc_->GetName().c_str(), i); - return SUCCESS; - } - - auto tensor_desc = op_desc_->GetInputDescPtr(static_cast(i)); - if (tensor_desc == nullptr) { - GELOGE(FAILED, "tensor_desc is null"); - return FAILED; - } - - if (data_buf.isDataSupportMemShare && support_mem_share) { - GELOGI("No need to copy input data, user's output data buffer can be shared."); - } else { - // Copy result to Databuf - int64_t size = v_input_size_[i]; - GELOGI("Tensor data size before: %ld", size); - - graphStatus graph_status = TensorUtils::GetTensorSizeInBytes(*tensor_desc, size); - if (graph_status != ge::GRAPH_SUCCESS) { - GELOGE(graph_status, "GetTensorSizeInBytes failed!"); - return FAILED; - } - - if (data_buf.length < size) { - GELOGE(FAILED, "Tensor data size: %ld data_buf length: %ld", size, data_buf.length); - return FAILED; - } else if (data_buf.length > size) { - GELOGW("Tensor data size: %ld data_buf length: %ld", size, data_buf.length); - } - - rtError_t rt_ret = rtMemcpy(data_buf.data, size, v_input_data_addr_[i], size, RT_MEMCPY_DEVICE_TO_HOST); - if (rt_ret != RT_ERROR_NONE) { - GELOGE(rt_ret, "rtmemcpy error"); - return FAILED; - } - GELOGI("Tensor data size: %ld data_buf length: %ld", size, data_buf.length); - } - - ++data_count; - GELOGD("Successfully copy the output tensor memory to buffer, output op : %s, output tensor index : %zu!", - op_desc_->GetName().c_str(), i); - - return SUCCESS; -} - -void Output::GetOutputData(vector &v_data_addr, vector &v_data_size) { - for (size_t i = 0; i < input_num_; ++i) { - v_data_addr.push_back(v_input_data_addr_[i]); - v_data_size.push_back(v_input_size_[i]); - } -} -} // namespace ge diff --git a/src/ge/graph/load/output/output.h b/src/ge/graph/load/output/output.h deleted file mode 100644 index d93b8de9..00000000 --- a/src/ge/graph/load/output/output.h +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright 2019-2020 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef GE_GRAPH_LOAD_OUTPUT_OUTPUT_H_ -#define GE_GRAPH_LOAD_OUTPUT_OUTPUT_H_ - -#include -#include - -#include "common/debug/log.h" -#include "common/op/attr_value_util.h" -#include "common/op/ge_op_utils.h" -#include "common/types.h" -#include "common/util.h" -#include "common/ge_types.h" -#include "graph/load/new_model_manager/davinci_model.h" -#include "graph/op_desc.h" -#include "graph/debug/ge_attr_define.h" - -namespace ge { -using std::string; -using std::vector; - -// The base class for all op -class Output { - public: - Output(const OpDescPtr &op_desc, DavinciModel *model); - virtual ~Output(); - - /// - /// @ingroup domi - /// @brief Initialize input/output params - /// @return Status - /// - virtual Status Init(); - - /// - /// @ingroup domi - /// @brief Copy Op Output to user space. - /// @brief when model running, Add one DataOp as input node, Add one Output Op as output node. - /// @return Status - /// - virtual Status CopyResult(OutputData &rslt, uint32_t data_begin, uint32_t &data_index, bool support_mem_share); - - /// - /// @ingroup domi - /// @brief Trans Output data to fp16 - /// @return Status - /// - Status SetDataBuf(DataBuffer &data_buf, uint32_t &data_count, size_t i, bool support_mem_share); - - /// - /// @ingroup domi - /// @brief Get Output data and size. - /// @return void - /// - void GetOutputData(vector &v_data_addr, vector &v_data_size); - - // Copy assignment operator and copy constructor are deleted - Output &operator=(const Output &output) = delete; - Output(const Output &output) = delete; - - protected: - // Model's base address - uint8_t *base_; - uint8_t *var_base_; - uint64_t logic_base_; - uint64_t logic_var_base_; - // The DavinciModel which ops belong to - DavinciModel *model_; - - ConstOpDescPtr op_desc_; - - // Input descriptions - size_t input_num_; - vector v_input_data_addr_; // init as:buf_base + op_def_->input(i)); - vector v_input_size_; -}; -} // namespace ge - -#endif // GE_GRAPH_LOAD_OUTPUT_OUTPUT_H_ diff --git a/src/ge/graph/manager/graph_caching_allocator.cc b/src/ge/graph/manager/graph_caching_allocator.cc index 5df6769b..cbeafa3f 100644 --- a/src/ge/graph/manager/graph_caching_allocator.cc +++ b/src/ge/graph/manager/graph_caching_allocator.cc @@ -34,9 +34,6 @@ const size_t bin_ranges[kNumBins] = {kRoundBlockSize * kKByteSize, 26 * kGByteSize}; static bool BlockComparator(const Block *left, const Block *right) { - if (left->device_id != right->device_id) { - return left->device_id < right->device_id; - } if (left->size != right->size) { return left->size < right->size; } @@ -267,20 +264,20 @@ Status CachingAllocator::TryExtendCache(size_t size, uint32_t device_id) { return ge::FAILED; } } - if (AddToBlockBin(memory_addr, memory_size) != ge::SUCCESS) { + if (AddToBlockBin(memory_addr, memory_size, device_id) != ge::SUCCESS) { (void)memory_allocator_->FreeMemory(memory_addr); return ge::FAILED; } return ge::SUCCESS; } -Status CachingAllocator::AddToBlockBin(uint8_t *ptr, size_t size) { +Status CachingAllocator::AddToBlockBin(uint8_t *ptr, size_t size, uint32_t device_id) { BlockBin *bin = GetBlockBin(size); if (bin == nullptr) { GELOGE(ge::FAILED, "Get block bin failed size = %zu", size); return ge::FAILED; } - Block *block = new (std::nothrow) Block(0, size, bin, nullptr); + Block *block = new (std::nothrow) Block(device_id, size, bin, nullptr); if (block == nullptr) { GELOGE(ge::FAILED, "Alloc block failed size = %zu", size); return ge::FAILED; @@ -339,5 +336,4 @@ void CachingAllocator::FreeBlockBins() { } } } - } // namespace ge diff --git a/src/ge/graph/manager/graph_caching_allocator.h b/src/ge/graph/manager/graph_caching_allocator.h index 75864ce7..94a5066a 100644 --- a/src/ge/graph/manager/graph_caching_allocator.h +++ b/src/ge/graph/manager/graph_caching_allocator.h @@ -32,7 +32,6 @@ #include "runtime/mem.h" namespace ge { - constexpr size_t kRoundBlockSize = 512; // all block sizes are rounded to at least 512 bytes constexpr double kSplitThreshold = 0.75; // split when malloc size <= small block size * kSpliThreshold constexpr size_t kKByteSize = 1024; @@ -69,6 +68,10 @@ class CachingAllocator { public: explicit CachingAllocator(rtMemType_t memory_type); + CachingAllocator(const CachingAllocator &) = delete; + + CachingAllocator &operator=(const CachingAllocator &) = delete; + virtual ~CachingAllocator() = default; /// @@ -137,9 +140,10 @@ class CachingAllocator { /// @brief add memory to right bin based on size /// @param [in] memory ptr /// @param [in] memory size + /// @param [in] device_id device id /// @return Status result of function /// - Status AddToBlockBin(uint8_t *ptr, size_t size); + Status AddToBlockBin(uint8_t *ptr, size_t size, uint32_t device_id); /// /// @ingroup ge_graph @@ -206,7 +210,5 @@ class CachingAllocator { // block bins by different block size BlockBin *free_block_bins_[kNumBins]; }; - -}; // namespace ge - +} // namespace ge #endif // GE_GRAPH_MANAGER_GRAPH_CACHING_ALLOCATOR_H_ diff --git a/src/ge/graph/manager/graph_manager.cc b/src/ge/graph/manager/graph_manager.cc index dd4855b6..bfd09c72 100644 --- a/src/ge/graph/manager/graph_manager.cc +++ b/src/ge/graph/manager/graph_manager.cc @@ -57,7 +57,6 @@ #include "graph/passes/flow_ctrl_pass.h" #include "graph/passes/hccl_group_pass.h" #include "graph/passes/hccl_memcpy_pass.h" -#include "graph/passes/identify_reference_pass.h" #include "graph/passes/identity_pass.h" #include "graph/passes/iterator_op_pass.h" #include "graph/passes/link_gen_mask_nodes_pass.h" @@ -74,7 +73,9 @@ #include "graph/passes/switch_data_edges_bypass.h" #include "graph/passes/switch_dead_branch_elimination.h" #include "graph/passes/switch_logic_remove_pass.h" -#include "graph/passes/switch_op_pass.h" +#include "graph/passes/merge_to_stream_merge_pass.h" +#include "graph/passes/switch_to_stream_switch_pass.h" +#include "graph/passes/attach_stream_label_pass.h" #include "graph/passes/transop_breadth_fusion_pass.h" #include "graph/passes/transop_depth_fusion_pass.h" #include "graph/passes/transop_nearby_allreduce_fusion_pass.h" @@ -83,6 +84,7 @@ #include "graph/passes/transpose_transdata_pass.h" #include "graph/passes/variable_op_pass.h" #include "graph/passes/variable_prepare_op_pass.h" +#include "graph/passes/ref_identity_delete_op_pass.h" #include "graph/passes/variable_ref_delete_op_pass.h" #include "graph/passes/variable_ref_useless_control_out_delete_pass.h" #include "graph/utils/tensor_adapter.h" @@ -347,12 +349,13 @@ Status GraphManager::SetSubgraph(uint64_t session_id, ComputeGraphPtr compute_gr return SUCCESS; } -#define GM_RUN_AND_DUMP(name, func, ...) \ +#define GM_RUN_AND_DUMP_PERF(name, func, ...) \ do { \ - GE_RUN(GraphManager, func, __VA_ARGS__); \ + GE_RUN_PERF(GraphManager, func, __VA_ARGS__); \ GE_DUMP(compute_graph, "PreRunAfter" name); \ GELOGI("Run %s on graph %s(%u) success.", name, compute_graph->GetName().c_str(), graph_node->GetGraphId()); \ } while (0) + Status GraphManager::PreRun(const GraphNodePtr &graph_node, const std::vector &inputs, GeRootModelPtr &ge_root_model, uint64_t session_id) { GE_CHECK_NOTNULL(graph_node); @@ -365,30 +368,30 @@ Status GraphManager::PreRun(const GraphNodePtr &graph_node, const std::vectorGetName().c_str()); GE_DUMP(compute_graph, "PreRunBegin"); - GM_RUN_AND_DUMP("OptimizeGraphPrepare", graph_optimize_.OptimizeOriginalGraphForQuantize, compute_graph); - GM_RUN_AND_DUMP("HandleSummaryOp", graph_optimize_.HandleSummaryOp, compute_graph); - GM_RUN_AND_DUMP("Prepare", graph_preparer_.PrepareDynShape, graph_node->GetGraph(), inputs, compute_graph, - session_id); - GM_RUN_AND_DUMP("OptimizeOriginalGraph", graph_optimize_.OptimizeOriginalGraph, compute_graph); + GM_RUN_AND_DUMP_PERF("OptimizeGraphPrepare", graph_optimize_.OptimizeOriginalGraphForQuantize, compute_graph); + GM_RUN_AND_DUMP_PERF("HandleSummaryOp", graph_optimize_.HandleSummaryOp, compute_graph); + GM_RUN_AND_DUMP_PERF("Prepare", graph_preparer_.PrepareDynShape, graph_node->GetGraph(), inputs, compute_graph, + session_id); + GM_RUN_AND_DUMP_PERF("OptimizeOriginalGraph", graph_optimize_.OptimizeOriginalGraph, compute_graph); - GM_RUN_AND_DUMP("PrepareRunningFormatRefiner", graph_preparer_.PrepareRunningFormatRefiner); - GM_RUN_AND_DUMP("RefineRunningFormat", graph_optimize_.OptimizeOriginalGraphJudgeInsert, compute_graph); + GM_RUN_AND_DUMP_PERF("PrepareRunningFormatRefiner", graph_preparer_.PrepareRunningFormatRefiner); + GM_RUN_AND_DUMP_PERF("RefineRunningFormat", graph_optimize_.OptimizeOriginalGraphJudgeInsert, compute_graph); GE_RUN(GraphManager, graph_preparer_.RecordAIPPInfo, compute_graph); if (IsTailingOptimization()) { - GM_RUN_AND_DUMP("OptimizeSwitchOp", graph_preparer_.SwitchOpOptimize, compute_graph); + GM_RUN_AND_DUMP_PERF("OptimizeSwitchOp", graph_preparer_.SwitchOpOptimize, compute_graph); } - GM_RUN_AND_DUMP("Optimize1", OptimizeStage1, compute_graph); - GM_RUN_AND_DUMP("InferShape2", compute_graph->InferShapeInNeed); + GM_RUN_AND_DUMP_PERF("Optimize1", OptimizeStage1, compute_graph); + GM_RUN_AND_DUMP_PERF("InferShape2", compute_graph->InferShapeInNeed); const char *unknown_shape_skip = std::getenv("EXPERIMENTAL_DYNAMIC_PARTITION"); if (unknown_shape_skip != nullptr) { PassManager graph_pass; GE_CHK_STATUS_RET(graph_pass.AddPass("PreRun::CtrlEdgeTransferPass", new (std::nothrow) CtrlEdgeTransferPass)) GE_CHK_STATUS_RET(graph_pass.Run(compute_graph)); } - - GM_RUN_AND_DUMP("OptimizeSubgraph", OptimizeSubgraph, graph_node, compute_graph, session_id); - GM_RUN_AND_DUMP("Optimize2", OptimizeStage2, compute_graph); - GM_RUN_AND_DUMP("Build", Build, graph_node, compute_graph, ge_root_model, session_id); + GE_CHK_STATUS_RET(graph_optimize_.IdentifyReference(compute_graph), "Identify reference failed."); + GM_RUN_AND_DUMP_PERF("OptimizeSubgraph", OptimizeSubgraph, graph_node, compute_graph, session_id); + GM_RUN_AND_DUMP_PERF("Optimize2", OptimizeStage2, compute_graph); + GM_RUN_AND_DUMP_PERF("Build", Build, graph_node, compute_graph, ge_root_model, session_id); // when set incre build, save om model and var manager GeModelPtr ge_model = nullptr; @@ -397,7 +400,7 @@ Status GraphManager::PreRun(const GraphNodePtr &graph_node, const std::vectorSetRunFlag(false); @@ -634,7 +637,7 @@ Status GraphManager::RunGraph(const GraphId &graph_id, const std::vectorgraph_run_async_listener_); Status ret = GraphLoader::LoadModelOnline(model_id_info.model_id, ge_root_model, graph_node->graph_run_async_listener_); - GE_TIMESTAMP_END(LoadGraph, "GraphManager::LoadGraphAsync"); + GE_TIMESTAMP_EVENT_END(LoadGraph, "GraphManager::LoadGraphAsync"); if (ret != SUCCESS) { GELOGE(ret, "[LoadGraphAsync] LoadGraphAsync Failed"); graph_node->SetRunFlag(false); @@ -2309,21 +2331,21 @@ Status GraphManager::OptimizeSubgraph(const GraphNodePtr &graph_node, ComputeGra GELOGE(FAILED, "failed get dynamic shape partitioned flag on partitioned graph."); return FAILED; } - GE_TIMESTAMP_END(GraphPartitionDynamicShape, "OptimizeSubgraph::GraphPartitionDynamicShape"); + GE_TIMESTAMP_EVENT_END(GraphPartitionDynamicShape, "OptimizeSubgraph::GraphPartitionDynamicShape"); GE_TIMESTAMP_START(GraphPartition); ret = graph_partitioner_.Partition(compute_graph, GraphPartitioner::kPartitioning); if (ret != SUCCESS) { GELOGE(ret, "Graph partition Failed"); return ret; } - GE_TIMESTAMP_END(GraphPartition, "OptimizeSubgraph::Partition1"); + GE_TIMESTAMP_EVENT_END(GraphPartition, "OptimizeSubgraph::Partition1"); GE_TIMESTAMP_START(SetSubgraph); ret = SetSubgraph(session_id, compute_graph); if (ret != SUCCESS) { GELOGE(ret, "Graph set subgraph Failed"); return ret; } - GE_TIMESTAMP_END(SetSubgraph, "OptimizeSubgraph::SetSubGraph"); + GE_TIMESTAMP_EVENT_END(SetSubgraph, "OptimizeSubgraph::SetSubGraph"); ComputeGraphPtr merged_compute_graph = nullptr; std::vector merged_sub_graph_list; @@ -2342,7 +2364,7 @@ Status GraphManager::OptimizeSubgraph(const GraphNodePtr &graph_node, ComputeGra sub_graph->SetSessionID(session_id); sub_graph->SetGraphID(graph_node->GetGraphId()); } - GE_TIMESTAMP_END(MergeSubgraph, "OptimizeSubgraph::MergeSubGraph"); + GE_TIMESTAMP_EVENT_END(MergeSubgraph, "OptimizeSubgraph::MergeSubGraph"); GE_DUMP(merged_compute_graph, "mergedComputeGraph"); compute_graph = merged_compute_graph; if (!AttrUtils::SetBool(*compute_graph, ATTR_NAME_DYNAMIC_SHAPE_PARTITIONED, dynamic_shape_partitioned)) { @@ -2368,8 +2390,7 @@ Status GraphManager::Build(const GraphNodePtr &graph_node, ComputeGraphPtr &comp } bool is_always_dump = false; - PropertiesManager &properties_manager = PropertiesManager::Instance(); - if (!properties_manager.GetDumpOutputPath().empty()) { + if (!PropertiesManager::Instance().GetDumpProperties(session_id).GetDumpPath().empty()) { is_always_dump = true; } diff --git a/src/ge/graph/manager/graph_manager.h b/src/ge/graph/manager/graph_manager.h index 8ab28316..fd9542e8 100644 --- a/src/ge/graph/manager/graph_manager.h +++ b/src/ge/graph/manager/graph_manager.h @@ -327,6 +327,6 @@ class GraphManager { std::mutex run_mutex_; }; -}; // namespace ge +} // namespace ge #endif // GE_GRAPH_MANAGER_GRAPH_MANAGER_H_ diff --git a/src/ge/graph/manager/graph_mem_allocator.h b/src/ge/graph/manager/graph_mem_allocator.h index 7bf82897..e4eeded3 100644 --- a/src/ge/graph/manager/graph_mem_allocator.h +++ b/src/ge/graph/manager/graph_mem_allocator.h @@ -190,6 +190,6 @@ class MemManager { std::map caching_allocator_map_; std::recursive_mutex allocator_mutex_; }; -}; // namespace ge +} // namespace ge #endif // GE_GRAPH_MANAGER_GRAPH_MEM_ALLOCATOR_H_ diff --git a/src/ge/graph/manager/graph_var_manager.cc b/src/ge/graph/manager/graph_var_manager.cc index 2982eb89..7ca0224b 100644 --- a/src/ge/graph/manager/graph_var_manager.cc +++ b/src/ge/graph/manager/graph_var_manager.cc @@ -91,7 +91,7 @@ ge::Status VarResource::SaveVarAddr(const std::string &var_name, const ge::GeTen std::string var_key = VarKey(var_name, tensor_desc); GELOGD("VarResource::SaveVarAddr, var_key = %s", var_key.c_str()); if (var_addr_mgr_map_.count(var_key) == 0) { - uint64_t logic_address = VarManager::Instance(0)->GetVarMemLogicBase() + + uint64_t logic_address = VarManager::Instance(session_id_)->GetVarMemLogicBase() + reinterpret_cast(reinterpret_cast(address)); GELOGI("SaveVarAddr node_name %s, tensor_desc format %s, type %s.", var_name.c_str(), TypeUtils::FormatToSerialString(tensor_desc.GetFormat()).c_str(), @@ -274,7 +274,7 @@ MemResource::MemResource() : total_size_(0), var_mem_size_(0) {} Status MemResource::AssignVarMem(const std::string &var_name, uint64_t size, uint64_t session_id, size_t &mem_offset) { size = (size + kSessionMemAlignSize - 1) / kSessionMemAlignSize * kSessionMemAlignSize; uint64_t real_size = size; - total_size_ = VarManager::Instance(0)->GetVarMemMaxSize(); + total_size_ = VarManager::Instance(session_id)->GetVarMemMaxSize(); if (total_size_ < var_mem_size_) { GELOGE(PARAM_INVALID, "total_size_: %lu is smaller than var_mem_size_: %lu", total_size_, var_mem_size_); return PARAM_INVALID; @@ -684,7 +684,8 @@ uint8_t *VarManager::GetVarMemoryAddr(uint8_t *logic_addr, rtMemType_t memory_ty if (mem_base == nullptr) { return nullptr; } - uint8_t *mem_addr = logic_addr + reinterpret_cast(mem_base) - VarManager::Instance(0)->GetVarMemLogicBase(); + uint8_t *mem_addr = + logic_addr + reinterpret_cast(mem_base) - VarManager::Instance(session_id_)->GetVarMemLogicBase(); return mem_addr; } diff --git a/src/ge/graph/manager/graph_var_manager.h b/src/ge/graph/manager/graph_var_manager.h index be839eee..2142d906 100644 --- a/src/ge/graph/manager/graph_var_manager.h +++ b/src/ge/graph/manager/graph_var_manager.h @@ -309,5 +309,5 @@ class VarManagerPool { std::mutex var_manager_mutex_; map var_manager_map_; }; -}; // namespace ge +} // namespace ge #endif // GE_GRAPH_MANAGER_GRAPH_VAR_MANAGER_H_ diff --git a/src/ge/graph/manager/model_manager/event_manager.h b/src/ge/graph/manager/model_manager/event_manager.h index 1d57dd52..a20afead 100644 --- a/src/ge/graph/manager/model_manager/event_manager.h +++ b/src/ge/graph/manager/model_manager/event_manager.h @@ -17,7 +17,6 @@ #ifndef GE_GRAPH_MANAGER_MODEL_MANAGER_EVENT_MANAGER_H_ #define GE_GRAPH_MANAGER_MODEL_MANAGER_EVENT_MANAGER_H_ - #include #include "common/fmk_error_codes.h" @@ -94,5 +93,5 @@ class EventManager { bool inited_; uint32_t current_idx_; }; // EventManager -}; // namespace ge +} // namespace ge #endif // GE_GRAPH_MANAGER_MODEL_MANAGER_EVENT_MANAGER_H_ diff --git a/src/ge/graph/manager/trans_var_data_utils.cc b/src/ge/graph/manager/trans_var_data_utils.cc index e8444c53..3f346c91 100644 --- a/src/ge/graph/manager/trans_var_data_utils.cc +++ b/src/ge/graph/manager/trans_var_data_utils.cc @@ -397,10 +397,11 @@ Status TransVarDataUtils::SyncTensorToHost(const string &var_name, const ge::GeT uint8_t *src_addr = nullptr; GE_CHK_STATUS_RET(VarManager::Instance(session_id)->GetVarAddr(var_name, src_tensor_desc, &src_addr)); - uint8_t *mem_addr = src_addr - - static_cast(reinterpret_cast(VarManager::Instance(0)->GetVarMemLogicBase())) + - static_cast( - reinterpret_cast(VarManager::Instance(session_id)->GetVarMemoryBase(RT_MEMORY_HBM))); + uint8_t *mem_addr = + src_addr - + static_cast(reinterpret_cast(VarManager::Instance(session_id)->GetVarMemLogicBase())) + + static_cast( + reinterpret_cast(VarManager::Instance(session_id)->GetVarMemoryBase(RT_MEMORY_HBM))); GE_CHK_RT_RET(rtMallocHost(reinterpret_cast(host_addr), src_tensor_size)); GE_CHK_RT_RET(rtMemcpy(*host_addr, src_tensor_size, mem_addr, src_tensor_size, RT_MEMCPY_DEVICE_TO_HOST)); @@ -413,10 +414,11 @@ Status TransVarDataUtils::SyncTensorToDevice(const string &var_name, const uint8 const ge::GeTensorDesc &dst_tensor_desc, uint64_t session_id) { uint8_t *dst_addr = nullptr; GE_CHK_STATUS_RET(VarManager::Instance(session_id)->GetVarAddr(var_name, dst_tensor_desc, &dst_addr)); - uint8_t *mem_addr = dst_addr - - static_cast(reinterpret_cast(VarManager::Instance(0)->GetVarMemLogicBase())) + - static_cast( - reinterpret_cast(VarManager::Instance(session_id)->GetVarMemoryBase(RT_MEMORY_HBM))); + uint8_t *mem_addr = + dst_addr - + static_cast(reinterpret_cast(VarManager::Instance(session_id)->GetVarMemLogicBase())) + + static_cast( + reinterpret_cast(VarManager::Instance(session_id)->GetVarMemoryBase(RT_MEMORY_HBM))); GE_CHK_RT_RET(rtMemcpy(mem_addr, addr_size, host_addr, addr_size, RT_MEMCPY_HOST_TO_DEVICE)); GELOGI("SyncTensorToDevice var_name %s, addr_size %u", var_name.c_str(), addr_size); diff --git a/src/ge/graph/manager/util/hcom_util.cc b/src/ge/graph/manager/util/hcom_util.cc index 4f6fe591..5f31c982 100644 --- a/src/ge/graph/manager/util/hcom_util.cc +++ b/src/ge/graph/manager/util/hcom_util.cc @@ -24,7 +24,6 @@ #include "graph/utils/type_utils.h" namespace ge { - Status HcomOmeUtil::GetHcclDataType(const ge::ConstOpDescPtr &op_desc, std::vector &kernel_hccl_infos) { GE_CHECK_NOTNULL(op_desc); @@ -101,6 +100,12 @@ Status HcomOmeUtil::GetHcomCount(const ge::ConstOpDescPtr &op_desc, hcclDataType GE_CHECK_NOTNULL(op_desc->GetInputDescPtr(i)); GE_CHK_STATUS_RET(ge::TensorUtils::GetSize(*op_desc->GetInputDescPtr(i), input_size), "get size from TensorDesc failed, op : %s, input index : %zu", op_desc->GetName().c_str(), i); + // dynamic shape hccl op get size from output tensor desc + if (op_desc->HasAttr(ATTR_NAME_IS_UNKNOWN_SHAPE)) { + GE_CHECK_NOTNULL(op_desc->GetOutputDescPtr(i)); + GE_CHK_STATUS_RET(ge::TensorUtils::GetSize(*op_desc->GetOutputDescPtr(i), input_size), + "get size from TensorDesc failed, op : %s, input index : %zu", op_desc->GetName().c_str(), i); + } GE_IF_BOOL_EXEC( op_desc->GetType() == HCOMREDUCESCATTER, int32_t rank_size = 0; @@ -114,6 +119,8 @@ Status HcomOmeUtil::GetHcomCount(const ge::ConstOpDescPtr &op_desc, hcclDataType total_size = total_size + block_size; continue;); int64_t shape_size = op_desc->GetInputDescPtr(i)->GetShape().GetShapeSize(); + GELOGD("hcom util node %s inputsize %ld, shapesize %ld, datasize %d.", op_desc->GetName().c_str(), input_size, + shape_size, size); GE_CHK_STATUS_RET(ge::CheckInt64Int32MulOverflow(shape_size, size), "Product of shape size and size beyond INT64_MAX"); GE_IF_BOOL_EXEC(is_allgather, block_size = shape_size * size;); diff --git a/src/ge/graph/manager/util/hcom_util.h b/src/ge/graph/manager/util/hcom_util.h index 40aac3e5..e31e3ef0 100644 --- a/src/ge/graph/manager/util/hcom_util.h +++ b/src/ge/graph/manager/util/hcom_util.h @@ -144,8 +144,6 @@ class HcomOmeUtil { /// static Status GetHorovodInputs(const ge::ConstOpDescPtr &op_desc, std::vector &kernel_hccl_infos); - - private: /// /// @ingroup domi_ome /// @brief GetHcomCount @@ -154,6 +152,8 @@ class HcomOmeUtil { /// static Status GetHcomCount(const ge::ConstOpDescPtr &op_desc, hcclDataType_t data_type, bool is_allgather, int &count); + + private: /// /// @ingroup domi_ome /// @brief GetHorovodCount diff --git a/src/ge/graph/manager/util/rt_context_util.cc b/src/ge/graph/manager/util/rt_context_util.cc index 05120f6a..e6344539 100644 --- a/src/ge/graph/manager/util/rt_context_util.cc +++ b/src/ge/graph/manager/util/rt_context_util.cc @@ -19,13 +19,30 @@ #include "framework/common/debug/ge_log.h" namespace ge { -void RtContextUtil::AddrtContext(rtContext_t context) { rtContexts_.emplace_back(context); } +void RtContextUtil::AddRtContext(uint64_t session_id, rtContext_t context) { + std::lock_guard lock(ctx_mutex_); + rt_contexts_[session_id].emplace_back(context); +} + +void RtContextUtil::DestroyRtContexts(uint64_t session_id) { + std::lock_guard lock(ctx_mutex_); + auto &contexts = rt_contexts_[session_id]; + DestroyRtContexts(session_id, contexts); +} + +void RtContextUtil::DestroyAllRtContexts() { + std::lock_guard lock(ctx_mutex_); + for (auto &ctx_pair : rt_contexts_) { + DestroyRtContexts(ctx_pair.first, ctx_pair.second); + } + rt_contexts_.clear(); +} -void RtContextUtil::DestroyrtContexts() { - GELOGI("The size of runtime context handle is %zu.", rtContexts_.size()); - for (auto &rtContext : rtContexts_) { +void RtContextUtil::DestroyRtContexts(uint64_t session_id, std::vector &contexts) { + GELOGI("Runtime context handle number of session %lu is %zu.", session_id, contexts.size()); + for (auto &rtContext : contexts) { (void)rtCtxDestroy(rtContext); } - rtContexts_.clear(); + contexts.clear(); } } // namespace ge diff --git a/src/ge/graph/manager/util/rt_context_util.h b/src/ge/graph/manager/util/rt_context_util.h index 93db9882..58cc0803 100644 --- a/src/ge/graph/manager/util/rt_context_util.h +++ b/src/ge/graph/manager/util/rt_context_util.h @@ -18,6 +18,8 @@ #define GE_GRAPH_MANAGER_UTIL_RT_CONTEXT_UTIL_H_ #include +#include +#include #include "runtime/context.h" @@ -29,13 +31,14 @@ class RtContextUtil { return instance; } - void AddrtContext(rtContext_t context); + void AddRtContext(uint64_t session_id, rtContext_t context); const rtContext_t GetNormalModeContext() const { return before_prerun_ctx_; } void SetNormalModeContext(rtContext_t context) { before_prerun_ctx_ = context; } - void DestroyrtContexts(); + void DestroyRtContexts(uint64_t session_id); + void DestroyAllRtContexts(); RtContextUtil &operator=(const RtContextUtil &) = delete; RtContextUtil(const RtContextUtil &RtContextUtil) = delete; @@ -44,8 +47,12 @@ class RtContextUtil { RtContextUtil() = default; ~RtContextUtil() {} - std::vector rtContexts_; + void DestroyRtContexts(uint64_t session_id, std::vector &contexts); + + std::map> rt_contexts_; rtContext_t before_prerun_ctx_ = nullptr; + + std::mutex ctx_mutex_; }; } // namespace ge diff --git a/src/ge/graph/optimize/graph_optimize.cc b/src/ge/graph/optimize/graph_optimize.cc index b42c2e01..09acae33 100644 --- a/src/ge/graph/optimize/graph_optimize.cc +++ b/src/ge/graph/optimize/graph_optimize.cc @@ -299,4 +299,36 @@ void GraphOptimize::TranFrameOp(ComputeGraphPtr &compute_graph) { } } } + +Status GraphOptimize::IdentifyReference(ComputeGraphPtr &compute_graph) { + for (auto &node : compute_graph->GetAllNodes()) { + GE_CHECK_NOTNULL(node); + auto op_desc = node->GetOpDesc(); + GE_CHECK_NOTNULL(op_desc); + auto input_name_index = op_desc->GetAllInputName(); + bool is_ref = false; + for (const auto &name_index : input_name_index) { + const int out_index = op_desc->GetOutputIndexByName(name_index.first); + if (out_index != -1) { + auto input_desc = op_desc->GetInputDesc(name_index.second); + input_desc.SetRefPortByIndex({name_index.second}); + op_desc->UpdateInputDesc(name_index.second, input_desc); + GELOGI("SetRefPort: set op[%s] input desc[%u-%s] ref.", op_desc->GetName().c_str(), name_index.second, + name_index.first.c_str()); + auto output_desc = op_desc->GetOutputDesc(static_cast(out_index)); + output_desc.SetRefPortByIndex({name_index.second}); + op_desc->UpdateOutputDesc(static_cast(out_index), output_desc); + GELOGI("SetRefPort: set op[%s] output desc[%u-%s] ref.", op_desc->GetName().c_str(), out_index, + name_index.first.c_str()); + is_ref = true; + } + } + if (is_ref) { + AttrUtils::SetBool(op_desc, ATTR_NAME_REFERENCE, is_ref); + GELOGI("param [node] %s is reference node, set attribute %s to be true.", node->GetName().c_str(), + ATTR_NAME_REFERENCE.c_str()); + } + } + return SUCCESS; +} } // namespace ge diff --git a/src/ge/graph/optimize/graph_optimize.h b/src/ge/graph/optimize/graph_optimize.h index 72709932..9741814d 100644 --- a/src/ge/graph/optimize/graph_optimize.h +++ b/src/ge/graph/optimize/graph_optimize.h @@ -67,6 +67,9 @@ class GraphOptimize { // handle summary node before preRun graph Status HandleSummaryOp(ComputeGraphPtr &compute_graph); + // Identify reference node before optimize subgraph + Status IdentifyReference(ComputeGraphPtr &compute_graph); + void TranFrameOp(ComputeGraphPtr &compute_graph); private: @@ -85,5 +88,5 @@ class GraphOptimize { std::map> summary_output_indexes_ = {}; std::string func_bin_path_; }; -}; // namespace ge +} // namespace ge #endif // GE_GRAPH_OPTIMIZE_GRAPH_OPTIMIZE_H_ diff --git a/src/ge/graph/optimize/summary_optimize.cc b/src/ge/graph/optimize/summary_optimize.cc index 8b38d602..a8325da3 100644 --- a/src/ge/graph/optimize/summary_optimize.cc +++ b/src/ge/graph/optimize/summary_optimize.cc @@ -80,7 +80,8 @@ Status GraphOptimize::HandleSummaryOp(ComputeGraphPtr &compute_graph) { del_nodes.emplace_back(node_ptr); } } - summary_output_indexes_.insert({compute_graph->GetGraphID(), summary_output_indexes}); + GE_IF_BOOL_EXEC(!summary_output_indexes.empty(), + summary_output_indexes_.insert({compute_graph->GetGraphID(), summary_output_indexes})); // add output nodes for summary std::vector> out_nodes_info; diff --git a/src/ge/graph/partition/dynamic_shape_partition.cc b/src/ge/graph/partition/dynamic_shape_partition.cc index 6a396eef..324129c4 100644 --- a/src/ge/graph/partition/dynamic_shape_partition.cc +++ b/src/ge/graph/partition/dynamic_shape_partition.cc @@ -62,15 +62,16 @@ Status DynamicShapePartitioner::Partition() { } GELOGD("Start dynamic shape partition graph %s.", root_graph_->GetName().c_str()); - REQUIRE_SUCCESS(MarkUnknownShapeNodes(), "Failed mark unknown shape nodes."); + REQUIRE_SUCCESS(MarkUnknownShapeNodes(), "Failed mark unknown shape nodes, root grah name:%s.", + root_graph_->GetName().c_str()); if (unknown_shape_nodes_.empty()) { GELOGD("Skip dynamic shape partition of graph %s as all nodes are known shape.", root_graph_->GetName().c_str()); REQUIRE(AttrUtils::SetBool(*root_graph_, ATTR_NAME_DYNAMIC_SHAPE_PARTITIONED, false), - "Failed set dynamic shape partitioned flag on root graph."); + "Failed set dynamic shape partitioned flag on root graph %s.", root_graph_->GetName().c_str()); return SUCCESS; } REQUIRE(AttrUtils::SetBool(*root_graph_, ATTR_NAME_DYNAMIC_SHAPE_PARTITIONED, true), - "Failed set dynamic shape partitioned flag on root graph."); + "Failed set dynamic shape partitioned flag on root graph %s.", root_graph_->GetName().c_str()); DumpGraph("_Before_DSP"); auto status = PartitionImpl(); @@ -107,21 +108,21 @@ void DynamicShapePartitioner::PruneUniqueClusters() { } Status DynamicShapePartitioner::BuildPartitionFrame() { - for (auto cluster : unique_clusters_) { + for (const auto &cluster : unique_clusters_) { REQUIRE_SUCCESS(cluster->BuildFrame(), "Failed build frame of cluster[%lu].", cluster->Id()); } return SUCCESS; } Status DynamicShapePartitioner::CombinePartitionFrame() { - for (auto cluster : unique_clusters_) { + for (const auto &cluster : unique_clusters_) { REQUIRE_SUCCESS(cluster->CombinePartitionFrame(), "Failed combine frame of cluster[%lu].", cluster->Id()); } return SUCCESS; } Status DynamicShapePartitioner::BuildPartitionSubgraph() { - for (auto cluster : unique_clusters_) { + for (const auto &cluster : unique_clusters_) { REQUIRE_SUCCESS(cluster->BuildPartitionSubgraph(), "Failed build subgraph of cluster[%lu].", cluster->Id()); } return SUCCESS; @@ -134,10 +135,10 @@ std::string DynamicShapePartitioner::DebugString() const { size_t netoutput = 0; std::stringstream ss; ss << "All unknown shape nodes:" << std::endl; - for (auto node : unknown_shape_nodes_) { + for (const auto &node : unknown_shape_nodes_) { ss << " [" << node->GetName() << "](" << node->GetType() << ")" << std::endl; } - for (auto cluster : unique_clusters_) { + for (const auto &cluster : unique_clusters_) { if (cluster->IsUnknownShape()) { unknown++; } else if (cluster->IsKnownShape()) { @@ -150,7 +151,7 @@ std::string DynamicShapePartitioner::DebugString() const { } ss << "All clusters:" << unique_clusters_.size() << ", data:" << data << ", known:" << known << ", unknown:" << unknown << ", netoutput:" << netoutput << std::endl; - for (auto cluster : unique_clusters_) { + for (const auto &cluster : unique_clusters_) { ss << " " << cluster->DebugString() << std::endl; } return ss.str(); @@ -158,13 +159,13 @@ std::string DynamicShapePartitioner::DebugString() const { void DynamicShapePartitioner::DumpGraph(const std::string &suffix) { GraphUtils::DumpGEGraphToOnnx(*root_graph_, root_graph_->GetName() + suffix); - for (auto sub_graph : root_graph_->GetAllSubgraphs()) { + for (const auto &sub_graph : root_graph_->GetAllSubgraphs()) { GraphUtils::DumpGEGraphToOnnx(*sub_graph, sub_graph->GetName() + suffix); } } void DynamicShapePartitioner::ClearResource() { - for (auto cluster : unique_clusters_) { + for (const auto &cluster : unique_clusters_) { cluster->Clear(); } node_2_cluster_.clear(); @@ -175,8 +176,7 @@ void DynamicShapePartitioner::ClearResource() { } Status DynamicShapePartitioner::MarkUnknownShapeNodes() { - auto graph = root_graph_; - for (auto &node : graph->GetDirectNode()) { + for (auto &node : root_graph_->GetDirectNode()) { REQUIRE_SUCCESS(CollectSpreadUnknownShapeNodes(node), "Failed collect spread unknown shape nodes %s.", node->GetName().c_str()); } @@ -186,7 +186,7 @@ Status DynamicShapePartitioner::MarkUnknownShapeNodes() { Status DynamicShapePartitioner::InitClusters() { auto graph = root_graph_; size_t rank = 0; - for (const auto node : graph->GetDirectNode()) { + for (const auto &node : graph->GetDirectNode()) { Cluster::Type type = Cluster::DATA; if (node->GetType() == DATA) { type = Cluster::DATA; @@ -208,7 +208,7 @@ Status DynamicShapePartitioner::InitClusters() { cluster->AddInput(node_2_cluster_[parent]); } } - for (const auto node : graph->GetDirectNode()) { + for (const auto &node : graph->GetDirectNode()) { GELOGD("Make cluster for node %s : %s.", node->GetName().c_str(), node_2_cluster_[node]->DebugString().c_str()); } return SUCCESS; @@ -220,8 +220,8 @@ Status DynamicShapePartitioner::TopologicalSortClusters() { std::queue ready_clusters; std::unordered_map cluster_pending_count; std::unordered_set seen_clusters; - for (auto iter = node_2_cluster_.begin(); iter != node_2_cluster_.end(); iter++) { - auto cluster = iter->second; + for (auto &iter : node_2_cluster_) { + auto cluster = iter.second; if (seen_clusters.count(cluster) != 0) { continue; } @@ -242,7 +242,7 @@ Status DynamicShapePartitioner::TopologicalSortClusters() { if (cluster->IsKnownShape()) { ordered_cluster_.push_back(cluster); } - for (auto out_cluster : cluster->Outputs()) { + for (const auto &out_cluster : cluster->Outputs()) { if (cluster_pending_count[out_cluster] > 0 && --cluster_pending_count[out_cluster] == 0) { ready_clusters.push(out_cluster); } @@ -273,16 +273,16 @@ static std::string ToString(const std::vector &clusters) { Status DynamicShapePartitioner::MergeClusters() { // Merge unknown shape clusters - for (auto cluster : ordered_cluster_) { - for (auto in_cluster : cluster->Inputs()) { + for (const auto &cluster : ordered_cluster_) { + for (const auto &in_cluster : cluster->Inputs()) { if (!in_cluster->IsUnknownShape()) { continue; } auto merged_clusters = cluster->MergeAllPathFrom(in_cluster); GELOGD("Merge all path cluster from %lu to %lu %s.", in_cluster->Id(), cluster->Id(), ToString(merged_clusters).c_str()); - for (auto merged_cluster : merged_clusters) { - for (auto node : merged_cluster->Nodes()) { + for (const auto &merged_cluster : merged_clusters) { + for (const auto &node : merged_cluster->Nodes()) { node_2_cluster_[node] = cluster; } } @@ -291,7 +291,7 @@ Status DynamicShapePartitioner::MergeClusters() { REQUIRE_SUCCESS(TopologicalSortClusters(), "Failed topological sort clusters after merge unknown shape clusters."); // Merge known shape clusters - for (auto cluster : ordered_cluster_) { + for (const auto &cluster : ordered_cluster_) { if (cluster->IsRefVariable() && cluster->Inputs().size() == 1) { auto in_cluster = *(cluster->Inputs().begin()); in_cluster->Merge(cluster); @@ -299,13 +299,13 @@ Status DynamicShapePartitioner::MergeClusters() { continue; } - for (auto in_cluster : cluster->Inputs()) { + for (const auto &in_cluster : cluster->Inputs()) { if (!in_cluster->IsKnownShape()) { continue; } if (cluster->TryMerge(in_cluster)) { GELOGD("Success merge known shape cluster from %lu to %lu.", in_cluster->Id(), cluster->Id()); - for (auto node : in_cluster->Nodes()) { + for (const auto &node : in_cluster->Nodes()) { node_2_cluster_[node] = cluster; } } @@ -333,7 +333,7 @@ Status DynamicShapePartitioner::CollectSpreadUnknownShapeNodes(NodePtr node) { if (IsUnknownShapeTensor(out_tensor)) { GELOGD("Collect node %s as unknown as output %lu is unknown.", node->GetName().c_str(), anchor_index); is_unknown = true; - auto anchor = node->GetOutDataAnchor(anchor_index); + auto anchor = node->GetOutDataAnchor(static_cast(anchor_index)); for (const auto peer_anchor : anchor->GetPeerInDataAnchors()) { if (peer_anchor != nullptr) { GELOGD("Collect node %s as has unknown input from %s:%lu.", peer_anchor->GetOwnerNode()->GetName().c_str(), @@ -349,7 +349,7 @@ Status DynamicShapePartitioner::CollectSpreadUnknownShapeNodes(NodePtr node) { if (IsUnknownShapeTensor(in_tensor)) { GELOGD("Collect node %s as unknown as input %lu is unknown.", node->GetName().c_str(), anchor_index); is_unknown = true; - auto anchor = node->GetInDataAnchor(anchor_index); + auto anchor = node->GetInDataAnchor(static_cast(anchor_index)); const auto peer_anchor = anchor->GetPeerOutAnchor(); if (peer_anchor != nullptr) { GELOGD("Collect node %s as has unknown output to %s:%lu.", peer_anchor->GetOwnerNode()->GetName().c_str(), @@ -453,15 +453,15 @@ std::string Cluster::DebugString() const { } ss << "[" << id_ << "](size:" << nodes_.size() << ")"; ss << "(" << min_ << "," << max_ << ")("; - for (auto cluster : in_clusters_) { + for (const auto &cluster : in_clusters_) { ss << cluster->id_ << ","; } ss << ")->("; - for (auto cluster : out_clusters_) { + for (const auto &cluster : out_clusters_) { ss << cluster->id_ << ","; } ss << ")|"; - for (auto node : nodes_) { + for (const auto &node : nodes_) { ss << (node->GetName() + "|"); } return ss.str(); @@ -507,12 +507,12 @@ void Cluster::Merge(ClusterPtr other) { in_clusters_.erase(other); out_clusters_.erase(other); auto in_clusters = other->in_clusters_; - for (auto cluster : in_clusters) { + for (const auto &cluster : in_clusters) { cluster->RemoveOutput(other); cluster->AddOutput(shared_from_this()); } auto out_clusters = other->out_clusters_; - for (auto cluster : out_clusters) { + for (const auto &cluster : out_clusters) { cluster->RemoveInput(other); cluster->AddInput(shared_from_this()); } @@ -529,7 +529,7 @@ bool Cluster::TryMerge(ClusterPtr other) { while (!forward_reached.empty()) { auto current_cluster = forward_reached.front(); forward_reached.pop(); - for (auto cluster : current_cluster->out_clusters_) { + for (const auto &cluster : current_cluster->out_clusters_) { if (cluster->max_ == max_ && current_cluster != other) { return false; } else if (cluster->min_ < max_) { @@ -557,7 +557,7 @@ std::vector Cluster::MergeAllPathFrom(ClusterPtr other) { while (!forward_reached_queue.empty()) { auto current_cluster = forward_reached_queue.front(); forward_reached_queue.pop(); - for (auto cluster : current_cluster->out_clusters_) { + for (const auto &cluster : current_cluster->out_clusters_) { if (cluster->min_ < max_ && cluster->max_ != max_ && forward_reached_clusters.count(cluster) == 0) { forward_reached_clusters.insert(cluster); forward_reached_queue.push(cluster); @@ -567,7 +567,7 @@ std::vector Cluster::MergeAllPathFrom(ClusterPtr other) { while (!backward_reached_queue.empty()) { auto current_cluster = backward_reached_queue.front(); backward_reached_queue.pop(); - for (auto cluster : current_cluster->in_clusters_) { + for (const auto &cluster : current_cluster->in_clusters_) { if (cluster->max_ > other->min_ && cluster->max_ != other->max_ && backward_reached_clusters.count(cluster) == 0) { backward_reached_clusters.insert(cluster); @@ -578,7 +578,7 @@ std::vector Cluster::MergeAllPathFrom(ClusterPtr other) { } } } - for (auto cluster : path_clusters) { + for (const auto &cluster : path_clusters) { Merge(cluster); } return path_clusters; @@ -598,11 +598,11 @@ void Cluster::AddFrameOutput(OutDataAnchorPtr anchor) { }; InDataAnchorPtr Cluster::GetFrameInDataAnchor(InDataAnchorPtr anchor) { - return partition_node_->GetInDataAnchor(inputs_index_[anchor]); + return partition_node_->GetInDataAnchor(static_cast(inputs_index_[anchor])); }; OutDataAnchorPtr Cluster::GetFrameOutDataAnchor(OutDataAnchorPtr anchor) { - return partition_node_->GetOutDataAnchor(outputs_index_[anchor]); + return partition_node_->GetOutDataAnchor(static_cast(outputs_index_[anchor])); }; InControlAnchorPtr Cluster::GetFrameInControlAnchor() { return partition_node_->GetInControlAnchor(); }; @@ -616,22 +616,25 @@ Status Cluster::BuildFrame() { auto node = nodes_.front(); auto in_control_anchor = node->GetInControlAnchor(); if (in_control_anchor != nullptr) { - for (auto peer_out_control_anchor : in_control_anchor->GetPeerOutControlAnchors()) { + for (const auto &peer_out_control_anchor : in_control_anchor->GetPeerOutControlAnchors()) { auto src_cluster = partitioner_->node_2_cluster_[peer_out_control_anchor->GetOwnerNode()]; if (src_cluster->id_ != id_) { - auto src_cluster = partitioner_->node_2_cluster_[peer_out_control_anchor->GetOwnerNode()]; - GraphUtils::RemoveEdge(peer_out_control_anchor, in_control_anchor); + REQUIRE_GRAPH_SUCCESS( + GraphUtils::RemoveEdge(peer_out_control_anchor, in_control_anchor), + "Failed remove edge from node %s index %d to node %s index %d.", + peer_out_control_anchor->GetOwnerNode()->GetName().c_str(), AnchorUtils::GetIdx(peer_out_control_anchor), + in_control_anchor->GetOwnerNode()->GetName().c_str(), AnchorUtils::GetIdx(in_control_anchor)); control_inputs_.insert(src_cluster); src_cluster->control_outputs_.insert(peer_out_control_anchor); } } } if (IsData()) { - for (auto anchor : node->GetAllOutDataAnchors()) { + for (const auto &anchor : node->GetAllOutDataAnchors()) { AddFrameOutput(anchor); } } else { - for (auto anchor : node->GetAllInDataAnchors()) { + for (const auto &anchor : node->GetAllInDataAnchors()) { AddFrameInput(anchor); } } @@ -660,7 +663,7 @@ Status Cluster::BuildPartitionFrame() { "Failed set shape flag."); REQUIRE_GRAPH_SUCCESS(GraphUtils::RemoveJustNode(graph, node), "Failed remove root graph node."); REQUIRE_GRAPH_SUCCESS(node->SetOwnerComputeGraph(subgraph_), "Failed set owner graph."); - for (auto anchor : node->GetAllInDataAnchors()) { + for (const auto &anchor : node->GetAllInDataAnchors()) { auto peer_out_anchor = anchor->GetPeerOutAnchor(); if (peer_out_anchor == nullptr) { continue; // Skip overhang input. @@ -674,7 +677,7 @@ Status Cluster::BuildPartitionFrame() { } auto in_control_anchor = node->GetInControlAnchor(); if (in_control_anchor != nullptr) { - for (auto peer_out_control_anchor : in_control_anchor->GetPeerOutControlAnchors()) { + for (const auto &peer_out_control_anchor : in_control_anchor->GetPeerOutControlAnchors()) { if (peer_out_control_anchor == nullptr) { continue; } @@ -689,9 +692,9 @@ Status Cluster::BuildPartitionFrame() { } } } - for (auto anchor : node->GetAllOutDataAnchors()) { + for (const auto &anchor : node->GetAllOutDataAnchors()) { auto peer_in_anchors = anchor->GetPeerInDataAnchors(); - for (auto peer_in_anchor : peer_in_anchors) { + for (const auto &peer_in_anchor : peer_in_anchors) { auto src_cluster = partitioner_->node_2_cluster_[peer_in_anchor->GetOwnerNode()]; if (src_cluster->id_ != id_) { AddFrameOutput(anchor); @@ -717,7 +720,7 @@ Status Cluster::BuildPartitionFrame() { } Status Cluster::CombinePartitionFrame() { - for (auto anchor : inputs_) { + for (const auto &anchor : inputs_) { auto peer_out_anchor = anchor->GetPeerOutAnchor(); auto src_cluster = partitioner_->node_2_cluster_[peer_out_anchor->GetOwnerNode()]; auto src_anchor = src_cluster->GetFrameOutDataAnchor(peer_out_anchor); @@ -729,7 +732,7 @@ Status Cluster::CombinePartitionFrame() { src_anchor->GetOwnerNode()->GetName().c_str(), src_anchor->GetIdx(), dst_anchor->GetOwnerNode()->GetName().c_str(), dst_anchor->GetIdx()); } - for (auto src_cluster : control_inputs_) { + for (const auto &src_cluster : control_inputs_) { auto src_anchor = src_cluster->GetFrameOutControlAnchor(); auto dst_anchor = GetFrameInControlAnchor(); REQUIRE_GRAPH_SUCCESS(GraphUtils::AddEdge(src_anchor, dst_anchor), "Failed add edge from %s:%d to %s:%d.", @@ -774,8 +777,8 @@ Status Cluster::BuildPartitionSubgraph() { REQUIRE_NOT_NULL(net_output_node, "Failed add netoutput node to subgraph."); REQUIRE_GRAPH_SUCCESS(net_output_node->SetOwnerComputeGraph(subgraph_), "Failed set owner graph of netoutput node."); parent_node_index = 0; - for (auto anchor : outputs_) { - auto output_desc = anchor->GetOwnerNode()->GetOpDesc()->GetOutputDesc(anchor->GetIdx()); + for (const auto &anchor : outputs_) { + auto output_desc = anchor->GetOwnerNode()->GetOpDesc()->GetOutputDesc(static_cast(anchor->GetIdx())); REQUIRE(AttrUtils::SetInt(output_desc, ATTR_NAME_PARENT_NODE_INDEX, parent_node_index), "Failed set parent_node_index on subgraph netoutput's input."); REQUIRE_GRAPH_SUCCESS(net_output_op->UpdateInputDesc(parent_node_index, output_desc), @@ -786,7 +789,7 @@ Status Cluster::BuildPartitionSubgraph() { anchor->GetIdx()); parent_node_index++; } - for (auto anchor : control_outputs_) { + for (const auto &anchor : control_outputs_) { REQUIRE_GRAPH_SUCCESS(GraphUtils::AddEdge(anchor, net_output_node->GetInControlAnchor()), "Faile add control edge from %s:%d to netoutput node.", anchor->GetOwnerNode()->GetName().c_str(), anchor->GetIdx()); diff --git a/src/ge/graph/partition/engine_place.cc b/src/ge/graph/partition/engine_place.cc index 74da0326..2d1a7f13 100644 --- a/src/ge/graph/partition/engine_place.cc +++ b/src/ge/graph/partition/engine_place.cc @@ -38,6 +38,7 @@ Status EnginePlacer::Run() { return FAILED; } // Assign engine for each node in the graph + instance_ptr->DNNEngineManagerObj().InitPerformanceStaistic(); for (const auto &node_ptr : compute_graph_->GetDirectNode()) { GE_CHECK_NOTNULL(node_ptr); GE_CHECK_NOTNULL(node_ptr->GetOpDesc()); @@ -60,12 +61,15 @@ Status EnginePlacer::Run() { return FAILED; } } + for (auto &it : instance_ptr->DNNEngineManagerObj().GetCheckSupportCost()) { + GEEVENT("The time cost of %s::CheckSupported is [%lu] micro second.", it.first.c_str(), it.second); + } GELOGI("Engine placer ends."); return SUCCESS; } Status EnginePlacer::AssignEngineAndLog(ge::ConstNodePtr node_ptr, const std::string &engine_name) { - if (node_ptr == nullptr || node_ptr->GetOpDesc() == nullptr) { + if ((node_ptr == nullptr) || (node_ptr->GetOpDesc() == nullptr)) { GELOGE(FAILED, "node_ptr is null."); return FAILED; } diff --git a/src/ge/graph/partition/graph_partition.cc b/src/ge/graph/partition/graph_partition.cc index 50cd7e81..907d672d 100644 --- a/src/ge/graph/partition/graph_partition.cc +++ b/src/ge/graph/partition/graph_partition.cc @@ -25,6 +25,7 @@ #include "framework/common/types.h" #include "graph/debug/ge_attr_define.h" #include "graph/manager/graph_manager_utils.h" +#include "graph/common/ge_call_wrapper.h" #include "graph/utils/graph_utils.h" #include "graph/utils/op_desc_utils.h" #include "graph/utils/type_utils.h" @@ -231,33 +232,33 @@ Status ge::GraphPartitioner::MergeSubGraph(ge::ComputeGraphPtr &output_merged_co ComputeGraphPtr new_sub_graph = MakeShared(original_compute_graph->GetName()); GE_CHECK_NOTNULL(new_sub_graph); output_merged_compute_graph = new_sub_graph; - GE_TIMESTAMP_START(MergeGraphRemoveNode); + GE_TIMESTAMP_START(MergeSubGraphRemoveNode); if (RemoveNodeAndEdgeBetweenEndPld(output_merged_compute_graph, sub_graph_list) != ge::SUCCESS) { GELOGE(GE_GRAPH_PARAM_NULLPTR, "[GraphPartitioner]: merging sub-graphs failed"); return FAILED; } - GE_TIMESTAMP_END(MergeGraphRemoveNode, "GraphPartitioner::MergeGraphRemoveNodeAndEdge"); - GE_TIMESTAMP_START(MergeGraphTopologicalSorting); + GE_TIMESTAMP_END(MergeSubGraphRemoveNode, "GraphPartitioner::MergeGraphRemoveNodeAndEdge"); + GE_TIMESTAMP_START(MergeSubGraphTopologicalSorting); Status ret = output_merged_compute_graph->TopologicalSorting(); if (ret != SUCCESS) { GELOGE(GE_GRAPH_TOPO_SORT_FAILED, "[GraphPartitioner]: output_merged_compute_graph->TopologicalSorting failed"); return FAILED; } - GE_TIMESTAMP_END(MergeGraphTopologicalSorting, "GraphPartitioner::MergeGraphTopologicalSorting"); + GE_TIMESTAMP_END(MergeSubGraphTopologicalSorting, "GraphPartitioner::MergeGraphTopologicalSorting"); // flush all nodes' engine of merged graph - GE_TIMESTAMP_START(MergeGraphEnginePlacerRun); + GE_TIMESTAMP_START(MergeSubGraphEnginePlacerRun); graph_info_.engine_placer_.SetComputeGraph(output_merged_compute_graph); if (graph_info_.engine_placer_.Run() != SUCCESS) { GELOGE(GE_GRAPH_INIT_FAILED, "[GraphPartitioner]: engine_placer run failed"); return FAILED; } - GE_TIMESTAMP_END(MergeGraphEnginePlacerRun, "GraphPartitioner::MergeGraphEnginePlacerRun"); + GE_TIMESTAMP_END(MergeSubGraphEnginePlacerRun, "GraphPartitioner::MergeGraphEnginePlacerRun"); GELOGI("Graph merge ends."); return SUCCESS; } Status ge::GraphPartitioner::UpdatePldOpDesc(const NodePtr &dst_node, int input_index, OpDescPtr &pld_op_desc) { - if (dst_node == nullptr || pld_op_desc == nullptr || dst_node->GetOpDesc() == nullptr) { + if ((dst_node == nullptr) || (pld_op_desc == nullptr) || (dst_node->GetOpDesc() == nullptr)) { GELOGE(FAILED, "parameter ptr is null."); return FAILED; } @@ -275,7 +276,7 @@ Status ge::GraphPartitioner::UpdatePldOpDesc(const NodePtr &dst_node, int input_ } Status ge::GraphPartitioner::UpdateEndOpDesc(const NodePtr &src_node, int output_index, OpDescPtr &end_op_desc) { - if (src_node == nullptr || end_op_desc == nullptr || src_node->GetOpDesc() == nullptr) { + if ((src_node == nullptr) || (end_op_desc == nullptr) || (src_node->GetOpDesc() == nullptr)) { GELOGE(FAILED, "parameter ptr is null."); return FAILED; } @@ -296,9 +297,9 @@ graphStatus ge::GraphPartitioner::AddPlaceHolderEndInSrcDstGraph(const AnchorPtr const AnchorPtr &peer_in_anchor, const ge::ComputeGraphPtr &pld_graph, const ge::ComputeGraphPtr &end_graph) { - GE_CHECK_NOTNULL(out_anchor); GE_CHECK_NOTNULL(peer_in_anchor); GE_CHECK_NOTNULL(pld_graph); + GE_CHECK_NOTNULL(out_anchor); GE_CHECK_NOTNULL(end_graph); const auto &src_node = out_anchor->GetOwnerNode(); const auto &dst_node = peer_in_anchor->GetOwnerNode(); @@ -313,6 +314,7 @@ graphStatus ge::GraphPartitioner::AddPlaceHolderEndInSrcDstGraph(const AnchorPtr GELOGW("SetInt peerIndex failed");) GE_IF_BOOL_EXEC(!AttrUtils::SetStr(end_op_desc, "parentOpType", dst_node->GetType()), GELOGW("SetStr parentOpType failed");) + GE_IF_BOOL_EXEC(!end_op_desc->SetExtAttr("parentNode", dst_node), GELOGW("SetEndExtAttr parentNode failed");) // replace input_desc of end with owner node's desc int output_index = ge::AnchorUtils::GetIdx(out_anchor); bool is_need_update_desc = (output_index >= 0) && (graph_info_.mode_ == kPartitioning); @@ -361,6 +363,7 @@ graphStatus ge::GraphPartitioner::AddPlaceHolderEndInSrcDstGraph(const AnchorPtr GELOGW("SetStr parentId failed");) GE_IF_BOOL_EXEC(!AttrUtils::SetInt(pld_op_desc, "anchorIndex", AnchorUtils::GetIdx(out_anchor)), GELOGW("SetInt anchorIndex failed");) + GE_IF_BOOL_EXEC(!pld_op_desc->SetExtAttr("parentNode", src_node), GELOGW("SetPldExtAttr parentNode failed");) // do not care over flow graph_info_.num_of_pld_end_++; // replace output_desc of pld with input node's output desc @@ -395,14 +398,14 @@ graphStatus ge::GraphPartitioner::AddPlaceHolderEndInSrcDstGraph(const AnchorPtr return FAILED; } graph_info_.index_2_end_[graph_info_.num_of_pld_end_] = new_end_node; - graph_info_.end_2_pld_[new_end_node] = new_pld_node; graph_info_.pld_2_end_[new_pld_node] = new_end_node; + graph_info_.end_2_pld_[new_end_node] = new_pld_node; return SUCCESS; } Status ge::GraphPartitioner::LinkInput2EndRemoveOrginalLink(ge::NodePtr input_node, ge::ComputeGraphPtr src_graph, ge::ComputeGraphPtr dst_graph) { - if (input_node == nullptr || src_graph == nullptr || dst_graph == nullptr) { + if ((input_node == nullptr) || (src_graph == nullptr) || (dst_graph == nullptr)) { GELOGE(FAILED, "parameter ptr is null."); return FAILED; } @@ -442,7 +445,7 @@ Status ge::GraphPartitioner::LinkInput2EndRemoveOrginalLink(ge::NodePtr input_no Status ge::GraphPartitioner::PutInputNodesInSubGraph(const ge::ComputeGraphPtr &src_graph, const ge::ComputeGraphPtr &dst_graph) { - if (src_graph == nullptr || dst_graph == nullptr) { + if ((src_graph == nullptr) || (dst_graph == nullptr)) { GELOGE(FAILED, "parameter ptr is null."); return FAILED; } @@ -849,34 +852,34 @@ Status ge::GraphPartitioner::PartitionSubGraph(ge::ComputeGraphPtr compute_graph GELOGE(GE_GRAPH_TOPO_SORT_FAILED, "[GraphPartitioner]: subGraphPtr->TopologicalSorting failed"); return FAILED; } - GE_TIMESTAMP_START(GraphPartitionInitialize); + GE_TIMESTAMP_START(PartitionSubGraphInitialize); if (Initialize(compute_graph) != SUCCESS) { GELOGE(GE_GRAPH_INIT_FAILED, "[GraphPartitioner]: initialize failed"); return FAILED; } - GE_TIMESTAMP_END(GraphPartitionInitialize, "GraphPartitioner::PartitionInitialize"); - GE_TIMESTAMP_START(GraphPartitionMarkClusters); + GE_TIMESTAMP_END(PartitionSubGraphInitialize, "GraphPartitioner::PartitionInitialize"); + GE_TIMESTAMP_START(PartitionSubGraphMarkClusters); MarkClusters(); - GE_TIMESTAMP_END(GraphPartitionMarkClusters, "GraphPartitioner::PartitionMarkClusters"); - GE_TIMESTAMP_START(GraphPartitionSplitSubGraphs); + GE_TIMESTAMP_END(PartitionSubGraphMarkClusters, "GraphPartitioner::PartitionMarkClusters"); + GE_TIMESTAMP_START(PartitionSubGraphSplitSubGraphs); if (SplitSubGraphs(compute_graph) != SUCCESS) { GELOGE(FAILED, "[GraphPartitioner]: SplitSubGraphs failed"); return FAILED; } - GE_TIMESTAMP_END(GraphPartitionSplitSubGraphs, "GraphPartitioner::PartitionSplitSubGraphs"); - GE_TIMESTAMP_START(GraphPartitionSortSubGraphs); + GE_TIMESTAMP_END(PartitionSubGraphSplitSubGraphs, "GraphPartitioner::PartitionSplitSubGraphs"); + GE_TIMESTAMP_START(PartitionSubGraphSortSubGraphs); if (SortSubGraphs(compute_graph) != ge::SUCCESS) { GELOGE(GE_GRAPH_TOPO_SORT_FAILED, "Graph Partition SortSubGraphs failed."); return ge::FAILED; } - GE_TIMESTAMP_END(GraphPartitionSortSubGraphs, "GraphPartitioner::PartitionSortSubGraphs"); - GE_TIMESTAMP_START(GraphPartitionAddPartitionsToGraphNode); + GE_TIMESTAMP_END(PartitionSubGraphSortSubGraphs, "GraphPartitioner::PartitionSortSubGraphs"); + GE_TIMESTAMP_START(PartitionSubGraphAddPartitionsToGraphNode); vector output_subgraphs; if (AddPartitionsToGraphNode(output_subgraphs, compute_graph) != ge::SUCCESS) { GELOGE(GE_GRAPH_EMPTY_PARTITION, "Graph Partition AddPartitionsToGraphNode failed."); return ge::FAILED; } - GE_TIMESTAMP_END(GraphPartitionAddPartitionsToGraphNode, "GraphPartitioner::PartitionAddPartitionsToGraphNode"); + GE_TIMESTAMP_END(PartitionSubGraphAddPartitionsToGraphNode, "GraphPartitioner::PartitionAddPartitionsToGraphNode"); GELOGI("Graph Partition ends. Adding partitions to SubGraphInfo, got %zu sub graphs", output_subgraphs.size()); graph_info_.mode_ = kMerging; // do not care over flow @@ -923,7 +926,7 @@ Status ge::GraphPartitioner::AddPlaceHolderEnd(const AnchorPtr &out_anchor, cons Status ge::GraphPartitioner::SortSubGraphs(const ge::ComputeGraphPtr &compute_graph) { uint32_t rank = kRankOne; // rank 0 for data graph ComputeGraphPtr new_input_nodes_sub_graph = MakeShared("inputNodeGraph"); - if (new_input_nodes_sub_graph == nullptr || compute_graph == nullptr) { + if ((new_input_nodes_sub_graph == nullptr) || (compute_graph == nullptr)) { GELOGE(FAILED, "[GraphPartitioner]: new_input_nodes_sub_graph or compute_graph is null."); return FAILED; } @@ -965,7 +968,7 @@ Status ge::GraphPartitioner::SortSubGraphs(const ge::ComputeGraphPtr &compute_gr } AnchorPtr ge::GraphPartitioner::GetEndInAnchor(const AnchorPtr &src_anchor, const NodePtr &end_node) { - if (src_anchor == nullptr || end_node == nullptr) { + if ((src_anchor == nullptr) || (end_node == nullptr)) { GELOGE(FAILED, "parameter ptr is null."); return nullptr; } @@ -979,7 +982,7 @@ AnchorPtr ge::GraphPartitioner::GetEndInAnchor(const AnchorPtr &src_anchor, cons } AnchorPtr ge::GraphPartitioner::GetPldOutAnchor(const NodePtr &pld_node, const AnchorPtr &dst_anchor) { - if (pld_node == nullptr || dst_anchor == nullptr) { + if ((pld_node == nullptr) || (dst_anchor == nullptr)) { GELOGE(FAILED, "parameter ptr is null."); return nullptr; } @@ -992,16 +995,16 @@ AnchorPtr ge::GraphPartitioner::GetPldOutAnchor(const NodePtr &pld_node, const A return pld_out_anchor; } -void ge::GraphPartitioner::AddEndPldInformationToSubGraphInfo(ge::SubGraphInfoPtr &sub_graph_info) { - if (sub_graph_info == nullptr) { +void ge::GraphPartitioner::AddEndPldInformationToSubGraphInfo(ge::SubGraphInfoPtr &subgraph_info) { + if (subgraph_info == nullptr) { GELOGE(FAILED, "parameter ptr is null."); return; } - auto sub_graph = sub_graph_info->GetSubGraph(); - GE_CHECK_NOTNULL_JUST_RETURN(sub_graph); + auto subgraph = subgraph_info->GetSubGraph(); + GE_CHECK_NOTNULL_JUST_RETURN(subgraph); NodetoNodeMap end_map; NodetoNodeMap pld_map; - for (const auto &node : sub_graph->GetDirectNode()) { + for (const auto &node : subgraph->GetDirectNode()) { if (node->GetType() == kEndType) { end_map[node] = graph_info_.end_2_pld_.at(node); } @@ -1009,8 +1012,8 @@ void ge::GraphPartitioner::AddEndPldInformationToSubGraphInfo(ge::SubGraphInfoPt pld_map[node] = graph_info_.pld_2_end_.at(node); } } - sub_graph_info->SetEnd2PldMap(end_map); - sub_graph_info->SetPld2EndMap(pld_map); + subgraph_info->SetEnd2PldMap(end_map); + subgraph_info->SetPld2EndMap(pld_map); } const Graph2SubGraphInfoList &ge::GraphPartitioner::GetSubGraphMap() { return graph_2_subgraph_list_; } diff --git a/src/ge/graph/passes/atomic_addr_clean_pass.cc b/src/ge/graph/passes/atomic_addr_clean_pass.cc index 7d9b8dec..ae69fd93 100644 --- a/src/ge/graph/passes/atomic_addr_clean_pass.cc +++ b/src/ge/graph/passes/atomic_addr_clean_pass.cc @@ -22,16 +22,12 @@ #include #include -#include "framework/common/debug/ge_log.h" #include "common/ge_inner_error_codes.h" #include "common/ge/ge_util.h" #include "graph/debug/ge_attr_define.h" #include "graph/utils/node_utils.h" #include "init/gelib.h" -namespace { -bool is_loop_graph = false; -} namespace ge { namespace { bool GraphShouldBeSkip(const ge::ComputeGraphPtr &graph) { @@ -44,7 +40,6 @@ bool GraphShouldBeSkip(const ge::ComputeGraphPtr &graph) { } // namespace Status AtomicAddrCleanPass::Run(ComputeGraphPtr graph) { - GE_TIMESTAMP_START(AtomicAddrCleanPass); if (graph == nullptr) { GELOGE(PARAM_INVALID, "param [graph] must not be null."); return PARAM_INVALID; @@ -71,10 +66,10 @@ Status AtomicAddrCleanPass::Run(ComputeGraphPtr graph) { } atomic_node_vec.push_back(node); } - if (!is_loop_graph && node->GetType() == LOOPCOND) { + if (!is_loop_graph_ && node->GetType() == LOOPCOND) { // there is loop in this graph GELOGD("There is no loop node. It will insert clean node follow atomic node."); - is_loop_graph = true; + is_loop_graph_ = true; } } if (atomic_node_vec.empty()) { @@ -83,7 +78,7 @@ Status AtomicAddrCleanPass::Run(ComputeGraphPtr graph) { } // 2.Insert clean node and link to atomic node Status ret; - if (is_loop_graph) { + if (is_loop_graph_) { ret = HandleLoopGraph(graph, atomic_node_vec); if (ret != SUCCESS) { return ret; @@ -95,7 +90,6 @@ Status AtomicAddrCleanPass::Run(ComputeGraphPtr graph) { } } GELOGD("AtomicAddrCleanPass end."); - GE_TIMESTAMP_END(AtomicAddrCleanPass, "GraphManager::AtomicAddrCleanPass"); return SUCCESS; } @@ -172,12 +166,14 @@ NodePtr AtomicAddrCleanPass::InsertAtomicAddrCleanNode(ComputeGraphPtr &graph) { if (!session_graph_id.empty()) { (void)AttrUtils::SetStr(op_desc, ATTR_NAME_SESSION_GRAPH_ID, session_graph_id); } + string node_name = op_desc->GetName(); // Only flush subgraph name - string node_name = (graph->GetParentGraph() != nullptr) - ? (graph->GetName() + "_" + op_desc->GetName() + session_graph_id) - : (op_desc->GetName() + session_graph_id); + if (graph->GetParentGraph() != nullptr) { + node_name = graph->GetName() + "_" + node_name; + } - op_desc->SetName(node_name); + string name = node_name + session_graph_id; + op_desc->SetName(name); GELOGI("Create cleanAddr op:%s.", op_desc->GetName().c_str()); // To avoid same name between graphs, set session graph id to this node NodePtr clean_addr_node = graph->AddNodeFront(op_desc); @@ -203,7 +199,7 @@ Status AtomicAddrCleanPass::LinkToAtomicNode(const NodePtr &atomic_node, NodePtr } GELOGD("Graph add cleanAddrNode op out ctrl edge, dst node: %s.", atomic_node->GetName().c_str()); std::string stream_label; - if (is_loop_graph && AttrUtils::GetStr(atomic_node->GetOpDesc(), ATTR_NAME_STREAM_LABEL, stream_label)) { + if (is_loop_graph_ && AttrUtils::GetStr(atomic_node->GetOpDesc(), ATTR_NAME_STREAM_LABEL, stream_label)) { if (!AttrUtils::SetStr(atomic_clean_node->GetOpDesc(), ATTR_NAME_STREAM_LABEL, stream_label)) { GELOGW("LinkToAtomicNode: SetStr failed"); return INTERNAL_ERROR; @@ -262,7 +258,7 @@ bool AtomicAddrCleanPass::IsAtomicOp(const NodePtr &node) { return true; } /// -/// @brief Clear Status, uesd for subgraph pass +/// @brief Clear Status, used for subgraph pass /// @return SUCCESS /// Status AtomicAddrCleanPass::ClearStatus() { diff --git a/src/ge/graph/passes/atomic_addr_clean_pass.h b/src/ge/graph/passes/atomic_addr_clean_pass.h index d2d8f2ce..3640beef 100644 --- a/src/ge/graph/passes/atomic_addr_clean_pass.h +++ b/src/ge/graph/passes/atomic_addr_clean_pass.h @@ -75,6 +75,7 @@ class AtomicAddrCleanPass : public GraphPass { bool IsAtomicOp(const NodePtr &node); vector hcom_node_vec_; + bool is_loop_graph_ = false; }; } // namespace ge diff --git a/src/ge/graph/passes/attach_stream_label_pass.cc b/src/ge/graph/passes/attach_stream_label_pass.cc new file mode 100644 index 00000000..0c342d8c --- /dev/null +++ b/src/ge/graph/passes/attach_stream_label_pass.cc @@ -0,0 +1,319 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graph/passes/attach_stream_label_pass.h" +#include "ge/ge_api_types.h" +#include "graph/common/omg_util.h" + +namespace ge { +Status AttachStreamLabelPass::Run(ComputeGraphPtr graph) { + GELOGD("AttachStreamLabelPass Enter."); + + FindNodes(graph); + for (const auto &node : need_label_nodes_) { + OpDescPtr op_desc = node->GetOpDesc(); + GE_CHECK_NOTNULL(op_desc); + if (!op_desc->HasAttr(ATTR_NAME_STREAM_LABEL)) { + GE_CHK_STATUS_RET(UpdateCondBranch(node), "Update cond branch failed, start node:%s.", node->GetName().c_str()); + } + } + GE_CHK_STATUS_RET(UpdateEnterNode(), "UpdateEnterNode failed."); + + GELOGD("AttachStreamLabelPass Leave."); + return SUCCESS; +} + +/// +/// @brief Clear Status, used for subgraph pass +/// @return +/// +Status AttachStreamLabelPass::ClearStatus() { + stream_switch_nodes_.clear(); + need_label_nodes_.clear(); + enter_nodes_.clear(); + branch_head_nodes_.clear(); + return SUCCESS; +} + +/// +/// @brief Find StreamSwitch / StreamMerge / Enter node +/// @param [in] graph +/// @return void +/// +void AttachStreamLabelPass::FindNodes(const ComputeGraphPtr &graph) { + for (const NodePtr &node : graph->GetDirectNode()) { + const std::string &type = node->GetType(); + if (type == STREAMSWITCH) { + stream_switch_nodes_.emplace_back(node); + } else if (type == STREAMMERGE) { + if ((node->GetOpDesc() != nullptr) && !node->GetOpDesc()->HasAttr(ATTR_NAME_NEXT_ITERATION)) { + need_label_nodes_.emplace_back(node); + } + } else if ((type == ENTER) || (type == REFENTER)) { + enter_nodes_.emplace_back(node); + } + } + + for (const auto &node : stream_switch_nodes_) { + for (const auto &out_ctrl_node : node->GetOutControlNodes()) { + MarkHeadNodes(out_ctrl_node, node); + } + need_label_nodes_.emplace_back(node); + } +} + +/// +/// @brief Mark node as head_node of stream_switch +/// @param [in] node +/// @param [in] stream_switch +/// @return void +/// +void AttachStreamLabelPass::MarkHeadNodes(const NodePtr &node, const NodePtr &stream_switch) { + static const std::set bypass_type_set = {IDENTITY, IDENTITYN, CAST, TRANSDATA, + TRANSPOSE, TRANSPOSED, RESHAPE}; + std::stack nodes; + nodes.push(node); + std::set visited; + while (!nodes.empty()) { + NodePtr cur_node = nodes.top(); + nodes.pop(); + if (visited.count(cur_node) > 0) { + continue; + } + GELOGD("branch_head_node %s of stream_switch %s.", cur_node->GetName().c_str(), stream_switch->GetName().c_str()); + branch_head_nodes_[cur_node] = stream_switch; + if (bypass_type_set.count(cur_node->GetType()) > 0) { + for (const auto &out_node : cur_node->GetOutAllNodes()) { + nodes.push(out_node); + } + } + visited.insert(cur_node); + } +} + +/// +/// @brief update cond branch +/// @param [in] node +/// @return Status +/// +Status AttachStreamLabelPass::UpdateCondBranch(const NodePtr &node) { + std::string stream_label; + std::unordered_set branch_nodes; + std::unordered_set visited; + std::stack nodes; + nodes.push(node); + + static const std::set end_type_set = {STREAMSWITCH, STREAMMERGE, MERGE}; + bool merge_flag = false; + bool exit_flag = false; + bool net_output_flag = false; + while (!nodes.empty()) { + NodePtr cur_node = nodes.top(); + nodes.pop(); + if (visited.count(cur_node) > 0) { + continue; + } + if (AttachFlag(cur_node, stream_label, merge_flag, exit_flag, net_output_flag) != SUCCESS) { + GELOGE(FAILED, "Attach flag for node %s failed.", cur_node->GetName().c_str()); + return FAILED; + } + + const std::string &type = cur_node->GetType(); + for (const auto &out_node : cur_node->GetOutAllNodes()) { + const std::string &out_type = out_node->GetType(); + bool stop_flag = (end_type_set.count(out_type) > 0) || + ((branch_head_nodes_.count(out_node) > 0) && (branch_head_nodes_[out_node] != node)) || + (((type == ENTER) || (type == REFENTER)) && (out_type != STREAMACTIVE)); + if (!stop_flag) { + nodes.push(out_node); + GELOGD("Insert branch node %s.", out_node->GetName().c_str()); + branch_nodes.insert(out_node); + } + } + visited.insert(cur_node); + } + + if (node->GetType() == STREAMSWITCH) { + GE_CHK_STATUS_RET(SetActiveLabelList(node, {stream_label}), "set active_label_list failed."); + } + + bool attach_flag = (merge_flag || exit_flag) && net_output_flag; + if (attach_flag) { + GELOGI("No need to keep on attaching label."); + return SUCCESS; + } + + for (const NodePtr &tmp_node : branch_nodes) { + GELOGD("Attach label %s to node: %s.", stream_label.c_str(), tmp_node->GetName().c_str()); + GE_CHK_STATUS_RET(SetStreamLabel(tmp_node, stream_label), "Set stream label failed."); + } + + return SUCCESS; +} + +/// +/// @brief attach flag +/// @param [in] node +/// @param [out] stream_label +/// @param [out] merge_flag +/// @param [out] exit_flag +/// @param [out] net_output_flag +/// @return Status +/// +Status AttachStreamLabelPass::AttachFlag(const NodePtr &node, std::string &stream_label, bool &merge_flag, + bool &exit_flag, bool &net_output_flag) { + const std::string &type = node->GetType(); + if (type == STREAMSWITCH) { + if (node->GetInDataNodes().empty()) { + GELOGE(INTERNAL_ERROR, "node %s has no input_data_node.", node->GetName().c_str()); + return INTERNAL_ERROR; + } + stream_label = node->GetInDataNodes().at(0)->GetName(); + GE_CHK_STATUS_RET(SetStreamLabel(node, stream_label), "Set stream label failed."); + bool value = false; + OpDescPtr op_desc = node->GetOpDesc(); + GE_CHECK_NOTNULL(op_desc); + GE_CHK_BOOL_EXEC(AttrUtils::GetBool(op_desc, ATTR_NAME_SWITCH_TRUE_BRANCH_FLAG, value), return FAILED, + "StreamSwitch get attr TRUE_BRANCH_STREAM failed."); + stream_label += (value ? "_t" : "_f"); + } else if (type == STREAMMERGE) { + stream_label = node->GetName(); + GE_CHK_STATUS_RET(SetStreamLabel(node, stream_label), "Set stream label failed."); + merge_flag = true; + } else if ((type == EXIT) || (type == REFEXIT)) { + GE_CHK_STATUS_RET(SetStreamLabel(node, stream_label), "Set stream label failed."); + exit_flag = true; + } else if (type == NETOUTPUT) { + net_output_flag = true; + } + + return SUCCESS; +} + +/// +/// @brief Update stream_label start with enter nodes +/// @return Status +/// +Status AttachStreamLabelPass::UpdateEnterNode() { + std::unordered_map> enter_active_map; + for (const auto &enter_node : enter_nodes_) { + for (const auto &out_ctrl_node : enter_node->GetOutControlNodes()) { + if (out_ctrl_node->GetType() != STREAMACTIVE) { + continue; + } + auto iter = enter_active_map.find(out_ctrl_node); + if (iter == enter_active_map.end()) { + enter_active_map[out_ctrl_node] = {enter_node}; + } else { + iter->second.emplace_back(enter_node); + } + } + } + + for (const auto &pair : enter_active_map) { + if (SetEnterLabel(pair.second, pair.first) != SUCCESS) { + GELOGE(FAILED, "Set stream_label for enter_nodes failed."); + return FAILED; + } + + NodePtr active_node = pair.first; + GE_CHECK_NOTNULL(active_node); + std::vector active_label_list; + if (!AttrUtils::GetListStr(active_node->GetOpDesc(), ATTR_NAME_ACTIVE_LABEL_LIST, active_label_list) || + (active_label_list.size() != 1) || active_label_list[0].empty()) { + GELOGE(INTERNAL_ERROR, "Get attr ATTR_NAME_ACTIVE_LABEL_LIST failed, node: %s.", active_node->GetName().c_str()); + return INTERNAL_ERROR; + } + + std::stack enter_nodes; + for (const auto &enter_node : pair.second) { + enter_nodes.emplace(enter_node); + } + if (UpdateLoopBranch(enter_nodes, active_label_list[0]) != SUCCESS) { + GELOGE(FAILED, "Update stream_label for loop_branch failed."); + return FAILED; + } + } + + return SUCCESS; +} + +/// +/// @brief Set stream_label for enter_nodes +/// @param [in] enter_nodes +/// @param [in] active_node +/// @return Status +/// +Status AttachStreamLabelPass::SetEnterLabel(const std::vector &enter_nodes, const NodePtr &active_node) { + std::string stream_label; + GE_CHECK_NOTNULL(active_node); + (void)AttrUtils::GetStr(active_node->GetOpDesc(), ATTR_NAME_STREAM_LABEL, stream_label); + + bool same_flag = true; + for (const auto &enter_node : enter_nodes) { + std::string tmp_label; + (void)AttrUtils::GetStr(enter_node->GetOpDesc(), ATTR_NAME_STREAM_LABEL, tmp_label); + if (tmp_label.empty() || (stream_label == tmp_label)) { + continue; + } + same_flag = false; + break; + } + + if (stream_label.empty()) { + if (same_flag) { + stream_label = active_node->GetName(); + } else { + GELOGW("stream_label of enter_active is empty while stream_label of some enter_node is not."); + return SUCCESS; + } + } + + for (const auto &enter_node : enter_nodes) { + GE_CHK_STATUS_RET(SetStreamLabel(enter_node, stream_label), "Set stream label failed."); + } + GE_CHK_STATUS_RET(SetStreamLabel(active_node, stream_label), "Set stream label failed."); + return SUCCESS; +} + +/// +/// @brief Update stream_label for loop_branch +/// @param [in] enter_nodes +/// @param [in] stream_label +/// @return Status +/// +Status AttachStreamLabelPass::UpdateLoopBranch(const std::stack &enter_nodes, + const std::string &stream_label) { + std::stack nodes(enter_nodes); + NodePtr cur_node = nullptr; + while (!nodes.empty()) { + cur_node = nodes.top(); + nodes.pop(); + for (const NodePtr &out_node : cur_node->GetOutAllNodes()) { + OpDescPtr out_desc = out_node->GetOpDesc(); + GE_CHECK_NOTNULL(out_desc); + std::string out_type = out_desc->GetType(); + if (out_desc->HasAttr(ATTR_NAME_STREAM_LABEL) || (out_type == ENTER) || (out_type == REFENTER)) { + continue; + } + GELOGD("Attach label %s to node: %s.", stream_label.c_str(), out_node->GetName().c_str()); + GE_CHK_STATUS_RET(SetStreamLabel(out_node, stream_label), "Set stream label failed."); + nodes.push(out_node); + } + } + return SUCCESS; +} +} // namespace ge diff --git a/src/ge/graph/passes/attach_stream_label_pass.h b/src/ge/graph/passes/attach_stream_label_pass.h new file mode 100644 index 00000000..743ce36e --- /dev/null +++ b/src/ge/graph/passes/attach_stream_label_pass.h @@ -0,0 +1,97 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_GRAPH_PASSES_ATTACH_STREAM_LABEL_PASS_H_ +#define GE_GRAPH_PASSES_ATTACH_STREAM_LABEL_PASS_H_ + +#include +#include "inc/graph_pass.h" + +namespace ge { +class AttachStreamLabelPass : public GraphPass { + public: + Status Run(ComputeGraphPtr graph); + + /// + /// @brief Clear Status, used for subgraph pass + /// @return + /// + Status ClearStatus() override; + + private: + /// + /// @brief Find StreamSwitch / StreamMerge / Enter node + /// @param [in] graph + /// @return void + /// + void FindNodes(const ComputeGraphPtr &graph); + + /// + /// @brief Mark node as head_node of stream_switch + /// @param [in] node + /// @param [in] stream_switch + /// @return void + /// + void MarkHeadNodes(const NodePtr &node, const NodePtr &stream_switch); + + /// + /// @brief update cond branch + /// @param [in] node + /// @return Status + /// + Status UpdateCondBranch(const NodePtr &node); + + /// + /// @brief attach flag + /// @param [in] node + /// @param [out] stream_label + /// @param [out] merge_flag + /// @param [out] exit_flag + /// @param [out] net_output_flag + /// @return Status + /// + static Status AttachFlag(const NodePtr &node, std::string &stream_label, bool &merge_flag, bool &exit_flag, + bool &net_output_flag); + + /// + /// @brief Update stream_label for loop_branch + /// @param [in] enter_nodes + /// @param [in] stream_label + /// @return Status + /// + static Status UpdateLoopBranch(const std::stack &enter_nodes, const std::string &stream_label); + + /// + /// @brief Update stream_label start with enter nodes + /// @return Status + /// + Status UpdateEnterNode(); + + /// + /// @brief Set stream_label for enter_nodes + /// @param [in] enter_nodes + /// @param [in] active_node + /// @return Status + /// + static Status SetEnterLabel(const std::vector &enter_nodes, const NodePtr &active_node); + + std::vector stream_switch_nodes_; + std::vector need_label_nodes_; + std::vector enter_nodes_; + std::unordered_map branch_head_nodes_; +}; +} // namespace ge +#endif // GE_GRAPH_PASSES_ATTACH_STREAM_LABEL_PASS_H_ diff --git a/src/ge/graph/passes/cast_remove_pass.cc b/src/ge/graph/passes/cast_remove_pass.cc index d18c4b4e..f7ff941c 100644 --- a/src/ge/graph/passes/cast_remove_pass.cc +++ b/src/ge/graph/passes/cast_remove_pass.cc @@ -69,7 +69,6 @@ bool CastRemovePass::HasSameDataType(OpDescPtr &begin_op_desc, OpDescPtr &end_op auto begin_out_desc = begin_op_desc->MutableOutputDesc(0); DataType begin_out_datatype = begin_out_desc->GetDataType(); - if (begin_out_datatype == end_out_datatype && (begin_out_datatype == DT_FLOAT16 || begin_out_datatype == DT_FLOAT)) { type = begin_out_datatype; return true; diff --git a/src/ge/graph/passes/common_subexpression_elimination_pass.cc b/src/ge/graph/passes/common_subexpression_elimination_pass.cc index a52535c1..18f2e857 100644 --- a/src/ge/graph/passes/common_subexpression_elimination_pass.cc +++ b/src/ge/graph/passes/common_subexpression_elimination_pass.cc @@ -83,6 +83,7 @@ Status CommonSubexpressionEliminationPass::Run(ComputeGraphPtr graph) { continue; } auto key = GetCseKey(node); + GELOGD("The node %s cse key %s", node->GetName().c_str(), key.c_str()); auto iter = keys_to_node.find(key); if (iter == keys_to_node.end()) { keys_to_node[key] = node; diff --git a/src/ge/graph/passes/compile_nodes_pass.cc b/src/ge/graph/passes/compile_nodes_pass.cc index def7655e..330569a2 100644 --- a/src/ge/graph/passes/compile_nodes_pass.cc +++ b/src/ge/graph/passes/compile_nodes_pass.cc @@ -23,6 +23,7 @@ #include "common/ge_inner_error_codes.h" #include "framework/common/debug/ge_log.h" #include "graph/debug/ge_attr_define.h" +#include "graph/common/ge_call_wrapper.h" #include "graph/op_desc.h" using domi::ImplyType; @@ -78,7 +79,7 @@ graphStatus CompileNodesPass::Run(ComputeGraphPtr graph) { return result; } GELOGI("[CompileNodesPass]: Optimize success."); - GE_TIMESTAMP_END(CompileNodesPass, "GraphManager::CompileNodesPass"); + GE_TIMESTAMP_EVENT_END(CompileNodesPass, "OptimizeStage2::ControlAttrOptimize::CompileNodesPass"); return GRAPH_SUCCESS; } @@ -101,7 +102,6 @@ graphStatus CompileNodesPass::GetSupportedKernel(const NodePtr &node, const std: } } OpsKernelInfoStorePtr kernel_info = instance->OpsKernelManagerObj().GetOpsKernelInfoStore(kernel_lib_name); - if (kernel_info == nullptr) { GELOGE(ge::GE_GRAPH_PARAM_NULLPTR, "Get op %s ops kernel info store failed", node->GetName().c_str()); return ge::GE_GRAPH_PARAM_NULLPTR; diff --git a/src/ge/graph/passes/cond_pass.cc b/src/ge/graph/passes/cond_pass.cc index 651cf98b..2f3f9333 100644 --- a/src/ge/graph/passes/cond_pass.cc +++ b/src/ge/graph/passes/cond_pass.cc @@ -226,7 +226,7 @@ Status CondPass::HandleScalarCond(const ComputeGraphPtr &graph, const OutDataAnc return FAILED; } - if (GraphUtils::InsertNodeBefore(out_anchor, {in_anchor}, cast_node) != GRAPH_SUCCESS) { + if (GraphUtils::InsertNodeAfter(out_anchor, {in_anchor}, cast_node) != GRAPH_SUCCESS) { GELOGE(FAILED, "Insert Cast node %s between %s->%s failed.", cast_node->GetName().c_str(), out_anchor->GetOwnerNode()->GetName().c_str(), in_anchor->GetOwnerNode()->GetName().c_str()); return FAILED; @@ -271,7 +271,7 @@ Status CondPass::InsertNode(const ComputeGraphPtr &graph, const OutDataAnchorPtr } AddRePassNode(new_node); - if (GraphUtils::InsertNodeBefore(out_anchor, {in_anchor}, new_node) != GRAPH_SUCCESS) { + if (GraphUtils::InsertNodeAfter(out_anchor, {in_anchor}, new_node) != GRAPH_SUCCESS) { GELOGE(FAILED, "Insert %s node %s between %s->%s failed.", type.c_str(), new_node->GetName().c_str(), out_anchor->GetOwnerNode()->GetName().c_str(), in_anchor->GetOwnerNode()->GetName().c_str()); return FAILED; diff --git a/src/ge/graph/passes/cond_remove_pass.cc b/src/ge/graph/passes/cond_remove_pass.cc index 8bc34fbc..1650be92 100644 --- a/src/ge/graph/passes/cond_remove_pass.cc +++ b/src/ge/graph/passes/cond_remove_pass.cc @@ -225,41 +225,40 @@ bool CondRemovePass::CheckIfCondConstInput(const OutDataAnchorPtr &cond_out_anch Status CondRemovePass::ReplaceIfCaseNodeWithPartitioncall(const NodePtr &node, const ComputeGraphPtr &save_branch) { // Add compute graph to new node - const auto &input_anchors = node->GetAllInAnchors(); - const auto &output_anchors = node->GetAllOutAnchors(); + const auto &input_desc_size = node->GetOpDesc()->GetInputsSize(); + const auto &output_desc_size = node->GetOpDesc()->GetOutputsSize(); // Create subgraph opdesc & node auto partitioncall_opdesc = - CreateSubgraphOpDesc(save_branch->GetName(), input_anchors.size() - kConditionIndexNum, output_anchors.size()); + CreateSubgraphOpDesc(save_branch->GetName(), input_desc_size - kConditionIndexNum, output_desc_size); auto partitioncall_node = node->GetOwnerComputeGraph()->AddNode(partitioncall_opdesc); // Link node's peerout anchors to new node's inanchors - for (const auto &input_anchor : input_anchors) { + for (const auto &input_anchor : node->GetAllInAnchors()) { for (const auto &peerout_anchor : input_anchor->GetPeerAnchors()) { if (GraphUtils::AddEdge(peerout_anchor, partitioncall_node->GetInAnchor( input_anchor->GetIdx() - kConditionIndexNum)) != ge::GRAPH_SUCCESS) { GELOGE(FAILED, "Add edge failed, from node:%s idx:%d to node:%s idx:%d, input num:%d, output num:%d", peerout_anchor->GetOwnerNode()->GetName().c_str(), peerout_anchor->GetIdx(), - partitioncall_node->GetName().c_str(), input_anchor->GetIdx(), input_anchors.size(), - output_anchors.size()); + partitioncall_node->GetName().c_str(), input_anchor->GetIdx(), input_desc_size, output_desc_size); return FAILED; } } } // Remove If / Case anchor and peer in anchor // Link new node's out anchors to node's peer inanchors - for (const auto &output_anchor : output_anchors) { + for (const auto &output_anchor : node->GetAllOutAnchors()) { for (const auto &peerin_anchor : output_anchor->GetPeerAnchors()) { if (GraphUtils::RemoveEdge(node->GetOutAnchor(output_anchor->GetIdx()), peerin_anchor) != ge::GRAPH_SUCCESS) { GELOGE(FAILED, "Remove edge failed, from node:%s idx:%d to node:%s idx:%d, input num:%d, output num:%d", node->GetName().c_str(), output_anchor->GetIdx(), peerin_anchor->GetOwnerNode()->GetName().c_str(), - peerin_anchor->GetIdx(), input_anchors.size(), output_anchors.size()); + peerin_anchor->GetIdx(), input_desc_size, output_desc_size); return FAILED; } if (GraphUtils::AddEdge(partitioncall_node->GetOutAnchor(output_anchor->GetIdx()), peerin_anchor) != ge::GRAPH_SUCCESS) { GELOGE(FAILED, "Add edge failed, from node:%s idx:%d to node:%s idx:%d, input num:%d, output num:%d", partitioncall_node->GetName().c_str(), output_anchor->GetIdx(), - peerin_anchor->GetOwnerNode()->GetName().c_str(), peerin_anchor->GetIdx(), input_anchors.size(), - output_anchors.size()); + peerin_anchor->GetOwnerNode()->GetName().c_str(), peerin_anchor->GetIdx(), input_desc_size, + output_desc_size); return FAILED; } } diff --git a/src/ge/graph/passes/constant_folding_pass.cc b/src/ge/graph/passes/constant_folding_pass.cc index 3ac7feb6..80bf7867 100644 --- a/src/ge/graph/passes/constant_folding_pass.cc +++ b/src/ge/graph/passes/constant_folding_pass.cc @@ -29,6 +29,18 @@ #include "inc/kernel.h" namespace ge { +const int64_t kStartCallNum = 1; + +const std::unordered_map> + &ConstantFoldingPass::GetGeConstantFoldingPerfStatistic() const { + return statistic_of_ge_constant_folding_; +} + +const std::unordered_map> + &ConstantFoldingPass::GetOpConstantFoldingPerfStatistic() const { + return statistic_of_op_constant_folding_; +} + Status ConstantFoldingPass::Run(ge::NodePtr &node) { GE_CHECK_NOTNULL(node); GELOGD("Begin to run constant folding on node %s", node->GetName().c_str()); @@ -50,6 +62,8 @@ Status ConstantFoldingPass::Run(ge::NodePtr &node) { auto inputs = OpDescUtils::GetInputData(input_nodes); vector outputs; + // Statistic of ge constant folding kernel + uint64_t start_time = GetCurrentTimestap(); auto ret = RunOpKernel(node, inputs, outputs); if (ret != SUCCESS) { auto op_kernel = folding_pass::GetKernelByType(node); @@ -59,7 +73,18 @@ Status ConstantFoldingPass::Run(ge::NodePtr &node) { return SUCCESS; } + // Statistic of op and fe constant folding kernel + start_time = GetCurrentTimestap(); ret = op_kernel->Compute(node_desc, inputs, outputs); + uint64_t cost_time = GetCurrentTimestap() - start_time; + if (statistic_of_ge_constant_folding_.find(node->GetType()) != statistic_of_ge_constant_folding_.end()) { + uint64_t &cnt = statistic_of_ge_constant_folding_[node->GetType()].first; + uint64_t &cur_cost_time = statistic_of_ge_constant_folding_[node->GetType()].second; + cnt++; + cur_cost_time += cost_time; + } else { + statistic_of_ge_constant_folding_[node->GetType()] = std::pair(kStartCallNum, cost_time); + } if (ret != SUCCESS) { if (ret == NOT_CHANGED) { GELOGD("Node %s type %s, compute terminates and exits the constant folding.", node->GetName().c_str(), @@ -70,6 +95,16 @@ Status ConstantFoldingPass::Run(ge::NodePtr &node) { return ret; } GELOGI("Node %s type %s, constant folding compute success.", node->GetName().c_str(), node->GetType().c_str()); + } else { + if (statistic_of_op_constant_folding_.find(node->GetType()) != statistic_of_op_constant_folding_.end()) { + uint64_t &cnt = statistic_of_op_constant_folding_[node->GetType()].first; + uint64_t &cost_time = statistic_of_op_constant_folding_[node->GetType()].second; + cnt++; + cost_time += GetCurrentTimestap() - start_time; + } else { + statistic_of_op_constant_folding_[node->GetType()] = + std::pair(kStartCallNum, GetCurrentTimestap() - start_time); + } } if (outputs.empty()) { diff --git a/src/ge/graph/passes/constant_folding_pass.h b/src/ge/graph/passes/constant_folding_pass.h index 1dcbcdc3..683b66f1 100644 --- a/src/ge/graph/passes/constant_folding_pass.h +++ b/src/ge/graph/passes/constant_folding_pass.h @@ -26,6 +26,12 @@ namespace ge { class ConstantFoldingPass : public FoldingPass { public: Status Run(ge::NodePtr &node) override; + const std::unordered_map> &GetGeConstantFoldingPerfStatistic() const; + const std::unordered_map> &GetOpConstantFoldingPerfStatistic() const; + + private: + std::unordered_map> statistic_of_op_constant_folding_; + std::unordered_map> statistic_of_ge_constant_folding_; }; } // namespace ge diff --git a/src/ge/graph/passes/control_trigger_pass.cc b/src/ge/graph/passes/control_trigger_pass.cc index 77fcbd69..0c00d553 100644 --- a/src/ge/graph/passes/control_trigger_pass.cc +++ b/src/ge/graph/passes/control_trigger_pass.cc @@ -15,16 +15,9 @@ */ #include "graph/passes/control_trigger_pass.h" - #include - #include "common/ge/ge_util.h" -#include "framework/common/debug/ge_log.h" -#include "framework/common/debug/log.h" -#include "framework/common/ge_inner_error_codes.h" -#include "framework/common/types.h" #include "graph/common/omg_util.h" -#include "graph/debug/ge_attr_define.h" #include "graph/utils/type_utils.h" namespace ge { @@ -444,7 +437,7 @@ Status ControlTriggerPass::FindPredInput(const NodePtr &switch_node) { return SUCCESS; } /// -/// @brief Clear Status, uesd for subgraph pass +/// @brief Clear Status, used for subgraph pass /// @return SUCCESS /// Status ControlTriggerPass::ClearStatus() { diff --git a/src/ge/graph/passes/hccl_memcpy_pass.cc b/src/ge/graph/passes/hccl_memcpy_pass.cc index 5325f56e..a9b3484b 100644 --- a/src/ge/graph/passes/hccl_memcpy_pass.cc +++ b/src/ge/graph/passes/hccl_memcpy_pass.cc @@ -28,6 +28,7 @@ namespace { const int32_t kAnchorSize = 1; const int kAnchorNum = 0; +const char *const kInputMutable = "_input_mutable"; } // namespace namespace ge { Status HcclMemcpyPass::Run(ge::ComputeGraphPtr graph) { @@ -35,7 +36,16 @@ Status HcclMemcpyPass::Run(ge::ComputeGraphPtr graph) { for (const auto &node : graph->GetDirectNode()) { auto op_desc = node->GetOpDesc(); GE_IF_BOOL_EXEC(op_desc == nullptr, continue); - if (!NeedInsertMemcpyOp(op_desc)) { + + bool node_input_mutable = false; + if (!AttrUtils::HasAttr(op_desc, kInputMutable)) { + continue; + } + + GE_IF_BOOL_EXEC(!AttrUtils::GetBool(op_desc, kInputMutable, node_input_mutable), + GELOGE(INTERNAL_ERROR, "node:%s get attr:_input_mutable failed.", node->GetName().c_str()); + return FAILED); + if (!node_input_mutable) { continue; } @@ -53,7 +63,7 @@ Status HcclMemcpyPass::Run(ge::ComputeGraphPtr graph) { NodePtr src_node = src_out_anchor->GetOwnerNode(); std::string src_type = src_node->GetType(); bool check_src_type = (src_type == CONSTANTOP) || (src_type == DATA) || (src_type == CONSTANT); - if (check_src_type && node->GetType() == HCOMALLREDUCE) { + if (check_src_type) { Status ret = ModifyEdgeConnection(graph, src_out_anchor, hccl_in_anchor); if (ret != SUCCESS) { GELOGE(INTERNAL_ERROR, "Failed to modify the connection."); @@ -135,16 +145,6 @@ std::string HcclMemcpyPass::CheckDuplicateName(const std::string &node_name) { return tmp_name; } -/// -/// @brief Check hcom op -/// @param [in] ge::ConstOpDescPtr op_desc -/// @return bool -/// -bool HcclMemcpyPass::NeedInsertMemcpyOp(const ge::ConstOpDescPtr &op_desc) const { - return (op_desc->GetType() == HCOMALLGATHER || op_desc->GetType() == HCOMALLREDUCE || - op_desc->GetType() == HCOMREDUCESCATTER); -} - /// /// @brief Modify edge connection /// @param [in] ComputeGraphPtr graph @@ -182,7 +182,7 @@ Status HcclMemcpyPass::ModifyEdgeConnection(const ComputeGraphPtr &graph, const return SUCCESS; } /// -/// @brief Clear Status, uesd for subgraph pass +/// @brief Clear Status, used for subgraph pass /// @return SUCCESS /// Status HcclMemcpyPass::ClearStatus() { diff --git a/src/ge/graph/passes/hccl_memcpy_pass.h b/src/ge/graph/passes/hccl_memcpy_pass.h index 9de96fbf..13863bd6 100644 --- a/src/ge/graph/passes/hccl_memcpy_pass.h +++ b/src/ge/graph/passes/hccl_memcpy_pass.h @@ -34,8 +34,6 @@ class HcclMemcpyPass : public GraphPass { std::string CheckDuplicateName(const std::string &node_name); - bool NeedInsertMemcpyOp(const ge::ConstOpDescPtr &op_desc) const; - Status ModifyEdgeConnection(const ComputeGraphPtr &graph, const OutDataAnchorPtr &src_out_anchor, const InDataAnchorPtr &hccl_in_anchor); diff --git a/src/ge/graph/passes/identify_reference_pass.cc b/src/ge/graph/passes/identify_reference_pass.cc deleted file mode 100644 index b4131287..00000000 --- a/src/ge/graph/passes/identify_reference_pass.cc +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2019-2020 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "graph/passes/identify_reference_pass.h" - -#include -#include "framework/common/debug/ge_log.h" -#include "graph/debug/ge_attr_define.h" - -namespace ge { -Status IdentifyReferencePass::Run(NodePtr &node) { - if (node == nullptr) { - GELOGE(PARAM_INVALID, "param [node] must not be null."); - return PARAM_INVALID; - } - auto op_desc = node->GetOpDesc(); - if (op_desc == nullptr) { - GELOGE(PARAM_INVALID, "OpDesc of param [node] must not be null."); - return PARAM_INVALID; - } - - auto input_names = op_desc->GetAllInputNames(); - auto outputs = op_desc->GetAllOutputName(); - for (auto &output : outputs) { - for (auto &input_name : input_names) { - if (input_name == output.first) { - bool is_ref = true; - if (AttrUtils::SetBool(op_desc, ATTR_NAME_REFERENCE, is_ref)) { - GELOGI("param [node] %s is reference node, set attribute %s to be true.", - node->GetName().c_str(), ATTR_NAME_REFERENCE.c_str()); - return SUCCESS; - } - } - } - } - - return SUCCESS; -} -} // namespace ge diff --git a/src/ge/graph/passes/infershape_pass.cc b/src/ge/graph/passes/infershape_pass.cc index 18767cea..8b44d31b 100644 --- a/src/ge/graph/passes/infershape_pass.cc +++ b/src/ge/graph/passes/infershape_pass.cc @@ -15,7 +15,7 @@ */ #include "graph/passes/infershape_pass.h" - +#include "common/util/error_manager/error_manager.h" #include "framework/common/debug/ge_log.h" #include "framework/common/ge_inner_error_codes.h" #include "graph/shape_refiner.h" @@ -24,6 +24,8 @@ namespace ge { Status InferShapePass::Run(NodePtr &node) { auto ret = ShapeRefiner::InferShapeAndType(node, !OptionExists(kOptimizeAfterSubGraph)); if (ret != GRAPH_SUCCESS) { + ErrorManager::GetInstance().ATCReportErrMessage("E35003", {"opname", "err_msg"}, + {node->GetName(), "check your model!"}); GELOGE(GE_GRAPH_INFERSHAPE_FAILED, "infershape failed. node: %s", node->GetName().c_str()); return GE_GRAPH_INFERSHAPE_FAILED; } diff --git a/src/ge/graph/passes/iterator_op_pass.cc b/src/ge/graph/passes/iterator_op_pass.cc index e1d452b1..1d11004d 100644 --- a/src/ge/graph/passes/iterator_op_pass.cc +++ b/src/ge/graph/passes/iterator_op_pass.cc @@ -73,14 +73,14 @@ Status IteratorOpPass::Run(ge::ComputeGraphPtr graph) { GE_IF_BOOL_EXEC(status != SUCCESS, GELOGW("Fail to Get var_desc of NODE_NAME_FLOWCTRL_LOOP_PER_ITER failed."); continue); Status ret; - ret = SetRtContext(rtContext_t(), RT_CTX_NORMAL_MODE); + ret = SetRtContext(graph->GetSessionID(), rtContext_t(), RT_CTX_NORMAL_MODE); // EOS will not be considered if ret is not SUCCESS. - GE_IF_BOOL_EXEC(ret != SUCCESS, GELOGW("Set rt context RT_CTX_GEN_MODE failed."); continue); + GE_IF_BOOL_EXEC(ret != SUCCESS, GELOGW("Set rt context RT_CTX_NORMAL_MODE failed."); continue); status = GetVariableValue(graph->GetSessionID(), ge_tensor_desc, NODE_NAME_FLOWCTRL_LOOP_PER_ITER, &loop_per_iter); - ret = SetRtContext(rtContext_t(), RT_CTX_GEN_MODE); + ret = SetRtContext(graph->GetSessionID(), rtContext_t(), RT_CTX_GEN_MODE); // The following process will be affected if ret is not SUCCESS. GE_IF_BOOL_EXEC(ret != SUCCESS, GELOGE(ret, "Set rt context RT_CTX_GEN_MODE failed."); return ret); @@ -108,7 +108,7 @@ Status IteratorOpPass::GetVariableValue(uint64_t session_id, const ge::GeTensorD // base_addr uint8_t *var_mem_base = VarManager::Instance(session_id)->GetVarMemoryBase(RT_MEMORY_HBM); GE_CHECK_NOTNULL(var_mem_base); - // offset + // offset + logic_base uint8_t *dev_ptr = nullptr; GE_CHK_STATUS_RET(VarManager::Instance(session_id)->GetVarAddr(var_name, tensor_desc, &dev_ptr), "Get variable %s address failed.", var_name.c_str()); @@ -279,11 +279,11 @@ ge::OpDescPtr IteratorOpPass::CreateMemcpyAsyncOp(const ge::NodePtr &pre_node) { return op_desc; } -Status IteratorOpPass::SetRtContext(rtContext_t rt_context, rtCtxMode_t mode) { +Status IteratorOpPass::SetRtContext(uint64_t session_id, rtContext_t rt_context, rtCtxMode_t mode) { GELOGI("set rt_context %d, device id:%u.", static_cast(mode), ge::GetContext().DeviceId()); GE_CHK_RT_RET(rtCtxCreate(&rt_context, mode, ge::GetContext().DeviceId())); GE_CHK_RT_RET(rtCtxSetCurrent(rt_context)); - RtContextUtil::GetInstance().AddrtContext(rt_context); + RtContextUtil::GetInstance().AddRtContext(session_id, rt_context); return SUCCESS; } } // namespace ge diff --git a/src/ge/graph/passes/iterator_op_pass.h b/src/ge/graph/passes/iterator_op_pass.h index e403020c..78b951e6 100644 --- a/src/ge/graph/passes/iterator_op_pass.h +++ b/src/ge/graph/passes/iterator_op_pass.h @@ -64,7 +64,7 @@ class IteratorOpPass : public GraphPass { /// ge::OpDescPtr CreateMemcpyAsyncOp(const ge::NodePtr &pre_node); - Status SetRtContext(rtContext_t rt_context, rtCtxMode_t mode); + Status SetRtContext(uint64_t session_id, rtContext_t rt_context, rtCtxMode_t mode); }; } // namespace ge #endif // GE_GRAPH_PASSES_ITERATOR_OP_PASS_H_ diff --git a/src/ge/graph/passes/link_gen_mask_nodes_pass.cc b/src/ge/graph/passes/link_gen_mask_nodes_pass.cc index ff150a54..63ca68a2 100644 --- a/src/ge/graph/passes/link_gen_mask_nodes_pass.cc +++ b/src/ge/graph/passes/link_gen_mask_nodes_pass.cc @@ -97,9 +97,16 @@ void LinkGenMaskNodesPass::GetAllGenMaskNodes(ComputeGraphPtr graph, vectorGetOpDesc() == nullptr) || (node->GetOpDesc()->HasAttr(ATTR_NAME_STREAM_LABEL))) { + continue; + } + auto in_data_nodes = node->GetInDataNodes(); if (in_data_nodes.size() > kGenMaskInputIndex) { NodePtr &gen_mask = in_data_nodes.at(kGenMaskInputIndex); + if ((gen_mask->GetOpDesc() == nullptr) || (gen_mask->GetOpDesc()->HasAttr(ATTR_NAME_STREAM_LABEL))) { + continue; + } if (AreAllInputsConst(gen_mask) && nodes_set.count(gen_mask) == 0) { gen_mask_nodes.emplace_back(gen_mask); nodes_set.emplace(gen_mask); diff --git a/src/ge/graph/passes/mark_same_addr_pass.cc b/src/ge/graph/passes/mark_same_addr_pass.cc new file mode 100644 index 00000000..06d63393 --- /dev/null +++ b/src/ge/graph/passes/mark_same_addr_pass.cc @@ -0,0 +1,81 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graph/passes/mark_same_addr_pass.h" + +namespace ge { +bool MarkSameAddrPass::IsNextNodeExpected(const ge::NodePtr &cur_node, const vector &next_nodes, + int &out_anchor_idx) { + for (auto out_anchor : cur_node->GetAllOutDataAnchors()) { + if (out_anchor == nullptr) { + continue; + } + for (auto in_anchor : out_anchor->GetPeerInDataAnchors()) { + if (in_anchor == nullptr) { + continue; + } + auto dst_node = in_anchor->GetOwnerNode(); + if (dst_node == nullptr) { + continue; + } + if (std::count(next_nodes.begin(), next_nodes.end(), dst_node->GetType()) > 0) { + out_anchor_idx = out_anchor->GetIdx(); + GELOGD("Current node is %s, next node is %s.", cur_node->GetName().c_str(), dst_node->GetName().c_str()); + return true; + } + } + } + return false; +} + +Status MarkSameAddrPass::Run(ComputeGraphPtr graph) { + GELOGD("MarkSameAddrPass begin."); + GE_CHECK_NOTNULL(graph); + auto parent_node = graph->GetParentNode(); + if (parent_node == nullptr) { + return SUCCESS; + } + auto parent_op_desc = parent_node->GetOpDesc(); + GE_CHECK_NOTNULL(parent_op_desc); + if (!parent_op_desc->HasAttr(ATTR_NAME_IS_UNKNOWN_SHAPE)) { + GELOGD("Graph[%s] do not have unknown shape attr. Parent node is %s", graph->GetName().c_str(), + parent_op_desc->GetName().c_str()); + return SUCCESS; + } + + bool is_unknown_shape = false; + (void)AttrUtils::GetBool(parent_op_desc, ATTR_NAME_IS_UNKNOWN_SHAPE, is_unknown_shape); + if (is_unknown_shape) { + GELOGD("Graph[%s] is unknown shape, do not need to set fixed addr attr. Parent node is %s", + graph->GetName().c_str(), parent_op_desc->GetName().c_str()); + return SUCCESS; + } + + int out_anchor_idx = 0; + for (const ge::NodePtr &node : graph->GetDirectNode()) { + auto op_desc = node->GetOpDesc(); + GE_CHECK_NOTNULL(op_desc); + vector next_nodes = {STREAMSWITCH, STREAMSWITCHN, LABELSWITCHBYINDEX}; + if (IsNextNodeExpected(node, next_nodes, out_anchor_idx)) { + string tensor_name = op_desc->GetOutputNameByIndex(out_anchor_idx); + (void)ge::AttrUtils::SetStr(node->GetOpDesc(), ATTR_DYNAMIC_SHAPE_FIXED_ADDR, tensor_name); + (void)ge::AttrUtils::SetInt(node->GetOpDesc(), ATTR_DYNAMIC_SHAPE_FIXED_ADDR_INDEX, out_anchor_idx); + } + } + GELOGD("MarkSameAddrPass end."); + return SUCCESS; +} +} // namespace ge diff --git a/src/ge/graph/passes/mark_same_addr_pass.h b/src/ge/graph/passes/mark_same_addr_pass.h new file mode 100644 index 00000000..ebfcf6b2 --- /dev/null +++ b/src/ge/graph/passes/mark_same_addr_pass.h @@ -0,0 +1,32 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graph/graph.h" +#include "inc/graph_pass.h" + +#ifndef GE_GRAPH_PASSES_MARK_SAME_ADDR_PASS_H_ +#define GE_GRAPH_PASSES_MARK_SAME_ADDR_PASS_H_ + +namespace ge { +class MarkSameAddrPass : public GraphPass { + public: + Status Run(ComputeGraphPtr graph); + + private: + bool IsNextNodeExpected(const ge::NodePtr &cur_node, const vector &next_nodes, int &out_anchor_idx); +}; +} // namespace ge +#endif // GE_GRAPH_PASSES_MARK_SAME_ADDR_PASS_H_ diff --git a/src/ge/graph/passes/merge_to_stream_merge_pass.cc b/src/ge/graph/passes/merge_to_stream_merge_pass.cc new file mode 100644 index 00000000..b785ddfa --- /dev/null +++ b/src/ge/graph/passes/merge_to_stream_merge_pass.cc @@ -0,0 +1,234 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graph/passes/merge_to_stream_merge_pass.h" +#include "common/ge/ge_util.h" +#include "ge/ge_api_types.h" +#include "graph/common/omg_util.h" + +namespace ge { +Status MergeToStreamMergePass::Run(ComputeGraphPtr graph) { + GELOGD("MergeToStreamMergePass Enter"); + + bypass_nodes_.clear(); + for (const auto &node : graph->GetDirectNode()) { + if ((node->GetType() != MERGE) && (node->GetType() != REFMERGE)) { + continue; + } + + OpDescPtr merge_op_desc = node->GetOpDesc(); + GE_CHECK_NOTNULL(merge_op_desc); + if (merge_op_desc->HasAttr(ATTR_INSERT_BY_MBATCH)) { + GE_CHK_STATUS_RET(AddMemcpyAsyncNodes(graph, node, true), "Merge add memcpy node failed."); + GE_CHK_STATUS_RET(SetStreamLabel(node, node->GetName()), "Set stream label failed"); + } else { + GE_CHK_STATUS_RET(ReplaceMergeNode(graph, node), "Add StreamMerge node failed."); + } + } + + for (const auto &node : bypass_nodes_) { + GE_CHK_BOOL_EXEC(GraphUtils::RemoveNodeWithoutRelink(graph, node) == GRAPH_SUCCESS, return FAILED, + "Remove merge node failed."); + } + + GELOGD("MergeToStreamMergePass Leave"); + return SUCCESS; +} + +/// +/// @brief Replace Merge Op +/// @param [in] graph +/// @param [in] merge_node +/// @return Status +/// +Status MergeToStreamMergePass::ReplaceMergeNode(const ComputeGraphPtr &graph, const NodePtr &merge_node) { + OpDescPtr merge_op_desc = merge_node->GetOpDesc(); + GE_CHECK_NOTNULL(merge_op_desc); + + const std::string &node_name = merge_node->GetName(); + GELOGI("Create StreamMerge Op, name=%s.", node_name.c_str()); + OpDescPtr op_desc = MakeShared(node_name, STREAMMERGE); + if (op_desc == nullptr) { + GELOGE(FAILED, "Create op_desc failed, StreamMerge:%s.", node_name.c_str()); + return FAILED; + } + + for (const InDataAnchorPtr &in_anchor : merge_node->GetAllInDataAnchors()) { + GE_CHK_BOOL_EXEC(op_desc->AddInputDesc(merge_op_desc->GetInputDesc(in_anchor->GetIdx())) == GRAPH_SUCCESS, + return FAILED, "Create StreamMerge op: add input desc failed."); + } + + for (const OutDataAnchorPtr &out_anchor : merge_node->GetAllOutDataAnchors()) { + GE_CHK_BOOL_EXEC(op_desc->AddOutputDesc(merge_op_desc->GetOutputDesc(out_anchor->GetIdx())) == GRAPH_SUCCESS, + return FAILED, "Create StreamMerge op: add output desc failed."); + } + + NodePtr stream_merge = graph->AddNode(op_desc); + GE_CHK_BOOL_EXEC(stream_merge != nullptr, return FAILED, "Insert StreamMerge node failed."); + GE_CHK_STATUS_RET(MoveEdges(merge_node, stream_merge), "Move edges failed."); + bypass_nodes_.insert(merge_node); + + if (merge_op_desc->HasAttr(ATTR_NAME_NEXT_ITERATION)) { + std::string next_iteration_name; + GE_IF_BOOL_EXEC(!AttrUtils::GetStr(merge_op_desc, ATTR_NAME_NEXT_ITERATION, next_iteration_name), + GELOGE(INTERNAL_ERROR, "Get ATTR_NAME_NEXT_ITERATION failed"); + return INTERNAL_ERROR); + GE_CHK_STATUS_RET(SetNextIteration(stream_merge, next_iteration_name), "Set next iteration failed"); + } + + return AddMemcpyAsyncNodes(graph, stream_merge, false); +} + +/// +/// @brief Add MemcpyAsync Op as StreamMerge in_node +/// @param [in] graph +/// @param [in] node +/// @param [in] multi_batch_flag +/// @return Status +/// +Status MergeToStreamMergePass::AddMemcpyAsyncNodes(const ComputeGraphPtr &graph, const NodePtr &node, + bool multi_batch_flag) { + GE_CHK_BOOL_EXEC(node != nullptr, return FAILED, "Param of pre node is null."); + for (const InDataAnchorPtr &in_data_anchor : node->GetAllInDataAnchors()) { + OutDataAnchorPtr peer_out_anchor = in_data_anchor->GetPeerOutAnchor(); + GE_IF_BOOL_EXEC(peer_out_anchor == nullptr, continue); + NodePtr in_node = peer_out_anchor->GetOwnerNode(); + const std::string &type = in_node->GetType(); + // For WhileLoop no need memcpy & active for merge. + GE_IF_BOOL_EXEC((type == ENTER) || (type == REFENTER) || (type == NEXTITERATION) || (type == REFNEXTITERATION), + continue); + + const std::string &memcpy_name = node->GetName() + "_input_" + std::to_string(in_data_anchor->GetIdx()); + NodePtr memcpy_node = CreateMemcpyAsyncNode(graph, memcpy_name, peer_out_anchor, multi_batch_flag); + GE_CHK_BOOL_EXEC(memcpy_node != nullptr, return FAILED, "Create MemcpyAsync node failed."); + GE_CHK_STATUS(GraphUtils::RemoveEdge(peer_out_anchor, in_data_anchor), "MemcpyAsync node remove edge failed."); + GE_CHK_STATUS(GraphUtils::AddEdge(peer_out_anchor, memcpy_node->GetInDataAnchor(0)), + "MemcpyAsync node add edge failed."); + GE_CHK_STATUS(GraphUtils::AddEdge(memcpy_node->GetOutDataAnchor(0), in_data_anchor), + "MemcpyAsync node add edge failed."); + + NodePtr active_node = CreateActiveNode(graph, memcpy_node); + GE_CHK_BOOL_EXEC(active_node != nullptr, return FAILED, "Create StreamActive node failed."); + GE_CHK_STATUS(GraphUtils::AddEdge(active_node->GetOutControlAnchor(), node->GetInControlAnchor()), + "StreamActive add ctrl edge failed."); + if (SetActiveLabelList(active_node, {node->GetName()}) != SUCCESS) { + GELOGE(FAILED, "SetActiveLabelList for node %s failed.", active_node->GetName().c_str()); + return FAILED; + } + } + + return SUCCESS; +} + +/// +/// @brief Add MemcpyAsync Node +/// @param [in] graph +/// @param [in] name +/// @param [in] out_data_anchor +/// @param [in] multi_batch_flag +/// @return ge::NodePtr +/// +NodePtr MergeToStreamMergePass::CreateMemcpyAsyncNode(const ComputeGraphPtr &graph, const std::string &name, + const OutDataAnchorPtr &out_data_anchor, bool multi_batch_flag) { + GE_CHK_BOOL_EXEC(out_data_anchor != nullptr, return nullptr, "Param of input node is null."); + OpDescPtr pre_op_desc = out_data_anchor->GetOwnerNode()->GetOpDesc(); + GE_CHK_BOOL_EXEC(pre_op_desc != nullptr, return nullptr, "OpDesc of pre node is invalid."); + + const std::string &memcpy_type = multi_batch_flag ? MEMCPYADDRASYNC : MEMCPYASYNC; + const std::string &node_name = name + "_" + memcpy_type; + GELOGI("Create MemcpyAsync op:%s.", node_name.c_str()); + OpDescPtr op_desc = MakeShared(node_name, memcpy_type); + if (op_desc == nullptr) { + GELOGE(FAILED, "Create op_desc failed, MemcpyAsync:%s.", node_name.c_str()); + return nullptr; + } + + GE_CHK_BOOL_EXEC(op_desc->AddInputDesc(pre_op_desc->GetOutputDesc(out_data_anchor->GetIdx())) == GRAPH_SUCCESS, + return nullptr, "Create MemcpyAsync op: add input desc failed."); + GE_CHK_BOOL_EXEC(op_desc->AddOutputDesc(pre_op_desc->GetOutputDesc(out_data_anchor->GetIdx())) == GRAPH_SUCCESS, + return nullptr, "Create MemcpyAsync op: add output desc failed."); + + return graph->AddNode(op_desc); +} + +/// +/// @brief Create Active Op +/// @param [in] graph +/// @param [in] node +/// @return ge::NodePtr +/// +NodePtr MergeToStreamMergePass::CreateActiveNode(const ComputeGraphPtr &graph, const NodePtr &node) { + const std::string &node_name = node->GetName() + "_" + STREAMACTIVE; + GELOGI("Create StreamActive op:%s.", node_name.c_str()); + OpDescPtr op_desc = MakeShared(node_name, STREAMACTIVE); + if (op_desc == nullptr) { + GELOGE(FAILED, "Create op_desc failed, StreamActive:%s.", node_name.c_str()); + return nullptr; + } + + NodePtr active_node = graph->AddNode(op_desc); + GE_CHK_BOOL_EXEC(active_node != nullptr, return nullptr, "Create StreamActive node failed."); + GE_IF_BOOL_EXEC(GraphUtils::AddEdge(node->GetOutControlAnchor(), active_node->GetInControlAnchor()) != SUCCESS, + GELOGE(INTERNAL_ERROR, "add edge failed"); + return nullptr); + GE_IF_BOOL_EXEC(SetSwitchBranchNodeLabel(active_node, node_name) != SUCCESS, + GELOGE(INTERNAL_ERROR, "set switch branch node label failed"); + return nullptr); + + return active_node; +} + +/// +/// @brief move edges from old_node to new_node +/// @param [in] old_node +/// @param [in] new_node +/// @return Status +/// +Status MergeToStreamMergePass::MoveEdges(const NodePtr &old_node, const NodePtr &new_node) { + for (const InDataAnchorPtr &in_data_anchor : old_node->GetAllInDataAnchors()) { + OutDataAnchorPtr peer_out_anchor = in_data_anchor->GetPeerOutAnchor(); + GE_IF_BOOL_EXEC(peer_out_anchor == nullptr, continue); + + GE_CHK_STATUS(GraphUtils::RemoveEdge(peer_out_anchor, in_data_anchor), "Merge remove in data edge failed."); + GE_CHK_STATUS(GraphUtils::AddEdge(peer_out_anchor, new_node->GetInDataAnchor(in_data_anchor->GetIdx())), + "StreamMerge add in data edge failed."); + } + + for (const OutDataAnchorPtr &out_data_anchor : old_node->GetAllOutDataAnchors()) { + for (const InDataAnchorPtr &peer_in_anchor : out_data_anchor->GetPeerInDataAnchors()) { + GE_CHK_STATUS(GraphUtils::RemoveEdge(out_data_anchor, peer_in_anchor), "Merge remove out data edge failed."); + GE_CHK_STATUS(GraphUtils::AddEdge(new_node->GetOutDataAnchor(out_data_anchor->GetIdx()), peer_in_anchor), + "StreamMerge add out data edge failed."); + } + } + + for (const NodePtr &in_ctrl_node : old_node->GetInControlNodes()) { + GE_CHK_STATUS(GraphUtils::RemoveEdge(in_ctrl_node->GetOutControlAnchor(), old_node->GetInControlAnchor()), + "Merge remove in ctrl edge failed."); + GE_CHK_STATUS(GraphUtils::AddEdge(in_ctrl_node->GetOutControlAnchor(), new_node->GetInControlAnchor()), + "StreamMerge add in ctrl edge failed."); + } + + for (const NodePtr &out_ctrl_node : old_node->GetOutControlNodes()) { + GE_CHK_STATUS(GraphUtils::RemoveEdge(old_node->GetOutControlAnchor(), out_ctrl_node->GetInControlAnchor()), + "Merge remove out ctrl edge failed."); + GE_CHK_STATUS(GraphUtils::AddEdge(new_node->GetOutControlAnchor(), out_ctrl_node->GetInControlAnchor()), + "StreamMerge add out ctrl edge failed."); + } + + return SUCCESS; +} +} // namespace ge diff --git a/src/ge/graph/passes/merge_to_stream_merge_pass.h b/src/ge/graph/passes/merge_to_stream_merge_pass.h new file mode 100644 index 00000000..9f713989 --- /dev/null +++ b/src/ge/graph/passes/merge_to_stream_merge_pass.h @@ -0,0 +1,75 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_GRAPH_PASSES_MERGE_TO_STREAM_MERGE_PASS_H_ +#define GE_GRAPH_PASSES_MERGE_TO_STREAM_MERGE_PASS_H_ + +#include "inc/graph_pass.h" + +namespace ge { +class MergeToStreamMergePass : public GraphPass { + public: + Status Run(ComputeGraphPtr graph); + + private: + /// + /// @brief Replace Merge Op + /// @param [in] graph + /// @param [in] merge_node + /// @return Status + /// + Status ReplaceMergeNode(const ComputeGraphPtr &graph, const NodePtr &merge_node); + + /// + /// @brief Add MemcpyAsync Op as StreamMerge in_node + /// @param [in] graph + /// @param [in] node + /// @param [in] multi_batch_flag + /// @return Status + /// + Status AddMemcpyAsyncNodes(const ComputeGraphPtr &graph, const NodePtr &node, bool multi_batch_flag); + + /// + /// @brief Add MemcpyAsync Node + /// @param [in] graph + /// @param [in] name + /// @param [in] out_data_anchor + /// @param [in] multi_batch_flag + /// @return ge::NodePtr + /// + NodePtr CreateMemcpyAsyncNode(const ComputeGraphPtr &graph, const std::string &name, + const OutDataAnchorPtr &out_data_anchor, bool multi_batch_flag); + + /// + /// @brief Create Active Op + /// @param [in] graph + /// @param [in] node + /// @return ge::NodePtr + /// + NodePtr CreateActiveNode(const ComputeGraphPtr &graph, const NodePtr &node); + + /// + /// @brief move edges from old_node to new_node + /// @param [in] old_node + /// @param [in] new_node + /// @return Status + /// + Status MoveEdges(const NodePtr &old_node, const NodePtr &new_node); + + std::set bypass_nodes_; +}; +} // namespace ge +#endif // GE_GRAPH_PASSES_MERGE_TO_STREAM_MERGE_PASS_H_ diff --git a/src/ge/graph/passes/multi_batch_pass.cc b/src/ge/graph/passes/multi_batch_pass.cc index bb0050be..26190168 100644 --- a/src/ge/graph/passes/multi_batch_pass.cc +++ b/src/ge/graph/passes/multi_batch_pass.cc @@ -32,7 +32,7 @@ namespace ge { Status MultiBatchPass::Run(ComputeGraphPtr graph) { GELOGD("MultiBatchPass Enter"); - GE_CHECK_NOTNULL(graph); + if (graph->GetParentGraph() != nullptr) { GELOGI("Subgraph %s skip the MultiBatchPass.", graph->GetName().c_str()); return SUCCESS; @@ -44,26 +44,26 @@ Status MultiBatchPass::Run(ComputeGraphPtr graph) { return SUCCESS; } if (ret != SUCCESS) { - GELOGE(FAILED, "FindPredValue fail."); + GELOGE(FAILED, "FindPredValue failed."); return FAILED; } std::vector> batch_shape; if (!CheckSwitchN(batch_shape)) { - GELOGE(FAILED, "CheckSwitchN fail."); + GELOGE(FAILED, "CheckSwitchN failed."); return FAILED; } FindSwitchOutNodes(batch_shape.size()); if (ReplaceSwitchN(graph, pred_value, batch_shape) != SUCCESS) { - GELOGE(FAILED, "Replace SwitchN nodes fail."); + GELOGE(FAILED, "Replace SwitchN nodes failed."); return FAILED; } - for (NodePtr &node : bypass_nodes_) { - if (graph->RemoveNode(node) != GRAPH_SUCCESS) { - GELOGE(FAILED, "Remove SwitchN nodes %s fail.", node->GetName().c_str()); + for (const NodePtr &node : bypass_nodes_) { + if (GraphUtils::RemoveNodeWithoutRelink(graph, node) != GRAPH_SUCCESS) { + GELOGE(FAILED, "Remove SwitchN nodes %s failed.", node->GetName().c_str()); return FAILED; } } @@ -79,19 +79,19 @@ Status MultiBatchPass::Run(ComputeGraphPtr graph) { /// @return Status /// Status MultiBatchPass::FindPredValue(const ComputeGraphPtr &graph, OutDataAnchorPtr &pred_value) { - for (NodePtr &node : graph->GetDirectNode()) { + for (const NodePtr &node : graph->GetDirectNode()) { if (node->GetType() != SWITCHN) { continue; } InDataAnchorPtr in_data_anchor = node->GetInDataAnchor(SWITCH_PRED_INPUT); if (in_data_anchor == nullptr) { - GELOGE(FAILED, "FindPredInput fail, in_data_anchor is null, node:%s.", node->GetName().c_str()); + GELOGE(FAILED, "FindPredInput failed, in_data_anchor is null, node:%s.", node->GetName().c_str()); return FAILED; } OutDataAnchorPtr pred_input = in_data_anchor->GetPeerOutAnchor(); if (pred_input == nullptr) { - GELOGE(FAILED, "FindPredInput fail, pred_input is null, node:%s.", node->GetName().c_str()); + GELOGE(FAILED, "FindPredInput failed, pred_input is null, node:%s.", node->GetName().c_str()); return FAILED; } @@ -110,7 +110,7 @@ Status MultiBatchPass::FindPredValue(const ComputeGraphPtr &graph, OutDataAnchor } if (pred_value == nullptr) { - GELOGE(FAILED, "FindPredInput fail, pred_value is null."); + GELOGE(FAILED, "FindPredInput failed, pred_value is null."); return FAILED; } @@ -126,7 +126,7 @@ Status MultiBatchPass::FindPredValue(const ComputeGraphPtr &graph, OutDataAnchor bool MultiBatchPass::CheckSwitchN(std::vector> &batch_shape) { // Check if output_num of different SwitchN is same uint32_t batch_num = 0; - for (NodePtr &node : switch_n_nodes_) { + for (const NodePtr &node : switch_n_nodes_) { uint32_t tmp_num = node->GetAllOutDataAnchorsSize(); if (batch_num == 0) { batch_num = tmp_num; @@ -140,21 +140,21 @@ bool MultiBatchPass::CheckSwitchN(std::vector> &batch_shape std::vector> idx_batch_shape; for (uint32_t i = 0; i < batch_num; i++) { idx_batch_shape.clear(); - for (NodePtr &node : switch_n_nodes_) { + for (const NodePtr &node : switch_n_nodes_) { std::vector output_dims; OpDescPtr op_desc = node->GetOpDesc(); if (op_desc == nullptr) { - GELOGE(FAILED, "CheckDims fail, get op_desc fail, node: %s.", node->GetName().c_str()); + GELOGE(FAILED, "CheckDims failed, get op_desc failed, node: %s.", node->GetName().c_str()); return false; } if (!AttrUtils::GetListInt(op_desc->GetOutputDesc(i), ATTR_NAME_SWITCHN_PRED_VALUE, output_dims)) { - GELOGE(FAILED, "CheckDims fail, get attr ATTR_NAME_SWITCHN_PRED_VALUE fail, batch_index=%u.", i); + GELOGE(FAILED, "CheckDims failed, get attr ATTR_NAME_SWITCHN_PRED_VALUE failed, batch_index=%u.", i); return false; } idx_batch_shape.emplace_back(output_dims); } if (!CheckDims(idx_batch_shape)) { - GELOGE(FAILED, "CheckDims fail, batch_index=%u.", i); + GELOGE(FAILED, "CheckDims failed, batch_index=%u.", i); return false; } @@ -187,11 +187,11 @@ void MultiBatchPass::FindSwitchOutNodes(uint32_t batch_num) { std::vector output_nodes; for (uint32_t i = 0; i < batch_num; i++) { output_nodes.clear(); - for (NodePtr &node : switch_n_nodes_) { + for (const NodePtr &node : switch_n_nodes_) { // idx is promised to be valid OutDataAnchorPtr out_data_anchor = node->GetOutDataAnchor(i); GE_CHECK_NOTNULL_JUST_RETURN(out_data_anchor); - for (InDataAnchorPtr &peer_in_anchor : out_data_anchor->GetPeerInDataAnchors()) { + for (const InDataAnchorPtr &peer_in_anchor : out_data_anchor->GetPeerInDataAnchors()) { output_nodes.emplace_back(peer_in_anchor->GetOwnerNode()); } } @@ -208,33 +208,33 @@ void MultiBatchPass::FindSwitchOutNodes(uint32_t batch_num) { /// @param [in] batch_shape /// @return Status /// -Status MultiBatchPass::ReplaceSwitchN(ComputeGraphPtr &graph, OutDataAnchorPtr &pred_value, +Status MultiBatchPass::ReplaceSwitchN(const ComputeGraphPtr &graph, const OutDataAnchorPtr &pred_value, const std::vector> &batch_shape) { NodePtr pred_value_node = pred_value->GetOwnerNode(); // Create SwitchCase node - const std::string switch_case_name = pred_value_node->GetName() + "_" + STREAMSWITCHN; + const std::string &switch_case_name = pred_value_node->GetName() + "_" + STREAMSWITCHN; NodePtr switch_case = CreateSwitchCaseNode(graph, switch_case_name, pred_value, batch_shape); if (switch_case == nullptr) { - GELOGE(FAILED, "CreateSwitchCaseNode %s fail.", switch_case_name.c_str()); + GELOGE(FAILED, "CreateSwitchCaseNode %s failed.", switch_case_name.c_str()); return FAILED; } - for (NodePtr &switch_n_node : switch_n_nodes_) { + for (const NodePtr &switch_n_node : switch_n_nodes_) { if (BypassSwitchN(switch_n_node, switch_case) != SUCCESS) { - GELOGE(FAILED, "Bypass SwitchN %s fail.", switch_case_name.c_str()); + GELOGE(FAILED, "Bypass SwitchN %s failed.", switch_case_name.c_str()); return FAILED; } } // Add switchCase input edge if (GraphUtils::AddEdge(pred_value, switch_case->GetInDataAnchor(0)) != GRAPH_SUCCESS) { - GELOGE(FAILED, "Add SwitchCase in_data_edge fail, %s->%s.", pred_value_node->GetName().c_str(), + GELOGE(FAILED, "Add SwitchCase in_data_edge failed, %s->%s.", pred_value_node->GetName().c_str(), switch_case->GetName().c_str()); return FAILED; } if (AttachLabel(switch_case) != SUCCESS) { - GELOGE(FAILED, "AttachLabel fail."); + GELOGE(FAILED, "AttachLabel failed."); return FAILED; } @@ -248,7 +248,7 @@ Status MultiBatchPass::ReplaceSwitchN(ComputeGraphPtr &graph, OutDataAnchorPtr & /// bool MultiBatchPass::CheckDims(const std::vector> &output_shape) const { if (output_shape.empty()) { - GELOGE(FAILED, "CheckDims fail: output_shape is empty."); + GELOGE(FAILED, "CheckDims failed: output_shape is empty."); return false; } @@ -257,7 +257,7 @@ bool MultiBatchPass::CheckDims(const std::vector> &output_s for (size_t i = 1; i < num; i++) { size_t tmp_dim_num = output_shape[i].size(); if (dim_num != tmp_dim_num) { - GELOGE(FAILED, "CheckDims fail: dim_num not equal, output_0:%zu, output_%zu:%zu.", dim_num, i, tmp_dim_num); + GELOGE(FAILED, "CheckDims failed: dim_num not equal, output_0:%zu, output_%zu:%zu.", dim_num, i, tmp_dim_num); return false; } } @@ -271,7 +271,7 @@ bool MultiBatchPass::CheckDims(const std::vector> &output_s for (size_t j = 1; j < num; j++) { int64_t tmp_dim_value = output_shape[j][i]; if (dim_value != tmp_dim_value) { - GELOGE(FAILED, "CheckDims fail: dim_value not equal, dim_index=%zu, dim_value_0:%ld, dim_value_%zu:%ld.", i, + GELOGE(FAILED, "CheckDims failed: dim_value not equal, dim_index=%zu, dim_value_0:%ld, dim_value_%zu:%ld.", i, dim_value, j, tmp_dim_value); return false; } @@ -289,41 +289,41 @@ bool MultiBatchPass::CheckDims(const std::vector> &output_s /// @param [in] batch_shape /// @return ge::NodePtr /// -NodePtr MultiBatchPass::CreateSwitchCaseNode(ComputeGraphPtr &graph, const std::string &name, +NodePtr MultiBatchPass::CreateSwitchCaseNode(const ComputeGraphPtr &graph, const std::string &name, const OutDataAnchorPtr &pred_value, const std::vector> &batch_shape) { OpDescPtr op_desc = MakeShared(name, STREAMSWITCHN); if (op_desc == nullptr) { - GELOGE(FAILED, "Create op_desc fail, StreamSwitchN:%s.", name.c_str()); + GELOGE(FAILED, "Create op_desc failed, StreamSwitchN:%s.", name.c_str()); return nullptr; } GELOGI("Create StreamSwitchN op:%s.", name.c_str()); OpDescPtr pred_desc = pred_value->GetOwnerNode()->GetOpDesc(); if (pred_desc == nullptr) { - GELOGE(FAILED, "Get pred_desc fail, StreamSwitchN:%s.", name.c_str()); + GELOGE(FAILED, "Get pred_desc failed, StreamSwitchN:%s.", name.c_str()); return nullptr; } if (op_desc->AddInputDesc(pred_desc->GetOutputDesc(pred_value->GetIdx())) != GRAPH_SUCCESS) { - GELOGE(FAILED, "AddInputDesc fail, StreamSwitchN:%s.", name.c_str()); + GELOGE(FAILED, "AddInputDesc failed, StreamSwitchN:%s.", name.c_str()); return nullptr; } NodePtr switch_case_node = graph->AddNode(op_desc); if (switch_case_node == nullptr) { - GELOGE(FAILED, "Create node fail, StreamSwitchN:%s.", name.c_str()); + GELOGE(FAILED, "Create node failed, StreamSwitchN:%s.", name.c_str()); return nullptr; } uint32_t batch_num = static_cast(batch_shape.size()); if (!AttrUtils::SetInt(op_desc, ATTR_NAME_BATCH_NUM, batch_num)) { - GELOGE(FAILED, "set attr ATTR_NAME_BATCH_NUM fail, StreamSwitchN:%s.", name.c_str()); + GELOGE(FAILED, "set attr ATTR_NAME_BATCH_NUM failed, StreamSwitchN:%s.", name.c_str()); return nullptr; } for (uint32_t i = 0; i < batch_num; i++) { - const std::string attr_name = ATTR_NAME_PRED_VALUE + "_" + std::to_string(i); + const std::string &attr_name = ATTR_NAME_PRED_VALUE + "_" + std::to_string(i); if (!AttrUtils::SetListInt(op_desc, attr_name, batch_shape[i])) { - GELOGE(FAILED, "set attr ATTR_NAME_PRED_VALUE fail, StreamSwitchN:%s.", name.c_str()); + GELOGE(FAILED, "set attr ATTR_NAME_PRED_VALUE failed, StreamSwitchN:%s.", name.c_str()); return nullptr; } } @@ -337,43 +337,43 @@ NodePtr MultiBatchPass::CreateSwitchCaseNode(ComputeGraphPtr &graph, const std:: /// @param [in] switch_case /// @return Status /// -Status MultiBatchPass::BypassSwitchN(NodePtr &switch_n_node, NodePtr &switch_case) { +Status MultiBatchPass::BypassSwitchN(const NodePtr &switch_n_node, const NodePtr &switch_case) { InDataAnchorPtr in_data_anchor = switch_n_node->GetInDataAnchor(SWITCH_DATA_INPUT); if (in_data_anchor == nullptr) { - GELOGE(FAILED, "Check in_data_anchor fail, SwitchN:%s.", switch_n_node->GetName().c_str()); + GELOGE(FAILED, "Check in_data_anchor failed, SwitchN:%s.", switch_n_node->GetName().c_str()); return FAILED; } OutDataAnchorPtr peer_data_anchor = in_data_anchor->GetPeerOutAnchor(); if (peer_data_anchor == nullptr) { - GELOGE(FAILED, "Check peer_data_anchor fail, SwitchN:%s.", switch_n_node->GetName().c_str()); + GELOGE(FAILED, "Check peer_data_anchor failed, SwitchN:%s.", switch_n_node->GetName().c_str()); return FAILED; } NodePtr data_input = peer_data_anchor->GetOwnerNode(); // Remove SwitchN data input if (GraphUtils::RemoveEdge(peer_data_anchor, in_data_anchor) != GRAPH_SUCCESS) { - GELOGE(FAILED, "Remove SwitchN in_data_edge fail, %s->%s.", data_input->GetName().c_str(), + GELOGE(FAILED, "Remove SwitchN in_data_edge failed, %s->%s.", data_input->GetName().c_str(), switch_n_node->GetName().c_str()); return FAILED; } if (GraphUtils::AddEdge(data_input->GetOutControlAnchor(), switch_case->GetInControlAnchor()) != GRAPH_SUCCESS) { - GELOGE(FAILED, "Add StreamSwitchN in_control_edge fail, %s->%s.", data_input->GetName().c_str(), + GELOGE(FAILED, "Add StreamSwitchN in_control_edge failed, %s->%s.", data_input->GetName().c_str(), switch_case->GetName().c_str()); return FAILED; } // Add SwitchCase control output - for (OutDataAnchorPtr &out_data_anchor : switch_n_node->GetAllOutDataAnchors()) { - for (InDataAnchorPtr &peer_in_anchor : out_data_anchor->GetPeerInDataAnchors()) { + for (const OutDataAnchorPtr &out_data_anchor : switch_n_node->GetAllOutDataAnchors()) { + for (const InDataAnchorPtr &peer_in_anchor : out_data_anchor->GetPeerInDataAnchors()) { NodePtr data_output = peer_in_anchor->GetOwnerNode(); if ((GraphUtils::RemoveEdge(out_data_anchor, peer_in_anchor) != GRAPH_SUCCESS) || (GraphUtils::AddEdge(peer_data_anchor, peer_in_anchor) != GRAPH_SUCCESS)) { - GELOGE(FAILED, "Bypass SwitchN data_edge fail, %s->%s->%s.", data_input->GetName().c_str(), + GELOGE(FAILED, "Bypass SwitchN data_edge failed, %s->%s->%s.", data_input->GetName().c_str(), switch_n_node->GetName().c_str(), data_output->GetName().c_str()); return FAILED; } if (GraphUtils::AddEdge(switch_case->GetOutControlAnchor(), data_output->GetInControlAnchor()) != GRAPH_SUCCESS) { - GELOGE(FAILED, "Add SwitchCase out_control_edge fail, %s->%s.", switch_case->GetName().c_str(), + GELOGE(FAILED, "Add SwitchCase out_control_edge failed, %s->%s.", switch_case->GetName().c_str(), data_output->GetName().c_str()); return FAILED; } @@ -390,17 +390,17 @@ Status MultiBatchPass::BypassSwitchN(NodePtr &switch_n_node, NodePtr &switch_cas /// @param [in] switch_case_node /// @return Status /// -Status MultiBatchPass::AttachLabel(NodePtr &switch_case_node) { +Status MultiBatchPass::AttachLabel(const NodePtr &switch_case_node) { std::vector stream_label_list; for (uint32_t i = 0; i < static_cast(batch_head_nodes_.size()); i++) { if (AttachBatchLabel(i) != SUCCESS) { - GELOGE(FAILED, "AttachBatchLabel fail, batch_idx=%u", i); + GELOGE(FAILED, "AttachBatchLabel failed, batch_idx=%u", i); return FAILED; } - const std::string stream_label = "stream_label_batch_" + std::to_string(i); + const std::string &stream_label = "stream_label_batch_" + std::to_string(i); if (AttachStreamLabel(i, stream_label) != SUCCESS) { - GELOGE(FAILED, "AttachStreamLabel fail, stream_label=%s", stream_label.c_str()); + GELOGE(FAILED, "AttachStreamLabel failed, stream_label=%s", stream_label.c_str()); return FAILED; } stream_label_list.emplace_back(stream_label); @@ -416,11 +416,11 @@ Status MultiBatchPass::AttachLabel(NodePtr &switch_case_node) { /// Status MultiBatchPass::AttachBatchLabel(uint32_t batch_idx) { std::stack nodes; - for (auto &node : batch_head_nodes_[batch_idx]) { + for (const auto &node : batch_head_nodes_[batch_idx]) { nodes.push(node); } - const std::string batch_label = "Batch_" + std::to_string(batch_idx); + const std::string &batch_label = "Batch_" + std::to_string(batch_idx); std::unordered_set handled_nodes; while (!nodes.empty()) { NodePtr cur_node = nodes.top(); @@ -434,7 +434,7 @@ Status MultiBatchPass::AttachBatchLabel(uint32_t batch_idx) { if (cur_desc->HasAttr(ATTR_NAME_BATCH_LABEL)) { std::string tmp_label; if (!AttrUtils::GetStr(cur_desc, ATTR_NAME_BATCH_LABEL, tmp_label)) { - GELOGE(FAILED, "get attr ATTR_NAME_BATCH_LABEL fail, node: %s.", cur_desc->GetName().c_str()); + GELOGE(FAILED, "get attr ATTR_NAME_BATCH_LABEL failed, node: %s.", cur_desc->GetName().c_str()); return FAILED; } if (tmp_label != batch_label) { @@ -445,14 +445,14 @@ Status MultiBatchPass::AttachBatchLabel(uint32_t batch_idx) { } GELOGD("Attach batch_label %s to node %s.", batch_label.c_str(), cur_desc->GetName().c_str()); if (!AttrUtils::SetStr(cur_desc, ATTR_NAME_BATCH_LABEL, batch_label)) { - GELOGE(FAILED, "set attr ATTR_NAME_BATCH_LABEL fail, node:%s.", cur_desc->GetName().c_str()); + GELOGE(FAILED, "set attr ATTR_NAME_BATCH_LABEL failed, node:%s.", cur_desc->GetName().c_str()); return FAILED; } - for (auto &out_node : cur_node->GetOutAllNodes()) { + for (const auto &out_node : cur_node->GetOutAllNodes()) { OpDescPtr op_desc = out_node->GetOpDesc(); GE_CHECK_NOTNULL(op_desc); - const std::string type = op_desc->GetType(); + const std::string &type = op_desc->GetType(); if ((type == MERGE) && (op_desc->HasAttr(ATTR_INSERT_BY_MBATCH))) { continue; } @@ -476,7 +476,7 @@ Status MultiBatchPass::AttachBatchLabel(uint32_t batch_idx) { /// Status MultiBatchPass::AttachStreamLabel(uint32_t batch_idx, const std::string &stream_label) { std::stack nodes; - for (auto &node : batch_head_nodes_[batch_idx]) { + for (const auto &node : batch_head_nodes_[batch_idx]) { nodes.push(node); } @@ -493,11 +493,11 @@ Status MultiBatchPass::AttachStreamLabel(uint32_t batch_idx, const std::string & GELOGD("Attach stream_label %s to node %s.", stream_label.c_str(), cur_desc->GetName().c_str()); if (SetStreamLabel(cur_node, stream_label) != SUCCESS) { - GELOGE(FAILED, "SetStreamLabel fail, node:%s.", cur_node->GetName().c_str()); + GELOGE(FAILED, "Set stream_label failed, node:%s.", cur_node->GetName().c_str()); return FAILED; } - for (auto &out_node : cur_node->GetOutAllNodes()) { + for (const auto &out_node : cur_node->GetOutAllNodes()) { nodes.push(out_node); } diff --git a/src/ge/graph/passes/multi_batch_pass.h b/src/ge/graph/passes/multi_batch_pass.h index 6e3f5e46..2e83262c 100644 --- a/src/ge/graph/passes/multi_batch_pass.h +++ b/src/ge/graph/passes/multi_batch_pass.h @@ -31,14 +31,15 @@ class MultiBatchPass : public GraphPass { Status FindPredValue(const ComputeGraphPtr &graph, OutDataAnchorPtr &pred_value); bool CheckSwitchN(std::vector> &batch_shape); void FindSwitchOutNodes(uint32_t batch_num); - Status ReplaceSwitchN(ComputeGraphPtr &graph, OutDataAnchorPtr &pred_value, + Status ReplaceSwitchN(const ComputeGraphPtr &graph, const OutDataAnchorPtr &pred_value, const std::vector> &batch_shape); bool CheckDims(const std::vector> &output_shape) const; - NodePtr CreateSwitchCaseNode(ComputeGraphPtr &graph, const std::string &name, const OutDataAnchorPtr &pred_value, + NodePtr CreateSwitchCaseNode(const ComputeGraphPtr &graph, const std::string &name, + const OutDataAnchorPtr &pred_value, const std::vector> &batch_shape); - Status BypassSwitchN(NodePtr &switch_n_node, NodePtr &switch_case_node); - Status AttachLabel(NodePtr &switch_case_node); + Status BypassSwitchN(const NodePtr &switch_n_node, const NodePtr &switch_case_node); + Status AttachLabel(const NodePtr &switch_case_node); Status AttachBatchLabel(uint32_t batch_idx); Status AttachStreamLabel(uint32_t batch_idx, const std::string &stream_label); diff --git a/src/ge/graph/passes/next_iteration_pass.cc b/src/ge/graph/passes/next_iteration_pass.cc index 138ad86b..c664ac53 100644 --- a/src/ge/graph/passes/next_iteration_pass.cc +++ b/src/ge/graph/passes/next_iteration_pass.cc @@ -16,19 +16,8 @@ #include "graph/passes/next_iteration_pass.h" -#include -#include -#include -#include -#include - #include "common/ge/ge_util.h" -#include "framework/common/debug/ge_log.h" -#include "framework/common/debug/log.h" -#include "framework/common/ge_inner_error_codes.h" -#include "framework/common/types.h" #include "graph/common/omg_util.h" -#include "graph/debug/ge_attr_define.h" namespace ge { Status NextIterationPass::Run(ComputeGraphPtr graph) { @@ -41,24 +30,24 @@ Status NextIterationPass::Run(ComputeGraphPtr graph) { if ((type != ENTER) && (type != REFENTER)) { continue; } - if (HandleEnterNode(node) != SUCCESS) { - GELOGE(INTERNAL_ERROR, "HandleEnterNode for node %s fail.", node->GetName().c_str()); + if (GroupEnterNode(node) != SUCCESS) { + GELOGE(INTERNAL_ERROR, "Group enter_node %s failed.", node->GetName().c_str()); return INTERNAL_ERROR; } } if (FindWhileGroups() != SUCCESS) { - GELOGE(INTERNAL_ERROR, "FindWhileGroups fail"); + GELOGE(INTERNAL_ERROR, "Find while groups failed."); return INTERNAL_ERROR; } if (!VerifyWhileGroup()) { - GELOGE(INTERNAL_ERROR, "VerifyWhileGroup fail"); + GELOGE(INTERNAL_ERROR, "Verify while groups failed."); return INTERNAL_ERROR; } if (HandleWhileGroup(graph) != SUCCESS) { - GELOGE(FAILED, "HandleWhileGroup fail"); + GELOGE(FAILED, "Handle while groups failed."); return FAILED; } @@ -67,16 +56,16 @@ Status NextIterationPass::Run(ComputeGraphPtr graph) { } /// -/// @brief Handle Enter node +/// @brief Group Enter node /// @param [in] enter_node /// @return Status /// -Status NextIterationPass::HandleEnterNode(const NodePtr &enter_node) { +Status NextIterationPass::GroupEnterNode(const NodePtr &enter_node) { OpDescPtr enter_desc = enter_node->GetOpDesc(); GE_CHECK_NOTNULL(enter_desc); std::string frame_name; if (!ge::AttrUtils::GetStr(enter_desc, ENTER_ATTR_FRAME_NAME, frame_name) || frame_name.empty()) { - GELOGE(FAILED, "Get attr ENTER_ATTR_FRAME_NAME fail, node: %s", enter_desc->GetName().c_str()); + GELOGE(FAILED, "Get attr ENTER_ATTR_FRAME_NAME failed, node: %s", enter_desc->GetName().c_str()); return FAILED; } @@ -84,7 +73,7 @@ Status NextIterationPass::HandleEnterNode(const NodePtr &enter_node) { if (iter == loop_group_map_.end()) { LoopCondGroupPtr loop_group = MakeShared(); if (loop_group == nullptr) { - GELOGE(FAILED, "MakeShared for LoopCondGroup fail."); + GELOGE(FAILED, "MakeShared for LoopCondGroup failed."); return FAILED; } loop_group->enter_nodes.emplace_back(enter_node); @@ -101,30 +90,30 @@ Status NextIterationPass::HandleEnterNode(const NodePtr &enter_node) { /// @return Status /// Status NextIterationPass::FindWhileGroups() { - for (auto &loop_group_iter : loop_group_map_) { - const std::string frame_name = loop_group_iter.first; - for (auto &enter_node : loop_group_iter.second->enter_nodes) { - for (auto &out_node : enter_node->GetOutAllNodes()) { - const std::string type = out_node->GetType(); + for (const auto &loop_group_iter : loop_group_map_) { + const std::string &frame_name = loop_group_iter.first; + for (const auto &enter_node : loop_group_iter.second->enter_nodes) { + for (const auto &out_node : enter_node->GetOutAllNodes()) { + const std::string &type = out_node->GetType(); if ((type != MERGE) && (type != REFMERGE)) { continue; } NodePtr next_node = nullptr; if (FindTargetNode(out_node, NEXTITERATION, true, next_node) != SUCCESS) { - GELOGE(INTERNAL_ERROR, "Get NextIteration node fail, frame_name: %s.", frame_name.c_str()); + GELOGE(INTERNAL_ERROR, "Get NextIteration node failed, frame_name: %s.", frame_name.c_str()); return INTERNAL_ERROR; } NodePtr switch_node = nullptr; if (FindTargetNode(out_node, SWITCH, false, switch_node) != SUCCESS) { - GELOGE(INTERNAL_ERROR, "Get Switch node fail, frame_name: %s.", frame_name.c_str()); + GELOGE(INTERNAL_ERROR, "Get Switch node failed, frame_name: %s.", frame_name.c_str()); return INTERNAL_ERROR; } NodePtr loop_cond = nullptr; if (FindTargetNode(switch_node, LOOPCOND, true, loop_cond) != SUCCESS) { - GELOGE(INTERNAL_ERROR, "Get LoopCond node fail, frame_name: %s.", frame_name.c_str()); + GELOGE(INTERNAL_ERROR, "Get LoopCond node failed, frame_name: %s.", frame_name.c_str()); return INTERNAL_ERROR; } @@ -148,21 +137,21 @@ Status NextIterationPass::FindWhileGroups() { /// bool NextIterationPass::VerifyWhileGroup() { // map - for (auto &loop_group_iter : loop_group_map_) { - const std::string frame_name = loop_group_iter.first; + for (const auto &loop_group_iter : loop_group_map_) { + const std::string &frame_name = loop_group_iter.first; if (frame_name.empty()) { - GELOGE(INTERNAL_ERROR, "VerifyWhileGroup fail, frame_name is empty."); + GELOGE(INTERNAL_ERROR, "Verify while group failed, frame_name is empty."); return false; } if (loop_group_iter.second->loop_cond == nullptr) { - GELOGE(INTERNAL_ERROR, "VerifyWhileGroup fail, LoopCond is null, frame_name: %s.", frame_name.c_str()); + GELOGE(INTERNAL_ERROR, "Verify while group failed, LoopCond is null, frame_name: %s.", frame_name.c_str()); return false; } - for (auto &pair_iter : loop_group_iter.second->merge_next_pairs) { + for (const auto &pair_iter : loop_group_iter.second->merge_next_pairs) { if ((pair_iter.first == nullptr) || (pair_iter.second == nullptr)) { - GELOGE(INTERNAL_ERROR, "VerifyWhileGroup fail, merge_node/next_node is null, frame_name: %s.", + GELOGE(INTERNAL_ERROR, "Verify while group failed, merge_node/next_node is null, frame_name: %s.", frame_name.c_str()); return false; } @@ -178,51 +167,51 @@ bool NextIterationPass::VerifyWhileGroup() { /// @return Status /// Status NextIterationPass::HandleWhileGroup(ComputeGraphPtr &graph) { - for (auto &loop_cond_iter : loop_group_map_) { - std::string cond_name = loop_cond_iter.second->loop_cond->GetName(); - GELOGI("HandleWhileGroup, LoopCond node: %s.", cond_name.c_str()); + for (const auto &loop_cond_iter : loop_group_map_) { + const std::string &cond_name = loop_cond_iter.second->loop_cond->GetName(); + GELOGI("Handle while group, LoopCond node: %s.", cond_name.c_str()); - // Create Active node, Enter->Active->Merge, NextItaration->Active->Merge + // Create Active node, Enter->Active->Merge, NextIteration->Active->Merge NodePtr enter_active = CreateActiveNode(graph, cond_name + "_Enter_" + STREAMACTIVE); NodePtr next_active = CreateActiveNode(graph, cond_name + "_Next_" + STREAMACTIVE); if ((enter_active == nullptr) || (next_active == nullptr)) { - GELOGE(INTERNAL_ERROR, "CreateActiveNode fail, cond_name: %s.", cond_name.c_str()); + GELOGE(INTERNAL_ERROR, "Create active node failed, cond_name: %s.", cond_name.c_str()); return INTERNAL_ERROR; } - for (auto &enter_node : loop_cond_iter.second->enter_nodes) { + for (const auto &enter_node : loop_cond_iter.second->enter_nodes) { // Enter --> Active if (GraphUtils::AddEdge(enter_node->GetOutControlAnchor(), enter_active->GetInControlAnchor()) != GRAPH_SUCCESS) { - GELOGE(INTERNAL_ERROR, "Add control edge fail"); + GELOGE(INTERNAL_ERROR, "Add control edge failed."); return INTERNAL_ERROR; } } - for (auto &pair : loop_cond_iter.second->merge_next_pairs) { + for (const auto &pair : loop_cond_iter.second->merge_next_pairs) { NodePtr merge_node = pair.first; NodePtr next_node = pair.second; // Active --> Merge if (GraphUtils::AddEdge(enter_active->GetOutControlAnchor(), merge_node->GetInControlAnchor()) != GRAPH_SUCCESS) { - GELOGE(INTERNAL_ERROR, "Add control edge fail"); + GELOGE(INTERNAL_ERROR, "Add control edge failed."); return INTERNAL_ERROR; } // NextIteration --> Active if (GraphUtils::AddEdge(next_node->GetOutControlAnchor(), next_active->GetInControlAnchor()) != GRAPH_SUCCESS) { - GELOGE(INTERNAL_ERROR, "Add control edge fail"); + GELOGE(INTERNAL_ERROR, "Add control edge failed."); return INTERNAL_ERROR; } // break link between NextIteration and Merge if (BreakNextIteration(next_node, merge_node) != SUCCESS) { - GELOGE(INTERNAL_ERROR, "BreakNextIteration failed"); + GELOGE(INTERNAL_ERROR, "Break NextIteration failed"); return INTERNAL_ERROR; } } if ((SetActiveLabelList(enter_active, {cond_name}) != SUCCESS) || (SetActiveLabelList(next_active, {cond_name}) != SUCCESS)) { - GELOGE(INTERNAL_ERROR, "SetActiveLabelList failed"); + GELOGE(INTERNAL_ERROR, "Set attr ACTIVE_LABEL_LIST failed."); return INTERNAL_ERROR; } } @@ -245,12 +234,12 @@ NodePtr NextIterationPass::CreateActiveNode(ComputeGraphPtr &graph, const std::s GELOGI("Create StreamActive op:%s.", op_desc->GetName().c_str()); NodePtr active_node = graph->AddNode(op_desc); if (active_node == nullptr) { - GELOGE(INTERNAL_ERROR, "Create node[%s] fail.", name.c_str()); + GELOGE(INTERNAL_ERROR, "Create node[%s] failed.", name.c_str()); return nullptr; } if (SetSwitchBranchNodeLabel(active_node, name) != SUCCESS) { - GELOGE(INTERNAL_ERROR, "SetSwitchBranchNodeLabel for node: %s failed.", active_node->GetName().c_str()); + GELOGE(INTERNAL_ERROR, "Set attr SWITCH_BRANCH_NODE_LABEL for node: %s failed.", active_node->GetName().c_str()); return nullptr; } @@ -268,18 +257,18 @@ Status NextIterationPass::BreakNextIteration(const NodePtr &next_node, NodePtr & GELOGE(PARAM_INVALID, "merge node or next node is null."); return PARAM_INVALID; } - for (auto &in_anchor : merge_node->GetAllInDataAnchors()) { + for (const auto &in_anchor : merge_node->GetAllInDataAnchors()) { OutDataAnchorPtr out_anchor = in_anchor->GetPeerOutAnchor(); if ((out_anchor == nullptr) || (out_anchor->GetOwnerNode() != next_node)) { continue; } if (GraphUtils::RemoveEdge(out_anchor, in_anchor) != SUCCESS) { - GELOGE(INTERNAL_ERROR, "Remove data edge fail, %s->%s.", next_node->GetName().c_str(), + GELOGE(INTERNAL_ERROR, "Remove data edge failed, %s->%s.", next_node->GetName().c_str(), merge_node->GetName().c_str()); return INTERNAL_ERROR; } if (SetNextIteration(merge_node, next_node->GetName()) != SUCCESS) { - GELOGE(INTERNAL_ERROR, "SetNextIteration for node %s fail.", merge_node->GetName().c_str()); + GELOGE(INTERNAL_ERROR, "Set attr NEXT_ITERATION for node %s failed.", merge_node->GetName().c_str()); return INTERNAL_ERROR; } } @@ -302,16 +291,16 @@ Status NextIterationPass::FindTargetNode(const NodePtr &node, const std::string } std::vector nodes; if (is_input) { - for (auto &tmp_node : node->GetInDataNodes()) { + for (const auto &tmp_node : node->GetInDataNodes()) { nodes.emplace_back(tmp_node); } } else { - for (auto &tmp_node : node->GetOutDataNodes()) { + for (const auto &tmp_node : node->GetOutDataNodes()) { nodes.emplace_back(tmp_node); } } - for (auto &tmp_node : nodes) { + for (const auto &tmp_node : nodes) { const std::string type = tmp_node->GetType(); if ((target_type == LOOPCOND) && (type == target_type)) { target_node = tmp_node; @@ -323,13 +312,14 @@ Status NextIterationPass::FindTargetNode(const NodePtr &node, const std::string } if (target_node == nullptr) { - GELOGE(INTERNAL_ERROR, "Find node %s fail", target_type.c_str()); + GELOGE(INTERNAL_ERROR, "Find node %s failed.", target_type.c_str()); return INTERNAL_ERROR; } return SUCCESS; } + /// -/// @brief Clear Status, uesd for subgraph pass +/// @brief Clear Status, used for subgraph pass /// @return SUCCESS /// Status NextIterationPass::ClearStatus() { diff --git a/src/ge/graph/passes/next_iteration_pass.h b/src/ge/graph/passes/next_iteration_pass.h index 4bbced4f..4cdf4b51 100644 --- a/src/ge/graph/passes/next_iteration_pass.h +++ b/src/ge/graph/passes/next_iteration_pass.h @@ -17,12 +17,6 @@ #ifndef GE_GRAPH_PASSES_NEXT_ITERATION_PASS_H_ #define GE_GRAPH_PASSES_NEXT_ITERATION_PASS_H_ -#include -#include -#include -#include -#include - #include "inc/graph_pass.h" struct LoopCondGroup { @@ -37,15 +31,64 @@ namespace ge { class NextIterationPass : public GraphPass { public: Status Run(ComputeGraphPtr graph); + + /// + /// @brief Clear Status, used for subgraph pass + /// @return SUCCESS + /// Status ClearStatus() override; private: - Status HandleEnterNode(const NodePtr &enter_node); + /// + /// @brief Group Enter node + /// @param [in] enter_node + /// @return Status + /// + Status GroupEnterNode(const NodePtr &enter_node); + + /// + /// @brief Find while groups + /// @return Status + /// Status FindWhileGroups(); + + /// + /// @brief Verify if valid + /// @return bool + /// bool VerifyWhileGroup(); + + /// + /// @brief Handle while group + /// @param [in] graph + /// @return Status + /// Status HandleWhileGroup(ComputeGraphPtr &graph); + + /// + /// @brief Create Active Node + /// @param [in] graph + /// @param [in] name + /// @return ge::NodePtr + /// NodePtr CreateActiveNode(ComputeGraphPtr &graph, const std::string &name); + + /// + /// @brief Break NextIteration Link & add name to merge attr + /// @param [in] next_node + /// @param [in] merge_node + /// @return Status + /// Status BreakNextIteration(const NodePtr &next_node, NodePtr &merge_node); + + /// + /// @brief find target node + /// @param [in] node + /// @param [in] target_type + /// @param [in] is_input + /// @param [out] target_node + /// @return Status + /// Status FindTargetNode(const NodePtr &node, const std::string &target_type, bool is_input, NodePtr &target_node); // map diff --git a/src/ge/graph/passes/pass_manager.cc b/src/ge/graph/passes/pass_manager.cc index eec33eef..5be54f0a 100644 --- a/src/ge/graph/passes/pass_manager.cc +++ b/src/ge/graph/passes/pass_manager.cc @@ -19,6 +19,7 @@ #include "common/types.h" #include "common/util.h" #include "graph/utils/node_utils.h" +#include "graph/common/ge_call_wrapper.h" #include "omg/omg_inner_types.h" namespace ge { diff --git a/src/ge/graph/passes/permute_pass.cc b/src/ge/graph/passes/permute_pass.cc index f5fd9dc5..3c0dfd4e 100644 --- a/src/ge/graph/passes/permute_pass.cc +++ b/src/ge/graph/passes/permute_pass.cc @@ -33,7 +33,6 @@ using domi::TENSORFLOW; namespace ge { Status PermutePass::Run(ComputeGraphPtr graph) { - GE_TIMESTAMP_START(PermutePass); GE_CHECK_NOTNULL(graph); std::vector isolate_nodes; for (NodePtr &node : graph->GetDirectNode()) { @@ -116,8 +115,6 @@ Status PermutePass::Run(ComputeGraphPtr graph) { GE_RETURN_WITH_LOG_IF_ERROR(graph->RemoveNode(node), "[%s]:remove permute node failed", node->GetOpDesc()->GetName().c_str()); }); - - GE_TIMESTAMP_END(PermutePass, "GraphManager::PermutePass"); return SUCCESS; } } // namespace ge diff --git a/src/ge/graph/passes/print_op_pass.h b/src/ge/graph/passes/print_op_pass.h index 64bf6573..15b0badc 100644 --- a/src/ge/graph/passes/print_op_pass.h +++ b/src/ge/graph/passes/print_op_pass.h @@ -31,6 +31,6 @@ class PrintOpPass : public BaseNodePass { public: Status Run(ge::NodePtr &node) override; }; -}; // namespace ge +} // namespace ge #endif // GE_GRAPH_PASSES_PRINT_OP_PASS_H_ diff --git a/src/ge/graph/passes/ref_identity_delete_op_pass.cc b/src/ge/graph/passes/ref_identity_delete_op_pass.cc new file mode 100644 index 00000000..5bc0fad6 --- /dev/null +++ b/src/ge/graph/passes/ref_identity_delete_op_pass.cc @@ -0,0 +1,225 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ref_identity_delete_op_pass.h" +#include +#include +#include "graph/common/transop_util.h" + +namespace ge { +Status RefIdentityDeleteOpPass::Run(ComputeGraphPtr graph) { + GE_CHECK_NOTNULL(graph); + for (auto &node : graph->GetAllNodes()) { + if (node->GetType() != REFIDENTITY) { + continue; + } + int input_index = 0; + NodePtr ref_node = GetRefNode(node, input_index); + CHECK_FALSE_EXEC(GetRefNode(node, input_index) != nullptr, + GELOGE(FAILED, "Ref node of RefIdentity[%s] not found", node->GetName().c_str()); + return FAILED); + CHECK_FALSE_EXEC(DealNoOutputRef(ref_node, node, input_index, graph) == SUCCESS, + GELOGE(FAILED, "Ref identity [%s] delete failed", node->GetName().c_str()); + return FAILED); + } + return SUCCESS; +} + +NodePtr RefIdentityDeleteOpPass::GetRefNode(const NodePtr &node, int &input_index) { + OutDataAnchorPtr out_anchor = node->GetOutDataAnchor(0); + CHECK_FALSE_EXEC(out_anchor != nullptr, return nullptr); + for (const auto &peer_in_anchor : out_anchor->GetPeerInDataAnchors()) { + CHECK_FALSE_EXEC(peer_in_anchor != nullptr, continue); + auto peer_node = peer_in_anchor->GetOwnerNode(); + CHECK_FALSE_EXEC(peer_node != nullptr, continue); + const auto &peer_op_desc = peer_node->GetOpDesc(); + CHECK_FALSE_EXEC(peer_op_desc != nullptr, return nullptr); + const auto &peer_input_desc = peer_op_desc->GetInputDescPtr(static_cast(peer_in_anchor->GetIdx())); + if (!peer_input_desc->GetRefPortIndex().empty()) { + input_index = peer_in_anchor->GetIdx(); + return peer_node; + } + } + return nullptr; +} + +Status RefIdentityDeleteOpPass::DealNoOutputRef(const NodePtr &node, const NodePtr &ref_identity, int input_index, + const ComputeGraphPtr &graph) { + NodePtr first_node = nullptr; + NodePtr variable_ref = GetVariableRef(node, ref_identity, first_node); + if (variable_ref == nullptr) { + GELOGE(FAILED, "[RefIdentityDeleteOpPass]Can not find variable ref for %s:%d", node->GetName().c_str(), + input_index); + return FAILED; + } + if (first_node->GetName() != variable_ref->GetName()) { + // Remove the control edge between ref node and variable ref + // Add a control edge between ref node and trans node + // +-----------+ +-----------+ + // +---------+RefIdentity| +-----------+RefIdentity| + // | +-----+-----+ | +-----+-----+ + // | | | | + // | v | v + // +-----v-----+ +----+----+ +-----v-----+ +----+----+ + // | TransNode | | RefNode | ==> | TransNode +<--C--+ RefNode | + // +-----+-----+ +----+----+ +-----+-----+ +---------+ + // | | | + // v C v + // +-----+-----+ | +-----+-----+ + // |VariableRef+<--------+ |VariableRef| + // +-----------+ +-----------+ + auto ret = ge::GraphUtils::AddEdge(node->GetOutControlAnchor(), first_node->GetInControlAnchor()); + if (ret != SUCCESS) { + GELOGE(FAILED, "Add control edge between ref node and trans node failed"); + return FAILED; + } + ret = ge::GraphUtils::RemoveEdge(node->GetOutControlAnchor(), variable_ref->GetInControlAnchor()); + if (ret != SUCCESS) { + GELOGE(FAILED, "Remove control edge between ref node and its peer node failed"); + return FAILED; + } + } else { + // +-----------+ +-----------+ + // +-----------+RefIdentity| +-----------+RefIdentity| + // | +-----+-----+ | +-----+-----+ + // | | | | + // | v | v + // +-----v-----+ +----+----+ +-----v-----+ +----+----+ + // |VariableRef+<--C--+ RefNode | ==> |VariableRef+<--C--+ RefNode | + // +-----+-----+ +----+----+ +-----------+ +----+----+ + // | | | + // | v v + // | +---+----+ +---+----+ + // +-----C------>+ | | | + // +--------+ +--------+ + auto ret = RemoveUselessControlEdge(node, variable_ref); + if (ret != SUCCESS) { + GELOGE(FAILED, "Remove useless control edge failed."); + return FAILED; + } + } + // remove ref identity + if (GraphUtils::IsolateNode(ref_identity, {0}) != GRAPH_SUCCESS) { + GELOGE(INTERNAL_ERROR, "Isolate removed node: %s, type: %s failed", ref_identity->GetName().c_str(), + variable_ref->GetType().c_str()); + return FAILED; + } + if (GraphUtils::RemoveNodeWithoutRelink(graph, ref_identity) != GRAPH_SUCCESS) { + GELOGE(INTERNAL_ERROR, "Remove node: %s, type: %s without relink failed", ref_identity->GetName().c_str(), + ref_identity->GetType().c_str()); + return FAILED; + } + return SUCCESS; +} + +ge::NodePtr RefIdentityDeleteOpPass::GetVariableRef(const NodePtr &ref, const NodePtr &ref_identity, + NodePtr &first_node) { + const auto &ref_identity_out_anchor = ref_identity->GetOutDataAnchor(0); + if (ref_identity_out_anchor == nullptr) { + return nullptr; + } + for (auto &peer_in_anchor : ref_identity_out_anchor->GetPeerInDataAnchors()) { + const auto &peer_node = peer_in_anchor->GetOwnerNode(); + if (peer_node == nullptr || peer_node->GetName() == ref->GetName()) { + continue; + } + // DFS to find variable ref node. + std::stack nodes_to_check; + nodes_to_check.push(peer_node); + GELOGI("[RefIdentityDeleteOpPass]Start to search variable ref node from %s.", peer_node->GetName().c_str()); + NodePtr cur_node = nullptr; + while (!nodes_to_check.empty()) { + cur_node = nodes_to_check.top(); + nodes_to_check.pop(); + const auto &type = cur_node->GetType(); + if (type == VARIABLE && CheckControlEdge(ref, cur_node)) { + // Target variable ref node found. + GELOGI("[RefIdentityDeleteOpPass]variable ref node[%s] found.", cur_node->GetName().c_str()); + first_node = peer_node; + return cur_node; + } + + int data_index = TransOpUtil::GetTransOpDataIndex(type); + if (data_index < 0) { + GELOGI("[RefIdentityDeleteOpPass]Find node[%s] that is not trans op[%s], stop to search its output.", + cur_node->GetName().c_str(), type.c_str()); + continue; + } + const auto &cur_out_anchor = cur_node->GetOutDataAnchor(0); + if (cur_out_anchor == nullptr) { + GELOGI("[RefIdentityDeleteOpPass]Get out anchor of [%s] failed, stop to search its output.", + cur_node->GetName().c_str()); + continue; + } + for (const auto &cur_peer_in_anchor : cur_out_anchor->GetPeerInDataAnchors()) { + const auto &cur_peer_node = cur_peer_in_anchor->GetOwnerNode(); + if (cur_peer_node == nullptr) { + continue; + } + nodes_to_check.push(cur_peer_node); + } + } + GELOGI("[RefIdentityDeleteOpPass]Can not find variable ref node from %s.", peer_node->GetName().c_str()); + } + GELOGI("[RefIdentityDeleteOpPass]Can not find variable ref node, return nullptr."); + return nullptr; +} + +bool RefIdentityDeleteOpPass::CheckControlEdge(const NodePtr &ref, const NodePtr &variable_ref) { + const auto &control_out_anchor = ref->GetOutControlAnchor(); + if (control_out_anchor == nullptr) { + return false; + } + const string &variable_ref_name = variable_ref->GetName(); + for (const auto &peer_in_control_anchor : control_out_anchor->GetPeerInControlAnchors()) { + const auto &node = peer_in_control_anchor->GetOwnerNode(); + if (node != nullptr && node->GetName() == variable_ref_name) { + return true; + } + } + return false; +} + +Status RefIdentityDeleteOpPass::RemoveUselessControlEdge(const NodePtr &ref, const NodePtr &variable_ref) { + map out_nodes_map; + for (const auto &out_anchor : ref->GetAllOutDataAnchors()) { + for (const auto &peer_in_anchor : out_anchor->GetPeerAnchors()) { + const auto &peer_node = peer_in_anchor->GetOwnerNode(); + if (peer_node == nullptr) { + continue; + } + out_nodes_map[peer_node->GetName()] = peer_node; + } + } + const auto &out_control_anchor = variable_ref->GetOutControlAnchor(); + GE_CHECK_NOTNULL(out_control_anchor); + for (const auto &peer_in_control_anchor : out_control_anchor->GetPeerInControlAnchors()) { + const auto &peer_node = peer_in_control_anchor->GetOwnerNode(); + if (peer_node == nullptr) { + continue; + } + if (out_nodes_map.find(peer_node->GetName()) != out_nodes_map.end()) { + auto ret = ge::GraphUtils::RemoveEdge(out_control_anchor, peer_in_control_anchor); + if (ret != SUCCESS) { + GELOGE(FAILED, "Remove control edge between variable ref node[%s] and ref node's peer node[%s] failed", + variable_ref->GetName().c_str(), peer_node->GetName().c_str()); + return FAILED; + } + } + } + return SUCCESS; +} +} // namespace ge diff --git a/src/ge/graph/passes/ref_identity_delete_op_pass.h b/src/ge/graph/passes/ref_identity_delete_op_pass.h new file mode 100644 index 00000000..3e42def4 --- /dev/null +++ b/src/ge/graph/passes/ref_identity_delete_op_pass.h @@ -0,0 +1,40 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_GRAPH_PASSES_REF_IDENTITY_DELETE_OP_PASS_H_ +#define GE_GRAPH_PASSES_REF_IDENTITY_DELETE_OP_PASS_H_ + +#include +#include +#include "framework/common/ge_inner_error_codes.h" +#include "inc/graph_pass.h" + +namespace ge { +class RefIdentityDeleteOpPass : public GraphPass { + public: + Status Run(ComputeGraphPtr graph); + + private: + Status DealNoOutputRef(const NodePtr &node, const NodePtr &ref_identity, int input_index, + const ComputeGraphPtr &graph); + NodePtr GetVariableRef(const NodePtr &ref, const NodePtr &ref_identity, NodePtr &first_node); + bool CheckControlEdge(const NodePtr &ref, const NodePtr &variable_ref); + Status RemoveUselessControlEdge(const NodePtr &ref, const NodePtr &variable_ref); + NodePtr GetRefNode(const NodePtr &node, int &input_index); +}; +} // namespace ge + +#endif // GE_GRAPH_PASSES_REF_IDENTITY_DELETE_OP_PASS_H_ diff --git a/src/ge/graph/passes/reshape_recovery_pass.cc b/src/ge/graph/passes/reshape_recovery_pass.cc index 787c8d83..07b08de9 100644 --- a/src/ge/graph/passes/reshape_recovery_pass.cc +++ b/src/ge/graph/passes/reshape_recovery_pass.cc @@ -30,6 +30,10 @@ NodePtr CreateReshape(const ConstGeTensorDescPtr &src, const ConstGeTensorDescPt if (ret != GRAPH_SUCCESS) { return nullptr; } + ret = reshape->AddInputDesc("shape", GeTensorDesc(GeShape(), Format(), DT_INT32)); + if (ret != GRAPH_SUCCESS) { + return nullptr; + } ret = reshape->AddOutputDesc("y", *dst); if (ret != GRAPH_SUCCESS) { return nullptr; @@ -49,7 +53,10 @@ Status InsertReshapeIfNeed(const NodePtr &node) { GE_CHECK_NOTNULL(dst_node); GE_CHECK_NOTNULL(dst_node->GetOpDesc()); auto dst_tensor = dst_node->GetOpDesc()->GetInputDescPtr(dst_anchor->GetIdx()); - if (src_tensor->GetShape().GetDims() != dst_tensor->GetShape().GetDims()) { + bool is_need_insert_reshape = src_tensor->GetShape().GetDims() != UNKNOWN_RANK && + dst_tensor->GetShape().GetDims() != UNKNOWN_RANK && + src_tensor->GetShape().GetDims() != dst_tensor->GetShape().GetDims(); + if (is_need_insert_reshape) { auto reshape = CreateReshape(src_tensor, dst_tensor, node->GetOwnerComputeGraph()); GE_CHECK_NOTNULL(reshape); auto ret = GraphUtils::InsertNodeBetweenDataAnchors(src_anchor, dst_anchor, reshape); diff --git a/src/ge/graph/passes/same_transdata_breadth_fusion_pass.cc b/src/ge/graph/passes/same_transdata_breadth_fusion_pass.cc index 3b4e4c19..d51f52e1 100644 --- a/src/ge/graph/passes/same_transdata_breadth_fusion_pass.cc +++ b/src/ge/graph/passes/same_transdata_breadth_fusion_pass.cc @@ -22,7 +22,6 @@ #include #include "common/ge_inner_error_codes.h" #include "common/types.h" -#include "framework/common/debug/ge_log.h" #include "graph/debug/ge_attr_define.h" #include "graph/utils/graph_utils.h" #include "graph/utils/op_desc_utils.h" @@ -117,20 +116,44 @@ void SameTransdataBreadthFusionPass::InsertSameTransdataNodeIndex(int anchors_in same_transdata_nodes.push_back(anchors_index); } +std::set SameTransdataBreadthFusionPass::GetInControlIdentityNodes(const NodePtr &node, + int subgraph_index) { + std::set in_node_names; + for (const auto &in_node : node->GetInControlNodes()) { + if (in_node->GetType() == IDENTITY) { + in_node_names.insert(in_node->GetName()); + } + } + for (const auto &subgraph_node : before_transdata_nodes_[subgraph_index]) { + for (const auto &in_node : subgraph_node->GetInControlNodes()) { + if (in_node->GetType() == IDENTITY) { + in_node_names.insert(in_node->GetName()); + } + } + } + GELOGD("control in nodes for %s(%d): %zu", node->GetName().c_str(), subgraph_index, in_node_names.size()); + return in_node_names; +} + void SameTransdataBreadthFusionPass::GetSameTransdataNode(vector &same_transdata_nodes) { auto iter = all_transdata_nodes_.begin(); same_transdata_nodes.push_back(iter->first); + auto node_for_compare_in_anchor = iter->second; GE_CHECK_NOTNULL_JUST_RETURN(node_for_compare_in_anchor); auto node_for_compare = node_for_compare_in_anchor->GetOwnerNode(); + + // Get op-desc, input/output desc, in-control-edges-from-identity, as the compare-key auto op_desc_for_compare = node_for_compare->GetOpDesc(); GE_CHECK_NOTNULL_JUST_RETURN(op_desc_for_compare); string op_compare_stream_label; (void)AttrUtils::GetStr(op_desc_for_compare, ATTR_NAME_STREAM_LABEL, op_compare_stream_label); + auto op_compare_in_ctrl_nodes = GetInControlIdentityNodes(node_for_compare, iter->first); auto input_desc_for_compare = op_desc_for_compare->GetInputDescPtr(node_for_compare_in_anchor->GetIdx()); GE_CHECK_NOTNULL_JUST_RETURN(input_desc_for_compare); auto output_desc_for_compare = op_desc_for_compare->GetOutputDescPtr(0); GE_CHECK_NOTNULL_JUST_RETURN(output_desc_for_compare); + iter = all_transdata_nodes_.erase(iter); while (iter != all_transdata_nodes_.end()) { auto in_anchor = iter->second; @@ -149,12 +172,14 @@ void SameTransdataBreadthFusionPass::GetSameTransdataNode(vector &same_tran auto output_desc_tmp = op_desc_tmp->GetOutputDescPtr(0); string op_tmp_stream_label; (void)AttrUtils::GetStr(op_desc_tmp, ATTR_NAME_STREAM_LABEL, op_tmp_stream_label); + auto op_tmp_in_ctrl_nodes = GetInControlIdentityNodes(node_tmp, iter->first); GE_CHECK_NOTNULL_JUST_RETURN(input_desc_tmp); GE_CHECK_NOTNULL_JUST_RETURN(output_desc_tmp); if ((op_compare_stream_label == op_tmp_stream_label) && (input_desc_tmp->GetFormat() == input_desc_for_compare->GetFormat()) && - (output_desc_tmp->GetFormat() == output_desc_for_compare->GetFormat())) { + (output_desc_tmp->GetFormat() == output_desc_for_compare->GetFormat()) && + (op_compare_in_ctrl_nodes == op_tmp_in_ctrl_nodes)) { GELOGD("same transdata node:%s, src node:%s", node_tmp->GetName().c_str(), node_for_compare->GetName().c_str()); InsertSameTransdataNodeIndex(iter->first, same_transdata_nodes); iter = all_transdata_nodes_.erase(iter); @@ -339,14 +364,13 @@ graphStatus SameTransdataBreadthFusionPass::ReLinkTransdataControlOutput2PreNode } graphStatus SameTransdataBreadthFusionPass::Run(ComputeGraphPtr graph) { - GE_TIMESTAMP_START(SameTransdataBreadthFusionPass); GELOGI("[SameTransdataBreadthFusionPass]: optimize begin."); if (graph == nullptr) { return GRAPH_SUCCESS; } for (auto &node : graph->GetDirectNode()) { - if (IsTransOp(node) || node->GetOutDataNodes().size() <= 1) { + if (IsTransOp(node) || node->GetOutDataNodesSize() <= 1) { continue; } @@ -374,7 +398,6 @@ graphStatus SameTransdataBreadthFusionPass::Run(ComputeGraphPtr graph) { } GELOGI("[SameTransdataBreadthFusionPass]: Optimize success."); - GE_TIMESTAMP_END(SameTransdataBreadthFusionPass, "GraphManager::SameTransdataBreadthFusionPass"); return GRAPH_SUCCESS; } diff --git a/src/ge/graph/passes/same_transdata_breadth_fusion_pass.h b/src/ge/graph/passes/same_transdata_breadth_fusion_pass.h index f4b44a59..a6a3bb26 100644 --- a/src/ge/graph/passes/same_transdata_breadth_fusion_pass.h +++ b/src/ge/graph/passes/same_transdata_breadth_fusion_pass.h @@ -42,7 +42,7 @@ class SameTransdataBreadthFusionPass : public GraphPass { void GetSubGraphNodesInfo(); void EraseInvalidAnchorsPair(); - + std::set GetInControlIdentityNodes(const NodePtr &node, int subgraph_index); OpDescPtr GetCastOp(const GeTensorDesc &in_desc, const GeTensorDesc &out_desc); graphStatus AddCastNode(const ComputeGraphPtr &graph, int anchors_index, OutDataAnchorPtr &pre_out_anchor, diff --git a/src/ge/graph/passes/subgraph_pass.cc b/src/ge/graph/passes/subgraph_pass.cc index d759aa12..80ce995a 100644 --- a/src/ge/graph/passes/subgraph_pass.cc +++ b/src/ge/graph/passes/subgraph_pass.cc @@ -15,7 +15,6 @@ */ #include "graph/passes/subgraph_pass.h" -#include #include "graph/utils/node_utils.h" #include "graph/utils/op_desc_utils.h" #include "graph/utils/tensor_utils.h" @@ -67,13 +66,13 @@ Status SubgraphPass::Run(ComputeGraphPtr graph) { /** * @ingroup ge - * @brief Check Subgraph NetOutput node + * @brief Check Subgraph Input node * @param [in] graph: ComputeGraph. - * @param [in] node: NetOutput node in Subgraph. + * @param [in] node: Data node in Subgraph. * @return: 0 for SUCCESS / others for FAILED */ Status SubgraphPass::SubgraphInputNode(const ComputeGraphPtr &graph, const NodePtr &node) { - GELOGD("Hadle input_node %s for graph %s.", node->GetName().c_str(), graph->GetName().c_str()); + GELOGD("Handle input_node %s for graph %s.", node->GetName().c_str(), graph->GetName().c_str()); // Data has and only has one output bool input_continues_required_flag = false; OutDataAnchorPtr out_data_anchor = node->GetOutDataAnchor(0); @@ -86,7 +85,7 @@ Status SubgraphPass::SubgraphInputNode(const ComputeGraphPtr &graph, const NodeP // Data->InputContinuesRequiredOp in subgraph need memcpy. if (input_continues_required_flag) { GELOGD("Data %s output_node required continues input.", node->GetName().c_str()); - std::string name = node->GetName() + "_" + MEMCPYASYNC + "_" + std::to_string(memcpy_num_++); + std::string name = node->GetName() + "_output_0_Memcpy"; if (InsertMemcpyNode(graph, out_data_anchor, in_anchors, name) != SUCCESS) { GELOGE(FAILED, "Insert memcpy after %s failed.", node->GetName().c_str()); return FAILED; @@ -123,7 +122,7 @@ Status SubgraphPass::SubgraphInputNode(const ComputeGraphPtr &graph, const NodeP GE_CHECK_NOTNULL(peer_out_anchor); GELOGD("Constant input %s links to While %s.", peer_out_anchor->GetOwnerNode()->GetName().c_str(), parent_node->GetName().c_str()); - std::string name = in_node->GetName() + "_" + MEMCPYASYNC + "_" + std::to_string(memcpy_num_++); + std::string name = parent_node->GetName() + "_input_" + std::to_string(in_data_anchor->GetIdx()) + "_Memcpy"; if (InsertMemcpyNode(parent_graph, peer_out_anchor, {in_data_anchor}, name) != SUCCESS) { GELOGE(FAILED, "Insert memcpy between %s and %s failed.", peer_out_anchor->GetOwnerNode()->GetName().c_str(), parent_node->GetName().c_str()); @@ -136,7 +135,7 @@ Status SubgraphPass::SubgraphInputNode(const ComputeGraphPtr &graph, const NodeP /** * @ingroup ge - * @brief Check Subgraph NetOutput node + * @brief Check Subgraph Output node * @param [in] graph: ComputeGraph. * @param [in] node: NetOutput node in Subgraph. * @return: 0 for SUCCESS / others for FAILED @@ -153,14 +152,14 @@ Status SubgraphPass::SubgraphOutputNode(const ComputeGraphPtr &graph, const Node // 1. Const->NetOutput in subgraph // 2. AtomicOp->NetOutput in subgraph // 3. OutputContinuesRequiredOp->NetOutput in subgraph - // 4. Data->NetOutput in subgraph but not while body + // 4. Data->NetOutput in subgraph but parent_node is not while std::string op_type; bool insert_flag = NodeUtils::GetConstOpType(in_node, op_type) || IsAtomicRequired(in_node, peer_out_anchor->GetIdx()) || IsOutputContinuesRequired(in_node) || - ((in_node->GetType() == DATA) && !IsWhileBodyOutput(in_data_anchor)); + ((in_node->GetType() == DATA) && (kWhileOpTypes.count(graph->GetParentNode()->GetType()) == 0)); if (insert_flag) { - GELOGI("Insert MemcpyAsync node between %s and %s.", node->GetName().c_str(), in_node->GetName().c_str()); - std::string name = in_node->GetName() + "_" + MEMCPYASYNC + "_" + std::to_string(memcpy_num_++); + GELOGD("Insert MemcpyAsync node between %s and %s.", in_node->GetName().c_str(), node->GetName().c_str()); + std::string name = node->GetName() + "_input_" + std::to_string(in_data_anchor->GetIdx()) + "_Memcpy"; if (InsertMemcpyNode(graph, peer_out_anchor, {in_data_anchor}, name) != SUCCESS) { GELOGE(FAILED, "Insert memcpy between %s and %s failed.", in_node->GetName().c_str(), node->GetName().c_str()); return FAILED; @@ -186,8 +185,8 @@ Status SubgraphPass::WhileInputNodes(const ComputeGraphPtr &graph, const NodePtr GE_CHECK_NOTNULL(in_node); // Input->While and Input link to other nodes need insert memcpy if (peer_out_anchor->GetPeerInDataAnchors().size() > 1) { - GELOGI("Input %s of While %s links to other nodes.", in_node->GetName().c_str(), node->GetName().c_str()); - std::string name = in_node->GetName() + "_" + MEMCPYASYNC + "_" + std::to_string(memcpy_num_++); + GELOGD("Input %s of While %s links to other nodes.", in_node->GetName().c_str(), node->GetName().c_str()); + std::string name = node->GetName() + "_input_" + std::to_string(in_data_anchor->GetIdx()) + "_Memcpy"; if (InsertMemcpyNode(graph, peer_out_anchor, {in_data_anchor}, name) != SUCCESS) { GELOGE(FAILED, "Insert memcpy between %s and %s failed.", in_node->GetName().c_str(), node->GetName().c_str()); return FAILED; @@ -206,231 +205,121 @@ Status SubgraphPass::WhileInputNodes(const ComputeGraphPtr &graph, const NodePtr * @return: 0 for SUCCESS / others for FAILED */ Status SubgraphPass::WhileBodySubgraph(const ComputeGraphPtr &graph, const NodePtr &node) { - ComputeGraphPtr while_body = GetWhileBodySubgraph(graph, node); + // index of body_subgraph is 1 + ComputeGraphPtr while_body = NodeUtils::GetSubgraph(*node, 1); if (while_body == nullptr) { GELOGE(FAILED, "while_body of %s is NULL.", node->GetName().c_str()); return FAILED; } - NodePtr output_node = while_body->FindFirstNodeMatchType(NETOUTPUT); - if (output_node == nullptr) { - GELOGE(FAILED, "net_output_node not exist in graph %s.", while_body->GetName().c_str()); - return FAILED; - } - OpDescPtr output_desc = output_node->GetOpDesc(); - GE_CHECK_NOTNULL(output_desc); - std::unordered_map> node_to_attr_index; - for (const InDataAnchorPtr &in_data_anchor : output_node->GetAllInDataAnchors()) { - uint32_t index = 0; - if (!AttrUtils::GetInt(output_desc->GetInputDesc(in_data_anchor->GetIdx()), ATTR_NAME_PARENT_NODE_INDEX, index)) { - GELOGE(FAILED, "Get attr PARENT_NODE_INDEX failed, node %s:%u.", output_node->GetName().c_str(), - in_data_anchor->GetIdx()); - return FAILED; + std::vector data_nodes; + std::set bypass_index; + NodePtr output_node = nullptr; + for (const auto &n : while_body->GetDirectNode()) { + const std::string &type = n->GetType(); + if (type == DATA) { + if (CheckInsertInputMemcpy(n, bypass_index)) { + data_nodes.emplace_back(n); + } + } else if (type == NETOUTPUT) { + if (output_node == nullptr) { + output_node = n; + } else { + GELOGE(FAILED, "while_body %s exists multi NetOutput nodes.", while_body->GetName().c_str()); + return FAILED; + } } - MarkOutputIndex(in_data_anchor->GetPeerOutAnchor(), index, node_to_attr_index); } - - std::set data_nodes; - std::set netoutput_input_indexes; - GetExchangeInOut(node_to_attr_index, data_nodes, netoutput_input_indexes); - return InsertMemcpyInWhileBody(while_body, data_nodes, output_node, netoutput_input_indexes); -} - -/** - * @ingroup ge - * @brief Get body subgraph of While op - * @param [in] graph: ComputeGraph. - * @param [in] node: While node. - * @return: body subgraph - */ -ComputeGraphPtr SubgraphPass::GetWhileBodySubgraph(const ComputeGraphPtr &graph, const NodePtr &node) { - OpDescPtr op_desc = node->GetOpDesc(); - if (op_desc == nullptr) { - GELOGE(FAILED, "op_desc is NULL."); - return nullptr; - } - - const std::vector &subgraph_instance_names = op_desc->GetSubgraphInstanceNames(); - std::string body_instance_name; - for (const std::string &instance_name : subgraph_instance_names) { - std::string subgraph_name; - if (op_desc->GetSubgraphNameByInstanceName(instance_name, subgraph_name) != GRAPH_SUCCESS) { - GELOGE(FAILED, "Get subgraph_name by instance_name %s failed, node:%s.", instance_name.c_str(), - node->GetName().c_str()); - return nullptr; - } - if (subgraph_name == ATTR_NAME_WHILE_BODY) { - body_instance_name = instance_name; - break; - } + if (output_node == nullptr) { + GELOGE(FAILED, "while_body %s has no output.", while_body->GetName().c_str()); + return FAILED; } - ComputeGraphPtr root_graph = GraphUtils::FindRootGraph(graph); - if (root_graph == nullptr) { - GELOGE(FAILED, "root_graph is NULL."); - return nullptr; + if ((InsertInputMemcpy(while_body, data_nodes) != SUCCESS) || + (InsertOutputMemcpy(while_body, output_node, bypass_index) != SUCCESS)) { + GELOGE(FAILED, "Insert memcpy node in while_body %s failed.", while_body->GetName().c_str()); + return FAILED; } - return root_graph->GetSubgraph(body_instance_name); + return SUCCESS; } /** * @ingroup ge - * @brief Mark output parent_node_index - * @param [in] peer_out_anchor: peer_out_anchor of NetOutput - * @param [in] index: parent_node_index of NetOutput - * @param [out] node_to_attr_index: key for node in subgraph, value for parent_node_index - * @return: void + * @brief Insert input memcpy node in while_body + * @param [in] graph: while_body + * @param [in] data_nodes: data_nodes + * @return: 0 for SUCCESS / others for FAILED */ -void SubgraphPass::MarkOutputIndex(const OutDataAnchorPtr &peer_out_anchor, uint32_t index, - std::unordered_map> &node_to_attr_index) { - if (peer_out_anchor == nullptr) { - return; - } - std::set visited_nodes; - std::stack nodes; - nodes.emplace(peer_out_anchor->GetOwnerNode()); - while (!nodes.empty()) { - NodePtr cur_node = nodes.top(); - nodes.pop(); - if (visited_nodes.count(cur_node) > 0) { - continue; - } - node_to_attr_index[cur_node].emplace_back(index); - for (const NodePtr &in_node : cur_node->GetInDataNodes()) { - nodes.emplace(in_node); - } - visited_nodes.emplace(cur_node); +Status SubgraphPass::InsertInputMemcpy(const ComputeGraphPtr &graph, const std::vector &data_nodes) { + if (data_nodes.empty()) { + GELOGD("No need to insert input memcpy node in while_body %s.", graph->GetName().c_str()); + return SUCCESS; } -} - -/** - * @ingroup ge - * @brief Get data_nodes / input_indexes of netoutput if need insert memcpy - * @param [in] node_to_attr_index: key for node in subgraph, value for parent_node_index - * @param [out] data_nodes: data_nodes need insert memcpy - * @param [out] netoutput_input_indexes: input_indexes of netoutput need insert memcpy - * @return: void - */ -void SubgraphPass::GetExchangeInOut(const std::unordered_map> &node_to_attr_index, - std::set &data_nodes, std::set &netoutput_input_indexes) { - for (const auto &item : node_to_attr_index) { - NodePtr node = item.first; - uint32_t input_index = 0; - if ((node->GetType() != DATA) || !AttrUtils::GetInt(node->GetOpDesc(), ATTR_NAME_PARENT_NODE_INDEX, input_index)) { - continue; - } - if (item.second.empty() || ((item.second.size() == 1) && (item.second[0] == input_index))) { - continue; - } - data_nodes.emplace(node); + std::string in_name = graph->GetName() + "_input_Memcpy"; + OpDescBuilder in_builder(in_name, MEMCPYASYNC); + for (size_t i = 0; i < data_nodes.size(); i++) { // Data node has and only has one output - OutDataAnchorPtr out_data_anchor = node->GetOutDataAnchor(0); - if (out_data_anchor == nullptr) { - continue; - } - for (const InDataAnchorPtr &peer_in_anchor : out_data_anchor->GetPeerInDataAnchors()) { - NodePtr out_node = peer_in_anchor->GetOwnerNode(); - if ((out_node->GetType() != NETOUTPUT) || (out_node->GetOpDesc() == nullptr)) { - continue; - } - uint32_t output_index = 0; - GeTensorDesc input_tensor = out_node->GetOpDesc()->GetInputDesc(peer_in_anchor->GetIdx()); - if (!AttrUtils::GetInt(input_tensor, ATTR_NAME_PARENT_NODE_INDEX, output_index)) { - continue; - } - if (input_index != output_index) { - netoutput_input_indexes.emplace(peer_in_anchor->GetIdx()); - } - } + in_builder.AddInput("x" + std::to_string(i), data_nodes[i]->GetOpDesc()->GetOutputDesc(0)) + .AddOutput("y" + std::to_string(i), data_nodes[i]->GetOpDesc()->GetOutputDesc(0)); } -} - -/** - * @ingroup ge - * @brief Insert memcpy node in while_body - * @param [in] graph: while_body - * @param [in] data_nodes: data_nodes need insert memcpy - * @param [in] output_node: NetOutput in while_body - * @param [in] netoutput_input_indexes: input_indexes of netoutput need insert memcpy - * @return: 0 for SUCCESS / others for FAILED - */ -Status SubgraphPass::InsertMemcpyInWhileBody(const ComputeGraphPtr &graph, const std::set &data_nodes, - const NodePtr &output_node, - const std::set &netoutput_input_indexes) { - for (const NodePtr &data_node : data_nodes) { + GELOGD("Insert memcpy after data_nodes of while_body %s.", graph->GetName().c_str()); + NodePtr in_memcpy = graph->AddNode(in_builder.Build()); + GE_CHECK_NOTNULL(in_memcpy); + for (size_t i = 0; i < data_nodes.size(); i++) { // Data node has and only has one output - OutDataAnchorPtr out_data_anchor = data_node->GetOutDataAnchor(0); + OutDataAnchorPtr out_data_anchor = data_nodes[i]->GetOutDataAnchor(0); std::vector in_anchors; for (const InDataAnchorPtr &peer_in_anchor : out_data_anchor->GetPeerInDataAnchors()) { in_anchors.emplace_back(peer_in_anchor); } - std::string name = data_node->GetName() + "_" + MEMCPYASYNC + "_" + std::to_string(memcpy_num_++); - GELOGD("Insert memcpy after while_body %s input_node %s.", graph->GetName().c_str(), data_node->GetName().c_str()); - if (InsertMemcpyNode(graph, out_data_anchor, in_anchors, name) != SUCCESS) { - GELOGE(FAILED, "Insert MemcpyAsync node %s after %s failed.", name.c_str(), data_node->GetName().c_str()); - return FAILED; - } - } - - for (uint32_t index : netoutput_input_indexes) { - InDataAnchorPtr in_data_anchor = output_node->GetInDataAnchor(index); - GE_CHECK_NOTNULL(in_data_anchor); - OutDataAnchorPtr peer_out_anchor = in_data_anchor->GetPeerOutAnchor(); - GE_CHECK_NOTNULL(peer_out_anchor); - std::string name = - peer_out_anchor->GetOwnerNode()->GetName() + "_" + MEMCPYASYNC + "_" + std::to_string(memcpy_num_++); - GELOGD("Insert memcpy after while_body %s output %u.", graph->GetName().c_str(), index); - if (InsertMemcpyNode(graph, peer_out_anchor, {in_data_anchor}, name) != SUCCESS) { - GELOGE(FAILED, "Insert MemcpyAsync node %s after %s failed.", name.c_str(), - peer_out_anchor->GetOwnerNode()->GetName().c_str()); + if (InsertNodeBetween(out_data_anchor, in_anchors, in_memcpy, i, i) != SUCCESS) { + GELOGE(FAILED, "Insert MemcpyAsync %s in while_body %s failed.", in_name.c_str(), graph->GetName().c_str()); return FAILED; } } - std::set memcpy_nodes; - std::set loop_body_nodes; - for (const NodePtr &data_node : data_nodes) { - // data_node has only one output node - NodePtr memcpy_node = data_node->GetOutDataNodes().at(0); - GE_CHECK_NOTNULL(memcpy_node); - memcpy_nodes.emplace(memcpy_node); - for (const NodePtr &out_node : memcpy_node->GetOutDataNodes()) { - loop_body_nodes.insert(out_node); - } - } - return InsertNoOp(graph, memcpy_nodes, loop_body_nodes); + return SUCCESS; } /** * @ingroup ge - * @brief Insert NoOp node between memcpy_nodes and loop_body_nodes + * @brief Insert output memcpy node in while_body * @param [in] graph: while_body - * @param [in] memcpy_nodes - * @param [in] loop_body_nodes + * @param [in] output_node: NetOutput + * @param [in] bypass_index * @return: 0 for SUCCESS / others for FAILED */ -Status SubgraphPass::InsertNoOp(const ComputeGraphPtr &graph, const std::set &memcpy_nodes, - const std::set &loop_body_nodes) { - if (memcpy_nodes.empty() || loop_body_nodes.empty()) { +Status SubgraphPass::InsertOutputMemcpy(const ComputeGraphPtr &graph, const NodePtr &output_node, + const std::set &bypass_index) { + if (output_node->GetAllInDataAnchorsSize() == bypass_index.size()) { + GELOGD("No need to insert output memcpy node in while_body %s, output_size=%zu, bypass_num=%zu.", + graph->GetName().c_str(), output_node->GetAllInDataAnchorsSize(), bypass_index.size()); return SUCCESS; } - OpDescBuilder noop_desc_builder("NoOp_for_Control", NOOP); - OpDescPtr noop_desc = noop_desc_builder.Build(); - NodePtr noop_node = graph->AddNode(noop_desc); - GE_CHECK_NOTNULL(noop_node); - for (const NodePtr &memcpy_node : memcpy_nodes) { - if (GraphUtils::AddEdge(memcpy_node->GetOutControlAnchor(), noop_node->GetInControlAnchor()) != GRAPH_SUCCESS) { - GELOGE(FAILED, "Add ctrl edge %s->%s failed.", memcpy_node->GetName().c_str(), noop_node->GetName().c_str()); - return FAILED; + std::string out_name = graph->GetName() + "_output_Memcpy"; + OpDescBuilder out_builder(out_name, MEMCPYASYNC); + for (size_t i = 0; i < output_node->GetAllInDataAnchorsSize(); i++) { + if (bypass_index.count(i) == 0) { + out_builder.AddInput("x" + std::to_string(i), output_node->GetOpDesc()->GetInputDesc(i)) + .AddOutput("y" + std::to_string(i), output_node->GetOpDesc()->GetInputDesc(i)); } } - for (const NodePtr &loop_body_node : loop_body_nodes) { - if (GraphUtils::AddEdge(noop_node->GetOutControlAnchor(), loop_body_node->GetInControlAnchor()) != GRAPH_SUCCESS) { - GELOGE(FAILED, "Add ctrl edge %s->%s failed.", noop_node->GetName().c_str(), loop_body_node->GetName().c_str()); - return FAILED; + GELOGD("Insert memcpy before NetOutput of while_body %s.", graph->GetName().c_str()); + NodePtr out_memcpy = graph->AddNode(out_builder.Build()); + GE_CHECK_NOTNULL(out_memcpy); + size_t cnt = 0; + for (size_t i = 0; i < output_node->GetAllInDataAnchorsSize(); i++) { + if (bypass_index.count(i) == 0) { + InDataAnchorPtr in_data_anchor = output_node->GetInDataAnchor(i); + OutDataAnchorPtr peer_out_anchor = in_data_anchor->GetPeerOutAnchor(); + if (InsertNodeBetween(peer_out_anchor, {in_data_anchor}, out_memcpy, cnt, cnt) != SUCCESS) { + GELOGE(FAILED, "Insert MemcpyAsync %s in while_body %s failed.", out_name.c_str(), graph->GetName().c_str()); + return FAILED; + } + cnt++; } } @@ -439,28 +328,39 @@ Status SubgraphPass::InsertNoOp(const ComputeGraphPtr &graph, const std::setnetoutput in while body - * @param [in] in_data_anchor - * @return: true for data->netoutput in while body / for false for others + * @brief Check is data->netoutput without change in while body + * @param [in] node: data node + * @param [out] bypass_index + * @return: false for data->netoutput without change in while body / for true for others */ -bool SubgraphPass::IsWhileBodyOutput(const InDataAnchorPtr &in_data_anchor) { - // Check is subgraph - NodePtr parent_node = in_data_anchor->GetOwnerNode()->GetOwnerComputeGraph()->GetParentNode(); - if (parent_node == nullptr) { - return false; +bool SubgraphPass::CheckInsertInputMemcpy(const NodePtr &node, std::set &bypass_index) { + uint32_t input_index = 0; + if (!AttrUtils::GetInt(node->GetOpDesc(), ATTR_NAME_PARENT_NODE_INDEX, input_index)) { + return true; } - // Check if parent_node is While - if (kWhileOpTypes.count(parent_node->GetType()) == 0) { - return false; + // Data node has and only has one output + OutDataAnchorPtr out_data_anchor = node->GetOutDataAnchor(0); + if ((out_data_anchor == nullptr) || (out_data_anchor->GetPeerInDataAnchors().size() != 1)) { + return true; + } + InDataAnchorPtr peer_in_anchor = out_data_anchor->GetPeerInDataAnchors().at(0); + if (peer_in_anchor->GetOwnerNode()->GetType() != NETOUTPUT) { + return true; } - // While cond / body - OpDescPtr op_desc = in_data_anchor->GetOwnerNode()->GetOpDesc(); - if (op_desc == nullptr) { - return false; + OpDescPtr op_desc = peer_in_anchor->GetOwnerNode()->GetOpDesc(); + uint32_t output_index = 0; + if ((op_desc == nullptr) || + !AttrUtils::GetInt(op_desc->GetInputDesc(peer_in_anchor->GetIdx()), ATTR_NAME_PARENT_NODE_INDEX, output_index)) { + return true; } - return AttrUtils::HasAttr(op_desc->GetInputDesc(in_data_anchor->GetIdx()), ATTR_NAME_PARENT_NODE_INDEX); + + if (input_index != output_index) { + return true; + } + bypass_index.insert(peer_in_anchor->GetIdx()); + return false; } /** @@ -542,7 +442,7 @@ Status SubgraphPass::InsertMemcpyNode(const ComputeGraphPtr &graph, const OutDat OpDescPtr op_desc = op_desc_builder.AddInput("x", in_node->GetOpDesc()->GetOutputDesc(0)) .AddOutput("y", in_node->GetOpDesc()->GetOutputDesc(0)) .Build(); - if (GraphUtils::InsertNodeBefore(out_anchor, in_anchors, graph->AddNode(op_desc)) != GRAPH_SUCCESS) { + if (GraphUtils::InsertNodeAfter(out_anchor, in_anchors, graph->AddNode(op_desc)) != GRAPH_SUCCESS) { GELOGE(FAILED, "Insert MemcpyAsync node %s after %s failed.", name.c_str(), in_node->GetName().c_str()); return FAILED; } @@ -550,4 +450,33 @@ Status SubgraphPass::InsertMemcpyNode(const ComputeGraphPtr &graph, const OutDat return SUCCESS; } +/// +/// @brief Insert node: src->insert_node:input_index, insert_node:output_index->dst +/// @param [in] src +/// @param [in] dsts +/// @param [in] insert_node +/// @param [in] input_index +/// @param [in] output_index +/// @return Status +/// +Status SubgraphPass::InsertNodeBetween(const OutDataAnchorPtr &src, const std::vector &dsts, + const NodePtr &insert_node, uint32_t input_index, uint32_t output_index) { + if (GraphUtils::AddEdge(src, insert_node->GetInDataAnchor(input_index)) != GRAPH_SUCCESS) { + GELOGE(FAILED, "Add data_edge %s:%d->%s:%u failed.", src->GetOwnerNode()->GetName().c_str(), src->GetIdx(), + insert_node->GetName().c_str(), input_index); + return FAILED; + } + for (const auto &dst : dsts) { + GELOGD("Insert node %s between %s->%s.", insert_node->GetName().c_str(), src->GetOwnerNode()->GetName().c_str(), + dst->GetOwnerNode()->GetName().c_str()); + if ((GraphUtils::RemoveEdge(src, dst) != GRAPH_SUCCESS) || + (GraphUtils::AddEdge(insert_node->GetOutDataAnchor(output_index), dst) != GRAPH_SUCCESS)) { + GELOGE(FAILED, "Replace data_edge %s:%d->%s:%d by %s:%u->%s:%d failed.", src->GetOwnerNode()->GetName().c_str(), + src->GetIdx(), dst->GetOwnerNode()->GetName().c_str(), dst->GetIdx(), insert_node->GetName().c_str(), + output_index, dst->GetOwnerNode()->GetName().c_str(), dst->GetIdx()); + return FAILED; + } + } + return SUCCESS; +} } // namespace ge diff --git a/src/ge/graph/passes/subgraph_pass.h b/src/ge/graph/passes/subgraph_pass.h index 2308b1bd..7ff2019f 100644 --- a/src/ge/graph/passes/subgraph_pass.h +++ b/src/ge/graph/passes/subgraph_pass.h @@ -17,12 +17,6 @@ #ifndef GE_GRAPH_PASSES_SUBGRAPH_PASS_H_ #define GE_GRAPH_PASSES_SUBGRAPH_PASS_H_ -#include -#include -#include -#include - -#include "graph/types.h" #include "inc/graph_pass.h" namespace ge { @@ -75,65 +69,32 @@ class SubgraphPass : public GraphPass { /** * @ingroup ge - * @brief Get body subgraph of While op - * @param [in] graph: ComputeGraph. - * @param [in] node: While node. - * @return: body subgraph - */ - ComputeGraphPtr GetWhileBodySubgraph(const ComputeGraphPtr &graph, const NodePtr &node); - - /** - * @ingroup ge - * @brief Mark output parent_node_index - * @param [in] peer_out_anchor: peer_out_anchor of NetOutput - * @param [in] index: parent_node_index of NetOutput - * @param [out] node_to_attr_index: key for node in subgraph, value for parent_node_index - * @return: void - */ - void MarkOutputIndex(const OutDataAnchorPtr &peer_out_anchor, uint32_t index, - std::unordered_map> &node_to_attr_index); - - /** - * @ingroup ge - * @brief Get data_nodes / input_indexes of netoutput if need insert memcpy - * @param [in] node_to_attr_index: key for node in subgraph, value for parent_node_index - * @param [out] data_nodes: data_nodes need insert memcpy - * @param [out] netoutput_input_indexes: input_indexes of netoutput need insert memcpy - * @return: void - */ - void GetExchangeInOut(const std::unordered_map> &node_to_attr_index, - std::set &data_nodes, std::set &netoutput_input_indexes); - - /** - * @ingroup ge - * @brief Insert memcpy node in while_body + * @brief Insert input memcpy node in while_body * @param [in] graph: while_body - * @param [in] data_nodes: data_nodes need insert memcpy - * @param [in] output_node: NetOutput in while_body - * @param [in] netoutput_input_indexes: input_indexes of netoutput need insert memcpy + * @param [in] data_nodes: data_nodes * @return: 0 for SUCCESS / others for FAILED */ - Status InsertMemcpyInWhileBody(const ComputeGraphPtr &graph, const std::set &data_nodes, - const NodePtr &output_node, const std::set &netoutput_input_indexes); + Status InsertInputMemcpy(const ComputeGraphPtr &graph, const std::vector &data_nodes); /** * @ingroup ge - * @brief Insert NoOp node between memcpy_nodes and loop_body_nodes + * @brief Insert output memcpy node in while_body * @param [in] graph: while_body - * @param [in] memcpy_nodes - * @param [in] loop_body_nodes + * @param [in] output_node: NetOutput + * @param [in] bypass_index * @return: 0 for SUCCESS / others for FAILED */ - Status InsertNoOp(const ComputeGraphPtr &graph, const std::set &memcpy_nodes, - const std::set &loop_body_nodes); + Status InsertOutputMemcpy(const ComputeGraphPtr &graph, const NodePtr &output_node, + const std::set &bypass_index); /** * @ingroup ge - * @brief Check is Data->NetOutput in while body - * @param [in] in_data_anchor - * @return: true for Data->NetOutput in while body / false for others + * @brief Check is data->netoutput without change in while body + * @param [in] node: data node + * @param [out] bypass_index + * @return: false for data->netoutput without change in while body / for true for others */ - bool IsWhileBodyOutput(const InDataAnchorPtr &in_data_anchor); + bool CheckInsertInputMemcpy(const NodePtr &node, std::set &bypass_index); /** * @ingroup ge @@ -172,8 +133,17 @@ class SubgraphPass : public GraphPass { Status InsertMemcpyNode(const ComputeGraphPtr &graph, const OutDataAnchorPtr &out_anchor, const std::vector &in_anchors, const std::string &name); - // Append index for new memcpy node. - uint32_t memcpy_num_{0}; + /// + /// @brief Insert node: src->insert_node:input_index, insert_node:output_index->dst + /// @param [in] src + /// @param [in] dsts + /// @param [in] insert_node + /// @param [in] input_index + /// @param [in] output_index + /// @return Status + /// + Status InsertNodeBetween(const OutDataAnchorPtr &src, const std::vector &dsts, + const NodePtr &insert_node, uint32_t input_index, uint32_t output_index); }; } // namespace ge #endif // GE_GRAPH_PASSES_SUBGRAPH_PASS_H_ diff --git a/src/ge/graph/passes/switch_op_pass.cc b/src/ge/graph/passes/switch_op_pass.cc deleted file mode 100644 index ed3e9b36..00000000 --- a/src/ge/graph/passes/switch_op_pass.cc +++ /dev/null @@ -1,1227 +0,0 @@ -/** - * Copyright 2019-2020 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "graph/passes/switch_op_pass.h" -#include -#include -#include -#include -#include -#include -#include -#include "common/ge/ge_util.h" -#include "framework/common/debug/ge_log.h" -#include "framework/common/debug/log.h" -#include "framework/common/ge_inner_error_codes.h" -#include "framework/common/types.h" -#include "ge/ge_api_types.h" -#include "graph/common/omg_util.h" -#include "graph/debug/ge_attr_define.h" -#include "graph/ge_context.h" -#include "graph/utils/type_utils.h" - -namespace ge { -Status SwitchOpPass::Run(ComputeGraphPtr graph) { - GELOGD("SwitchOpPass Enter"); - GE_CHK_STATUS_RET(CheckCycleDependence(graph), "CheckCycleDependence fail."); - - for (auto &switch_node : switch_nodes_) { - GE_CHK_STATUS_RET(ReplaceSwitchNode(graph, switch_node), "Add StreamSwitch node fail."); - } - - for (auto &merge_node : merge_nodes_) { - OpDescPtr merge_op_desc = merge_node->GetOpDesc(); - GE_CHECK_NOTNULL(merge_op_desc); - if (merge_op_desc->HasAttr(ATTR_INSERT_BY_MBATCH)) { - GE_CHK_STATUS_RET(AddMemcpyAsyncNodes(graph, merge_node, true), "Merge add memcpy node fail."); - GE_CHK_STATUS_RET(SetStreamLabel(merge_node, merge_node->GetName()), "Set stream label failed"); - } else { - GE_CHK_STATUS_RET(ReplaceMergeNode(graph, merge_node), "Add StreamMerge node fail."); - } - } - - GE_CHK_STATUS_RET(CombineSwitchNode(graph), "Combine StreamSwitch nodes fail."); - - for (auto &node : bypass_nodes_) { - GE_CHK_BOOL_EXEC(graph->RemoveNode(node) == GRAPH_SUCCESS, return FAILED, "Remove switch node fail."); - } - - for (auto &node : stream_switch_nodes_) { - for (auto &out_ctrl_node : node->GetOutControlNodes()) { - MarkHeadNodes(out_ctrl_node, node); - } - } - - for (auto &node : need_label_nodes_) { - OpDescPtr op_desc = node->GetOpDesc(); - GE_CHECK_NOTNULL(op_desc); - if (!op_desc->HasAttr(ATTR_NAME_STREAM_LABEL)) { - GE_CHK_STATUS_RET(UpdateCondBranch(node), "Set cond branch fail, start node:%s", node->GetName().c_str()); - } - } - - GE_CHK_STATUS_RET(UpdateEnterNode(), "UpdateEnterNode fail."); - - GELOGD("SwitchOpPass Leave"); - return SUCCESS; -} - -/// -/// @brief Replace Switch Op -/// @param [in] graph -/// @param [in] switch_node -/// @return Status -/// -Status SwitchOpPass::ReplaceSwitchNode(ComputeGraphPtr &graph, NodePtr &switch_node) { - std::string type; - GE_CHK_STATUS_RET(GetOriginalType(switch_node, type), "Get node type fail."); - GE_CHK_BOOL_EXEC((type == SWITCH) || (type == REFSWITCH), return FAILED, "Type of input node is not switch."); - - OutDataAnchorPtr peer_data_anchor = nullptr; - OutDataAnchorPtr peer_cond_anchor = nullptr; - GE_CHK_BOOL_EXEC(BypassSwitchNode(switch_node, peer_data_anchor, peer_cond_anchor) == SUCCESS, return FAILED, - "Bypass switch node %s fail.", switch_node->GetName().c_str()); - GE_CHECK_NOTNULL(peer_data_anchor); - GE_CHECK_NOTNULL(peer_cond_anchor); - OpDescPtr cond_desc = peer_cond_anchor->GetOwnerNode()->GetOpDesc(); - GE_CHECK_NOTNULL(cond_desc); - DataType cond_data_type = cond_desc->GetOutputDesc(peer_cond_anchor->GetIdx()).GetDataType(); - GE_CHK_BOOL_EXEC(cond_data_type == DT_BOOL, return FAILED, - "SwitchNode not support datatype %s, datatype of cond_input should be bool", - TypeUtils::DataTypeToSerialString(cond_data_type).c_str()); - - OpDescPtr switch_desc = switch_node->GetOpDesc(); - GE_CHECK_NOTNULL(switch_desc); - bool cyclic_flag = switch_desc->HasAttr(ATTR_NAME_CYCLIC_DEPENDENCE_FLAG); - - std::set out_node_list; - for (OutDataAnchorPtr &out_data_anchor : switch_node->GetAllOutDataAnchors()) { - bool true_branch_flag = (static_cast(out_data_anchor->GetIdx()) == SWITCH_TRUE_OUTPUT); - NodePtr stream_switch = nullptr; - out_node_list.clear(); - for (auto &peer_in_anchor : out_data_anchor->GetPeerAnchors()) { - GE_IF_BOOL_EXEC(stream_switch == nullptr, { - std::string suffix = (true_branch_flag ? "_t" : "_f"); - stream_switch = CreateStreamSwitchNode(graph, switch_node, suffix, peer_cond_anchor); - GE_CHK_BOOL_EXEC(stream_switch != nullptr, return FAILED, "Create stream_switch node fail."); - if (SetSwitchTrueBranchFlag(stream_switch, true_branch_flag) != SUCCESS) { - GELOGE(FAILED, "SetSwitchTrueBranchFlag for node %s fail.", stream_switch->GetName().c_str()); - return FAILED; - } - if (MarkBranchs(peer_cond_anchor, stream_switch, true_branch_flag) != SUCCESS) { - GELOGE(FAILED, "MarkBranchs for stream_switch %s fail.", stream_switch->GetName().c_str()); - return FAILED; - } - - if (!cyclic_flag) { - GE_CHK_STATUS(GraphUtils::AddEdge(peer_data_anchor->GetOwnerNode()->GetOutControlAnchor(), - stream_switch->GetInControlAnchor()), - "StreamSwitch node add ctl edge fail."); - } - }); - - GE_CHK_STATUS(GraphUtils::RemoveEdge(out_data_anchor, peer_in_anchor), "Remove Switch data output fail."); - - NodePtr out_node = peer_in_anchor->GetOwnerNode(); - GE_CHK_STATUS_RET(GetOriginalType(out_node, type), "Get node type fail."); - if ((type == MERGE) || (type == REFMERGE)) { - NodePtr memcpy_node = CreateMemcpyAsyncNode(graph, peer_data_anchor, false); - GE_CHK_BOOL_EXEC(memcpy_node != nullptr, return FAILED, "Create memcpy_async node fail."); - GE_CHK_STATUS(GraphUtils::AddEdge(peer_data_anchor, memcpy_node->GetInDataAnchor(0)), - "MemcpyAsync node add edge fail."); - GE_CHK_STATUS(GraphUtils::AddEdge(memcpy_node->GetOutDataAnchor(0), peer_in_anchor), - "MemcpyAsync node add edge fail."); - GE_CHK_STATUS(GraphUtils::AddEdge(stream_switch->GetOutControlAnchor(), memcpy_node->GetInControlAnchor()), - "MemcpyAsync node add ctl edge fail."); - out_node_list.insert(memcpy_node->GetName()); - } else { - GE_CHK_STATUS(GraphUtils::AddEdge(peer_data_anchor, peer_in_anchor), "StreamSwitch node add edge fail."); - GE_CHK_STATUS(GraphUtils::AddEdge(stream_switch->GetOutControlAnchor(), out_node->GetInControlAnchor()), - "StreamSwitch node add ctl edge fail."); - out_node_list.insert(out_node->GetName()); - } - } - GE_IF_BOOL_EXEC(stream_switch != nullptr, { - CopyControlEdges(switch_node, stream_switch, true); - switch_node_map_[stream_switch] = out_node_list; - if (SetOriginalNodeName(stream_switch, switch_node->GetName()) != SUCCESS) { - GELOGE(FAILED, "SetOriginalNodeName for node %s fail.", stream_switch->GetName().c_str()); - return FAILED; - } - }); - } - - RemoveControlEdges(switch_node); - (void)bypass_nodes_.insert(switch_node); - - return SUCCESS; -} - -/// -/// @brief Replace Merge Op -/// @param [in] graph -/// @param [in] merge_node -/// @return Status -/// -Status SwitchOpPass::ReplaceMergeNode(ComputeGraphPtr &graph, NodePtr &merge_node) { - std::string type; - GE_CHK_STATUS_RET(GetOriginalType(merge_node, type), "Get node type fail."); - GE_CHK_BOOL_EXEC((type == MERGE) || (type == REFMERGE), return FAILED, "Type of input node is not merge."); - - OpDescPtr merge_op_desc = merge_node->GetOpDesc(); - GE_CHECK_NOTNULL(merge_op_desc); - - const std::string node_name = merge_node->GetName(); - GELOGI("Create StreamMerge Op, name=%s.", node_name.c_str()); - OpDescPtr op_desc = MakeShared(node_name, STREAMMERGE); - if (op_desc == nullptr) { - GELOGE(FAILED, "Create op_desc fail, StreamMerge:%s.", node_name.c_str()); - return FAILED; - } - - for (InDataAnchorPtr &in_anchor : merge_node->GetAllInDataAnchors()) { - GE_CHK_BOOL_EXEC(op_desc->AddInputDesc(merge_op_desc->GetInputDesc(in_anchor->GetIdx())) == GRAPH_SUCCESS, - return FAILED, "Create StreamMerge op: add input desc fail."); - } - - for (OutDataAnchorPtr &out_anchor : merge_node->GetAllOutDataAnchors()) { - GE_CHK_BOOL_EXEC(op_desc->AddOutputDesc(merge_op_desc->GetOutputDesc(out_anchor->GetIdx())) == GRAPH_SUCCESS, - return FAILED, "Create StreamMerge op: add output desc fail."); - } - - NodePtr stream_merge = graph->AddNode(op_desc); - GE_CHK_BOOL_EXEC(stream_merge != nullptr, return FAILED, "Insert StreamMerge node fail."); - - for (InDataAnchorPtr &in_data_anchor : merge_node->GetAllInDataAnchors()) { - OutDataAnchorPtr peer_out_anchor = in_data_anchor->GetPeerOutAnchor(); - GE_IF_BOOL_EXEC(peer_out_anchor == nullptr, continue); - - GE_CHK_STATUS(GraphUtils::RemoveEdge(peer_out_anchor, in_data_anchor), "Remove Merge data input fail."); - GE_CHK_STATUS(GraphUtils::AddEdge(peer_out_anchor, stream_merge->GetInDataAnchor(in_data_anchor->GetIdx())), - "StreamMerge node add edge fail."); - } - - for (OutDataAnchorPtr &out_data_anchor : merge_node->GetAllOutDataAnchors()) { - for (InDataAnchorPtr &peer_in_anchor : out_data_anchor->GetPeerInDataAnchors()) { - GE_CHK_STATUS(GraphUtils::RemoveEdge(out_data_anchor, peer_in_anchor), "Remove Merge data output fail."); - GE_CHK_STATUS(GraphUtils::AddEdge(stream_merge->GetOutDataAnchor(out_data_anchor->GetIdx()), peer_in_anchor), - "StreamMerge node add edge fail."); - } - } - - ReplaceControlEdges(merge_node, stream_merge); - - if (merge_op_desc->HasAttr(ATTR_NAME_NEXT_ITERATION)) { - std::string next_iteration_name; - GE_IF_BOOL_EXEC(!AttrUtils::GetStr(merge_op_desc, ATTR_NAME_NEXT_ITERATION, next_iteration_name), - GELOGE(INTERNAL_ERROR, "get ATTR_NAME_NEXT_ITERATION failed"); - return INTERNAL_ERROR); - - GE_CHK_STATUS_RET(SetNextIteration(stream_merge, next_iteration_name), "set next iteration failed"); - } else { - need_label_nodes_.emplace_back(stream_merge); - } - - (void)bypass_nodes_.insert(merge_node); - - GE_CHK_STATUS_RET(AddMemcpyAsyncNodes(graph, stream_merge, false), "StreamMerge add memcpy node fail."); - - return SUCCESS; -} - -/// -/// @brief Create StreamSwitch Node -/// @param [in] graph -/// @param [in] switch_node -/// @param [in] suffix -/// @param [in] peer_cond_anchor -/// @return ge::NodePtr -/// -NodePtr SwitchOpPass::CreateStreamSwitchNode(ComputeGraphPtr &graph, const NodePtr &switch_node, - const std::string &suffix, OutDataAnchorPtr &peer_cond_anchor) { - GE_CHK_BOOL_EXEC(switch_node != nullptr, return nullptr, "Param of merge node is null."); - OpDescPtr switch_op_desc = switch_node->GetOpDesc(); - GE_CHK_BOOL_EXEC(switch_op_desc != nullptr, return nullptr, "OpDesc of Switch node is invalid."); - GE_IF_BOOL_EXEC(switch_op_desc->GetInputsSize() != SWITCH_INPUT_NUM, { - GELOGE(FAILED, "Switch input param invalid, input_size=%lu, should be %u", switch_op_desc->GetInputsSize(), - SWITCH_INPUT_NUM); - return nullptr; - }); - - const std::string node_name = switch_node->GetName() + "_" + STREAMSWITCH + suffix; - GELOGI("Create StreamSwitch, name=%s.", node_name.c_str()); - OpDescPtr op_desc = MakeShared(node_name, STREAMSWITCH); - if (op_desc == nullptr) { - GELOGE(FAILED, "Create op_desc fail, StreamSwitch:%s.", node_name.c_str()); - return nullptr; - } - // mark hccl group id - std::string hccl_group_id; - if (AttrUtils::GetStr(switch_node->GetOpDesc(), ATTR_NAME_HCCL_FUSED_GROUP, hccl_group_id)) { - (void)AttrUtils::SetStr(op_desc, ATTR_NAME_HCCL_FUSED_GROUP, hccl_group_id); - GELOGI("Set attr ATTR_NAME_HCCL_FUSED_GROUP for Stream_Switch%s, value is %s.", node_name.c_str(), - hccl_group_id.c_str()); - } else { - GELOGI("Can not find attr ATTR_NAME_HCCL_FUSED_GROUP for node %s.", switch_node->GetName().c_str()); - } - - if (!AttrUtils::SetInt(op_desc, ATTR_NAME_SWITCH_DATA_TYPE, RT_SWITCH_INT32) || - !AttrUtils::SetInt(op_desc, ATTR_NAME_STREAM_SWITCH_COND, (int64_t)RT_EQUAL)) { - GELOGE(INTERNAL_ERROR, "set int failed"); - return nullptr; - } - - // Already checked, first input is Variable will passed, second is condition will checked. - GeTensorDesc cond_input_desc = switch_op_desc->GetInputDesc(SWITCH_PRED_INPUT); - GeTensorDesc input_desc(GeShape(cond_input_desc.GetShape().GetDims()), cond_input_desc.GetFormat(), DT_INT32); - GE_CHK_BOOL_EXEC(op_desc->AddInputDesc(input_desc) == GRAPH_SUCCESS, return nullptr, - "Create StreamSwitch node: add input desc fail."); - GE_CHK_BOOL_EXEC(op_desc->AddInputDesc(input_desc) == GRAPH_SUCCESS, return nullptr, - "Create StreamSwitch node: add input desc fail."); - - NodePtr stream_switch = graph->AddNode(op_desc); - GE_CHK_BOOL_EXEC(stream_switch != nullptr, return nullptr, "Insert StreamSwitch node fail."); - - GE_CHK_STATUS(GraphUtils::AddEdge(peer_cond_anchor, stream_switch->GetInDataAnchor(0)), - "StreamSwitch node add cond edge fail."); - - return stream_switch; -} - -/// -/// @brief Add MemcpyAsync Node -/// @param [in] graph -/// @param [in] in_node -/// @param [in] multi_batch_flag -/// @return ge::NodePtr -/// -NodePtr SwitchOpPass::CreateMemcpyAsyncNode(ComputeGraphPtr &graph, const OutDataAnchorPtr &out_data_anchor, - bool multi_batch_flag) { - GE_CHK_BOOL_EXEC(out_data_anchor != nullptr, return nullptr, "Param of input node is null."); - OpDescPtr pre_op_desc = out_data_anchor->GetOwnerNode()->GetOpDesc(); - GE_CHK_BOOL_EXEC(pre_op_desc != nullptr, return nullptr, "OpDesc of pre node is invalid."); - - std::string memcpy_type = multi_batch_flag ? MEMCPYADDRASYNC : MEMCPYASYNC; - std::string node_name = pre_op_desc->GetName() + "_" + memcpy_type; - node_name = CheckDuplicateName(node_name); - GELOGI("Create MemcpyAsync op:%s.", node_name.c_str()); - OpDescPtr op_desc = MakeShared(node_name, memcpy_type); - if (op_desc == nullptr) { - GELOGE(FAILED, "Create op_desc fail, MemcpyAsync:%s.", node_name.c_str()); - return nullptr; - } - - GE_CHK_BOOL_EXEC(op_desc->AddInputDesc(pre_op_desc->GetOutputDesc(out_data_anchor->GetIdx())) == GRAPH_SUCCESS, - return nullptr, "Create MemcpyAsync op: add input desc fail."); - GE_CHK_BOOL_EXEC(op_desc->AddOutputDesc(pre_op_desc->GetOutputDesc(out_data_anchor->GetIdx())) == GRAPH_SUCCESS, - return nullptr, "Create MemcpyAsync op: add output desc fail."); - - NodePtr memcpy_node = graph->AddNode(op_desc); - GE_CHK_BOOL_EXEC(memcpy_node != nullptr, return nullptr, "Insert MemcpyAsync node fail."); - - return memcpy_node; -} - -/// -/// @brief Combine switch nodes link to same cond -/// @param [in] graph -/// @return Status -/// -Status SwitchOpPass::CombineSwitchNode(ComputeGraphPtr &graph) { - for (auto iter = cond_node_map_.begin(); iter != cond_node_map_.end(); ++iter) { - for (auto group_iter = iter->second.begin(); group_iter != iter->second.end(); ++group_iter) { - OutDataAnchorPtr peer_cond_anchor = iter->first; - GE_CHECK_NOTNULL(peer_cond_anchor); - std::list false_switch_list = group_iter->second[SWITCH_FALSE_OUTPUT]; - std::list true_switch_list = group_iter->second[SWITCH_TRUE_OUTPUT]; - std::set same_cond_switch; - same_cond_switch.insert(false_switch_list.begin(), false_switch_list.end()); - same_cond_switch.insert(true_switch_list.begin(), true_switch_list.end()); - - NodePtr cond_node = peer_cond_anchor->GetOwnerNode(); - GELOGI("CombineSwitchNode: cond_node=%s", cond_node->GetName().c_str()); - - NodePtr cast_node = CreateCastOp(graph, peer_cond_anchor); - GE_CHK_BOOL_EXEC(cast_node != nullptr, return FAILED, "Create cast_node fail."); - - NodePtr active_node = CreateActiveNode(graph, cond_node); - GE_CHK_BOOL_EXEC(active_node != nullptr, return FAILED, "Create StreamActive node fail."); - GE_CHK_STATUS(GraphUtils::AddEdge(cast_node->GetOutControlAnchor(), active_node->GetInControlAnchor()), - "StreamActive add ctl edge fail."); - if (SetActiveLabelList(active_node, {cast_node->GetName()}) != SUCCESS) { - GELOGE(FAILED, "SetActiveLabelList for node %s fail.", active_node->GetName().c_str()); - return FAILED; - } - - const std::string cond_group = cond_node->GetName(); - for (uint32_t i = 0; i < SWITCH_OUTPUT_NUM; ++i) { - bool true_branch_flag = (i == SWITCH_TRUE_OUTPUT); - std::list &switch_list = (true_branch_flag ? true_switch_list : false_switch_list); - GE_IF_BOOL_EXEC(switch_list.empty(), continue); - - // select first stream_switch - NodePtr stream_switch = switch_list.front(); - OpDescPtr switch_desc = stream_switch->GetOpDesc(); - GE_CHECK_NOTNULL(switch_desc); - std::string node_name = cond_group + "/" + STREAMSWITCH + (true_branch_flag ? "_t" : "_f"); - node_name = CheckDuplicateName(node_name); - switch_desc->SetName(node_name); - stream_switch_nodes_.emplace_back(stream_switch); - need_label_nodes_.emplace_back(stream_switch); - - // 0_input: original pred input, 1_input: constant node - GE_CHK_STATUS_RET(AddConstNode(graph, stream_switch), "Add const node fail"); - GE_CHK_STATUS(GraphUtils::RemoveEdge(peer_cond_anchor, stream_switch->GetInDataAnchor(0)), - "StreamSwitch remove data edge fail."); - GE_CHK_STATUS(GraphUtils::AddEdge(cast_node->GetOutDataAnchor(0), stream_switch->GetInDataAnchor(0)), - "Cast add data edge fail."); - - for (NodePtr &node : switch_list) { - GE_CHECK_NOTNULL(node); - GE_IF_BOOL_EXEC(node != stream_switch, { - GE_CHK_STATUS(GraphUtils::RemoveEdge(peer_cond_anchor, node->GetInDataAnchor(0)), - "StreamSwitch remove data edge fail."); - }); - GE_CHK_STATUS(ModifySwitchInCtlEdges(node, cast_node, same_cond_switch), "ModifySwitchInCtlEdges fail"); - GE_CHK_STATUS(ModifySwitchOutCtlEdges(node, stream_switch, active_node), "ModifySwitchOutCtlEdges fail"); - } - - GE_CHK_STATUS(GraphUtils::AddEdge(active_node->GetOutControlAnchor(), stream_switch->GetInControlAnchor()), - "StreamActive add ctl edge fail."); - } - } - } - return SUCCESS; -} - -/// -/// @brief Create Active Op -/// @param [in] graph -/// @param [in] cond_node -/// @return ge::NodePtr -/// -NodePtr SwitchOpPass::CreateActiveNode(ComputeGraphPtr &graph, NodePtr &node) { - GE_CHK_BOOL_EXEC(node != nullptr, return nullptr, "Param of pre cond_node is null."); - std::string node_name = node->GetName() + "_" + STREAMACTIVE; - node_name = CheckDuplicateName(node_name); - GELOGI("Create StreamActive op:%s.", node_name.c_str()); - OpDescPtr op_desc = MakeShared(node_name, STREAMACTIVE); - if (op_desc == nullptr) { - GELOGE(FAILED, "Create op_desc fail, StreamActive:%s.", node_name.c_str()); - return nullptr; - } - - NodePtr active_node = graph->AddNode(op_desc); - GE_CHK_BOOL_EXEC(active_node != nullptr, return nullptr, "Create StreamActive node fail."); - - GE_IF_BOOL_EXEC(GraphUtils::AddEdge(node->GetOutControlAnchor(), active_node->GetInControlAnchor()) != SUCCESS, - GELOGE(INTERNAL_ERROR, "add edge failed"); - return nullptr); - - GE_IF_BOOL_EXEC(SetSwitchBranchNodeLabel(active_node, node_name) != SUCCESS, - GELOGE(INTERNAL_ERROR, "set switch branch node label failed"); - return nullptr); - - return active_node; -} - -/// -/// @brief Add MemcpyAsync Op as StreamMerge in_node -/// @param [in] graph -/// @param [in] node -/// @param [in] multi_batch_flag -/// @return Status -/// -Status SwitchOpPass::AddMemcpyAsyncNodes(ComputeGraphPtr &graph, NodePtr &node, bool multi_batch_flag) { - GE_CHK_BOOL_EXEC(node != nullptr, return FAILED, "Param of pre node is null."); - for (InDataAnchorPtr &in_data_anchor : node->GetAllInDataAnchors()) { - OutDataAnchorPtr peer_out_anchor = in_data_anchor->GetPeerOutAnchor(); - GE_IF_BOOL_EXEC(peer_out_anchor == nullptr, continue); - NodePtr in_node = peer_out_anchor->GetOwnerNode(); - - const std::string type = in_node->GetType(); - // For WhileLoop no need memcpy & active for merge. - GE_IF_BOOL_EXEC((type == ENTER) || (type == REFENTER) || (type == NEXTITERATION) || (type == REFNEXTITERATION), - continue); - - GE_IF_BOOL_EXEC(type != MEMCPYASYNC, { - in_node = CreateMemcpyAsyncNode(graph, peer_out_anchor, multi_batch_flag); - GE_CHK_BOOL_EXEC(in_node != nullptr, return FAILED, "Create MemcpyAsync node fail."); - GE_CHK_STATUS(GraphUtils::RemoveEdge(peer_out_anchor, in_data_anchor), "MemcpyAsync node remove edge fail."); - GE_CHK_STATUS(GraphUtils::AddEdge(peer_out_anchor, in_node->GetInDataAnchor(0)), - "MemcpyAsync node add edge fail."); - GE_CHK_STATUS(GraphUtils::AddEdge(in_node->GetOutDataAnchor(0), in_data_anchor), - "MemcpyAsync node add edge fail."); - }); - - NodePtr active_node = CreateActiveNode(graph, in_node); - GE_CHK_BOOL_EXEC(active_node != nullptr, return FAILED, "Create StreamActive node fail."); - GE_CHK_STATUS(GraphUtils::AddEdge(active_node->GetOutControlAnchor(), node->GetInControlAnchor()), - "StreamActive add ctl edge fail."); - if (SetActiveLabelList(active_node, {node->GetName()}) != SUCCESS) { - GELOGE(FAILED, "SetActiveLabelList for node %s fail.", active_node->GetName().c_str()); - return FAILED; - } - } - - return SUCCESS; -} - -/// -/// @brief Bypass Switch Node -/// @param [in] switch_node -/// @param [out] peer_data_anchor -/// @param [out] peer_cond_anchor -/// @return Status -/// -Status SwitchOpPass::BypassSwitchNode(NodePtr &switch_node, OutDataAnchorPtr &peer_data_anchor, - OutDataAnchorPtr &peer_cond_anchor) { - GE_CHK_BOOL_EXEC(switch_node != nullptr, return FAILED, "Switch_node is null."); - for (uint32_t idx = 0; idx < SWITCH_INPUT_NUM; ++idx) { - InDataAnchorPtr in_data_anchor = switch_node->GetInDataAnchor(idx); - GE_CHK_BOOL_EXEC(in_data_anchor != nullptr, return FAILED, "node[%s]Check Switch input anchor fail.", - switch_node->GetName().c_str()); - OutDataAnchorPtr peer_out_anchor = in_data_anchor->GetPeerOutAnchor(); - GE_CHK_BOOL_EXEC(peer_out_anchor != nullptr, return FAILED, "node[%s]Check Pre node output anchor fail.", - switch_node->GetName().c_str()); - // Remove Switch data input. - GE_CHK_STATUS_RET(GraphUtils::RemoveEdge(peer_out_anchor, in_data_anchor), "remove edge failed"); - - if (idx == SWITCH_DATA_INPUT) { - peer_data_anchor = peer_out_anchor; - } else { - if (FindSwitchCondInput(false, peer_out_anchor) != SUCCESS) { - GELOGE(FAILED, "FindSwitchCondInput fail, switch=%s", switch_node->GetName().c_str()); - return FAILED; - } - peer_cond_anchor = peer_out_anchor; - } - } - - return SUCCESS; -} - -/// -/// @brief Find Switch cond input -/// @param [in] pass_switch_flag -/// @param [out] peer_cond_anchor -/// @return Status -/// -Status SwitchOpPass::FindSwitchCondInput(bool pass_switch_flag, OutDataAnchorPtr &peer_cond_anchor) { - NodePtr tmp_node = nullptr; - string type; - bool need_pass_type = true; - while (need_pass_type) { - if (tmp_node == nullptr) { - GE_CHECK_NOTNULL(peer_cond_anchor); - tmp_node = peer_cond_anchor->GetOwnerNode(); - } else { - InDataAnchorPtr in_data_anchor = tmp_node->GetInDataAnchor(SWITCH_DATA_INPUT); - GE_CHECK_NOTNULL(in_data_anchor); - peer_cond_anchor = in_data_anchor->GetPeerOutAnchor(); - GE_CHECK_NOTNULL(peer_cond_anchor); - tmp_node = peer_cond_anchor->GetOwnerNode(); - } - - GE_CHK_STATUS_RET(GetOriginalType(tmp_node, type), "Get node type fail"); - need_pass_type = (pass_switch_flag && ((type == SWITCH) || (type == REFSWITCH))); - } - - return SUCCESS; -} - -int64_t SwitchOpPass::GetGroupId(const NodePtr &node) { - string tailing_optimization_option; - bool is_tailing_optimization = false; - auto ret = GetContext().GetOption(OPTION_EXEC_ENABLE_TAILING_OPTIMIZATION, tailing_optimization_option); - if (ret == GRAPH_SUCCESS) { - // "1" means it's True from frontend option - is_tailing_optimization = (tailing_optimization_option == "1"); - GELOGI("Option ge.exec.isTailingOptimization is %s", tailing_optimization_option.c_str()); - } - if (!is_tailing_optimization) { - return 0; - } - - string hccl_group_id; - if (!AttrUtils::GetStr(node->GetOpDesc(), ATTR_NAME_HCCL_FUSED_GROUP, hccl_group_id)) { - GELOGI("Node is %s, can not find hccl group id", node->GetName().c_str()); - return 0; - } - auto key_index = hccl_group_id.find_last_of('_'); - auto key_num = hccl_group_id.substr(key_index + 1, hccl_group_id.length() - key_index); - GELOGI("Node is %s,Hccl group id is %s, key_num is %s", node->GetName().c_str(), hccl_group_id.c_str(), - key_num.c_str()); - int64_t num = atoi(key_num.c_str()); - if (num == 0) { - return 0; - } - GELOGI("Hccl group id is %s, group id is %ld", hccl_group_id.c_str(), num); - return num; -} - -/// -/// @brief Mark Switch Branch -/// @param [in] peer_cond_anchor -/// @param [in] stream_switch -/// @param [in] true_branch_flag -/// @return Status -/// -Status SwitchOpPass::MarkBranchs(OutDataAnchorPtr &peer_cond_anchor, NodePtr &stream_switch, bool true_branch_flag) { - uint32_t index = true_branch_flag ? SWITCH_TRUE_OUTPUT : SWITCH_FALSE_OUTPUT; - GE_CHECK_NOTNULL(stream_switch); - auto it = cond_node_map_.find(peer_cond_anchor); - if (it != cond_node_map_.end()) { - int64_t switch_group_id = GetGroupId(stream_switch); - auto switch_group_it = it->second.find(switch_group_id); - if (switch_group_it == it->second.end()) { - std::list false_node_list; - std::list true_node_list; - std::list &node_list = true_branch_flag ? true_node_list : false_node_list; - node_list.emplace_back(stream_switch); - std::vector> switch_list; - switch_list.emplace_back(false_node_list); - switch_list.emplace_back(true_node_list); - (void)it->second.emplace(switch_group_id, switch_list); - } else { - GE_IF_BOOL_EXEC(switch_group_it->second.size() != SWITCH_OUTPUT_NUM, { - GELOGE(INTERNAL_ERROR, "cond_node_map_ check size fail, node: %s", stream_switch->GetName().c_str()); - return FAILED; - }); - switch_group_it->second[index].emplace_back(stream_switch); - } - } else { - int64_t switch_group_id = GetGroupId(stream_switch); - map>> switch_group_map; - std::list false_node_list; - std::list true_node_list; - std::list &node_list = true_branch_flag ? true_node_list : false_node_list; - node_list.emplace_back(stream_switch); - std::vector> switch_list; - switch_list.emplace_back(false_node_list); - switch_list.emplace_back(true_node_list); - (void)switch_group_map.emplace(switch_group_id, switch_list); - auto result = cond_node_map_.insert( - std::pair>>>(peer_cond_anchor, switch_group_map)); - GE_IF_BOOL_EXEC(!result.second, { - GELOGE(INTERNAL_ERROR, "cond_node_map_ insert fail, node: %s", stream_switch->GetName().c_str()); - return FAILED; - }); - } - return SUCCESS; -} - -/// -/// @brief Create cast node -/// @param [in] graph -/// @param [in] peer_cond_anchor -/// @return NodePtr -/// -NodePtr SwitchOpPass::CreateCastOp(ComputeGraphPtr &graph, OutDataAnchorPtr &peer_cond_anchor) { - GE_CHK_BOOL_EXEC(peer_cond_anchor != nullptr, return nullptr, "Param of pre cond_node is null."); - OpDescPtr cond_desc = peer_cond_anchor->GetOwnerNode()->GetOpDesc(); - GE_CHK_BOOL_EXEC(cond_desc != nullptr, return nullptr, "Get cond_desc fail."); - - std::string cast_name = cond_desc->GetName() + "_" + CAST; - cast_name = CheckDuplicateName(cast_name); - GELOGI("Create cast_node: %s, input datatype:DT_BOOL, out datatype:DT_INT32", cast_name.c_str()); - OpDescPtr cast_desc = MakeShared(cast_name, CAST); - if (cast_desc == nullptr) { - GELOGE(FAILED, "Create op_desc fail, Cast:%s.", cast_name.c_str()); - return nullptr; - } - if (!(AttrUtils::SetInt(cast_desc, CAST_ATTR_SRCT, (int64_t)DT_BOOL) && - AttrUtils::SetInt(cast_desc, CAST_ATTR_DSTT, (int64_t)DT_INT32) && - AttrUtils::SetInt(cast_desc, CAST_ATTR_DST_TYPE, (int64_t)DT_INT32) && - AttrUtils::SetBool(cast_desc, CAST_ATTR_TRUNCATE, false))) { - GELOGE(FAILED, "Set CAST_ATTR_SRCT or CAST_ATTR_DSTT or CAST_ATTR_DST_TYPE or CAST_ATTR_TRUNCATE fail, node: %s.", - cast_name.c_str()); - return nullptr; - } - GeTensorDesc tensor_desc = cond_desc->GetOutputDesc(peer_cond_anchor->GetIdx()); - tensor_desc.SetDataType(DT_BOOL); - GE_CHK_BOOL_EXEC(cast_desc->AddInputDesc(tensor_desc) == SUCCESS, return nullptr, "Cast_node add input desc fail."); - tensor_desc.SetDataType(DT_INT32); - GE_CHK_BOOL_EXEC(cast_desc->AddOutputDesc(tensor_desc) == SUCCESS, return nullptr, "Cast_node add output desc fail."); - - NodePtr cast_node = graph->AddNode(cast_desc); - GE_CHK_BOOL_EXEC(cast_node != nullptr, return nullptr, "Create cast_node fail."); - - GE_CHK_STATUS(GraphUtils::AddEdge(peer_cond_anchor, cast_node->GetInDataAnchor(0)), "Cast add data edge fail."); - - return cast_node; -} - -/// -/// @brief Add const node as switch input1 -/// @param [in] graph -/// @param [in] stream_switch -/// @return Status -/// -Status SwitchOpPass::AddConstNode(ComputeGraphPtr &graph, NodePtr &stream_switch) { - GE_CHK_BOOL_EXEC(stream_switch != nullptr, return FAILED, "stream_switch is null."); - OpDescPtr op_desc = stream_switch->GetOpDesc(); - GE_CHECK_NOTNULL(op_desc); - bool value = false; - GE_CHK_BOOL_EXEC(AttrUtils::GetBool(op_desc, ATTR_NAME_SWITCH_TRUE_BRANCH_FLAG, value), return FAILED, - "StreamSwitch get attr TRUE_BRANCH_STREAM fail."); - - const std::string const_node_name = op_desc->GetName() + "_Constant_" + (value ? "t" : "f"); - GELOGI("Create const op: %s", const_node_name.c_str()); - OpDescPtr const_op_desc = MakeShared(const_node_name, CONSTANT); - if (const_op_desc == nullptr) { - GELOGE(FAILED, "Create op_desc fail, Constant:%s.", const_node_name.c_str()); - return FAILED; - } - - auto resize_value = (int32_t)value; - GeTensorDesc data_desc = op_desc->GetInputDesc(1); - GeTensorPtr const_value = - MakeShared(data_desc, reinterpret_cast(&resize_value), sizeof(int32_t)); - if (const_value == nullptr) { - GELOGE(FAILED, "Create tensor fail."); - return FAILED; - } - GE_CHK_BOOL_EXEC(AttrUtils::SetTensor(const_op_desc, ATTR_NAME_WEIGHTS, const_value), return FAILED); - GE_CHK_BOOL_EXEC(const_op_desc->AddOutputDesc(data_desc) == GRAPH_SUCCESS, return FAILED, - "Create Const op: add output desc fail."); - - NodePtr const_node = graph->AddNode(const_op_desc); - GE_CHK_BOOL_EXEC(const_node != nullptr, return FAILED, "Insert Const node fail."); - GE_CHK_STATUS(GraphUtils::AddEdge(const_node->GetOutDataAnchor(0), stream_switch->GetInDataAnchor(1)), - "StreamSwitch node add ctl edge fail."); - - return SUCCESS; -} - -/// -/// @brief update cond branch -/// @param [in] node -/// @return Status -/// -Status SwitchOpPass::UpdateCondBranch(NodePtr &node) { - std::string stream_label; - std::unordered_set branch_nodes; - std::unordered_set handled_set; - std::stack nodes; - nodes.push(node); - - static const std::set end_type_set = {STREAMSWITCH, STREAMMERGE, MERGE}; - bool merge_flag = false; - bool exit_flag = false; - bool net_output_flag = false; - - while (!nodes.empty()) { - NodePtr cur_node = nodes.top(); - nodes.pop(); - if (handled_set.count(cur_node) > 0) { - continue; - } - GE_CHECK_NOTNULL(cur_node); - if (UpdateAttachFlag(cur_node, stream_label, merge_flag, exit_flag, net_output_flag) != SUCCESS) { - GELOGE(FAILED, "UpdateAttachFlag fail, cur_node: %s.", cur_node->GetName().c_str()); - return FAILED; - } - - const std::string type = cur_node->GetType(); - for (auto &out_node : cur_node->GetOutAllNodes()) { - const std::string out_type = out_node->GetType(); - bool stop_flag = (end_type_set.count(out_type) > 0) || - ((branch_head_nodes_.count(out_node) > 0) && (branch_head_nodes_[out_node] != node)) || - (((type == ENTER) || (type == REFENTER)) && (out_type != STREAMACTIVE)); - if (!stop_flag) { - nodes.push(out_node); - GELOGD("branch_nodes insert %s", out_node->GetName().c_str()); - branch_nodes.insert(out_node); - } - } - handled_set.insert(cur_node); - } - - if (node->GetType() == STREAMSWITCH) { - GE_CHK_STATUS_RET(SetActiveLabelList(node, {stream_label}), "set active_label_list failed"); - } - - bool attach_flag = (merge_flag || exit_flag) && net_output_flag; - if (attach_flag) { - GELOGI("No need to keep on attaching label."); - return SUCCESS; - } - - for (NodePtr tmp_node : branch_nodes) { - GELOGD("Attach label %s to node: %s", stream_label.c_str(), tmp_node->GetName().c_str()); - GE_CHK_STATUS_RET(SetStreamLabel(tmp_node, stream_label), "set stream label failed"); - } - - return SUCCESS; -} - -/// -/// @brief update attach flag -/// @param [in] node -/// @param [out] stream_label -/// @param [out] merge_flag -/// @param [out] exit_flag -/// @param [out] net_output_flag -/// @return Status -/// -Status SwitchOpPass::UpdateAttachFlag(const NodePtr &node, std::string &stream_label, bool &merge_flag, bool &exit_flag, - bool &net_output_flag) { - const std::string type = node->GetType(); - if (type == STREAMSWITCH) { - if (node->GetInDataNodes().empty()) { - GELOGE(INTERNAL_ERROR, "cur_node %s has no input_data_node", node->GetName().c_str()); - return INTERNAL_ERROR; - } - stream_label = node->GetInDataNodes().at(0)->GetName(); - GE_CHK_STATUS_RET(SetStreamLabel(node, stream_label), "set stream label failed"); - bool value = false; - OpDescPtr op_desc = node->GetOpDesc(); - GE_CHECK_NOTNULL(op_desc); - GE_CHK_BOOL_EXEC(AttrUtils::GetBool(op_desc, ATTR_NAME_SWITCH_TRUE_BRANCH_FLAG, value), return FAILED, - "StreamSwitch get attr TRUE_BRANCH_STREAM fail."); - stream_label += (value ? "_t" : "_f"); - } else if (type == STREAMMERGE) { - stream_label = node->GetName(); - GE_CHK_STATUS_RET(SetStreamLabel(node, stream_label), "set stream label failed"); - merge_flag = true; - } else if ((type == EXIT) || (type == REFEXIT)) { - GE_CHK_STATUS_RET(SetStreamLabel(node, stream_label), "set stream label failed"); - exit_flag = true; - } else if (type == NETOUTPUT) { - net_output_flag = true; - } - - return SUCCESS; -} - -/// -/// @brief update loop branch -/// @param [in] enter_nodes -/// @param [in] stream_label -/// @return Status -/// -Status SwitchOpPass::UpdateLoopBranch(const std::stack &enter_nodes, const std::string &stream_label) { - std::stack nodes(enter_nodes); - NodePtr cur_node = nullptr; - while (!nodes.empty()) { - cur_node = nodes.top(); - nodes.pop(); - for (NodePtr &out_node : cur_node->GetOutAllNodes()) { - OpDescPtr out_desc = out_node->GetOpDesc(); - GE_CHECK_NOTNULL(out_desc); - if (out_desc->HasAttr(ATTR_NAME_STREAM_LABEL)) { - continue; - } - GELOGD("Attach label %s to node: %s", stream_label.c_str(), out_node->GetName().c_str()); - GE_CHK_STATUS_RET(SetStreamLabel(out_node, stream_label), "set stream label failed"); - nodes.push(out_node); - } - } - - return SUCCESS; -} - -/// -/// @brief update enter nodes -/// @return Status -/// -Status SwitchOpPass::UpdateEnterNode() { - std::unordered_map> enter_active_map; - for (auto &enter_node : enter_nodes_) { - for (auto &out_ctrl_node : enter_node->GetOutControlNodes()) { - if (out_ctrl_node->GetType() != STREAMACTIVE) { - continue; - } - auto iter = enter_active_map.find(out_ctrl_node); - if (iter == enter_active_map.end()) { - enter_active_map[out_ctrl_node] = {enter_node}; - } else { - iter->second.emplace_back(enter_node); - } - } - } - - for (auto &pair : enter_active_map) { - std::string stream_label; - NodePtr active_node = pair.first; - GE_CHECK_NOTNULL(active_node); - OpDescPtr active_desc = active_node->GetOpDesc(); - GE_CHECK_NOTNULL(active_desc); - (void)AttrUtils::GetStr(active_desc, ATTR_NAME_STREAM_LABEL, stream_label); - if (stream_label.empty()) { - stream_label = active_desc->GetName(); - GE_CHK_STATUS_RET(SetStreamLabel(active_node, stream_label), "set stream label failed"); - } - std::stack enter_nodes; - for (auto &enter_node : pair.second) { - GE_CHK_STATUS_RET(SetStreamLabel(enter_node, stream_label), "set stream label failed"); - enter_nodes.emplace(enter_node); - } - - std::vector active_label_list; - if (!AttrUtils::GetListStr(active_desc, ATTR_NAME_ACTIVE_LABEL_LIST, active_label_list) || - (active_label_list.size() != 1) || active_label_list[0].empty()) { - GELOGE(INTERNAL_ERROR, "Get attr ATTR_NAME_ACTIVE_LABEL_LIST fail, node: %s", active_desc->GetName().c_str()); - return INTERNAL_ERROR; - } - if (UpdateLoopBranch(enter_nodes, active_label_list[0]) != SUCCESS) { - GELOGE(FAILED, "UpdateLoopBranch fail."); - return FAILED; - } - } - - return SUCCESS; -} - -/// -/// @brief Check duplicate node_name -/// @param [in] node_name -/// @return std::string -/// -std::string SwitchOpPass::CheckDuplicateName(const std::string &node_name) { - std::string tmp_name = node_name; - auto iter = node_num_map_.find(tmp_name); - if (iter != node_num_map_.end()) { - tmp_name = tmp_name + "_" + std::to_string(iter->second); - (iter->second)++; - } else { - node_num_map_[tmp_name] = 1; - } - return tmp_name; -} - -/// -/// @brief Check cyclic dependence -/// @param [in] graph -/// @return Status -/// -Status SwitchOpPass::CheckCycleDependence(ComputeGraphPtr &graph) { - std::string type; - std::unordered_map> cond_switch_map; - for (NodePtr &node : graph->GetDirectNode()) { - GE_CHK_STATUS_RET(GetOriginalType(node, type), "Get node type fail"); - if ((type == SWITCH) || (type == REFSWITCH)) { - InDataAnchorPtr in_cond_anchor = node->GetInDataAnchor(SWITCH_PRED_INPUT); - GE_CHK_BOOL_EXEC(in_cond_anchor != nullptr, return INTERNAL_ERROR, "Check Switch in_cond_anchor fail."); - OutDataAnchorPtr peer_out_anchor = in_cond_anchor->GetPeerOutAnchor(); - GE_CHK_BOOL_EXEC(peer_out_anchor != nullptr, return INTERNAL_ERROR, "Check Switch peer_out_anchor fail."); - if (FindSwitchCondInput(true, peer_out_anchor) != SUCCESS) { - GELOGE(FAILED, "FindSwitchCondInput fail, switch=%s", node->GetName().c_str()); - return FAILED; - } - - NodePtr cond_node = peer_out_anchor->GetOwnerNode(); - auto iter = cond_switch_map.find(cond_node); - if (iter == cond_switch_map.end()) { - cond_switch_map[cond_node] = {node}; - } else { - iter->second.emplace_back(node); - } - - switch_nodes_.emplace_back(node); - } else if ((type == MERGE) || (type == REFMERGE)) { - merge_nodes_.emplace_back(node); - } else if ((type == ENTER) || (type == REFENTER)) { - enter_nodes_.emplace_back(node); - } - } - - MarkCycleDependence(cond_switch_map); - - return SUCCESS; -} - -/// -/// @brief Mark cyclic dependence -/// @param [in] graph -/// @param [in] cond_switch_map -/// @return void -/// -void SwitchOpPass::MarkCycleDependence(const std::unordered_map> &cond_switch_map) { - std::stack out_nodes; - NodePtr tmp_node = nullptr; - std::unordered_set handled_set; - for (auto &iter : cond_switch_map) { - std::set switch_nodes(iter.second.begin(), iter.second.end()); - for (auto &switch_node : switch_nodes) { - GE_CHECK_NOTNULL_JUST_RETURN(switch_node); - GELOGD("CheckCycleDependence: cond_node=%s, switch=%s", iter.first->GetName().c_str(), - switch_node->GetName().c_str()); - for (const NodePtr &node : switch_node->GetOutAllNodes()) { - out_nodes.push(node); - } - } - handled_set.clear(); - while (!out_nodes.empty()) { - tmp_node = out_nodes.top(); - GE_CHECK_NOTNULL_JUST_RETURN(tmp_node); - out_nodes.pop(); - if (handled_set.count(tmp_node) > 0) { - continue; - } - GELOGD("CheckCycleDependence: tmp_node=%s", tmp_node->GetName().c_str()); - for (NodePtr &out_node : tmp_node->GetOutAllNodes()) { - if (switch_nodes.find(out_node) == switch_nodes.end()) { - out_nodes.push(out_node); - continue; - } - GE_IF_BOOL_EXEC(SetCyclicDependenceFlag(out_node) != SUCCESS, GELOGW("set cyclic dependence failed"); return ); - auto map_iter = switch_cyclic_map_.find(out_node); - if (map_iter == switch_cyclic_map_.end()) { - switch_cyclic_map_[out_node] = {tmp_node->GetName()}; - } else { - map_iter->second.insert(tmp_node->GetName()); - } - } - handled_set.insert(tmp_node); - } - } - - return; -} - -/// -/// @brief Modify in ctl edge for switch_node -/// @param [in] switch_node -/// @param [in] cast_node -/// @param [in] same_cond_switch -/// @return Status -/// -Status SwitchOpPass::ModifySwitchInCtlEdges(NodePtr &switch_node, NodePtr &cast_node, - const std::set &same_cond_switch) { - GE_CHECK_NOTNULL(switch_node); - GE_CHECK_NOTNULL(cast_node); - GELOGI("ModifySwitchInCtlEdges: switch_node=%s, active_node=%s", switch_node->GetName().c_str(), - cast_node->GetName().c_str()); - - std::string orig_switch_name = switch_node->GetName(); - OpDescPtr switch_desc = switch_node->GetOpDesc(); - GE_CHECK_NOTNULL(switch_desc); - if (!AttrUtils::GetStr(switch_desc, ATTR_NAME_ORIG_NODE_NAME, orig_switch_name) || orig_switch_name.empty()) { - GELOGE(INTERNAL_ERROR, "Get attr ATTR_NAME_ORIG_NODE_NAME fail, node: %s", switch_desc->GetName().c_str()); - return INTERNAL_ERROR; - } - - for (NodePtr &in_ctl_node : switch_node->GetInControlNodes()) { - GE_CHK_STATUS(GraphUtils::RemoveEdge(in_ctl_node->GetOutControlAnchor(), switch_node->GetInControlAnchor()), - "Remove ctl edge fail."); - GE_IF_BOOL_EXEC(!in_ctl_node->GetOutControlAnchor()->IsLinkedWith(cast_node->GetInControlAnchor()), { - GE_CHK_STATUS(GraphUtils::AddEdge(in_ctl_node->GetOutControlAnchor(), cast_node->GetInControlAnchor()), - "Add ctl edge fail."); - }); - - GE_IF_BOOL_EXEC(in_ctl_node->GetType() != STREAMSWITCH, continue); - if (same_cond_switch.count(in_ctl_node) > 0) { - GE_CHK_STATUS(GraphUtils::RemoveEdge(in_ctl_node->GetOutControlAnchor(), cast_node->GetInControlAnchor()), - "Remove ctl edge fail."); - continue; - } - auto find_res1 = switch_node_map_.find(in_ctl_node); - GE_IF_BOOL_EXEC(find_res1 == switch_node_map_.end(), { - GELOGE(INTERNAL_ERROR, "StreamSwitch node %s not found in switch_node_map_.", in_ctl_node->GetName().c_str()); - return INTERNAL_ERROR; - }); - auto find_res2 = find_res1->second.find(orig_switch_name); - auto find_res3 = find_res1->second.find(cast_node->GetName()); - GE_IF_BOOL_EXEC((find_res2 != find_res1->second.end()) && (find_res3 == find_res1->second.end()), { - find_res1->second.erase(find_res2); - find_res1->second.insert(cast_node->GetName()); - continue; - }); - } - - return SUCCESS; -} - -/// -/// @brief Modify out ctl edge for switch_node -/// @param [in] switch_node -/// @param [in] stream_switch -/// @param [in] active_node -/// @return Status -/// -Status SwitchOpPass::ModifySwitchOutCtlEdges(NodePtr &switch_node, NodePtr &stream_switch, NodePtr &active_node) { - GE_CHECK_NOTNULL(switch_node); - GE_CHECK_NOTNULL(stream_switch); - GE_CHECK_NOTNULL(active_node); - GELOGI("ModifySwitchOutCtlEdges: switch_node=%s, stream_switch=%s, active_node=%s", switch_node->GetName().c_str(), - stream_switch->GetName().c_str(), active_node->GetName().c_str()); - auto find_res = switch_node_map_.find(switch_node); - GE_IF_BOOL_EXEC(find_res == switch_node_map_.end(), { - GELOGE(INTERNAL_ERROR, "StreamSwitch node %s not found in switch_node_map_.", switch_node->GetName().c_str()); - return INTERNAL_ERROR; - }); - GE_IF_BOOL_EXEC(find_res->second.empty(), { - GELOGE(INTERNAL_ERROR, "true_nodes of StreamSwitch node %s is empty.", switch_node->GetName().c_str()); - return INTERNAL_ERROR; - }); - - for (NodePtr &node : switch_node->GetOutControlNodes()) { - GE_CHK_STATUS(GraphUtils::RemoveEdge(switch_node->GetOutControlAnchor(), node->GetInControlAnchor()), - "Remove ctl edge fail."); - OpDescPtr op_desc = node->GetOpDesc(); - GE_CHECK_NOTNULL(op_desc); - std::string orig_name = op_desc->GetName(); - GE_IF_BOOL_EXEC(op_desc->HasAttr(ATTR_NAME_ORIG_NODE_NAME), { - if (!AttrUtils::GetStr(op_desc, ATTR_NAME_ORIG_NODE_NAME, orig_name) || orig_name.empty()) { - GELOGE(INTERNAL_ERROR, "Get attr ATTR_NAME_ORIG_NODE_NAME fail, node: %s.", op_desc->GetName().c_str()); - return INTERNAL_ERROR; - } - }); - if (find_res->second.find(orig_name) == find_res->second.end()) { - auto active_out_control_anchor = active_node->GetOutControlAnchor(); - GE_CHECK_NOTNULL(active_out_control_anchor); - GE_IF_BOOL_EXEC(!active_out_control_anchor->IsLinkedWith(node->GetInControlAnchor()), { - GE_CHK_STATUS(GraphUtils::AddEdge(active_out_control_anchor, node->GetInControlAnchor()), "Add ctl edge fail."); - }); - } else { - auto stream_switch_out_control_anchor = stream_switch->GetOutControlAnchor(); - GE_CHECK_NOTNULL(stream_switch_out_control_anchor); - GE_IF_BOOL_EXEC(!stream_switch_out_control_anchor->IsLinkedWith(node->GetInControlAnchor()), { - GE_CHK_STATUS(GraphUtils::AddEdge(stream_switch_out_control_anchor, node->GetInControlAnchor()), - "Add ctl edge fail."); - }); - } - } - - GE_IF_BOOL_EXEC(switch_node != stream_switch, (void)bypass_nodes_.insert(switch_node)); - - return SUCCESS; -} - -/// -/// @brief Copy Control Edges -/// @param [in] old_node -/// @param [in] new_node -/// @param [in] input_check_flag -/// @return void -/// -void SwitchOpPass::CopyControlEdges(NodePtr &old_node, NodePtr &new_node, bool input_check_flag) { - GE_CHECK_NOTNULL_JUST_RETURN(old_node); - GE_CHECK_NOTNULL_JUST_RETURN(new_node); - GE_IF_BOOL_EXEC(old_node == new_node, return ); - auto iter = switch_cyclic_map_.find(old_node); - bool check_flag = input_check_flag && (iter != switch_cyclic_map_.end()); - for (NodePtr &node : old_node->GetInControlNodes()) { - if (check_flag && (iter->second.count(node->GetName()) > 0)) { - for (auto &out_node : old_node->GetOutAllNodes()) { - auto out_control_anchor = node->GetOutControlAnchor(); - GE_CHECK_NOTNULL_JUST_RETURN(out_control_anchor); - GE_IF_BOOL_EXEC(!out_control_anchor->IsLinkedWith(out_node->GetInControlAnchor()), { - GE_CHK_STATUS(GraphUtils::AddEdge(out_control_anchor, out_node->GetInControlAnchor()), "Add ctl edge fail."); - }); - } - } else { - auto out_control_anchor = node->GetOutControlAnchor(); - GE_CHECK_NOTNULL_JUST_RETURN(out_control_anchor); - GE_IF_BOOL_EXEC(!out_control_anchor->IsLinkedWith(new_node->GetInControlAnchor()), { - GE_CHK_STATUS(GraphUtils::AddEdge(out_control_anchor, new_node->GetInControlAnchor()), "Add in ctl edge fail."); - }); - } - } - - for (NodePtr &node : old_node->GetOutControlNodes()) { - GE_IF_BOOL_EXEC(!new_node->GetOutControlAnchor()->IsLinkedWith(node->GetInControlAnchor()), { - GE_CHK_STATUS(GraphUtils::AddEdge(new_node->GetOutControlAnchor(), node->GetInControlAnchor()), - "Add out ctl edge fail."); - }); - } -} - -/// -/// @brief Remove Control Edges -/// @param [in] node -/// @return void -/// -void SwitchOpPass::RemoveControlEdges(NodePtr &node) { - GE_CHECK_NOTNULL_JUST_RETURN(node); - for (NodePtr &in_node : node->GetInControlNodes()) { - GE_CHK_STATUS(GraphUtils::RemoveEdge(in_node->GetOutControlAnchor(), node->GetInControlAnchor()), - "Remove in ctl edge fail."); - } - - for (auto &out_data_anchor : node->GetAllOutDataAnchors()) { - for (auto &in_ctrl_anchor : out_data_anchor->GetPeerInControlAnchors()) { - GE_CHK_STATUS(GraphUtils::RemoveEdge(out_data_anchor, in_ctrl_anchor), "Remove in ctl edge fail."); - } - } - - auto out_control_anchor = node->GetOutControlAnchor(); - GE_CHECK_NOTNULL_JUST_RETURN(out_control_anchor); - for (auto &peer_anchor : out_control_anchor->GetPeerAnchors()) { - GE_CHK_STATUS(GraphUtils::RemoveEdge(out_control_anchor, peer_anchor), "Remove out ctl edge fail."); - } -} - -/// -/// @brief Replace Control Edges -/// @param [in] old_node -/// @param [in] new_node -/// @return void -/// -void SwitchOpPass::ReplaceControlEdges(NodePtr &old_node, NodePtr &new_node) { - GE_IF_BOOL_EXEC(old_node == new_node, return ); - CopyControlEdges(old_node, new_node); - RemoveControlEdges(old_node); -} - -/// -/// @brief Mark node as head_node of stream_switch -/// @param [in] node -/// @param [in] stream_switch -/// @return void -/// -void SwitchOpPass::MarkHeadNodes(const NodePtr &node, const NodePtr &stream_switch) { - std::stack nodes; - nodes.push(node); - std::set visited; - while (!nodes.empty()) { - NodePtr cur_node = nodes.top(); - nodes.pop(); - if (visited.count(cur_node) > 0) { - continue; - } - GELOGD("branch_head_node %s of stream_switch %s", cur_node->GetName().c_str(), stream_switch->GetName().c_str()); - branch_head_nodes_[cur_node] = stream_switch; - if ((cur_node->GetType() == IDENTITY) || (cur_node->GetType() == IDENTITYN)) { - for (auto &out_node : cur_node->GetOutAllNodes()) { - nodes.push(out_node); - } - } - visited.insert(cur_node); - } -} - -/// -/// @brief Clear Status, uesd for subgraph pass -/// @return -/// -Status SwitchOpPass::ClearStatus() { - switch_nodes_.clear(); - merge_nodes_.clear(); - enter_nodes_.clear(); - switch_cyclic_map_.clear(); - bypass_nodes_.clear(); - branch_head_nodes_.clear(); - stream_switch_nodes_.clear(); - need_label_nodes_.clear(); - cond_node_map_.clear(); - switch_node_map_.clear(); - node_num_map_.clear(); - return SUCCESS; -} -} // namespace ge diff --git a/src/ge/graph/passes/switch_to_stream_switch_pass.cc b/src/ge/graph/passes/switch_to_stream_switch_pass.cc new file mode 100644 index 00000000..ef8879dd --- /dev/null +++ b/src/ge/graph/passes/switch_to_stream_switch_pass.cc @@ -0,0 +1,755 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graph/passes/switch_to_stream_switch_pass.h" +#include +#include "common/ge/ge_util.h" +#include "framework/common/debug/ge_log.h" +#include "framework/common/debug/log.h" +#include "framework/common/ge_inner_error_codes.h" +#include "framework/common/types.h" +#include "ge/ge_api_types.h" +#include "graph/common/omg_util.h" +#include "graph/debug/ge_attr_define.h" +#include "graph/ge_context.h" +#include "graph/utils/type_utils.h" + +namespace ge { +Status SwitchToStreamSwitchPass::Run(ComputeGraphPtr graph) { + GELOGD("SwitchToStreamSwitchPass Enter"); + + GE_CHK_STATUS_RET(CheckCycleDependence(graph), "Check cyclic dependence failed."); + for (const auto &switch_node : switch_nodes_) { + GE_CHK_STATUS_RET(ReplaceSwitchNode(graph, switch_node), "Replace Switch by StreamSwitch failed."); + } + GE_CHK_STATUS_RET(CombineSwitchNode(graph), "Combine StreamSwitch nodes failed."); + + for (const auto &node : bypass_nodes_) { + GE_CHK_BOOL_EXEC(graph->IsolateNode(node) == GRAPH_SUCCESS, return FAILED, "Isolate node failed."); + GE_CHK_BOOL_EXEC(GraphUtils::RemoveNodeWithoutRelink(graph, node) == GRAPH_SUCCESS, return FAILED, + "Remove switch node failed."); + } + + GELOGD("SwitchToStreamSwitchPass Leave"); + return SUCCESS; +} + +/// +/// @brief Clear Status, used for subgraph pass +/// @return +/// +Status SwitchToStreamSwitchPass::ClearStatus() { + switch_nodes_.clear(); + switch_cyclic_map_.clear(); + bypass_nodes_.clear(); + stream_switch_nodes_.clear(); + cond_node_map_.clear(); + switch_node_map_.clear(); + node_num_map_.clear(); + return SUCCESS; +} + +/// +/// @brief Check cyclic dependence +/// @param [in] graph +/// @return Status +/// +Status SwitchToStreamSwitchPass::CheckCycleDependence(const ComputeGraphPtr &graph) { + std::string type; + std::unordered_map> cond_switch_map; + for (const NodePtr &node : graph->GetDirectNode()) { + GE_CHK_STATUS_RET(GetOriginalType(node, type), "Get node type failed."); + if ((type == SWITCH) || (type == REFSWITCH)) { + InDataAnchorPtr in_cond_anchor = node->GetInDataAnchor(SWITCH_PRED_INPUT); + GE_CHECK_NOTNULL(in_cond_anchor); + OutDataAnchorPtr peer_out_anchor = in_cond_anchor->GetPeerOutAnchor(); + GE_CHECK_NOTNULL(peer_out_anchor); + if (FindSwitchCondInput(true, peer_out_anchor) != SUCCESS) { + GELOGE(FAILED, "Find pred_input for switch_node %s failed.", node->GetName().c_str()); + return FAILED; + } + + NodePtr cond_node = peer_out_anchor->GetOwnerNode(); + auto iter = cond_switch_map.find(cond_node); + if (iter == cond_switch_map.end()) { + cond_switch_map[cond_node] = {node}; + } else { + iter->second.emplace_back(node); + } + switch_nodes_.emplace_back(node); + } + } + + MarkCycleDependence(cond_switch_map); + return SUCCESS; +} + +/// +/// @brief Mark cyclic dependence +/// @param [in] graph +/// @param [in] cond_switch_map +/// @return void +/// +void SwitchToStreamSwitchPass::MarkCycleDependence( + const std::unordered_map> &cond_switch_map) { + std::stack out_nodes; + NodePtr tmp_node = nullptr; + std::unordered_set visited; + for (const auto &iter : cond_switch_map) { + std::set switch_nodes(iter.second.begin(), iter.second.end()); + for (const auto &switch_node : switch_nodes) { + GELOGD("MarkCycleDependence: cond_node=%s, switch=%s.", iter.first->GetName().c_str(), + switch_node->GetName().c_str()); + for (const auto &node : switch_node->GetOutAllNodes()) { + out_nodes.push(node); + } + } + visited.clear(); + while (!out_nodes.empty()) { + tmp_node = out_nodes.top(); + out_nodes.pop(); + if (visited.count(tmp_node) > 0) { + continue; + } + GELOGD("MarkCycleDependence: tmp_node=%s.", tmp_node->GetName().c_str()); + for (const NodePtr &out_node : tmp_node->GetOutAllNodes()) { + if (switch_nodes.find(out_node) == switch_nodes.end()) { + out_nodes.push(out_node); + continue; + } + GE_IF_BOOL_EXEC(SetCyclicDependenceFlag(out_node) != SUCCESS, GELOGW("set cyclic dependence attr failed."); + return ); + auto map_iter = switch_cyclic_map_.find(out_node); + if (map_iter == switch_cyclic_map_.end()) { + switch_cyclic_map_[out_node] = {tmp_node->GetName()}; + } else { + map_iter->second.insert(tmp_node->GetName()); + } + } + visited.insert(tmp_node); + } + } + + return; +} + +/// +/// @brief Replace Switch Op +/// @param [in] graph +/// @param [in] switch_node +/// @return Status +/// +Status SwitchToStreamSwitchPass::ReplaceSwitchNode(const ComputeGraphPtr &graph, const NodePtr &switch_node) { + OutDataAnchorPtr peer_data_anchor = nullptr; + OutDataAnchorPtr peer_cond_anchor = nullptr; + GE_CHK_BOOL_EXEC(BypassSwitchNode(switch_node, peer_data_anchor, peer_cond_anchor) == SUCCESS, return FAILED, + "Bypass switch node %s failed.", switch_node->GetName().c_str()); + GE_CHECK_NOTNULL(peer_data_anchor); + GE_CHECK_NOTNULL(peer_cond_anchor); + OpDescPtr cond_desc = peer_cond_anchor->GetOwnerNode()->GetOpDesc(); + GE_CHECK_NOTNULL(cond_desc); + DataType cond_data_type = cond_desc->GetOutputDesc(peer_cond_anchor->GetIdx()).GetDataType(); + GE_CHK_BOOL_EXEC(cond_data_type == DT_BOOL, return FAILED, + "pred_input of Switch only support DT_BOOL data_type, but %s exactly.", + TypeUtils::DataTypeToSerialString(cond_data_type).c_str()); + + OpDescPtr switch_desc = switch_node->GetOpDesc(); + GE_CHECK_NOTNULL(switch_desc); + bool cyclic_flag = switch_desc->HasAttr(ATTR_NAME_CYCLIC_DEPENDENCE_FLAG); + std::set out_node_list; + for (const auto &out_data_anchor : switch_node->GetAllOutDataAnchors()) { + bool true_branch_flag = (static_cast(out_data_anchor->GetIdx()) == SWITCH_TRUE_OUTPUT); + NodePtr stream_switch = nullptr; + out_node_list.clear(); + for (const auto &peer_in_anchor : out_data_anchor->GetPeerAnchors()) { + GE_IF_BOOL_EXEC(stream_switch == nullptr, { + stream_switch = CreateStreamSwitchNode(graph, switch_node, true_branch_flag ? "_t" : "_f", peer_cond_anchor); + GE_CHK_BOOL_EXEC(stream_switch != nullptr, return FAILED, "Create stream_switch node failed."); + if (SetSwitchTrueBranchFlag(stream_switch, true_branch_flag) != SUCCESS) { + GELOGE(FAILED, "SetSwitchTrueBranchFlag for node %s failed.", stream_switch->GetName().c_str()); + return FAILED; + } + if (MarkBranches(peer_cond_anchor, stream_switch, true_branch_flag) != SUCCESS) { + GELOGE(FAILED, "Mark branches for stream_switch %s failed.", stream_switch->GetName().c_str()); + return FAILED; + } + + if (!cyclic_flag) { + GE_CHK_STATUS(GraphUtils::AddEdge(peer_data_anchor->GetOwnerNode()->GetOutControlAnchor(), + stream_switch->GetInControlAnchor()), + "StreamSwitch node add ctl edge failed."); + } + }); + + GE_CHK_STATUS(GraphUtils::RemoveEdge(out_data_anchor, peer_in_anchor), "Remove Switch data output failed."); + + NodePtr out_node = peer_in_anchor->GetOwnerNode(); + GE_CHK_STATUS(GraphUtils::AddEdge(peer_data_anchor, peer_in_anchor), "StreamSwitch node add edge failed."); + GE_CHK_STATUS(GraphUtils::AddEdge(stream_switch->GetOutControlAnchor(), out_node->GetInControlAnchor()), + "StreamSwitch node add ctl edge failed."); + out_node_list.insert(out_node->GetName()); + } + + GE_IF_BOOL_EXEC(stream_switch != nullptr, { + MoveCtrlEdges(switch_node, stream_switch); + switch_node_map_[stream_switch] = out_node_list; + if (SetOriginalNodeName(stream_switch, switch_node->GetName()) != SUCCESS) { + GELOGE(FAILED, "SetOriginalNodeName for node %s failed.", stream_switch->GetName().c_str()); + return FAILED; + } + }); + } + + (void)bypass_nodes_.insert(switch_node); + return SUCCESS; +} + +/// +/// @brief Bypass Switch Node +/// @param [in] switch_node +/// @param [out] peer_data_anchor +/// @param [out] peer_cond_anchor +/// @return Status +/// +Status SwitchToStreamSwitchPass::BypassSwitchNode(const NodePtr &switch_node, OutDataAnchorPtr &peer_data_anchor, + OutDataAnchorPtr &peer_cond_anchor) { + for (uint32_t idx = 0; idx < SWITCH_INPUT_NUM; ++idx) { + InDataAnchorPtr in_data_anchor = switch_node->GetInDataAnchor(idx); + GE_CHECK_NOTNULL(in_data_anchor); + OutDataAnchorPtr peer_out_anchor = in_data_anchor->GetPeerOutAnchor(); + GE_CHECK_NOTNULL(peer_out_anchor); + // Remove Switch data input. + if (GraphUtils::RemoveEdge(peer_out_anchor, in_data_anchor) != GRAPH_SUCCESS) { + GELOGE(FAILED, "Remove data edge %s->%s failed.", peer_out_anchor->GetOwnerNode()->GetName().c_str(), + switch_node->GetName().c_str()); + return FAILED; + } + + if (idx == SWITCH_DATA_INPUT) { + peer_data_anchor = peer_out_anchor; + } else { + if (FindSwitchCondInput(false, peer_out_anchor) != SUCCESS) { + GELOGE(FAILED, "Find pred_input for switch_node %s failed.", switch_node->GetName().c_str()); + return FAILED; + } + peer_cond_anchor = peer_out_anchor; + } + } + + return SUCCESS; +} + +/// +/// @brief Find Switch cond input +/// @param [in] pass_switch_flag +/// @param [out] peer_cond_anchor +/// @return Status +/// +Status SwitchToStreamSwitchPass::FindSwitchCondInput(bool pass_switch_flag, OutDataAnchorPtr &peer_cond_anchor) { + NodePtr tmp_node = nullptr; + string type; + bool need_pass_type = true; + while (need_pass_type) { + if (tmp_node == nullptr) { + tmp_node = peer_cond_anchor->GetOwnerNode(); + } else { + InDataAnchorPtr in_data_anchor = tmp_node->GetInDataAnchor(SWITCH_DATA_INPUT); + GE_CHECK_NOTNULL(in_data_anchor); + peer_cond_anchor = in_data_anchor->GetPeerOutAnchor(); + GE_CHECK_NOTNULL(peer_cond_anchor); + tmp_node = peer_cond_anchor->GetOwnerNode(); + } + + GE_CHK_STATUS_RET(GetOriginalType(tmp_node, type), "Get node type failed."); + need_pass_type = (pass_switch_flag && ((type == SWITCH) || (type == REFSWITCH))); + } + + return SUCCESS; +} + +/// +/// @brief Create StreamSwitch Node +/// @param [in] graph +/// @param [in] switch_node +/// @param [in] suffix +/// @param [in] peer_cond_anchor +/// @return ge::NodePtr +/// +NodePtr SwitchToStreamSwitchPass::CreateStreamSwitchNode(const ComputeGraphPtr &graph, const NodePtr &switch_node, + const std::string &suffix, + const OutDataAnchorPtr &peer_cond_anchor) { + OpDescPtr switch_op_desc = switch_node->GetOpDesc(); + GE_CHK_BOOL_EXEC(switch_op_desc != nullptr, return nullptr, "OpDesc of Switch node is invalid."); + GE_IF_BOOL_EXEC(switch_op_desc->GetInputsSize() != SWITCH_INPUT_NUM, { + GELOGE(FAILED, "Switch input param invalid, input_size=%lu, should be %u.", switch_op_desc->GetInputsSize(), + SWITCH_INPUT_NUM); + return nullptr; + }); + + const std::string &node_name = switch_node->GetName() + "_" + STREAMSWITCH + suffix; + GELOGI("Create StreamSwitch, name=%s.", node_name.c_str()); + OpDescPtr op_desc = MakeShared(node_name, STREAMSWITCH); + if (op_desc == nullptr) { + GELOGE(FAILED, "Create op_desc failed, StreamSwitch:%s.", node_name.c_str()); + return nullptr; + } + + // mark hccl group id + std::string hccl_group_id; + if (AttrUtils::GetStr(switch_node->GetOpDesc(), ATTR_NAME_HCCL_FUSED_GROUP, hccl_group_id)) { + (void)AttrUtils::SetStr(op_desc, ATTR_NAME_HCCL_FUSED_GROUP, hccl_group_id); + GELOGD("Set attr ATTR_NAME_HCCL_FUSED_GROUP for Stream_Switch %s, value is %s.", node_name.c_str(), + hccl_group_id.c_str()); + } + + if (!AttrUtils::SetInt(op_desc, ATTR_NAME_SWITCH_DATA_TYPE, RT_SWITCH_INT32) || + !AttrUtils::SetInt(op_desc, ATTR_NAME_STREAM_SWITCH_COND, (int64_t)RT_EQUAL)) { + GELOGE(INTERNAL_ERROR, "set int failed"); + return nullptr; + } + + // Already checked, first input is Variable will passed, second is condition will checked. + GeTensorDesc cond_input_desc = switch_op_desc->GetInputDesc(SWITCH_PRED_INPUT); + GeTensorDesc input_desc(GeShape(cond_input_desc.GetShape().GetDims()), cond_input_desc.GetFormat(), DT_INT32); + GE_CHK_BOOL_EXEC(op_desc->AddInputDesc(input_desc) == GRAPH_SUCCESS, return nullptr, + "Create StreamSwitch node: add input desc failed."); + GE_CHK_BOOL_EXEC(op_desc->AddInputDesc(input_desc) == GRAPH_SUCCESS, return nullptr, + "Create StreamSwitch node: add input desc failed."); + + NodePtr stream_switch = graph->AddNode(op_desc); + GE_CHK_BOOL_EXEC(stream_switch != nullptr, return nullptr, "Insert StreamSwitch node failed."); + GE_CHK_STATUS(GraphUtils::AddEdge(peer_cond_anchor, stream_switch->GetInDataAnchor(0)), + "StreamSwitch node add cond edge failed."); + + return stream_switch; +} + +/// +/// @brief Mark Switch Branch +/// @param [in] peer_cond_anchor +/// @param [in] stream_switch +/// @param [in] true_branch_flag +/// @return Status +/// +Status SwitchToStreamSwitchPass::MarkBranches(const OutDataAnchorPtr &peer_cond_anchor, const NodePtr &stream_switch, + bool true_branch_flag) { + uint32_t index = true_branch_flag ? SWITCH_TRUE_OUTPUT : SWITCH_FALSE_OUTPUT; + auto it = cond_node_map_.find(peer_cond_anchor); + if (it != cond_node_map_.end()) { + int64_t switch_group_id = GetGroupId(stream_switch); + auto switch_group_it = it->second.find(switch_group_id); + if (switch_group_it == it->second.end()) { + std::list false_node_list; + std::list true_node_list; + std::list &node_list = true_branch_flag ? true_node_list : false_node_list; + node_list.emplace_back(stream_switch); + std::vector> switch_list; + switch_list.emplace_back(false_node_list); + switch_list.emplace_back(true_node_list); + it->second[switch_group_id] = switch_list; + } else { + GE_IF_BOOL_EXEC(switch_group_it->second.size() != SWITCH_OUTPUT_NUM, { + GELOGE(INTERNAL_ERROR, "Check size failed, node: %s", stream_switch->GetName().c_str()); + return FAILED; + }); + switch_group_it->second[index].emplace_back(stream_switch); + } + } else { + int64_t switch_group_id = GetGroupId(stream_switch); + map>> switch_group_map; + std::list false_node_list; + std::list true_node_list; + std::list &node_list = true_branch_flag ? true_node_list : false_node_list; + node_list.emplace_back(stream_switch); + std::vector> switch_list; + switch_list.emplace_back(false_node_list); + switch_list.emplace_back(true_node_list); + switch_group_map[switch_group_id] = switch_list; + cond_node_map_[peer_cond_anchor] = switch_group_map; + } + return SUCCESS; +} + +/// +/// @brief Get group_id for switch_node +/// @param [in] node +/// @return group_id +/// +int64_t SwitchToStreamSwitchPass::GetGroupId(const NodePtr &node) { + string tailing_optimization_option; + bool is_tailing_optimization = false; + if (GetContext().GetOption(OPTION_EXEC_ENABLE_TAILING_OPTIMIZATION, tailing_optimization_option) == GRAPH_SUCCESS) { + // "1" means it's True from frontend option + is_tailing_optimization = (tailing_optimization_option == "1"); + GELOGI("Option ge.exec.isTailingOptimization is %s", tailing_optimization_option.c_str()); + } + if (!is_tailing_optimization) { + return 0; + } + + string hccl_group_id; + if (!AttrUtils::GetStr(node->GetOpDesc(), ATTR_NAME_HCCL_FUSED_GROUP, hccl_group_id)) { + GELOGI("Node %s can not find hccl group id.", node->GetName().c_str()); + return 0; + } + auto key_index = hccl_group_id.find_last_of('_'); + auto key_num = hccl_group_id.substr(key_index + 1, hccl_group_id.length() - key_index); + GELOGI("Node:%s, hccl_group_id=%s, key_num=%s", node->GetName().c_str(), hccl_group_id.c_str(), key_num.c_str()); + int64_t num = atoi(key_num.c_str()); + if (num == 0) { + return 0; + } + + GELOGI("Hccl_group_id is %s, group_id is %ld", hccl_group_id.c_str(), num); + return num; +} + +/// +/// @brief Combine switch nodes link to same cond +/// @param [in] graph +/// @return Status +/// +Status SwitchToStreamSwitchPass::CombineSwitchNode(const ComputeGraphPtr &graph) { + for (auto iter = cond_node_map_.begin(); iter != cond_node_map_.end(); ++iter) { + for (auto group_iter = iter->second.begin(); group_iter != iter->second.end(); ++group_iter) { + std::list false_switch_list = group_iter->second[SWITCH_FALSE_OUTPUT]; + std::list true_switch_list = group_iter->second[SWITCH_TRUE_OUTPUT]; + std::set same_cond_switch; + same_cond_switch.insert(false_switch_list.begin(), false_switch_list.end()); + same_cond_switch.insert(true_switch_list.begin(), true_switch_list.end()); + + OutDataAnchorPtr peer_cond_anchor = iter->first; + NodePtr cond_node = peer_cond_anchor->GetOwnerNode(); + GELOGI("CombineSwitchNode: cond_node=%s.", cond_node->GetName().c_str()); + + NodePtr cast_node = CreateCastOp(graph, peer_cond_anchor); + GE_CHK_BOOL_EXEC(cast_node != nullptr, return FAILED, "Create cast_node failed."); + + NodePtr active_node = CreateActiveNode(graph, cond_node); + GE_CHK_BOOL_EXEC(active_node != nullptr, return FAILED, "Create StreamActive node failed."); + GE_CHK_STATUS(GraphUtils::AddEdge(cast_node->GetOutControlAnchor(), active_node->GetInControlAnchor()), + "StreamActive add ctl edge failed."); + if (SetActiveLabelList(active_node, {cast_node->GetName()}) != SUCCESS) { + GELOGE(FAILED, "Set active_label_list attr for node %s failed.", active_node->GetName().c_str()); + return FAILED; + } + + const std::string &cond_group = cond_node->GetName(); + for (uint32_t i = 0; i < SWITCH_OUTPUT_NUM; ++i) { + bool true_branch_flag = (i == SWITCH_TRUE_OUTPUT); + std::list &switch_list = (true_branch_flag ? true_switch_list : false_switch_list); + GE_IF_BOOL_EXEC(switch_list.empty(), continue); + + // select first stream_switch + NodePtr stream_switch = switch_list.front(); + OpDescPtr switch_desc = stream_switch->GetOpDesc(); + GE_CHECK_NOTNULL(switch_desc); + switch_desc->SetName(CheckDuplicateName(cond_group + "/" + STREAMSWITCH + (true_branch_flag ? "_t" : "_f"))); + stream_switch_nodes_.emplace_back(stream_switch); + + // 0_input: original pred input, 1_input: constant node + GE_CHK_STATUS_RET(AddConstNode(graph, stream_switch), "Add const node failed."); + GE_CHK_STATUS(GraphUtils::RemoveEdge(peer_cond_anchor, stream_switch->GetInDataAnchor(0)), + "StreamSwitch remove data edge failed."); + GE_CHK_STATUS(GraphUtils::AddEdge(cast_node->GetOutDataAnchor(0), stream_switch->GetInDataAnchor(0)), + "Cast add data edge failed."); + + for (const NodePtr &node : switch_list) { + GE_IF_BOOL_EXEC(node != stream_switch, { + GE_CHK_STATUS(GraphUtils::RemoveEdge(peer_cond_anchor, node->GetInDataAnchor(0)), + "StreamSwitch remove data edge failed."); + }); + GE_CHK_STATUS(ModifySwitchInCtlEdges(node, cast_node, same_cond_switch), "ModifySwitchInCtlEdges failed."); + GE_CHK_STATUS(ModifySwitchOutCtlEdges(node, stream_switch, active_node), "ModifySwitchOutCtlEdges failed."); + } + + GE_CHK_STATUS(GraphUtils::AddEdge(active_node->GetOutControlAnchor(), stream_switch->GetInControlAnchor()), + "StreamActive add ctl edge failed."); + } + } + } + return SUCCESS; +} + +/// +/// @brief Create Active Op +/// @param [in] graph +/// @param [in] cond_node +/// @return ge::NodePtr +/// +NodePtr SwitchToStreamSwitchPass::CreateActiveNode(const ComputeGraphPtr &graph, const NodePtr &node) { + const std::string &node_name = CheckDuplicateName(node->GetName() + "_" + STREAMACTIVE); + GELOGI("Create StreamActive op:%s.", node_name.c_str()); + OpDescPtr op_desc = MakeShared(node_name, STREAMACTIVE); + if (op_desc == nullptr) { + GELOGE(FAILED, "Create op_desc failed, StreamActive:%s.", node_name.c_str()); + return nullptr; + } + + NodePtr active_node = graph->AddNode(op_desc); + GE_CHK_BOOL_EXEC(active_node != nullptr, return nullptr, "Create StreamActive node failed."); + + GE_IF_BOOL_EXEC(GraphUtils::AddEdge(node->GetOutControlAnchor(), active_node->GetInControlAnchor()) != SUCCESS, + GELOGE(INTERNAL_ERROR, "add edge failed"); + return nullptr); + + GE_IF_BOOL_EXEC(SetSwitchBranchNodeLabel(active_node, node_name) != SUCCESS, + GELOGE(INTERNAL_ERROR, "set switch branch node label failed"); + return nullptr); + + return active_node; +} + +/// +/// @brief Create cast node +/// @param [in] graph +/// @param [in] peer_cond_anchor +/// @return NodePtr +/// +NodePtr SwitchToStreamSwitchPass::CreateCastOp(const ComputeGraphPtr &graph, const OutDataAnchorPtr &peer_cond_anchor) { + OpDescPtr cond_desc = peer_cond_anchor->GetOwnerNode()->GetOpDesc(); + GE_CHK_BOOL_EXEC(cond_desc != nullptr, return nullptr, "Get cond_desc failed."); + + const std::string &cast_name = CheckDuplicateName(cond_desc->GetName() + "_" + CAST); + GELOGI("Create cast_node: %s, input datatype:DT_BOOL, out datatype:DT_INT32", cast_name.c_str()); + OpDescPtr cast_desc = MakeShared(cast_name, CAST); + if (cast_desc == nullptr) { + GELOGE(FAILED, "Create op_desc failed, Cast:%s.", cast_name.c_str()); + return nullptr; + } + if (!(AttrUtils::SetInt(cast_desc, CAST_ATTR_SRCT, (int64_t)DT_BOOL) && + AttrUtils::SetInt(cast_desc, CAST_ATTR_DSTT, (int64_t)DT_INT32) && + AttrUtils::SetInt(cast_desc, CAST_ATTR_DST_TYPE, (int64_t)DT_INT32) && + AttrUtils::SetBool(cast_desc, CAST_ATTR_TRUNCATE, false))) { + GELOGE(FAILED, "Set CAST_ATTR_SRCT or CAST_ATTR_DSTT or CAST_ATTR_DST_TYPE or CAST_ATTR_TRUNCATE failed, node: %s.", + cast_name.c_str()); + return nullptr; + } + + GeTensorDesc tensor_desc = cond_desc->GetOutputDesc(peer_cond_anchor->GetIdx()); + tensor_desc.SetDataType(DT_BOOL); + GE_CHK_BOOL_EXEC(cast_desc->AddInputDesc(tensor_desc) == SUCCESS, return nullptr, "Cast_node add input desc failed."); + tensor_desc.SetDataType(DT_INT32); + GE_CHK_BOOL_EXEC(cast_desc->AddOutputDesc(tensor_desc) == SUCCESS, return nullptr, + "Cast_node add output desc failed."); + + NodePtr cast_node = graph->AddNode(cast_desc); + GE_CHK_BOOL_EXEC(cast_node != nullptr, return nullptr, "Create cast_node failed."); + GE_CHK_STATUS(GraphUtils::AddEdge(peer_cond_anchor, cast_node->GetInDataAnchor(0)), "Cast add data edge failed."); + + return cast_node; +} + +/// +/// @brief Add const node as switch input1 +/// @param [in] graph +/// @param [in] stream_switch +/// @return Status +/// +Status SwitchToStreamSwitchPass::AddConstNode(const ComputeGraphPtr &graph, const NodePtr &stream_switch) { + OpDescPtr op_desc = stream_switch->GetOpDesc(); + GE_CHECK_NOTNULL(op_desc); + bool value = false; + GE_CHK_BOOL_EXEC(AttrUtils::GetBool(op_desc, ATTR_NAME_SWITCH_TRUE_BRANCH_FLAG, value), return FAILED, + "StreamSwitch get attr TRUE_BRANCH_STREAM failed."); + + const std::string &const_node_name = op_desc->GetName() + "_Constant_" + (value ? "t" : "f"); + GELOGI("Create const op: %s", const_node_name.c_str()); + OpDescPtr const_op_desc = MakeShared(const_node_name, CONSTANT); + if (const_op_desc == nullptr) { + GELOGE(FAILED, "Create op_desc failed, Constant:%s.", const_node_name.c_str()); + return FAILED; + } + + auto resize_value = (int32_t)value; + GeTensorDesc data_desc = op_desc->GetInputDesc(1); + GeTensorPtr const_value = + MakeShared(data_desc, reinterpret_cast(&resize_value), sizeof(int32_t)); + if (const_value == nullptr) { + GELOGE(FAILED, "Create tensor failed."); + return FAILED; + } + GE_CHK_BOOL_EXEC(AttrUtils::SetTensor(const_op_desc, ATTR_NAME_WEIGHTS, const_value), return FAILED); + GE_CHK_BOOL_EXEC(const_op_desc->AddOutputDesc(data_desc) == GRAPH_SUCCESS, return FAILED, + "Create Const op: add output desc failed."); + + NodePtr const_node = graph->AddNode(const_op_desc); + GE_CHK_BOOL_EXEC(const_node != nullptr, return FAILED, "Insert Const node failed."); + GE_CHK_STATUS(GraphUtils::AddEdge(const_node->GetOutDataAnchor(0), stream_switch->GetInDataAnchor(1)), + "StreamSwitch node add ctl edge failed."); + + return SUCCESS; +} + +/// +/// @brief Modify in ctl edge for switch_node +/// @param [in] switch_node +/// @param [in] cast_node +/// @param [in] same_cond_switch +/// @return Status +/// +Status SwitchToStreamSwitchPass::ModifySwitchInCtlEdges(const NodePtr &switch_node, const NodePtr &cast_node, + const std::set &same_cond_switch) { + GELOGI("ModifySwitchInCtlEdges: switch_node=%s, active_node=%s", switch_node->GetName().c_str(), + cast_node->GetName().c_str()); + std::string orig_switch_name = switch_node->GetName(); + OpDescPtr switch_desc = switch_node->GetOpDesc(); + GE_CHECK_NOTNULL(switch_desc); + if (!AttrUtils::GetStr(switch_desc, ATTR_NAME_ORIG_NODE_NAME, orig_switch_name) || orig_switch_name.empty()) { + GELOGE(INTERNAL_ERROR, "Get attr ATTR_NAME_ORIG_NODE_NAME failed, node: %s", switch_desc->GetName().c_str()); + return INTERNAL_ERROR; + } + + for (const NodePtr &in_ctl_node : switch_node->GetInControlNodes()) { + GE_CHK_STATUS(GraphUtils::RemoveEdge(in_ctl_node->GetOutControlAnchor(), switch_node->GetInControlAnchor()), + "Remove ctl edge failed."); + GE_IF_BOOL_EXEC(!in_ctl_node->GetOutControlAnchor()->IsLinkedWith(cast_node->GetInControlAnchor()), { + GE_CHK_STATUS(GraphUtils::AddEdge(in_ctl_node->GetOutControlAnchor(), cast_node->GetInControlAnchor()), + "Add ctl edge failed."); + }); + + GE_IF_BOOL_EXEC(in_ctl_node->GetType() != STREAMSWITCH, continue); + if (same_cond_switch.count(in_ctl_node) > 0) { + GE_CHK_STATUS(GraphUtils::RemoveEdge(in_ctl_node->GetOutControlAnchor(), cast_node->GetInControlAnchor()), + "Remove ctl edge failed."); + continue; + } + + auto find_res1 = switch_node_map_.find(in_ctl_node); + GE_IF_BOOL_EXEC(find_res1 == switch_node_map_.end(), { + GELOGE(INTERNAL_ERROR, "StreamSwitch node %s not found in switch_node_map_.", in_ctl_node->GetName().c_str()); + return INTERNAL_ERROR; + }); + auto find_res2 = find_res1->second.find(orig_switch_name); + auto find_res3 = find_res1->second.find(cast_node->GetName()); + GE_IF_BOOL_EXEC((find_res2 != find_res1->second.end()) && (find_res3 == find_res1->second.end()), { + find_res1->second.erase(find_res2); + find_res1->second.insert(cast_node->GetName()); + continue; + }); + } + + return SUCCESS; +} + +/// +/// @brief Modify out ctl edge for switch_node +/// @param [in] switch_node +/// @param [in] stream_switch +/// @param [in] active_node +/// @return Status +/// +Status SwitchToStreamSwitchPass::ModifySwitchOutCtlEdges(const NodePtr &switch_node, const NodePtr &stream_switch, + const NodePtr &active_node) { + GELOGI("ModifySwitchOutCtlEdges: switch_node=%s, stream_switch=%s, active_node=%s", switch_node->GetName().c_str(), + stream_switch->GetName().c_str(), active_node->GetName().c_str()); + auto find_res = switch_node_map_.find(switch_node); + GE_IF_BOOL_EXEC(find_res == switch_node_map_.end(), { + GELOGE(INTERNAL_ERROR, "StreamSwitch node %s not found in switch_node_map_.", switch_node->GetName().c_str()); + return INTERNAL_ERROR; + }); + GE_IF_BOOL_EXEC(find_res->second.empty(), { + GELOGE(INTERNAL_ERROR, "true_nodes of StreamSwitch node %s is empty.", switch_node->GetName().c_str()); + return INTERNAL_ERROR; + }); + + for (const NodePtr &node : switch_node->GetOutControlNodes()) { + GE_CHK_STATUS(GraphUtils::RemoveEdge(switch_node->GetOutControlAnchor(), node->GetInControlAnchor()), + "Remove ctl edge failed."); + OpDescPtr op_desc = node->GetOpDesc(); + GE_CHECK_NOTNULL(op_desc); + std::string orig_name = op_desc->GetName(); + GE_IF_BOOL_EXEC(op_desc->HasAttr(ATTR_NAME_ORIG_NODE_NAME), { + if (!AttrUtils::GetStr(op_desc, ATTR_NAME_ORIG_NODE_NAME, orig_name) || orig_name.empty()) { + GELOGE(INTERNAL_ERROR, "Get attr ATTR_NAME_ORIG_NODE_NAME failed, node: %s.", op_desc->GetName().c_str()); + return INTERNAL_ERROR; + } + }); + if (find_res->second.find(orig_name) == find_res->second.end()) { + auto active_out_ctrl_anchor = active_node->GetOutControlAnchor(); + GE_CHECK_NOTNULL(active_out_ctrl_anchor); + GE_IF_BOOL_EXEC(!active_out_ctrl_anchor->IsLinkedWith(node->GetInControlAnchor()), { + GE_CHK_STATUS(GraphUtils::AddEdge(active_out_ctrl_anchor, node->GetInControlAnchor()), "Add ctl edge failed."); + }); + } else { + auto switch_out_ctrl_anchor = stream_switch->GetOutControlAnchor(); + GE_CHECK_NOTNULL(switch_out_ctrl_anchor); + GE_IF_BOOL_EXEC(!switch_out_ctrl_anchor->IsLinkedWith(node->GetInControlAnchor()), { + GE_CHK_STATUS(GraphUtils::AddEdge(switch_out_ctrl_anchor, node->GetInControlAnchor()), "Add ctl edge failed."); + }); + } + } + + GE_IF_BOOL_EXEC(switch_node != stream_switch, (void)bypass_nodes_.insert(switch_node)); + return SUCCESS; +} + +/// +/// @brief Check duplicate node_name +/// @param [in] node_name +/// @return std::string +/// +std::string SwitchToStreamSwitchPass::CheckDuplicateName(const std::string &node_name) { + std::string tmp_name = node_name; + auto iter = node_num_map_.find(tmp_name); + if (iter != node_num_map_.end()) { + tmp_name = tmp_name + "_" + std::to_string(iter->second); + (iter->second)++; + } else { + node_num_map_[tmp_name] = 1; + } + return tmp_name; +} + +/// +/// @brief Move Control Edges +/// @param [in] old_node +/// @param [in] new_node +/// @return void +/// +void SwitchToStreamSwitchPass::MoveCtrlEdges(const NodePtr &old_node, const NodePtr &new_node) { + GE_IF_BOOL_EXEC(old_node == new_node, return ); + auto iter = switch_cyclic_map_.find(old_node); + bool check_flag = (iter != switch_cyclic_map_.end()); + for (const NodePtr &in_node : old_node->GetInControlNodes()) { + auto out_ctrl_anchor = in_node->GetOutControlAnchor(); + GE_CHECK_NOTNULL_JUST_RETURN(out_ctrl_anchor); + if (check_flag && (iter->second.count(in_node->GetName()) > 0)) { + for (const auto &out_node : old_node->GetOutAllNodes()) { + GE_IF_BOOL_EXEC(!out_ctrl_anchor->IsLinkedWith(out_node->GetInControlAnchor()), { + GE_CHK_STATUS(GraphUtils::AddEdge(out_ctrl_anchor, out_node->GetInControlAnchor()), + "Add in ctrl edge failed."); + }); + } + } else { + GE_IF_BOOL_EXEC(!out_ctrl_anchor->IsLinkedWith(new_node->GetInControlAnchor()), { + GE_CHK_STATUS(GraphUtils::AddEdge(out_ctrl_anchor, new_node->GetInControlAnchor()), "Add in ctrl edge failed."); + }); + } + GE_CHK_STATUS(GraphUtils::RemoveEdge(out_ctrl_anchor, old_node->GetInControlAnchor()), + "Remove in ctrl edge failed."); + } + + for (const NodePtr &out_node : old_node->GetOutControlNodes()) { + GE_IF_BOOL_EXEC(!new_node->GetOutControlAnchor()->IsLinkedWith(out_node->GetInControlAnchor()), { + GE_CHK_STATUS(GraphUtils::AddEdge(new_node->GetOutControlAnchor(), out_node->GetInControlAnchor()), + "Add out ctrl edge failed."); + }); + GE_CHK_STATUS(GraphUtils::RemoveEdge(old_node->GetOutControlAnchor(), out_node->GetInControlAnchor()), + "Remove out ctrl edge failed."); + } +} +} // namespace ge diff --git a/src/ge/graph/passes/switch_op_pass.h b/src/ge/graph/passes/switch_to_stream_switch_pass.h similarity index 61% rename from src/ge/graph/passes/switch_op_pass.h rename to src/ge/graph/passes/switch_to_stream_switch_pass.h index 202b919c..15fe9dce 100644 --- a/src/ge/graph/passes/switch_op_pass.h +++ b/src/ge/graph/passes/switch_to_stream_switch_pass.h @@ -14,15 +14,9 @@ * limitations under the License. */ -#ifndef GE_GRAPH_PASSES_SWITCH_OP_PASS_H_ -#define GE_GRAPH_PASSES_SWITCH_OP_PASS_H_ - -#include -#include -#include -#include -#include -#include +#ifndef GE_GRAPH_PASSES_SWITCH_TO_STREAM_SWITCH_PASS_H_ +#define GE_GRAPH_PASSES_SWITCH_TO_STREAM_SWITCH_PASS_H_ + #include "inc/graph_pass.h" namespace ge { @@ -91,78 +85,158 @@ namespace ge { +-----------+ +-----------+ +-----------+ +-----| Less |----+ +-----------+ */ -class SwitchOpPass : public GraphPass { +class SwitchToStreamSwitchPass : public GraphPass { public: Status Run(ComputeGraphPtr graph); + + /// + /// @brief Clear Status, used for subgraph pass + /// @return + /// Status ClearStatus() override; private: - Status ReplaceSwitchNode(ComputeGraphPtr &graph, NodePtr &switch_node); - - Status ReplaceMergeNode(ComputeGraphPtr &graph, NodePtr &merge_node); - - NodePtr CreateStreamSwitchNode(ComputeGraphPtr &graph, const NodePtr &switch_node, const std::string &suffix, - OutDataAnchorPtr &peer_cond_anchor); - - NodePtr CreateMemcpyAsyncNode(ComputeGraphPtr &graph, const OutDataAnchorPtr &out_data_anchor, bool multi_batch_flag); - - Status CombineSwitchNode(ComputeGraphPtr &graph); - - NodePtr CreateActiveNode(ComputeGraphPtr &graph, NodePtr &node); - - Status AddMemcpyAsyncNodes(ComputeGraphPtr &graph, NodePtr &stream_merge_node, bool multi_batch_flag); - - Status BypassSwitchNode(NodePtr &switch_node, OutDataAnchorPtr &peer_data_anchor, OutDataAnchorPtr &peer_cond_anchor); + /// + /// @brief Check cyclic dependence + /// @param [in] graph + /// @return Status + /// + Status CheckCycleDependence(const ComputeGraphPtr &graph); + + /// + /// @brief Mark cyclic dependence + /// @param [in] graph + /// @param [in] cond_switch_map + /// @return void + /// + void MarkCycleDependence(const std::unordered_map> &cond_switch_map); + /// + /// @brief Replace Switch Op + /// @param [in] graph + /// @param [in] switch_node + /// @return Status + /// + Status ReplaceSwitchNode(const ComputeGraphPtr &graph, const NodePtr &switch_node); + + /// + /// @brief Bypass Switch Node + /// @param [in] switch_node + /// @param [out] peer_data_anchor + /// @param [out] peer_cond_anchor + /// @return Status + /// + Status BypassSwitchNode(const NodePtr &switch_node, OutDataAnchorPtr &peer_data_anchor, + OutDataAnchorPtr &peer_cond_anchor); + + /// + /// @brief Find Switch cond input + /// @param [in] pass_switch_flag + /// @param [out] peer_cond_anchor + /// @return Status + /// Status FindSwitchCondInput(bool pass_switch_flag, OutDataAnchorPtr &peer_cond_anchor); - Status MarkBranchs(OutDataAnchorPtr &peer_cond_anchor, NodePtr &stream_switch_node, bool true_branch_flag); - - NodePtr CreateCastOp(ComputeGraphPtr &graph, OutDataAnchorPtr &peer_cond_anchor); - - Status AddConstNode(ComputeGraphPtr &graph, NodePtr &stream_switch_node); - - Status UpdateCondBranch(NodePtr &node); - - Status UpdateAttachFlag(const NodePtr &node, std::string &stream_label, bool &merge_flag, bool &exit_flag, - bool &net_output_flag); - - Status UpdateLoopBranch(const std::stack &enter_nodes, const std::string &stream_label); - - Status UpdateEnterNode(); + /// + /// @brief Create StreamSwitch Node + /// @param [in] graph + /// @param [in] switch_node + /// @param [in] suffix + /// @param [in] peer_cond_anchor + /// @return ge::NodePtr + /// + NodePtr CreateStreamSwitchNode(const ComputeGraphPtr &graph, const NodePtr &switch_node, const std::string &suffix, + const OutDataAnchorPtr &peer_cond_anchor); + + /// + /// @brief Mark Switch Branch + /// @param [in] peer_cond_anchor + /// @param [in] stream_switch + /// @param [in] true_branch_flag + /// @return Status + /// + Status MarkBranches(const OutDataAnchorPtr &peer_cond_anchor, const NodePtr &stream_switch_node, + bool true_branch_flag); + + /// + /// @brief Get group_id for switch_node + /// @param [in] node + /// @return group_id + /// + int64_t GetGroupId(const NodePtr &node); + /// + /// @brief Combine switch nodes link to same cond + /// @param [in] graph + /// @return Status + /// + Status CombineSwitchNode(const ComputeGraphPtr &graph); + + /// + /// @brief Create cast node + /// @param [in] graph + /// @param [in] peer_cond_anchor + /// @return NodePtr + /// + NodePtr CreateCastOp(const ComputeGraphPtr &graph, const OutDataAnchorPtr &peer_cond_anchor); + + /// + /// @brief Create Active Op + /// @param [in] graph + /// @param [in] cond_node + /// @return ge::NodePtr + /// + NodePtr CreateActiveNode(const ComputeGraphPtr &graph, const NodePtr &node); + + /// + /// @brief Add const node as switch input1 + /// @param [in] graph + /// @param [in] stream_switch + /// @return Status + /// + Status AddConstNode(const ComputeGraphPtr &graph, const NodePtr &stream_switch_node); + + /// + /// @brief Modify in ctl edge for switch_node + /// @param [in] switch_node + /// @param [in] cast_node + /// @param [in] same_cond_switch + /// @return Status + /// + Status ModifySwitchInCtlEdges(const NodePtr &switch_node, const NodePtr &cast_node, + const std::set &same_cond_switch); + + /// + /// @brief Modify out ctl edge for switch_node + /// @param [in] switch_node + /// @param [in] stream_switch + /// @param [in] active_node + /// @return Status + /// + Status ModifySwitchOutCtlEdges(const NodePtr &switch_node, const NodePtr &stream_switch, const NodePtr &active_node); + + /// + /// @brief Check duplicate node_name + /// @param [in] node_name + /// @return std::string + /// std::string CheckDuplicateName(const std::string &node_name); - Status CheckCycleDependence(ComputeGraphPtr &graph); - - void MarkCycleDependence(const std::unordered_map> &cond_switch_map); - - Status ModifySwitchInCtlEdges(NodePtr &switch_node, NodePtr &cast_node, const std::set &same_cond_switch); - - Status ModifySwitchOutCtlEdges(NodePtr &switch_node, NodePtr &stream_switch, NodePtr &active_node); - - void CopyControlEdges(NodePtr &old_node, NodePtr &new_node, bool input_check_flag = false); - - void RemoveControlEdges(NodePtr &node); - - void ReplaceControlEdges(NodePtr &old_node, NodePtr &new_node); - - int64_t GetGroupId(const NodePtr &node); - - void MarkHeadNodes(const NodePtr &node, const NodePtr &stream_switch); + /// + /// @brief Move Control Edges + /// @param [in] old_node + /// @param [in] new_node + /// @return void + /// + void MoveCtrlEdges(const NodePtr &old_node, const NodePtr &new_node); std::vector switch_nodes_; - std::vector merge_nodes_; - std::vector enter_nodes_; std::unordered_map> switch_cyclic_map_; - std::set bypass_nodes_; - std::unordered_map branch_head_nodes_; std::vector stream_switch_nodes_; - std::vector need_label_nodes_; std::unordered_map>>> cond_node_map_; std::unordered_map> switch_node_map_; std::unordered_map node_num_map_; }; } // namespace ge -#endif // GE_GRAPH_PASSES_SWITCH_OP_PASS_H_ +#endif // GE_GRAPH_PASSES_SWITCH_TO_STREAM_SWITCH_PASS_H_ diff --git a/src/ge/graph/passes/transop_breadth_fusion_pass.cc b/src/ge/graph/passes/transop_breadth_fusion_pass.cc index 53f9e825..d8df4a22 100644 --- a/src/ge/graph/passes/transop_breadth_fusion_pass.cc +++ b/src/ge/graph/passes/transop_breadth_fusion_pass.cc @@ -19,14 +19,12 @@ #include #include -#include "framework/common/debug/ge_log.h" #include "common/types.h" #include "graph/common/transop_util.h" #include "graph/utils/node_utils.h" namespace ge { Status TransOpBreadthFusionPass::Run(ge::ComputeGraphPtr graph) { - GE_TIMESTAMP_START(TransOpBreadthFusionPass); if (graph == nullptr) { return SUCCESS; } @@ -47,7 +45,6 @@ Status TransOpBreadthFusionPass::Run(ge::ComputeGraphPtr graph) { } } } - GE_TIMESTAMP_END(TransOpBreadthFusionPass, "GraphManager::TransOpBreadthFusionPass"); return SUCCESS; } diff --git a/src/ge/graph/passes/transop_depth_fusion_pass.cc b/src/ge/graph/passes/transop_depth_fusion_pass.cc index c0c854b6..afeca3c4 100644 --- a/src/ge/graph/passes/transop_depth_fusion_pass.cc +++ b/src/ge/graph/passes/transop_depth_fusion_pass.cc @@ -17,7 +17,6 @@ #include "graph/passes/transop_depth_fusion_pass.h" #include -#include "framework/common/debug/ge_log.h" #include "common/ge_inner_error_codes.h" #include "common/types.h" #include "graph/compute_graph.h" @@ -29,7 +28,6 @@ namespace ge { graphStatus TransOpDepthFusionPass::Run(ComputeGraphPtr graph) { - GE_TIMESTAMP_START(TransOpDepthFusionPass); GELOGI("[TransOpDepthFusionPass]: optimize in depth begin..."); if (graph == nullptr) { return GRAPH_SUCCESS; @@ -53,7 +51,6 @@ graphStatus TransOpDepthFusionPass::Run(ComputeGraphPtr graph) { } } GELOGI("[TransOpDepthFusionPass]: Optimize in depth success..."); - GE_TIMESTAMP_END(TransOpDepthFusionPass, "GraphManager::TransOpDepthFusionPass"); return GRAPH_SUCCESS; } diff --git a/src/ge/graph/passes/transop_symmetry_elimination_pass.cc b/src/ge/graph/passes/transop_symmetry_elimination_pass.cc index 38b6684b..2ff7cd82 100644 --- a/src/ge/graph/passes/transop_symmetry_elimination_pass.cc +++ b/src/ge/graph/passes/transop_symmetry_elimination_pass.cc @@ -24,7 +24,6 @@ namespace { const int kTransOpOutIndex = 0; static std::map precision_loss_transfer_map = {{ge::DT_FLOAT, ge::DT_BOOL}}; - } // namespace namespace ge { Status TransOpSymmetryEliminationPass::Run(NodePtr &node) { diff --git a/src/ge/graph/passes/transop_without_reshape_fusion_pass.cc b/src/ge/graph/passes/transop_without_reshape_fusion_pass.cc index ba4cd031..1d97d9a1 100644 --- a/src/ge/graph/passes/transop_without_reshape_fusion_pass.cc +++ b/src/ge/graph/passes/transop_without_reshape_fusion_pass.cc @@ -22,7 +22,6 @@ #include "common/ge/ge_util.h" #include "common/ge_inner_error_codes.h" #include "common/types.h" -#include "framework/common/debug/ge_log.h" #include "graph/compute_graph.h" #include "graph/debug/ge_attr_define.h" #include "graph/ge_tensor.h" @@ -733,7 +732,6 @@ void TransOpWithoutReshapeFusionPass::RemoveNousedNodes(const ComputeGraphPtr &g } graphStatus TransOpWithoutReshapeFusionPass::Run(ComputeGraphPtr graph) { - GE_TIMESTAMP_START(TransOpWithoutReshapeFusionPass); GELOGI("[TransOpWithoutReshapeFusionPass]: optimize begin."); if (graph == nullptr) { return GRAPH_SUCCESS; @@ -786,7 +784,6 @@ graphStatus TransOpWithoutReshapeFusionPass::Run(ComputeGraphPtr graph) { } } GELOGI("[TransOpWithoutReshapeFusionPass]: Optimize end."); - GE_TIMESTAMP_END(TransOpWithoutReshapeFusionPass, "GraphManager::TransOpWithoutReshapeFusionPass"); return GRAPH_SUCCESS; } diff --git a/src/ge/graph/passes/variable_op_pass.cc b/src/ge/graph/passes/variable_op_pass.cc index 175a049a..8c34cd36 100644 --- a/src/ge/graph/passes/variable_op_pass.cc +++ b/src/ge/graph/passes/variable_op_pass.cc @@ -20,7 +20,6 @@ #include "common/formats/formats.h" #include "common/formats/utils/formats_trans_utils.h" -#include "framework/common/debug/ge_log.h" #include "graph/ge_context.h" #include "graph/graph.h" #include "graph/manager/graph_var_manager.h" @@ -115,7 +114,6 @@ bool IsTransSupport(const TransNodeInfo &trans_info) { } // namespace Status VariableOpPass::Run(ge::ComputeGraphPtr graph) { - GE_TIMESTAMP_START(VariableOpPass); if (graph == nullptr) { GELOGE(INTERNAL_ERROR, "Failed to run variable op pass, null graph"); return INTERNAL_ERROR; @@ -190,9 +188,15 @@ Status VariableOpPass::Run(ge::ComputeGraphPtr graph) { if (UpdateIOFormatInfo(end_iter->output, node_set) != SUCCESS) { return GE_GRAPH_VARIABLE_OP_PASS_FAILED; } + + // renew var desc if the trans_road is all reshape or reformat + ret = RenewVarDesc(graph->GetSessionID(), node, fusion_road); + if (ret != SUCCESS) { + GELOGE(FAILED, "var manager renew var[%s] descriptor failed!", node->GetName().c_str()); + return FAILED; + } } - GE_TIMESTAMP_END(VariableOpPass, "GraphManager::VariableOpPass"); return SUCCESS; } @@ -604,4 +608,28 @@ Status VariableOpPass::RenewVarDesc(ge::ComputeGraphPtr &graph) { } return SUCCESS; } + +Status VariableOpPass::RenewVarDesc(uint64_t session_id, const NodePtr &node, const VarTransRoad &fusion_road) { + // renew var desc if the trans_road is all reshape or reformat + for (auto &road : fusion_road) { + if (road.node_type != RESHAPE && road.node_type != REFORMAT) { + return SUCCESS; + } + } + + if (!ge::VarManager::Instance(session_id)->IsVarExist(node->GetName())) { + GELOGD("var manager does not exist var node[%s]", node->GetName().c_str()); + return SUCCESS; + } + GELOGD("var manager exist var node[%s]", node->GetName().c_str()); + GE_CHECK_NOTNULL(node->GetOpDesc()); + Status ret = ge::VarManager::Instance(session_id)->RenewCurVarDesc(node->GetName(), node->GetOpDesc()); + if (ret != SUCCESS) { + GELOGE(FAILED, "var manager renew var[%s] descriptor failed!", node->GetName().c_str()); + return FAILED; + } + + return SUCCESS; +} + } // namespace ge diff --git a/src/ge/graph/passes/variable_op_pass.h b/src/ge/graph/passes/variable_op_pass.h index 4e194a0c..e17980e9 100644 --- a/src/ge/graph/passes/variable_op_pass.h +++ b/src/ge/graph/passes/variable_op_pass.h @@ -66,6 +66,7 @@ class VariableOpPass : public GraphPass { Status UpdateIOFormatInfo(const GeTensorDesc &final_output, std::set &nodes); Status RenewVarDesc(ge::ComputeGraphPtr &graph); + Status RenewVarDesc(uint64_t session_id, const NodePtr &node, const VarTransRoad &fusion_road); std::map> var_and_var_ref_map_; diff --git a/src/ge/graph/passes/variable_prepare_op_pass.cc b/src/ge/graph/passes/variable_prepare_op_pass.cc index 4db78a46..d93e1003 100644 --- a/src/ge/graph/passes/variable_prepare_op_pass.cc +++ b/src/ge/graph/passes/variable_prepare_op_pass.cc @@ -30,6 +30,7 @@ namespace ge { std::map> VariablePrepareOpPass::ref_node_without_prototype_map_{ {REFSWITCH, {{0, 0}, {0, 1}}}}; + Status VariablePrepareOpPass::Run(ComputeGraphPtr graph) { GE_CHECK_NOTNULL(graph); for (const auto &node : graph->GetDirectNode()) { @@ -62,7 +63,6 @@ Status VariablePrepareOpPass::Run(ComputeGraphPtr graph) { GELOGI("{ %d : %d }", index_iter->first, index_iter->second); } } - return SUCCESS; } @@ -73,10 +73,13 @@ Status VariablePrepareOpPass::DealVariableNode(NodePtr &var_node) { GE_CHECK_NOTNULL(dst_node); InDataAnchorPtr dst_in_data_anchor = dst_node_and_inanchor.second; GE_CHECK_NOTNULL(dst_in_data_anchor); - int out_index = GetWritableNodeOutIndex(dst_node, dst_in_data_anchor->GetIdx()); + auto input_index = dst_in_data_anchor->GetIdx(); + int out_index = GetWritableNodeOutIndex(dst_node, input_index); if (out_index >= 0) { - Status ret = DealWritableNode(dst_node, var_node, out_index); + Status ret = DealWritableNode(dst_node, input_index, var_node); if (ret != SUCCESS) { + GELOGE(FAILED, "Deal writable node[%s] failed, input index: %d, var: %s.", dst_node->GetName().c_str(), + input_index, var_node->GetName().c_str()); return FAILED; } } @@ -84,84 +87,97 @@ Status VariablePrepareOpPass::DealVariableNode(NodePtr &var_node) { return SUCCESS; } -Status VariablePrepareOpPass::DealWritableNode(ge::NodePtr &writable_node, ge::NodePtr &var_node, int out_index) { - GE_CHECK_NOTNULL(writable_node); - GE_CHECK_NOTNULL(var_node); - NodePtr final_writable_node = writable_node; - bool is_have_peer_node = false; - for (auto &dst_node_and_inanchor : writable_node->GetOutDataNodesAndAnchors()) { - NodePtr dst_node = dst_node_and_inanchor.first; - GE_CHECK_NOTNULL(dst_node); - InDataAnchorPtr dst_in_data_anchor = dst_node_and_inanchor.second; - GE_CHECK_NOTNULL(dst_in_data_anchor); - is_have_peer_node = true; - int current_out_index = GetWritableNodeOutIndex(dst_node, dst_in_data_anchor->GetIdx()); - if (current_out_index >= 0) { - final_writable_node = GetFinalWritableNode(dst_node, current_out_index); - out_index = current_out_index; - } - - GE_CHECK_NOTNULL(final_writable_node); - Status ret = AddVariableRef(final_writable_node, var_node, out_index); - if (ret != SUCCESS) { - GELOGE(FAILED, "add variable ref failed"); - return FAILED; +Status VariablePrepareOpPass::DealWritableNode(const ge::NodePtr &writable_node, int input_index, + const ge::NodePtr &var_node) { + // Find the last ref node: + // If the ref input has corresponding output, add variable ref after it. + // If the ref input has no corresponding output, insert RefIdentity and variable ref before it. + // If ref node with control output was found while finding the last ref node, add variable ref after it. + std::stack> nodes_to_check; + nodes_to_check.push({writable_node, input_index}); + while (!nodes_to_check.empty()) { + auto node_index = nodes_to_check.top(); + nodes_to_check.pop(); + auto cur_node = node_index.first; + int cur_input_index = node_index.second; + // Collect ref node after cur node + const auto nodes_size = nodes_to_check.size(); + // Add peer ref output node of current node to stack + CHECK_FALSE_EXEC(GetPeerNodeOfRefInput(cur_node, cur_input_index, nodes_to_check) == SUCCESS, + GELOGE(FAILED, "GetPeerNodeOfRefInput for node[%s] failed.", cur_node->GetName().c_str()); + return FAILED); + auto output_index = GetWritableNodeOutIndex(cur_node, cur_input_index); + CHECK_FALSE_EXEC(output_index >= 0, + GELOGE(FAILED, "Get writable node[%s] ref input[%d]'s corresponding out index failed: %d.", + cur_node->GetName().c_str(), cur_input_index, output_index); + return FAILED); + if (nodes_size == nodes_to_check.size()) { + const auto &op_desc = cur_node->GetOpDesc(); + GE_CHECK_NOTNULL(op_desc); + // No need to add variable_ref for frameworkop + if (op_desc->GetType() == FRAMEWORKOP) { + GELOGD("No need to add variable_ref for frameworkop"); + continue; + } + if (static_cast(output_index) < op_desc->GetOutputsSize()) { + // Add variable ref node after ref output for final ref node + CHECK_FALSE_EXEC(AddVariableRef(cur_node, var_node, output_index) == SUCCESS, + GELOGE(FAILED, "Add variable ref failed"); + return FAILED); + } else { + // Insert variable ref node before ref input without corresponding ref output + CHECK_FALSE_EXEC(InsertVariableRef(cur_node, cur_input_index, var_node) == SUCCESS, + GELOGE(FAILED, "Insert variable ref and ref identity failed"); + return FAILED); + } + continue; } - } - if (final_writable_node->GetName() == writable_node->GetName() && !is_have_peer_node) { - Status ret = AddVariableRef(final_writable_node, var_node, out_index); - if (ret != SUCCESS) { - return FAILED; + if (HasControlOut(cur_node)) { + // Add variable ref node after ref output for ref node has control output. + CHECK_FALSE_EXEC(AddVariableRef(cur_node, var_node, output_index) == SUCCESS, + GELOGE(FAILED, "Add variable ref failed"); + return FAILED); } } return SUCCESS; } -NodePtr VariablePrepareOpPass::GetFinalWritableNode(ge::NodePtr &writable_node, int &out_index) { - NodePtr current_node = writable_node; - std::unordered_set seen_node; - while (true) { - if (seen_node.count(current_node.get())) { - GELOGE(FAILED, "There is a ring structure in the graph"); - return nullptr; - } - seen_node.insert(current_node.get()); - OutDataAnchorPtr out_anchor = current_node->GetOutDataAnchor(out_index); - if (out_anchor == nullptr) { - GELOGE(FAILED, "Failed to get data anchor by index %d", out_index); - return nullptr; - } - bool found_writeable_node = false; - auto peer_in_anchors = out_anchor->GetPeerInDataAnchors(); - for (auto &peer_in_anchor : peer_in_anchors) { - if (peer_in_anchor == nullptr) { - GELOGE(FAILED, "peer in data anchor is nullptr, node %s:%s", current_node->GetType().c_str(), - current_node->GetName().c_str()); - continue; - } - - NodePtr peer_node = peer_in_anchor->GetOwnerNode(); - int current_out_index = GetWritableNodeOutIndex(peer_node, peer_in_anchor->GetIdx()); - if (current_out_index >= 0) { - current_node = peer_node; - out_index = current_out_index; - found_writeable_node = true; - break; - } +Status VariablePrepareOpPass::GetPeerNodeOfRefInput(const ge::NodePtr &node, int input_index, + std::stack> &nodes) { + auto output_index = GetWritableNodeOutIndex(node, input_index); + if (output_index == -1) { + GELOGE(PARAM_INVALID, "Node[%s] is not a ref node.", node->GetName().c_str()); + return PARAM_INVALID; + } + const auto &op_desc = node->GetOpDesc(); + GE_CHECK_NOTNULL(op_desc); + if (static_cast(output_index) == op_desc->GetOutputsSize()) { + return SUCCESS; + } + if (output_index >= static_cast(node->GetAllOutDataAnchorsSize())) { + GELOGW("Can not get %d th output anchor of %s", output_index, node->GetName().c_str()); + return SUCCESS; + } + const auto &out_anchor = node->GetOutDataAnchor(output_index); + GE_CHECK_NOTNULL(out_anchor); + for (const auto &peer_in_anchor : out_anchor->GetPeerInDataAnchors()) { + auto peer_node = peer_in_anchor->GetOwnerNode(); + if (peer_node == nullptr) { + continue; } - if (!found_writeable_node) { - GELOGD("final writable node is %s", current_node->GetName().c_str()); - return current_node; + const int peer_in_index = peer_in_anchor->GetIdx(); + if (GetWritableNodeOutIndex(peer_node, peer_in_index) != -1) { + nodes.push({peer_node, peer_in_index}); } } + return SUCCESS; } -Status VariablePrepareOpPass::AddVariableRef(ge::NodePtr &final_writable_node, ge::NodePtr &var_node, int index) { +Status VariablePrepareOpPass::AddVariableRef(ge::NodePtr &final_writable_node, const ge::NodePtr &var_node, int index) { GE_CHECK_NOTNULL(final_writable_node); GE_CHECK_NOTNULL(var_node); - - if (final_writable_node->GetType() == FRAMEWORKOP) { - GELOGD("No need to add variable_ref for frameworkop"); + if (index >= static_cast(final_writable_node->GetAllOutDataAnchorsSize())) { + GELOGW("Can not get %d th output anchor of %s", index, final_writable_node->GetName().c_str()); return SUCCESS; } // Check for duplicate creation @@ -181,7 +197,8 @@ Status VariablePrepareOpPass::AddVariableRef(ge::NodePtr &final_writable_node, g // creat variable_ref std::stringstream variable_ref_name; variable_ref_name << "_TO_" << final_writable_node->GetName() << "_REF_" << index; - NodePtr variable_ref_node = CreatVariableRef(var_node->GetName() + variable_ref_name.str(), var_node); + NodePtr variable_ref_node = CreateVariableRef(var_node->GetName() + variable_ref_name.str(), var_node); + GE_CHECK_NOTNULL(variable_ref_node); Status ret_check = CheckStreamLabel(variable_ref_node, final_writable_node); if (ret_check != SUCCESS) { GELOGE(FAILED, "check stream lable failed"); @@ -189,23 +206,12 @@ Status VariablePrepareOpPass::AddVariableRef(ge::NodePtr &final_writable_node, g } GELOGI("Add variable_ref between [%s] and [%s]", var_node->GetName().c_str(), variable_ref_node->GetName().c_str()); - GE_CHECK_NOTNULL(variable_ref_node); - // add control anchor between variable_ref and final peer node + // add control anchor between variable_ref and final peer node // variable_ref_node need to execute before other nodes - auto final_writable_outAnchors = final_writable_node->GetAllOutAnchors(); - for (auto &final_writable_outAnchor : final_writable_outAnchors) { - GE_CHECK_NOTNULL(final_writable_outAnchor); - for (auto &final_writable_peerAnchor : final_writable_outAnchor->GetPeerAnchors()) { - GE_CHECK_NOTNULL(final_writable_peerAnchor); - NodePtr peer_node = final_writable_peerAnchor->GetOwnerNode(); - graphStatus ret = - ge::GraphUtils::AddEdge(variable_ref_node->GetOutControlAnchor(), peer_node->GetInControlAnchor()); - if (ret != GRAPH_SUCCESS) { - GELOGE(FAILED, "add control anchor between variable_ref and final_writable peer node failed"); - return FAILED; - } - } - } + CHECK_FALSE_EXEC(AddControlEdge(final_writable_node, variable_ref_node) == SUCCESS, + GELOGE(FAILED, "Add control edges between variable ref node and output nodes of ref node failed"); + return FAILED); + graphStatus ret = ge::GraphUtils::AddEdge(out_anchor, variable_ref_node->GetInDataAnchor(0)); if (ret != GRAPH_SUCCESS) { GELOGE(FAILED, "add data anchor between variable_ref and final_writable peer node failed"); @@ -214,7 +220,110 @@ Status VariablePrepareOpPass::AddVariableRef(ge::NodePtr &final_writable_node, g return SUCCESS; } -ge::NodePtr VariablePrepareOpPass::CreatVariableRef(const std::string &variable_ref_name, ge::NodePtr &var_node) { +Status VariablePrepareOpPass::InsertVariableRef(ge::NodePtr &node, int in_index, const ge::NodePtr &var_node) { + GE_CHECK_NOTNULL(node); + GE_CHECK_NOTNULL(var_node); + // Check connection between two nodes + const auto in_anchor = node->GetInDataAnchor(in_index); + GE_CHECK_NOTNULL(in_anchor); + auto peer_out_anchor = in_anchor->GetPeerOutAnchor(); + GE_CHECK_NOTNULL(peer_out_anchor); + auto peer_in_node = peer_out_anchor->GetOwnerNode(); + GE_CHECK_NOTNULL(peer_in_node); + + // Create ref_identity + std::stringstream ref_identity_name; + ref_identity_name << "RefIdentity_" << peer_in_node->GetName() << "_" << peer_out_anchor->GetIdx() << "_TO_" + << node->GetName() << "_" << in_index; + NodePtr ref_identity_node = CreateRefIdentity(ref_identity_name.str(), node, static_cast(in_index)); + GE_CHECK_NOTNULL(ref_identity_node); + + // Create variable_ref + std::stringstream variable_ref_name; + variable_ref_name << "_TO_" << node->GetName() << "_REF_" << in_index; + NodePtr variable_ref_node = CreateVariableRef(var_node->GetName() + variable_ref_name.str(), var_node); + GE_CHECK_NOTNULL(variable_ref_node); + Status ret_check = CheckStreamLabel(variable_ref_node, node); + if (ret_check != SUCCESS) { + GELOGE(FAILED, "check stream lable failed"); + return FAILED; + } + + GELOGI("Insert variable_ref of [%s] between [%s] and [%s]", var_node->GetName().c_str(), + peer_in_node->GetName().c_str(), node->GetName().c_str()); + // add control anchor between variable_ref and node + // variable_ref_node need to execute before other nodes + CHECK_FALSE_EXEC(AddControlEdge(node, variable_ref_node) == SUCCESS, + GELOGE(FAILED, "Add control edges between variable ref node and output nodes of ref node failed"); + return FAILED); + + // Insert variable ref node between two nodes and remove the original edge. + CHECK_FALSE_EXEC(ge::GraphUtils::RemoveEdge(peer_out_anchor, in_anchor) == SUCCESS, + GELOGE(FAILED, "Remove edge between ref node and its peer node failed"); + return FAILED); + CHECK_FALSE_EXEC(ge::GraphUtils::AddEdge(peer_out_anchor, ref_identity_node->GetInDataAnchor(0)) == SUCCESS, + GELOGE(FAILED, "Add data edge between pre node and ref_identity failed"); + return FAILED); + CHECK_FALSE_EXEC(ge::GraphUtils::AddEdge(ref_identity_node->GetOutDataAnchor(0), in_anchor) == SUCCESS, + GELOGE(FAILED, "Add data edge between ref_identity and ref node failed"); + return FAILED); + + // Add edge from ref identity node to variable ref node. + CHECK_FALSE_EXEC( + ge::GraphUtils::AddEdge(ref_identity_node->GetOutDataAnchor(0), variable_ref_node->GetInDataAnchor(0)) == SUCCESS, + GELOGE(FAILED, "Add data edge between ref_identity and variable_ref failed"); + return FAILED); + CHECK_FALSE_EXEC( + ge::GraphUtils::AddEdge(node->GetOutControlAnchor(), variable_ref_node->GetInControlAnchor()) == SUCCESS, + GELOGE(FAILED, "Add control edge between ref_identity and variable_ref failed"); + return FAILED); + return SUCCESS; +} + +Status VariablePrepareOpPass::AddControlEdge(const ge::NodePtr &node, const ge::NodePtr &variable_ref_node) { + auto out_anchors = node->GetAllOutAnchors(); + for (auto &out_anchor : out_anchors) { + GE_CHECK_NOTNULL(out_anchor); + for (auto &peer_in_anchor : out_anchor->GetPeerAnchors()) { + GE_CHECK_NOTNULL(peer_in_anchor); + NodePtr peer_node = peer_in_anchor->GetOwnerNode(); + GE_CHECK_NOTNULL(peer_node); + CHECK_FALSE_EXEC( + ge::GraphUtils::AddEdge(variable_ref_node->GetOutControlAnchor(), peer_node->GetInControlAnchor()) == SUCCESS, + GELOGE(FAILED, "Add control edge between variable_ref and ref node's peer node failed"); + return FAILED); + } + } + return SUCCESS; +} + +ge::NodePtr VariablePrepareOpPass::CreateRefIdentity(const std::string &ref_identity_name, const ge::NodePtr &node, + uint32_t input_index) { + OpDescPtr op_desc = node->GetOpDesc(); + if (op_desc == nullptr) { + GELOGE(FAILED, "opdesc is nullptr"); + return nullptr; + } + + OpDescPtr ref_identity_op_desc = MakeShared(ref_identity_name.c_str(), REFIDENTITY); + if (ref_identity_op_desc == nullptr) { + GELOGE(FAILED, "ref_identity op desc is nullptr"); + return nullptr; + } + + GE_IF_BOOL_EXEC(ref_identity_op_desc->AddOutputDesc(op_desc->GetInputDesc(input_index)) != SUCCESS, + GELOGW("add output desc edge failed"); + return nullptr); + GE_IF_BOOL_EXEC(ref_identity_op_desc->AddInputDesc(op_desc->GetInputDesc(input_index)) != SUCCESS, + GELOGW("add input desc edge failed"); + return nullptr); + NodePtr ref_identity_node = node->GetOwnerComputeGraph()->AddNode(ref_identity_op_desc); + GE_IF_BOOL_EXEC(ref_identity_node == nullptr, GELOGW("ref_identity_node is null"); return nullptr); + return ref_identity_node; +} + +ge::NodePtr VariablePrepareOpPass::CreateVariableRef(const std::string &variable_ref_name, + const ge::NodePtr &var_node) { OpDescPtr var_op_desc = var_node->GetOpDesc(); if (var_op_desc == nullptr) { GELOGE(FAILED, "get var opdesc is nullptr"); @@ -250,7 +359,6 @@ int VariablePrepareOpPass::GetWritableNodeOutIndex(const NodePtr &node, int inpu } GELOGD("get writable node and input index %s:%d", node->GetName().c_str(), input_index); auto node_type = node->GetType(); - if (node_type == FRAMEWORKOP) { std::string original_type; GE_IF_BOOL_EXEC(GetOriginalType(node, original_type) != SUCCESS, GELOGW("Get node original type fail")); @@ -266,25 +374,17 @@ void VariablePrepareOpPass::GenerateRefTypeAndInputOutputMap(const NodePtr &node GELOGW("op_desc in null, please check node:[%s]", node->GetName().c_str()); return; } - for (const auto &out_ancohor : node->GetAllOutDataAnchors()) { - int output_index = out_ancohor->GetIdx(); - string output_name = op_desc->GetOutputNameByIndex(output_index); - GELOGD("output name:[%s]", output_name.c_str()); - - int input_index = op_desc->GetInputIndexByName(output_name); - if (input_index == -1) { + for (const auto &name_index : op_desc->GetAllInputName()) { + // Record the index of output with the same name as input, thinking of them as a pair of ref input and output. + const int out_index = op_desc->GetOutputIndexByName(name_index.first); + if (out_index != -1) { + ref_input_output_map_[node->GetType()][name_index.second] = out_index; continue; } - auto ref_type_and_input_output_iter = ref_input_output_map_.find(node->GetType()); - if (ref_type_and_input_output_iter != ref_input_output_map_.end()) { - auto &input_output_index_map = ref_type_and_input_output_iter->second; - if (input_output_index_map.find(input_index) == input_output_index_map.end()) { - input_output_index_map.emplace(input_index, output_index); - GELOGD("Add RefInputOutputMap %s:{ %d, %d }", node->GetType().c_str(), input_index, output_index); - } - } else { - ref_input_output_map_.insert({node->GetType(), {{input_index, output_index}}}); - GELOGD("Create RefInputOutputMap { %s:{ %d, %d } }", node->GetType().c_str(), input_index, output_index); + // Record the ref input without corresponding output. + const auto &input_desc = op_desc->GetInputDesc(name_index.second); + if (!input_desc.GetRefPortIndex().empty()) { + ref_input_output_map_[node->GetType()][name_index.second] = static_cast(op_desc->GetOutputsSize()); } } } @@ -317,4 +417,15 @@ Status VariablePrepareOpPass::CheckStreamLabel(const ge::NodePtr &var_ref_node, } return SUCCESS; } + +bool VariablePrepareOpPass::HasControlOut(const ge::NodePtr &node) { + const auto &out_control_anchor = node->GetOutControlAnchor(); + for (const auto &peer_in_control_anchor : out_control_anchor->GetPeerInControlAnchors()) { + if (peer_in_control_anchor == nullptr || peer_in_control_anchor->GetOwnerNode() == nullptr) { + continue; + } + return true; + } + return false; +} } // namespace ge diff --git a/src/ge/graph/passes/variable_prepare_op_pass.h b/src/ge/graph/passes/variable_prepare_op_pass.h index c8b9883e..f024a464 100644 --- a/src/ge/graph/passes/variable_prepare_op_pass.h +++ b/src/ge/graph/passes/variable_prepare_op_pass.h @@ -18,6 +18,7 @@ #define GE_GRAPH_PASSES_VARIABLE_PREPARE_OP_PASS_H_ #include +#include #include #include "framework/common/ge_inner_error_codes.h" @@ -30,15 +31,19 @@ class VariablePrepareOpPass : public GraphPass { private: Status DealVariableNode(ge::NodePtr &node); - Status DealWritableNode(ge::NodePtr &writable_node, ge::NodePtr &var_node, int out_index); - NodePtr GetFinalWritableNode(ge::NodePtr &writable_node, int &out_index); - Status AddVariableRef(ge::NodePtr &node, ge::NodePtr &var_node, int index); - NodePtr CreatVariableRef(const std::string &variable_ref_name, ge::NodePtr &var_node); + Status DealWritableNode(const ge::NodePtr &writable_node, int input_index, const ge::NodePtr &var_node); + Status GetPeerNodeOfRefInput(const ge::NodePtr &node, int input_index, std::stack> &nodes); + Status AddVariableRef(ge::NodePtr &node, const ge::NodePtr &var_node, int index); + Status InsertVariableRef(ge::NodePtr &node, int in_index, const ge::NodePtr &var_node); + Status AddControlEdge(const ge::NodePtr &node, const ge::NodePtr &variable_ref_node); + NodePtr CreateVariableRef(const std::string &variable_ref_name, const ge::NodePtr &var_node); + NodePtr CreateRefIdentity(const std::string &ref_identity_name, const ge::NodePtr &node, uint32_t input_index); int GetWritableNodeOutIndex(const NodePtr &node, int input_index); void GenerateRefTypeAndInputOutputMap(const NodePtr &node); int FindRefOutIndex(const std::string &node_type, int input_index, const std::map> &ref_map); Status CheckStreamLabel(const ge::NodePtr &var_ref_node, const ge::NodePtr &final_writable_node); + bool HasControlOut(const ge::NodePtr &node); std::map> ref_input_output_map_; static std::map> ref_node_without_prototype_map_; diff --git a/src/ge/graph/passes/variable_ref_delete_op_pass.cc b/src/ge/graph/passes/variable_ref_delete_op_pass.cc index cd5b9fe9..32236814 100644 --- a/src/ge/graph/passes/variable_ref_delete_op_pass.cc +++ b/src/ge/graph/passes/variable_ref_delete_op_pass.cc @@ -16,18 +16,10 @@ #include "graph/passes/variable_ref_delete_op_pass.h" #include -#include "framework/common/debug/ge_log.h" namespace ge { Status VariableRefDeleteOpPass::Run(ge::ComputeGraphPtr graph) { - GE_TIMESTAMP_START(VariableRefDeleteOpPass); GE_CHECK_NOTNULL(graph); - - for (auto &node : graph->GetDirectNode()) { - GELOGD("before VariableRefDeleteOpPass, graph has node: %s, and node name: %s", node->GetType().c_str(), - node->GetName().c_str()); - } - for (auto &node : graph->GetDirectNode()) { GE_CHECK_NOTNULL(node->GetOpDesc()); std::string ref_var_src_var_name; @@ -42,13 +34,6 @@ Status VariableRefDeleteOpPass::Run(ge::ComputeGraphPtr graph) { return FAILED; } } - - for (auto &node : graph->GetDirectNode()) { - GELOGD("after VariableRefDeleteOpPass, graph has node: %s, and node name: %s", node->GetType().c_str(), - node->GetName().c_str()); - } - GE_TIMESTAMP_END(VariableRefDeleteOpPass, "GraphManager::VariableRefDeleteOpPass"); - return SUCCESS; } @@ -68,21 +53,21 @@ Status VariableRefDeleteOpPass::DealVariableRef(ge::ComputeGraphPtr &graph, ge:: // get previous node of variable_ref NodePtr peer_node = inAnchor0->GetPeerOutAnchor()->GetOwnerNode(); - // add attr [REF_VAR_SRC_VAR_NAME] to the previous node of the variable_ref - GE_CHECK_NOTNULL(peer_node->GetOpDesc()); - bool is_set_str = ge::AttrUtils::SetStr(peer_node->GetOpDesc(), REF_VAR_SRC_VAR_NAME, ref_var_src_var_name); - + // add attr [REF_VAR_SRC_VAR_NAME] to the previous op output desc of the variable_ref + auto op_desc = peer_node->GetOpDesc(); + GE_CHECK_NOTNULL(op_desc); + auto out_desc = op_desc->GetOutputDesc(static_cast(index)); + bool is_set_str = ge::AttrUtils::SetStr(out_desc, REF_VAR_SRC_VAR_NAME, ref_var_src_var_name); + (void)op_desc->UpdateOutputDesc(static_cast(index), out_desc); ge::NodePtr ref_var_src_var = GraphUtils::FindNodeFromAllNodes(graph, ref_var_src_var_name); if (ref_var_src_var == nullptr) { - GELOGE(FAILED, "get ref_var_src_var failed"); + GELOGE(FAILED, "Can not find source variable[%s] of variable ref[%s]", ref_var_src_var_name.c_str(), + variable_ref->GetName().c_str()); return FAILED; } - - GE_CHECK_NOTNULL(ref_var_src_var->GetOpDesc()); - bool is_set_index = ge::AttrUtils::SetInt(ref_var_src_var->GetOpDesc(), REF_VAR_PRE_PEER_OUT_INDEX, index); - if (is_set_str && is_set_index) { - GELOGI("[%s]: add attr [REF_VAR_SRC_VAR_NAME: %s ] ", peer_node->GetName().c_str(), ref_var_src_var_name.c_str()); - GELOGI("[%s]: add attr [REF_VAR_PRE_PEER_OUT_INDEX: %d]", ref_var_src_var->GetName().c_str(), index); + if (is_set_str) { + GELOGI("[%s-%d]: add attr [REF_VAR_SRC_VAR_NAME: %s ] ", peer_node->GetName().c_str(), index, + ref_var_src_var_name.c_str()); } // remove variable_ref diff --git a/src/ge/graph/passes/variable_ref_useless_control_out_delete_pass.cc b/src/ge/graph/passes/variable_ref_useless_control_out_delete_pass.cc index bd153184..1321cf20 100644 --- a/src/ge/graph/passes/variable_ref_useless_control_out_delete_pass.cc +++ b/src/ge/graph/passes/variable_ref_useless_control_out_delete_pass.cc @@ -17,7 +17,6 @@ #include "variable_ref_useless_control_out_delete_pass.h" namespace ge { - Status VariableRefUselessControlOutDeletePass::Run(ge::ComputeGraphPtr graph) { GE_CHECK_NOTNULL(graph); for (const auto &node : graph->GetDirectNode()) { diff --git a/src/ge/graph/preprocess/graph_preprocess.cc b/src/ge/graph/preprocess/graph_preprocess.cc index 9c82a06d..94818698 100644 --- a/src/ge/graph/preprocess/graph_preprocess.cc +++ b/src/ge/graph/preprocess/graph_preprocess.cc @@ -19,9 +19,12 @@ #include #include #include +#include "common/formats/format_transfers/format_transfer_fractal_nz.h" +#include "common/formats/format_transfers/format_transfer_fractal_z.h" #include "common/formats/format_transfers/format_transfer_nchw_nc1hwc0.h" #include "common/formats/format_transfers/format_transfer_nhwc_nc1hwc0.h" #include "common/formats/format_transfers/format_transfer_transpose.h" +#include "common/formats/utils/formats_trans_utils.h" #include "common/helper/model_helper.h" #include "common/math/math_util.h" #include "common/op/ge_op_utils.h" @@ -80,7 +83,9 @@ #include "graph/passes/switch_dead_branch_elimination.h" #include "graph/passes/switch_fusion_pass.h" #include "graph/passes/switch_logic_remove_pass.h" -#include "graph/passes/switch_op_pass.h" +#include "graph/passes/merge_to_stream_merge_pass.h" +#include "graph/passes/switch_to_stream_switch_pass.h" +#include "graph/passes/attach_stream_label_pass.h" #include "graph/passes/switch_split_pass.h" #include "graph/passes/unused_const_pass.h" #include "graph/passes/unused_op_remove_pass.h" @@ -96,7 +101,6 @@ #include "runtime/dev.h" #include "graph/passes/dimension_adjust_pass.h" -#include "graph/passes/identify_reference_pass.h" #include "graph/passes/link_gen_mask_nodes_pass.h" #include "graph/passes/permute_pass.h" #include "graph/passes/reshape_remove_pass.h" @@ -134,14 +138,14 @@ OpDescPtr CreateTensorShape(const GeTensorDesc &data_tensor) { auto dim_cnt = static_cast(dst_ge_shape.GetDimNum()); if (dim_cnt == 0) { // if the dim_cnt is 0, the tensor is a scalar tensor->MutableTensorDesc().SetShape(GeShape()); - int64_t dst_shape = 1; - if (tensor->SetData(reinterpret_cast(&dst_shape), sizeof(int64_t)) != GRAPH_SUCCESS) { + int32_t dst_shape = 1; + if (tensor->SetData(reinterpret_cast(&dst_shape), sizeof(int32_t)) != GRAPH_SUCCESS) { GELOGE(INTERNAL_ERROR, "tensor set data failed"); return nullptr; } } else { tensor->MutableTensorDesc().SetShape(GeShape(std::vector({dim_cnt}))); - unique_ptr dst_shape(new (std::nothrow) int64_t[dim_cnt]()); + unique_ptr dst_shape(new (std::nothrow) int32_t[dim_cnt]()); if (dst_shape == nullptr) { GELOGE(INTERNAL_ERROR, "Create unique ptr failed"); return nullptr; @@ -151,7 +155,7 @@ OpDescPtr CreateTensorShape(const GeTensorDesc &data_tensor) { } GE_IF_BOOL_EXEC( - tensor->SetData(reinterpret_cast(dst_shape.get()), dim_cnt * sizeof(int64_t)) != GRAPH_SUCCESS, + tensor->SetData(reinterpret_cast(dst_shape.get()), dim_cnt * sizeof(int32_t)) != GRAPH_SUCCESS, GELOGE(INTERNAL_ERROR, "tensor set data failed"); return nullptr;) } @@ -648,7 +652,39 @@ Status ModifyFormatAndShapeForSingleTensor(const GeTensorDescPtr &input_output) input_output->SetShape(ge::GeShape(dst_shape_dims)); return SUCCESS; } +Status ModifyDataNetOutputFormatAndShape(OpDescPtr &op_desc, uint32_t index, Format storage_format, + vector &dst_shape_dims) { + GE_CHECK_NOTNULL(op_desc); + const GeTensorDescPtr &input = op_desc->MutableInputDesc(index); + GE_CHECK_NOTNULL(input); + ge::Format old_format = input->GetFormat(); + std::vector old_shape = input->GetShape().GetDims(); + + input->SetShape(ge::GeShape(dst_shape_dims)); + input->SetFormat(storage_format); + auto output = op_desc->MutableOutputDesc(index); + GE_CHECK_NOTNULL(output); + output->SetShape(ge::GeShape(dst_shape_dims)); + output->SetFormat(storage_format); + + int64_t size = 0; + graphStatus graph_status = TensorUtils::GetTensorMemorySizeInBytes(*output, size); + if (graph_status != ge::GRAPH_SUCCESS) { + GELOGE(graph_status, "GetTensorSizeInBytes failed!"); + return FAILED; + } + ge::TensorUtils::SetSize(*input, size); + ge::TensorUtils::SetSize(*output, size); + + GELOGI( + "Modify Data NetOutput format and shape success, node:%s, index:%d, old_shape:%s, old_Format:%s, " + "new_shape:%s, new_format:%s, new_size:%u", + op_desc->GetName().c_str(), index, formats::JoinToString(old_shape).c_str(), + ge::TypeUtils::FormatToSerialString(old_format).c_str(), formats::JoinToString(dst_shape_dims).c_str(), + ge::TypeUtils::FormatToSerialString(storage_format).c_str(), size); + return SUCCESS; +} Status ProcessInputNC1HWC0(NodePtr &node_ptr, bool &is_dynamic_batch, NodePtr &switchn_node) { GE_CHECK_NOTNULL(node_ptr); auto op_desc = node_ptr->GetOpDesc(); @@ -1054,7 +1090,6 @@ Status ProcessInputFP16DynShape(NodePtr &node_ptr, bool &is_dynamic_batch, NodeP return SUCCESS; } input->SetDataType(DT_FLOAT16); - input->SetOriginDataType(DT_FLOAT16); int64_t input_shape_size = 0; int64_t output_shape_size = 0; ge::graphStatus input_graph_status = ge::TensorUtils::GetTensorSizeInBytes(*input, input_shape_size); @@ -1067,7 +1102,6 @@ Status ProcessInputFP16DynShape(NodePtr &node_ptr, bool &is_dynamic_batch, NodeP const GeTensorDescPtr &output = op_desc->MutableOutputDesc(0); GE_CHECK_NOTNULL(output); output->SetDataType(DT_FLOAT16); - output->SetOriginDataType(DT_FLOAT16); ge::TensorUtils::SetSize(*output, output_shape_size); if (is_dynamic_batch) { GELOGI("The node [%s] dtype set fp16", switchn_node->GetName().c_str()); @@ -1076,12 +1110,10 @@ Status ProcessInputFP16DynShape(NodePtr &node_ptr, bool &is_dynamic_batch, NodeP auto switchn_input = switchn_op_desc->MutableInputDesc(0); GE_CHECK_NOTNULL(switchn_input); switchn_input->SetDataType(DT_FLOAT16); - switchn_input->SetOriginDataType(DT_FLOAT16); for (uint32_t i = 0; i < switchn_node->GetAllOutDataAnchorsSize(); ++i) { const GeTensorDescPtr &switchn_output = switchn_op_desc->MutableOutputDesc(i); GE_CHECK_NOTNULL(switchn_output); switchn_output->SetDataType(DT_FLOAT16); - switchn_output->SetOriginDataType(DT_FLOAT16); } } return SUCCESS; @@ -1100,10 +1132,6 @@ Status ProcessInputNC1HWC0DynShape(NodePtr &node_ptr, bool &is_dynamic_batch, No GELOGE(INTERNAL_ERROR, "The format [%s] is unsupported", TypeUtils::FormatToSerialString(old_format).c_str()); return FAILED; } - if (old_format == FORMAT_NC1HWC0) { - GELOGI("No need to transfer format"); - return SUCCESS; - } if (ModifyInputFormatAndShape(node_ptr) != SUCCESS) { GELOGE(INTERNAL_ERROR, "modify format and shape failed"); return FAILED; @@ -1139,7 +1167,7 @@ Status ProcessDataNodeDynShape(NodePtr &node_ptr) { } for (auto const &next_node : node_ptr->GetOutNodes()) { if (next_node->GetType() == AIPP) { - ErrorManager::GetInstance().ATCReportErrMessage("E10049", {"opname"}, {node_ptr->GetName()}); + ErrorManager::GetInstance().ATCReportErrMessage("E10034", {"opname"}, {node_ptr->GetName()}); GELOGE(INTERNAL_ERROR, "This input op [%s] is linked to aipp, can not be set to fp16, " "please check your atc parameter --insert_op_conf, --input_fp16_nodes.", @@ -1171,6 +1199,42 @@ Status ProcessDataNodeDynShape(NodePtr &node_ptr) { return SUCCESS; } +Status GetStorageFormatAndShape(OpDescPtr &op_desc, const GeTensorDescPtr &tensor_desc_ptr, Format &storage_format, + vector &dst_shape_dims) { + GE_CHECK_NOTNULL(op_desc); + GE_CHECK_NOTNULL(tensor_desc_ptr); + + storage_format = FORMAT_RESERVED; + int64_t format = FORMAT_RESERVED; + dst_shape_dims.clear(); + if (ge::AttrUtils::GetInt(*tensor_desc_ptr, ATTR_NAME_STORAGE_FORMAT, format)) { + storage_format = static_cast(format); + vector storage_shape; + if (ge::AttrUtils::GetListInt(*tensor_desc_ptr, ATTR_NAME_STORAGE_SHAPE, storage_shape)) { + for (auto dim : storage_shape) { + dst_shape_dims.push_back(static_cast(dim)); + } + GELOGI("Update node by storage format, node: [%s], storage_format: [%s], storage_shape:[%s]", + op_desc->GetName().c_str(), TypeUtils::FormatToSerialString(storage_format).c_str(), + formats::JoinToString(storage_shape).c_str()); + } else { + GELOGE(PARAM_INVALID, + "Update node by storage format failed, storage_shape not set. " + "node: [%s], storage_format [%s]", + op_desc->GetName().c_str(), TypeUtils::FormatToSerialString(storage_format).c_str()); + return FAILED; + } + + ge::Format old_format = tensor_desc_ptr->GetFormat(); + auto old_shape = tensor_desc_ptr->GetShape().GetDims(); + if (old_format == storage_format && old_shape == dst_shape_dims) { + GELOGI("Update node by storage format, not changed."); + storage_format = FORMAT_RESERVED; + return SUCCESS; + } + } + return SUCCESS; +} Status ProcessNetoutputNodeFp16Nc1hwc0DynShape(GeTensorDesc &src_desc, GeTensorDescPtr &net_output_input_desc, NodePtr &node) { bool is_dynamic = CheckOpType(node, MERGE); @@ -1180,24 +1244,16 @@ Status ProcessNetoutputNodeFp16Nc1hwc0DynShape(GeTensorDesc &src_desc, GeTensorD ge::Format src_format = src_desc.GetFormat(); net_output_input_desc->SetDataType(DT_FLOAT16); - net_output_input_desc->SetOriginDataType(DT_FLOAT16); if (is_dynamic) { auto merge_output = src_op_desc->MutableOutputDesc(0); GE_CHECK_NOTNULL(merge_output); merge_output->SetDataType(DT_FLOAT16); - merge_output->SetOriginDataType(DT_FLOAT16); for (uint32_t i = 0; i < node->GetAllInDataAnchorsSize(); ++i) { auto merge_input = src_op_desc->MutableInputDesc(i); GE_CHECK_NOTNULL(merge_input); merge_input->SetDataType(DT_FLOAT16); - merge_input->SetOriginDataType(DT_FLOAT16); } } - - if (src_format == FORMAT_NC1HWC0) { - GELOGI("Format is NC1HWC0, no need to transfer"); - return SUCCESS; - } std::vector dst_shape_dims; std::vector src_shape_dims = src_shape.GetDims(); if (TransferShape2NC1HWC0(src_format, src_shape_dims, DT_FLOAT16, FORMAT_NC1HWC0, dst_shape_dims) != SUCCESS) { @@ -1291,17 +1347,14 @@ Status ProcessNetoutputNodeDynShape(NodePtr &node, std::string &output_type) { if (NeedUpdateOutputByOutputTypeParm(output_type, src_node, src_index, output_data_type)) { GELOGI("Enter into process output_type schedule"); net_output_input_desc->SetDataType(output_data_type); - net_output_input_desc->SetOriginDataType(output_data_type); if (is_dynamic) { auto merge_output = src_op_desc->MutableOutputDesc(0); GE_CHECK_NOTNULL(merge_output); merge_output->SetDataType(output_data_type); - merge_output->SetOriginDataType(output_data_type); for (uint32_t i = 0; i < src_node->GetAllInDataAnchorsSize(); ++i) { auto merge_input = src_op_desc->MutableInputDesc(i); GE_CHECK_NOTNULL(merge_input); merge_input->SetDataType(output_data_type); - merge_input->SetOriginDataType(output_data_type); } } continue; @@ -1337,7 +1390,6 @@ Status ProcessNetoutputNodeDynShape(NodePtr &node, std::string &output_type) { } return SUCCESS; } - } // namespace GraphPrepare::GraphPrepare() : compute_graph_(nullptr) {} @@ -1431,6 +1483,8 @@ Status GraphPrepare::Init(const ge::Graph &graph, uint64_t session_id) { if (compute_graph_ != nullptr) { compute_graph_->SetSessionID(session_id); } + session_id_ = session_id; + Status ret = CheckGraph(); if (ret != SUCCESS) { GELOGE(ret, "RunGraph graph check fail, ret:%u", ret); @@ -1442,7 +1496,6 @@ Status GraphPrepare::Init(const ge::Graph &graph, uint64_t session_id) { GELOGE(ret, "RunGraph check ref op fail, ret:%u", ret); return ret; } - return SUCCESS; } @@ -1467,13 +1520,13 @@ Status GraphPrepare::CheckGraph() { } Status GraphPrepare::CheckRefInputNode(const NodePtr &node, const std::string &input_name, - const std::unordered_set &ref_nodes) { + const std::set &ref_nodes) { // Acceptable input types should be ref node, variable or Switch operator, which is issued by ME for dynamic - // lossscale and would be optimized in SwitchOpPass. Since ME dont differentiate between RefSwitch and Switch, - // and only issue Switch. - static std::unordered_set acceptable_types = {ge::VARIABLE, ge::VARIABLEV2, ge::VARHANDLEOP, - ge::REFSWITCH, ge::REFMERGE, ge::REFENTER, - ge::REFNEXTITERATION, ge::REFEXIT, ge::SWITCH}; + // lossscale and would be optimized in SwitchToStreamSwitchPass. + // Since ME dont differentiate between RefSwitch and Switch, and only issue Switch. + static std::set acceptable_types = {ge::VARIABLE, ge::VARIABLEV2, ge::VARHANDLEOP, + ge::REFSWITCH, ge::REFMERGE, ge::REFENTER, + ge::REFNEXTITERATION, ge::REFEXIT, ge::SWITCH}; GE_CHECK_NOTNULL(node); const auto &op_desc = node->GetOpDesc(); GE_CHECK_NOTNULL(op_desc); @@ -1499,7 +1552,6 @@ Status GraphPrepare::CheckRefInputNode(const NodePtr &node, const std::string &i } } bool is_acceptable = (acceptable_types.find(input_type) != acceptable_types.end()); - if (!is_acceptable) { GELOGE(PARAM_INVALID, "The ref input of ref node %s[%s] must be ref node or variable, but %s[%s]isn't.", node->GetName().c_str(), node->GetType().c_str(), input_op_desc->GetName().c_str(), @@ -1512,7 +1564,7 @@ Status GraphPrepare::CheckRefInputNode(const NodePtr &node, const std::string &i Status GraphPrepare::CheckRefOp() { GE_CHECK_NOTNULL(compute_graph_); - std::unordered_set ref_nodes; + std::set ref_nodes; for (const NodePtr &node : compute_graph_->GetDirectNode()) { if (node == nullptr) { GELOGE(PARAM_INVALID, "param [node] must not be null."); @@ -1524,20 +1576,15 @@ Status GraphPrepare::CheckRefOp() { return PARAM_INVALID; } - auto input_names = op_desc->GetAllInputNames(); + auto input_name_index = op_desc->GetAllInputName(); auto outputs = op_desc->GetAllOutputName(); - std::unordered_set all_output_name; - - for (auto &output : outputs) { - all_output_name.insert(output.first); - } - for (const auto &input_name : input_names) { - if (all_output_name.find(input_name) != all_output_name.end()) { - if (CheckRefInputNode(node, input_name, ref_nodes) != SUCCESS) { + for (const auto &name_index : input_name_index) { + if (op_desc->GetOutputIndexByName(name_index.first) != -1) { + if (CheckRefInputNode(node, name_index.first, ref_nodes) != SUCCESS) { GELOGE(PARAM_INVALID, "CheckRefInputNode failed."); return PARAM_INVALID; } - (void)ref_nodes.insert(node); + (void)ref_nodes.insert(node); // no need to check value } } } @@ -1548,7 +1595,7 @@ Status GraphPrepare::SetRtContext(rtContext_t rt_context, rtCtxMode_t mode) { GELOGI("set rt_context %d, device id:%u.", static_cast(mode), ge::GetContext().DeviceId()); GE_CHK_RT_RET(rtCtxCreate(&rt_context, mode, ge::GetContext().DeviceId())); GE_CHK_RT_RET(rtCtxSetCurrent(rt_context)); - RtContextUtil::GetInstance().AddrtContext(rt_context); + RtContextUtil::GetInstance().AddRtContext(session_id_, rt_context); return SUCCESS; } @@ -1566,6 +1613,8 @@ Status GraphPrepare::AdjustDataOpOutput(const NodePtr &node) { int64_t tensor_size = 0; graphStatus graph_status = TensorUtils::GetTensorMemorySizeInBytes(output, tensor_size); if (graph_status != GRAPH_SUCCESS) { + ErrorManager::GetInstance().ATCReportErrMessage("E19012", {"function", "reason"}, + {"GetTensorMemorySizeInBytes", "opname is " + node->GetName()}); GELOGE(graph_status, "GetTensorMemorySizeInBytes failed!"); return FAILED; } @@ -1599,12 +1648,16 @@ Status GraphPrepare::UpdateInput(const std::vector &user_input) { GeTensorDesc desc(user_input[index].GetTensorDesc()); auto format = desc.GetFormat(); auto origin_format = desc.GetOriginFormat(); - bool is_internal = TypeUtils::IsInternalFormat(format) || TypeUtils::IsInternalFormat(origin_format); - bool need_check_internal_format = (!options_.is_single_op) && is_internal; + // data maybe internal format [FRACTAL_NZ] at singleop process such as GEMM. + bool need_check_internal_format = (!IsTansDataOpData(input_node)) && (!options_.is_single_op); if (need_check_internal_format) { - GELOGE(PARAM_INVALID, "Input format %s or origin_format %s is not support.", - TypeUtils::FormatToSerialString(format).c_str(), TypeUtils::FormatToSerialString(origin_format).c_str()); - return FAILED; + bool is_internal = TypeUtils::IsInternalFormat(format) || TypeUtils::IsInternalFormat(origin_format); + if (is_internal) { + GELOGE(PARAM_INVALID, "Input format %s or origin_format %s is not support.", + TypeUtils::FormatToSerialString(format).c_str(), + TypeUtils::FormatToSerialString(origin_format).c_str()); + return FAILED; + } } auto data_type = desc.GetDataType(); @@ -1623,7 +1676,8 @@ Status GraphPrepare::UpdateInput(const std::vector &user_input) { GE_IF_BOOL_EXEC(ge::TensorUtils::GetSize(desc, size) != GRAPH_SUCCESS, GELOGE(INTERNAL_ERROR, "TensorUtils GetSize failed"); return FAILED); - if ((size != 0) && (shape_size != size)) { + bool size_check = (size != 0 && shape_size != size); + if (size_check) { GELOGE(PARAM_INVALID, "input data size =%ld, shape_size =%ld.", size, shape_size); return FAILED; } @@ -1771,6 +1825,55 @@ Status GraphPrepare::OptimizeAfterInfershapeByAtcParams() { return SUCCESS; } +Status GraphPrepare::UpdateDataNetOutputByStorageFormat() { + for (auto &node_ptr : compute_graph_->GetAllNodes()) { + GE_CHECK_NOTNULL(node_ptr); + if (node_ptr->GetType() == DATA) { + uint32_t index = 0; + auto op_desc = node_ptr->GetOpDesc(); + GE_CHECK_NOTNULL(op_desc); + const GeTensorDescPtr input = op_desc->MutableInputDesc(index); + Format storage_format = FORMAT_RESERVED; + vector dst_shape_dims; + if (GetStorageFormatAndShape(op_desc, input, storage_format, dst_shape_dims) != SUCCESS) { + GELOGE(INTERNAL_ERROR, "Get storage format for input failed"); + return FAILED; + } + + if (storage_format == FORMAT_RESERVED) { + continue; + } + + if (ModifyDataNetOutputFormatAndShape(op_desc, index, storage_format, dst_shape_dims) != SUCCESS) { + GELOGE(INTERNAL_ERROR, "Modify format and shape for inputfailed"); + return FAILED; + } + } + + if (node_ptr->GetType() == ge::NETOUTPUT) { + auto op_desc = node_ptr->GetOpDesc(); + GE_CHECK_NOTNULL(op_desc); + for (uint32_t index = 0; index < op_desc->GetOutputsSize(); index++) { + const GeTensorDescPtr output = op_desc->MutableOutputDesc(index); + Format storage_format = FORMAT_RESERVED; + vector dst_shape_dims; + if (GetStorageFormatAndShape(op_desc, output, storage_format, dst_shape_dims) != SUCCESS) { + GELOGE(INTERNAL_ERROR, "Get storage format from output failed"); + return FAILED; + } + if (storage_format == FORMAT_RESERVED) { + continue; + } + if (ModifyDataNetOutputFormatAndShape(op_desc, index, storage_format, dst_shape_dims) != SUCCESS) { + GELOGE(INTERNAL_ERROR, "Modify format and shape for output failed"); + return FAILED; + } + } + } + } + return SUCCESS; +} + void GraphPrepare::ProcessCCEFormat() { static const char *const parser_priority = std::getenv("PARSER_PRIORITY"); static const bool keep_cce = parser_priority != nullptr && string(parser_priority) == "cce"; @@ -1955,9 +2058,7 @@ Status GraphPrepare::PrepareDynShape(ConstGraphPtr graph, const std::vector(options_.framework_type); const Graph &const_graph = *graph; @@ -1989,7 +2090,6 @@ Status GraphPrepare::PrepareRunningFormatRefiner() { PassManager pass_manager; GE_CHK_STATUS_RET(pass_manager.AddPass("PrepareRunningFormatRefiner::VariablePrepareOpPass", new (std::nothrow) VariablePrepareOpPass)) - GE_CHK_STATUS_RET(pass_manager.AddPass("PrepareRunningFormatRefiner::SubgraphPass", new (std::nothrow) SubgraphPass)) GE_TIMESTAMP_START(pass_manager); auto ret = pass_manager.Run(compute_graph); GE_TIMESTAMP_END(pass_manager, "GraphPrepare::PrepareRunningFormatRefiner"); @@ -2053,10 +2153,6 @@ Status GraphPrepare::GenerateInfershapeGraph(ConstGraphPtr graph) { Status GraphPrepare::Prepare(ConstGraphPtr graph, const std::vector &user_input, ge::ComputeGraphPtr &compute_graph, VarAccelerateCtrl &var_acc_ctrl, uint64_t session_id) { - // train graph flag - if (options_.train_graph_flag) { - domi::GetContext().train_flag = true; - } domi::GetContext().type = static_cast(options_.framework_type); if (graph == nullptr) { @@ -2071,7 +2167,7 @@ Status GraphPrepare::Prepare(ConstGraphPtr graph, const std::vector &u } GraphOptimize graph_optimize; - if (!domi::GetContext().train_flag) { + if (!options_.train_graph_flag && !domi::GetContext().train_flag) { GE_DUMP(compute_graph_, "BeforeOriginalGraphForQuantize"); GE_TIMESTAMP_START(OptimizeOriginalGraphForQuantize); ret = graph_optimize.OptimizeOriginalGraphForQuantize(compute_graph_); @@ -2302,10 +2398,10 @@ Status GraphPrepare::PrepareOptimize() { GEPass ge_passes(compute_graph_); NamesToPass names_to_passes; EnterPass enter_pass; - PrintOpPass print_pass; names_to_passes.emplace_back("EnterPass", &enter_pass); CondPass cond_pass; names_to_passes.emplace_back("CondPass", &cond_pass); + PrintOpPass print_pass; if (options_.enable_print_op_pass) { names_to_passes.emplace_back("PrintOpPass", &print_pass); } @@ -2478,7 +2574,9 @@ Status GraphPrepare::OptimizeForPreprocess() { (void)graph_pass.AddPass("OptimizeForPreprocess::PrunePass", new PrunePass); (void)graph_pass.AddPass("OptimizeForPreprocess::NextIterationPass", new NextIterationPass); (void)graph_pass.AddPass("OptimizeForPreprocess::ControlTriggerPass", new ControlTriggerPass); - (void)graph_pass.AddPass("OptimizeForPreprocess::SwitchOpPass", new SwitchOpPass); + (void)graph_pass.AddPass("OptimizeForPreprocess::MergeToStreamMergePass", new MergeToStreamMergePass); + (void)graph_pass.AddPass("OptimizeForPreprocess::SwitchToStreamSwitchPass", new SwitchToStreamSwitchPass); + (void)graph_pass.AddPass("OptimizeForPreprocess::AttachStreamLabelPass", new AttachStreamLabelPass); (void)graph_pass.AddPass("OptimizeForPreprocess::HcclMemcpyPass", new HcclMemcpyPass); GE_IF_BOOL_EXEC(options_.train_graph_flag, (void)graph_pass.AddPass("OptimizeForPreprocess::FlowCtrlPass", new FlowCtrlPass);); @@ -2560,8 +2658,6 @@ Status GraphPrepare::NewOptimizeGraphBeforeSubGraph(VarAccelerateCtrl &var_acc_c GEPass ge_passes_for_shape(compute_graph_); NamesToPass names_to_passes_for_shape; - IdentifyReferencePass identify_reference_pass; - names_to_passes_for_shape.emplace_back("IdentifyReferencePass", &identify_reference_pass); CastRemovePass cast_remove_pass; names_to_passes_for_shape.emplace_back("CastRemovePass", &cast_remove_pass); TransposeTransDataPass transpose_transdata_pass; @@ -2693,6 +2789,12 @@ Status GraphPrepare::CheckAndUpdateInput(const std::vector &user_input return SUCCESS; } Status GraphPrepare::UpdateInputOutputByOptions() { + auto ret = UpdateDataNetOutputByStorageFormat(); + if (ret != SUCCESS) { + GELOGE(ret, "Update format acoording to storage format failed."); + return ret; + } + if (options_.train_graph_flag) { GELOGI("This is train mode, no need to do this schedule."); return SUCCESS; @@ -2736,6 +2838,21 @@ bool GraphPrepare::IsBroadCastOpData(const ge::NodePtr &var_node) { return false; } +bool GraphPrepare::IsTansDataOpData(const ge::NodePtr &var_node) { + for (auto &out_anchor : var_node->GetAllOutDataAnchors()) { + GE_RT_FALSE_CHECK_NOTNULL(out_anchor); + for (auto &in_anchor : out_anchor->GetPeerInDataAnchors()) { + GE_RT_FALSE_CHECK_NOTNULL(in_anchor); + ge::NodePtr dst_node = in_anchor->GetOwnerNode(); + GE_RT_FALSE_CHECK_NOTNULL(dst_node); + if (dst_node->GetType() == TRANSDATA) { + return true; + } + } + } + return false; +} + bool GraphPrepare::ConfirmUseOpAndIndexByAnchor(const ge::InDataAnchorPtr &in_anchor, const map> &confirm_ops, ge::NodePtr &use_node) { GE_RT_FALSE_CHECK_NOTNULL(in_anchor); diff --git a/src/ge/graph/preprocess/graph_preprocess.h b/src/ge/graph/preprocess/graph_preprocess.h index b90caa86..bae2a885 100644 --- a/src/ge/graph/preprocess/graph_preprocess.h +++ b/src/ge/graph/preprocess/graph_preprocess.h @@ -59,8 +59,7 @@ class GraphPrepare { Status Init(const ge::Graph &graph, uint64_t session_id = 0); Status Preprocess(const std::vector &user_input); Status CheckGraph(); - Status CheckRefInputNode(const NodePtr &node, const std::string &input_name, - const std::unordered_set &ref_nodes); + Status CheckRefInputNode(const NodePtr &node, const std::string &input_name, const std::set &ref_nodes); Status CheckRefOp(); Status SetRtContext(rtContext_t rt_context, rtCtxMode_t mode); Status AdjustDataOpOutput(const NodePtr &node); @@ -69,6 +68,7 @@ class GraphPrepare { Status CheckConstOp(); Status VerifyConstOp(const NodePtr &node); Status CheckUserInput(const std::vector &user_input); + Status UpdateDataNetOutputByStorageFormat(); Status OptimizeForPreprocess(); Status PrepareOptimize(); Status InferShapeForPreprocess(); @@ -88,6 +88,8 @@ class GraphPrepare { Status UpdateInputOutputByOptions(); bool IsBroadCastOpData(const ge::NodePtr &var_node); + bool IsTansDataOpData(const ge::NodePtr &var_node); + void AdjustBroadCastOpData(const ge::NodePtr &var_node); bool IsAssignOpData(const ge::NodePtr &var_node); @@ -104,6 +106,7 @@ class GraphPrepare { ge::ComputeGraphPtr compute_graph_; GraphManagerOptions options_; + uint64_t session_id_ = 0; }; } // namespace ge #endif // GE_GRAPH_PREPROCESS_GRAPH_PREPROCESS_H_ diff --git a/src/ge/graph/preprocess/insert_op/ge_aipp_op.cc b/src/ge/graph/preprocess/insert_op/ge_aipp_op.cc index 22128394..f35b6d3a 100644 --- a/src/ge/graph/preprocess/insert_op/ge_aipp_op.cc +++ b/src/ge/graph/preprocess/insert_op/ge_aipp_op.cc @@ -389,8 +389,8 @@ Status AippOp::SetDefaultParams() { GELOGI("parse aipp params:input_format:%s, csc_switch:%d.", domi::AippOpParams::InputFormat_Name(aipp_params_->input_format()).c_str(), aipp_params_->csc_switch()); - GELOGI("parse aipp params:mean_chn_0:%d, mean_chn_1:%d, mean_chn_2:%d.", aipp_params_->mean_chn_0(), - aipp_params_->mean_chn_1(), aipp_params_->mean_chn_2()); + GELOGI("parse aipp params:mean_chn_0:%d, mean_chn_1:%d, mean_chn_2:%d, mean_chn_3:%d.", aipp_params_->mean_chn_0(), + aipp_params_->mean_chn_1(), aipp_params_->mean_chn_2(), aipp_params_->mean_chn_3()); GELOGI("parse aipp params:min_chn_0:%f, min_chn_1:%f, min_chn_2:%f.", aipp_params_->min_chn_0(), aipp_params_->min_chn_1(), aipp_params_->min_chn_2()); diff --git a/src/ge/graph/preprocess/insert_op/util_insert_aipp_op.cc b/src/ge/graph/preprocess/insert_op/util_insert_aipp_op.cc index 2963cd5a..8bb0c6c4 100644 --- a/src/ge/graph/preprocess/insert_op/util_insert_aipp_op.cc +++ b/src/ge/graph/preprocess/insert_op/util_insert_aipp_op.cc @@ -45,7 +45,7 @@ static void ConvertShape2Nhwc(Format &format, vector &shape_vec) { return; } if (format != FORMAT_NCHW) { - GELOGW("The format is not NCHW, current format is %s", TypeUtils::FormatToSerialString(format).c_str()); + GELOGW("The format is not NCHW, current format is %s.", TypeUtils::FormatToSerialString(format).c_str()); return; } vector shape_vec_tmp; @@ -245,7 +245,6 @@ Status InsertNewOpUtil::UpdatePrevNodeByAipp(NodePtr &node, std::set &s GELOGE(FAILED, "Can not get size from aipp [%s]", aipp_op_desc->GetName().c_str()); return FAILED; } - // Save the input size of aipp node, which will be used in dumping aipp node or fused aipp node (void)AttrUtils::SetInt(aipp_input, ATTR_NAME_INPUT_ORIGIN_SIZE, size); auto in_data_anchor = node->GetInDataAnchor(0); @@ -324,7 +323,8 @@ Status InsertNewOpUtil::UpdateDataBySwitchN(const NodePtr &switchn, const NodePt auto data_opdesc = data->GetOpDesc(); GE_CHECK_NOTNULL(data_opdesc); - Format old_format = output_desc->GetFormat(); + Format old_format = data_opdesc->MutableOutputDesc(0)->GetFormat(); + auto ret = data_opdesc->UpdateOutputDesc(0, *input_desc); if (ret != GRAPH_SUCCESS) { GELOGE(INTERNAL_ERROR, "Failed to update data %s output using switchn %s", data->GetName().c_str(), @@ -465,15 +465,18 @@ Status InsertNewOpUtil::RecordAIPPInfoToData(const ComputeGraphPtr &graph) { GetInputOutputInfo(data_node, aipp_it, input, output); input_dims.emplace_back(input); output_dims.emplace_back(output); + + // When static aipp is set, need to get the model input dims which processed by aipp + GE_RETURN_IF_ERROR(SetModelInputDims(data_node, aipp_it)); } if (!AttrUtils::SetListStr(data_node->GetOpDesc(), ATTR_NAME_AIPP_INPUTS, input_dims)) { - GELOGE(FAILED, "SetListInt of %s failed.", ATTR_NAME_AIPP_INPUTS.c_str()); + GELOGE(FAILED, "SetListStr of %s failed.", ATTR_NAME_AIPP_INPUTS.c_str()); return FAILED; } if (!AttrUtils::SetListStr(data_node->GetOpDesc(), ATTR_NAME_AIPP_OUTPUTS, output_dims)) { - GELOGE(FAILED, "SetListInt of %s failed.", ATTR_NAME_AIPP_OUTPUTS.c_str()); + GELOGE(FAILED, "SetListStr of %s failed.", ATTR_NAME_AIPP_OUTPUTS.c_str()); return FAILED; } } @@ -518,4 +521,41 @@ Status InsertNewOpUtil::GetInputOutputInfo(NodePtr &data_node, NodePtr &aipp_nod data_node->GetName().c_str(), aipp_node->GetName().c_str(), input.c_str(), output.c_str()); return SUCCESS; } + +Status InsertNewOpUtil::SetModelInputDims(NodePtr &data_node, NodePtr &aipp_node) { + GE_CHECK_NOTNULL(data_node); + GE_CHECK_NOTNULL(aipp_node); + OpDescPtr data_opdesc = data_node->GetOpDesc(); + GE_CHECK_NOTNULL(data_opdesc); + OpDescPtr aipp_opdesc = aipp_node->GetOpDesc(); + GE_CHECK_NOTNULL(aipp_opdesc); + + // In dynamic bacth/hw scenario, the new model input dims only need be set once + if (data_node->GetOpDesc()->HasAttr(ATTR_NAME_INPUT_DIMS)) { + GELOGD("Data %s already has attribute %s", data_node->GetOpDesc()->GetName().c_str(), ATTR_NAME_INPUT_DIMS.c_str()); + return SUCCESS; + } + vector model_input_dims; + vector origin_input_dims; + if (AttrUtils::GetListInt(aipp_opdesc, ATTR_NAME_INPUT_DIMS, model_input_dims) && !model_input_dims.empty()) { + // When dynamic bacth/hw is set, N or HW need to be set to -1 + if (AttrUtils::GetListInt(data_opdesc, ATTR_MBATCH_ORIGIN_INPUT_DIMS, origin_input_dims) && + !origin_input_dims.empty()) { + GELOGI("In dynamic bacth/hw scenario, N or HW need to be set to -1. model_input_dims: %s, origin_input_dims: %s", + formats::JoinToString(model_input_dims).c_str(), formats::JoinToString(origin_input_dims).c_str()); + for (size_t i = 0; i < origin_input_dims.size(); ++i) { + // N or HW need to be set to -1 + if (origin_input_dims[i] < 0) { + model_input_dims[i] = origin_input_dims[i]; + } + } + } + GELOGD("After set H/W to -1, the model input dims: %s.", formats::JoinToString(model_input_dims).c_str()); + if (!AttrUtils::SetListInt(data_opdesc, ATTR_NAME_INPUT_DIMS, model_input_dims)) { + GELOGE(FAILED, "SetListInt of %s failed.", ATTR_NAME_INPUT_DIMS.c_str()); + return FAILED; + } + } + return SUCCESS; +} } // namespace ge diff --git a/src/ge/graph/preprocess/insert_op/util_insert_aipp_op.h b/src/ge/graph/preprocess/insert_op/util_insert_aipp_op.h index b39b3005..93a96ca2 100644 --- a/src/ge/graph/preprocess/insert_op/util_insert_aipp_op.h +++ b/src/ge/graph/preprocess/insert_op/util_insert_aipp_op.h @@ -67,6 +67,7 @@ class InsertNewOpUtil { Status GetDataRelatedNode(NodePtr &node, std::map> &data_next_node_map); Status GetAllAipps(const NodePtr &node, std::vector &aipps); Status GetInputOutputInfo(NodePtr &data_node, NodePtr &aipp_node, std::string &input, std::string &output); + Status SetModelInputDims(NodePtr &data_node, NodePtr &aipp_node); }; } // namespace ge diff --git a/src/ge/graph/preprocess/multi_batch_copy_graph.cc b/src/ge/graph/preprocess/multi_batch_copy_graph.cc index e063398f..d06a493d 100644 --- a/src/ge/graph/preprocess/multi_batch_copy_graph.cc +++ b/src/ge/graph/preprocess/multi_batch_copy_graph.cc @@ -44,6 +44,7 @@ const int kSwitchNPredIndex = 1; const int kDataOutIndex = 0; const int kDataInIndex = 0; const int kMergeDataOutIndex = 0; +const int kStaticOutput = -1; const size_t kMaxShapesCount = 100; const size_t kMinShapesCount = 2; @@ -125,8 +126,12 @@ Status CalcShape(const std::vector &batch_shape, GeShape &data_shape) { for (size_t i = 0; i < data_shape.GetDimNum(); ++i) { if (data_shape.GetDim(i) < 0) { if (batch_shape_index >= batch_shape.size()) { + ErrorManager::GetInstance().ATCReportErrMessage( + "E19012", {"function", "reason"}, + {"CalcShape", "the batch shape count " + std::to_string(batch_shape.size()) + + " does not match the data shape " + data_shape.ToString()}); GELOGE(PARAM_INVALID, - "Failed to calc tensor shape, the batch shape count %zu, doees not match the data shape %s", + "Failed to calc tensor shape, the batch shape count %zu, does not match the data shape %s", batch_shape.size(), data_shape.ToString().c_str()); return PARAM_INVALID; } @@ -134,6 +139,10 @@ Status CalcShape(const std::vector &batch_shape, GeShape &data_shape) { } } if (batch_shape_index != batch_shape.size()) { + ErrorManager::GetInstance().ATCReportErrMessage( + "E19012", {"function", "reason"}, + {"CalcShape", "the batch shape count " + std::to_string(batch_shape.size()) + " does not match the data shape " + + data_shape.ToString()}); GELOGE(PARAM_INVALID, "Failed to calc tensor shape, the batch shape count %zu, does not match the data shape %s", batch_shape.size(), data_shape.ToString().c_str()); return PARAM_INVALID; @@ -198,7 +207,7 @@ Status CheckDataShape(const std::vector &nodes) { } } if (unknown_shape_count == 0) { - ErrorManager::GetInstance().ATCReportErrMessage("E10055"); + ErrorManager::GetInstance().ATCReportErrMessage("E10040"); GELOGE(PARAM_INVALID, "Need unknow shape data when user set --dynamic_batch_size or --dynamic_image_size, please check."); return PARAM_INVALID; @@ -278,6 +287,8 @@ Status MultiBatchGraphCopyer::CreateNewNodes() { case kNodeOutBatchBranch: ret = InsertMergeForEdgeNode(node); break; + case kNodeNotSupportNode: + break; default: GELOGE(INTERNAL_ERROR, "Unexpected status %d on node %s", static_cast(branch_status), node->GetName().c_str()); @@ -290,7 +301,13 @@ Status MultiBatchGraphCopyer::CreateNewNodes() { } return SUCCESS; } + NodeStatus MultiBatchGraphCopyer::GetNodeStatus(const NodePtr &node) { + // node with subgraph is not supported + if (!(node->GetOpDesc()->GetSubgraphInstanceNames().empty())) { + return kNodeNotSupportNode; + } + if (node->GetType() == NETOUTPUT) { return kNodeOutBatchBranch; } @@ -304,6 +321,7 @@ NodeStatus MultiBatchGraphCopyer::GetNodeStatus(const NodePtr &node) { } return kNodeOutBatchBranch; } + NodePtr MultiBatchGraphCopyer::InsertMergeNode(const NodePtr &node, int index) { if (index < 0) { // the merge node must has data inputs, if origin connection is a control @@ -476,7 +494,7 @@ Status MultiBatchGraphCopyer::CheckArguments() { return PARAM_INVALID; } if (shapes_.size() < kMinShapesCount) { - ErrorManager::GetInstance().ATCReportErrMessage("E10050", {"shapesize", "minshapesize"}, + ErrorManager::GetInstance().ATCReportErrMessage("E10035", {"shapesize", "minshapesize"}, {std::to_string(shapes_.size()), std::to_string(kMinShapesCount)}); GELOGE(PARAM_INVALID, "Input parameter[--dynamic_batch_size or --dynamic_image_size]'s " @@ -485,7 +503,7 @@ Status MultiBatchGraphCopyer::CheckArguments() { return PARAM_INVALID; } if (shapes_.size() > kMaxShapesCount) { - ErrorManager::GetInstance().ATCReportErrMessage("E10051", {"shapesize", "maxshapesize"}, + ErrorManager::GetInstance().ATCReportErrMessage("E10036", {"shapesize", "maxshapesize"}, {std::to_string(shapes_.size()), std::to_string(kMaxShapesCount)}); GELOGE(PARAM_INVALID, "Input parameter[--dynamic_batch_size or --dynamic_image_size]'s " @@ -497,7 +515,7 @@ Status MultiBatchGraphCopyer::CheckArguments() { size_t shape_size = shapes_.at(0).size(); for (auto &shape : shapes_) { if (shape_size != shape.size()) { - ErrorManager::GetInstance().ATCReportErrMessage("E10052", {"shapesize1", "shapesize2"}, + ErrorManager::GetInstance().ATCReportErrMessage("E10037", {"shapesize1", "shapesize2"}, {std::to_string(shape_size), std::to_string(shape.size())}); GELOGE(PARAM_INVALID, "Input parameter[--dynamic_batch_size or --dynamic_image_size]'s " @@ -507,7 +525,7 @@ Status MultiBatchGraphCopyer::CheckArguments() { } for (auto dim : shape) { if (dim <= 0) { - ErrorManager::GetInstance().ATCReportErrMessage("E10053", {"dim"}, {std::to_string(dim)}); + ErrorManager::GetInstance().ATCReportErrMessage("E10038", {"dim"}, {std::to_string(dim)}); GELOGE(PARAM_INVALID, "Invalid dim %ld, all dims must be greater than 0", dim); return PARAM_INVALID; } @@ -515,7 +533,7 @@ Status MultiBatchGraphCopyer::CheckArguments() { shapes_set.insert(shape); } if (shapes_set.size() != shapes_.size()) { - ErrorManager::GetInstance().ATCReportErrMessage("E10054"); + ErrorManager::GetInstance().ATCReportErrMessage("E10039"); GELOGE(PARAM_INVALID, "Input parameter[--dynamic_batch_size or --dynamic_image_size] exist duplicate shapes, please check"); return PARAM_INVALID; @@ -947,15 +965,18 @@ Status GetDynamicOutputShape(ComputeGraphPtr &graph) { GELOGE(PARAM_INVALID, "Graph is null ,para is invalid"); return PARAM_INVALID; } - for (auto &node : graph->GetAllNodes()) { + for (auto &node : graph->GetDirectNode()) { if (node->GetType() == NETOUTPUT) { auto netoutput_desc = node->GetOpDesc(); auto inputnode_to_netoutput = node->GetInAllNodes(); + std::vector dynamic_output_index; for (size_t j = 0; j < inputnode_to_netoutput.size(); j++) { bool ret = false; (void)AttrUtils::GetBool(inputnode_to_netoutput.at(j)->GetOpDesc(), ATTR_INSERT_BY_MBATCH, ret); if (inputnode_to_netoutput.at(j)->GetType() == MERGE && ret) { - GELOGI("Find the merge node %s with mbatch attr", inputnode_to_netoutput.at(j)->GetName().c_str()); + GELOGI("Find the merge node %s with mbatch attr and the index is %zu", + inputnode_to_netoutput.at(j)->GetName().c_str(), j); + dynamic_output_index.emplace_back(j); for (size_t i = 0; i < inputnode_to_netoutput.at(j)->GetInNodes().size(); i++) { auto input_desc = inputnode_to_netoutput.at(j)->GetOpDesc(); auto input_tensor_desc = input_desc->GetInputDesc(i); @@ -967,6 +988,17 @@ Status GetDynamicOutputShape(ComputeGraphPtr &graph) { } } if (dynamic_output_dims.size() > 0) { + for (size_t k = 0; k < inputnode_to_netoutput.size(); k++) { + auto it = std::find(dynamic_output_index.begin(), dynamic_output_index.end(), k); + if (it != dynamic_output_index.end()) { + continue; + } + auto tensor_desc = netoutput_desc->GetInputDesc(k); + auto shape = tensor_desc.GetShape().ToString(); + std::string static_output_shape = std::to_string(kStaticOutput) + "," + std::to_string(k) + "," + shape; + GELOGI("The static output shape msg is %s", static_output_shape.c_str()); + dynamic_output_dims.emplace_back(static_output_shape); + } if (!AttrUtils::SetListStr(netoutput_desc, ATTR_NAME_DYNAMIC_OUTPUT_DIMS, dynamic_output_dims)) { GELOGE(FAILED, "Set dynamic output dims attr failed"); return FAILED; diff --git a/src/ge/graph/preprocess/multi_batch_copy_graph.h b/src/ge/graph/preprocess/multi_batch_copy_graph.h index 2500645f..bf1d53b3 100644 --- a/src/ge/graph/preprocess/multi_batch_copy_graph.h +++ b/src/ge/graph/preprocess/multi_batch_copy_graph.h @@ -33,6 +33,7 @@ enum NodeStatus { kNodeInBatchBranch, kNodeOutBatchBranch, kNodeStartNode, + kNodeNotSupportNode, }; class MultiBatchGraphCopyer { diff --git a/src/ge/host_kernels/add_kernel.cc b/src/ge/host_kernels/add_kernel.cc index 6d6a049c..afef1c37 100644 --- a/src/ge/host_kernels/add_kernel.cc +++ b/src/ge/host_kernels/add_kernel.cc @@ -133,25 +133,24 @@ Status AddKernel::BCastAdd(const OpDescPtr &op_desc_ptr, const std::vector &input) { if (op_desc_ptr == nullptr) { - GELOGE(PARAM_INVALID, "Op_desc_ptr must not be null."); + GELOGW("Op_desc_ptr must not be null."); return PARAM_INVALID; } // check how many inputs if ((input.size() != kAddInputSize) || (op_desc_ptr->GetOutputsSize() != kAddOutputSize)) { - GELOGE(PARAM_INVALID, "The number of input for add must be %zu, output number must be %zu.", kAddInputSize, - kAddOutputSize); + GELOGW("The number of input for add must be %zu, output number must be %zu.", kAddInputSize, kAddOutputSize); return PARAM_INVALID; } // input vector elements must not be null if ((input[kAddFirstInput] == nullptr) || (input[kAddSecondInput] == nullptr)) { - GELOGE(PARAM_INVALID, "Input vector elements must not be null."); + GELOGW("Input vector elements must not be null."); return PARAM_INVALID; } // Inputs must have the same datatype. DataType data_type_0 = input[kAddFirstInput]->GetTensorDesc().GetDataType(); DataType data_type_1 = input[kAddSecondInput]->GetTensorDesc().GetDataType(); if (data_type_0 != data_type_1) { - GELOGE(PARAM_INVALID, "Data type of inputs for add not matched, data_type_0:%s, data_type_1:%s", + GELOGW("Data type of inputs for add not matched, data_type_0:%s, data_type_1:%s", TypeUtils::DataTypeToSerialString(data_type_0).c_str(), TypeUtils::DataTypeToSerialString(data_type_1).c_str()); return PARAM_INVALID; @@ -192,7 +191,7 @@ Status AddKernel::Compute(const OpDescPtr op_desc_ptr, const std::vector x2_dims; const auto &op_in_desc = op_desc_ptr->MutableInputDesc(0); GE_CHECK_NOTNULL(op_in_desc); - ; DataType data_type = op_in_desc->GetDataType(); bool result = (OpUtils::GetShapeDataFromConstTensor(input[0], data_type, x1_dims) == SUCCESS) && (OpUtils::GetShapeDataFromConstTensor(input[1], data_type, x2_dims) == SUCCESS); diff --git a/src/ge/host_kernels/concat_offset_kernel.cc b/src/ge/host_kernels/concat_offset_kernel.cc index 2e609d68..0a870949 100644 --- a/src/ge/host_kernels/concat_offset_kernel.cc +++ b/src/ge/host_kernels/concat_offset_kernel.cc @@ -41,7 +41,7 @@ Status ConcatOffsetKernel::Compute(const OpDescPtr op_desc_ptr, const vector(reinterpret_cast(input_0->GetData().data()))); // validate inputs if (static_cast(input.size()) != (N + kNumOne) || input.size() <= kConcatOffsetInputIndexOne) { - GELOGE(PARAM_INVALID, "The number of input for concat offset must be equal with %d, and must be more than one.", - (N + kNumOne)); + GELOGW("The number of input for concat offset must be equal with %d, and must be more than one.", (N + kNumOne)); return NOT_CHANGED; } @@ -59,7 +58,7 @@ Status ConcatOffsetKernel::Compute(const OpDescPtr op_desc_ptr, const vectorGetTensorDesc().GetShape(); int64_t output_size = output_shape.GetShapeSize(); if (concat_dim >= output_size) { - GELOGE(PARAM_INVALID, "Concat dim is biger than the size of output_shape."); + GELOGW("Concat dim is biger than the size of output_shape."); return NOT_CHANGED; } GELOGI("Output shape size is %ld", output_size); @@ -79,7 +78,7 @@ Status ConcatOffsetKernel::Compute(const OpDescPtr op_desc_ptr, const vectorGetOutputDesc(0); GeTensorPtr output_ptr = MakeShared(output_tensor_desc); if (output_ptr == nullptr) { - GELOGE(MEMALLOC_FAILED, "Failed to fold node %s, out of memeory", op_desc_ptr->GetName().c_str()); + GELOGW("Failed to fold node %s, out of memeory", op_desc_ptr->GetName().c_str()); return NOT_CHANGED; } @@ -87,7 +86,7 @@ Status ConcatOffsetKernel::Compute(const OpDescPtr op_desc_ptr, const vectorMutableTensorDesc().SetShape(output_shape); GE_IF_BOOL_EXEC(output_ptr->SetData(reinterpret_cast(buf.get()), static_cast(sizeof(DT_INT32) * output_size)) != GRAPH_SUCCESS, - GELOGE(INTERNAL_ERROR, "set data failed"); + GELOGW("set data failed"); return NOT_CHANGED); v_output.push_back(output_ptr); // caculate offset diff --git a/src/ge/host_kernels/dynamic_stitch_kernel.cc b/src/ge/host_kernels/dynamic_stitch_kernel.cc index c8a19e44..c1245535 100644 --- a/src/ge/host_kernels/dynamic_stitch_kernel.cc +++ b/src/ge/host_kernels/dynamic_stitch_kernel.cc @@ -63,11 +63,11 @@ Status DynamicStitchKernel::Compute(const OpDescPtr op_desc_ptr, const vector &input) { if (op_desc_ptr == nullptr) { - GELOGE(PARAM_INVALID, "Input op_desc is nullptr."); + GELOGW("Input op_desc is nullptr."); return PARAM_INVALID; } if (op_desc_ptr->GetOutputsSize() == 0) { - GELOGE(PARAM_INVALID, "Current output_desc is empty."); + GELOGW("Current output_desc is empty."); return PARAM_INVALID; } // validate input @@ -78,7 +78,7 @@ Status DynamicStitchKernel::ValidateParams(const OpDescPtr &op_desc_ptr, const s } for (const auto &in : input) { if (in == nullptr) { - GELOGE(PARAM_INVALID, "input is nullptr."); + GELOGW("input is nullptr."); return PARAM_INVALID; } } @@ -150,7 +150,7 @@ Status DynamicStitchKernel::GenData(const vector &input, GeTen // 2.allocate memery for output std::unique_ptr buf(new (std::nothrow) uint8_t[allowance]); if (buf == nullptr) { - GELOGE(MEMALLOC_FAILED, "new buffer failed"); + GELOGW("new buffer failed"); return INTERNAL_ERROR; } // 3.copy data from input_data along with the sequence of input_indices @@ -164,7 +164,7 @@ Status DynamicStitchKernel::GenData(const vector &input, GeTen output_ptr->MutableTensorDesc().SetShape(merged_shape); Status ret = output_ptr->SetData(buf.get(), allowance); if (ret != GRAPH_SUCCESS) { - GELOGE(INTERNAL_ERROR, "set data failed"); + GELOGW("set data failed"); return NOT_CHANGED; } return SUCCESS; diff --git a/src/ge/host_kernels/empty_kernel.cc b/src/ge/host_kernels/empty_kernel.cc index 856caf50..a5e5fbcf 100644 --- a/src/ge/host_kernels/empty_kernel.cc +++ b/src/ge/host_kernels/empty_kernel.cc @@ -38,7 +38,7 @@ const size_t kShapeMaxDims = 1; } // namespace Status EmptyKernel::EmptyCheck(const OpDescPtr &op_desc_ptr, const std::vector &input) { if (op_desc_ptr == nullptr) { - GELOGE(PARAM_INVALID, "Parameter's invalid, Input opDescPtr is nullptr."); + GELOGW("Parameter's invalid, Input opDescPtr is nullptr."); return PARAM_INVALID; } // check input size @@ -46,20 +46,19 @@ Status EmptyKernel::EmptyCheck(const OpDescPtr &op_desc_ptr, const std::vectorGetAllInputsDesc().size() != kEmptyInputsSize) || (input.size() != kEmptyInputsSize) || (op_desc_ptr->GetAllOutputsDesc().size() != kEmptyOutputsSize)); if (size_check) { - GELOGE(PARAM_INVALID, "Input/Output size error. InDesc size:%zu, OutDesc size:%zu, in size:%zu ", + GELOGW("Input/Output size error. InDesc size:%zu, OutDesc size:%zu, in size:%zu ", op_desc_ptr->GetAllInputsDesc().size(), op_desc_ptr->GetAllOutputsDesc().size(), input.size()); return PARAM_INVALID; } if (input.at(kEmptyFirstInput) == nullptr) { - GELOGE(PARAM_INVALID, "Parameter's invalid, first input is nullptr."); + GELOGW("Parameter's invalid, first input is nullptr."); return PARAM_INVALID; } ConstGeTensorPtr shape = input.at(kEmptyFirstInput); // Check if the dimension is 1-D if (shape->GetTensorDesc().GetShape().GetDimNum() > kShapeMaxDims) { - GELOGE(PARAM_INVALID, "Check if the dimension is 1-D failed, dims:%zu", - shape->GetTensorDesc().GetShape().GetDimNum()); + GELOGW("Check if the dimension is 1-D failed, dims:%zu", shape->GetTensorDesc().GetShape().GetDimNum()); return PARAM_INVALID; } return SUCCESS; @@ -84,7 +83,7 @@ Status EmptyKernel::Compute(const OpDescPtr op_desc_ptr, const std::vector(shape, shape_vec, total_data_size); } else { - GELOGE(PARAM_INVALID, "shape type must be DT_INT32 or DT_INT64."); + GELOGW("shape type must be DT_INT32 or DT_INT64."); return NOT_CHANGED; } diff --git a/src/ge/host_kernels/expanddims_kernel.cc b/src/ge/host_kernels/expanddims_kernel.cc index 1d17ad48..15648573 100644 --- a/src/ge/host_kernels/expanddims_kernel.cc +++ b/src/ge/host_kernels/expanddims_kernel.cc @@ -66,7 +66,7 @@ Status ExpanddimsKernel::Compute(const ge::OpDescPtr op_desc_ptr, const std::vec auto output_tensor_desc = op_desc_ptr->GetOutputDesc(kExpandDimsIndexZero); GeTensorPtr output_ptr = MakeShared(output_tensor_desc); if (output_ptr == nullptr) { - GELOGE(MEMALLOC_FAILED, "Failed to fold node %s, out of memory", op_desc_ptr->GetName().c_str()); + GELOGW("Failed to fold node %s, out of memory", op_desc_ptr->GetName().c_str()); return NOT_CHANGED; } diff --git a/src/ge/host_kernels/floordiv_kernel.cc b/src/ge/host_kernels/floordiv_kernel.cc index 4175df92..05eded80 100644 --- a/src/ge/host_kernels/floordiv_kernel.cc +++ b/src/ge/host_kernels/floordiv_kernel.cc @@ -260,7 +260,7 @@ Status FloorDivKernel::Compute(const OpDescPtr op_desc_ptr, const std::vectorGetOutputDesc(0); GeTensorPtr output_ptr = MakeShared(output_tensor_desc); if (output_ptr == nullptr) { - GELOGE(MEMALLOC_FAILED, "make_shared ge::GeTensor failed, node name %s.", op_desc_ptr->GetName().c_str()); + GELOGW("make_shared ge::GeTensor failed, node name %s.", op_desc_ptr->GetName().c_str()); return NOT_CHANGED; } diff --git a/src/ge/host_kernels/floormod_kernel.cc b/src/ge/host_kernels/floormod_kernel.cc index a8c16c9d..7ad746de 100644 --- a/src/ge/host_kernels/floormod_kernel.cc +++ b/src/ge/host_kernels/floormod_kernel.cc @@ -122,7 +122,7 @@ Status FloorModKernel::Compute(const OpDescPtr op_desc_ptr, const std::vector(op_desc_ptr->GetOutputDesc(kFloorModFirstOutput)); if (output_ptr == nullptr) { - GELOGE(MEMALLOC_FAILED, "make_shared ge::GeTensor failed, node name %s.", op_desc_ptr->GetName().c_str()); + GELOGW("make_shared ge::GeTensor failed, node name %s.", op_desc_ptr->GetName().c_str()); return NOT_CHANGED; } diff --git a/src/ge/host_kernels/gather_v2_kernel.cc b/src/ge/host_kernels/gather_v2_kernel.cc index c8cc3006..7413395a 100644 --- a/src/ge/host_kernels/gather_v2_kernel.cc +++ b/src/ge/host_kernels/gather_v2_kernel.cc @@ -274,7 +274,7 @@ Status GatherV2Kernel::SaveIndicesByDataType(ConstGeTensorPtr indices_tensor_ptr auto indices_ptr = const_cast(reinterpret_cast(indices_tensor_ptr->GetData().data())); for (int64_t i = 0; i < indices_shape.GetShapeSize(); i++) { if (*(indices_ptr + i) < 0 || *(indices_ptr + i) >= x_shape.GetDim(axis)) { - GELOGE(NOT_CHANGED, "indices %ld value is not in range [0, %ld)", i, x_shape.GetDim(axis)); + GELOGW("indices %ld value is not in range [0, %ld)", i, x_shape.GetDim(axis)); return NOT_CHANGED; } indicates_.push_back(*(indices_ptr + i)); @@ -284,7 +284,7 @@ Status GatherV2Kernel::SaveIndicesByDataType(ConstGeTensorPtr indices_tensor_ptr auto indices_ptr = const_cast(reinterpret_cast(indices_tensor_ptr->GetData().data())); for (int64_t i = 0; i < indices_shape.GetShapeSize(); i++) { if (*(indices_ptr + i) < 0 || *(indices_ptr + i) >= x_shape.GetDim(axis)) { - GELOGE(NOT_CHANGED, "indices %ld value is not in range [0, %ld)", i, x_shape.GetDim(axis)); + GELOGW("indices %ld value is not in range [0, %ld)", i, x_shape.GetDim(axis)); return NOT_CHANGED; } indicates_.push_back(*(indices_ptr + i)); @@ -296,19 +296,19 @@ Status GatherV2Kernel::SaveIndicesByDataType(ConstGeTensorPtr indices_tensor_ptr Status GatherV2Kernel::Check(const OpDescPtr &op_desc_ptr, const vector &input, vector &v_output) const { if (op_desc_ptr == nullptr) { - GELOGE(NOT_CHANGED, "input opdesc is nullptr."); + GELOGW("input opdesc is nullptr."); return NOT_CHANGED; } if (input.size() != kGatherV2InpotNum) { - GELOGE(NOT_CHANGED, "The number of input for GatherV2 must be %zu.", kGatherV2InpotNum); + GELOGW("The number of input for GatherV2 must be %zu.", kGatherV2InpotNum); return NOT_CHANGED; } bool is_null = (input[kGatherV2InputIndexZero] == nullptr || input[kGatherV2InputIndexOne] == nullptr || input[kGatherV2InputIndexTwo] == nullptr); if (is_null) { - GELOGE(NOT_CHANGED, "some input is nullptr."); + GELOGW("some input is nullptr."); return NOT_CHANGED; } ConstGeTensorPtr tensor0 = input.at(kGatherV2InputIndexZero); @@ -318,7 +318,7 @@ Status GatherV2Kernel::Check(const OpDescPtr &op_desc_ptr, const vectorGetData().size() == 0) || (tensor1->GetData().size() == 0) || (tensor2->GetData().size() == 0)); if (size_is_zero) { - GELOGE(NOT_CHANGED, "some input size is zero."); + GELOGW("some input size is zero."); return NOT_CHANGED; } @@ -326,13 +326,13 @@ Status GatherV2Kernel::Check(const OpDescPtr &op_desc_ptr, const vectorGetTensorDesc().GetShape(); // axis must be scalar if (axis_shape.GetDimNum() != 0) { - GELOGE(NOT_CHANGED, "axis must be scalar but its shape is %zu", axis_shape.GetDimNum()); + GELOGW("axis must be scalar but its shape is %zu", axis_shape.GetDimNum()); return NOT_CHANGED; } auto axis_data_type = tensor2->GetTensorDesc().GetDataType(); bool is_valid_axis_data_type = axis_data_type == DT_INT32 || axis_data_type == DT_INT64; if (!is_valid_axis_data_type) { - GELOGE(NOT_CHANGED, "axis datatype must be DT_INT32 or DT_INT64"); + GELOGW("axis datatype must be DT_INT32 or DT_INT64"); return NOT_CHANGED; } @@ -340,11 +340,11 @@ Status GatherV2Kernel::Check(const OpDescPtr &op_desc_ptr, const vectorGetTensorDesc().GetDataType(); bool is_valid_indices_data_type = indices_data_type == DT_INT32 || indices_data_type == DT_INT64; if (!is_valid_indices_data_type) { - GELOGE(NOT_CHANGED, "indices datatype must be DT_INT32 or DT_INT64"); + GELOGW("indices datatype must be DT_INT32 or DT_INT64"); return NOT_CHANGED; } if (indices_shape.GetDimNum() > kMaxIndicatesDims) { - GELOGE(NOT_CHANGED, "indices input only support 0 or 1 dims"); + GELOGW("indices input only support 0 or 1 dims"); return NOT_CHANGED; } return SUCCESS; @@ -372,7 +372,7 @@ Status GatherV2Kernel::Compute(const OpDescPtr op_desc_ptr, const vectorGetName().c_str()); @@ -390,13 +390,13 @@ Status GatherV2Kernel::Compute(const OpDescPtr op_desc_ptr, const vector= 0 ? axis : axis + x_shape.GetDimNum(); // check axis value if (axis < 0 || (axis + 1) > static_cast(x_shape.GetDimNum())) { - GELOGE(NOT_CHANGED, "axis is invalid"); + GELOGW("axis is invalid"); return NOT_CHANGED; } auto indices_data_type = tensor1->GetTensorDesc().GetDataType(); ret = SaveIndicesByDataType(tensor1, x_shape, indices_shape, indices_data_type, static_cast(axis)); if (ret != SUCCESS) { - GELOGE(NOT_CHANGED, "Save indeices by data type failed!"); + GELOGW("Save indeices by data type failed!"); return ret; } @@ -420,7 +420,7 @@ Status GatherV2Kernel::Compute(const OpDescPtr op_desc_ptr, const vector(op_desc_ptr->GetOutputDesc(0)); if (output_ptr == nullptr) { - GELOGE(MEMALLOC_FAILED, "make_shared ge::GeTensor failed, node name %s.", op_desc_ptr->GetName().c_str()); + GELOGW("make_shared ge::GeTensor failed, node name %s.", op_desc_ptr->GetName().c_str()); return NOT_CHANGED; } output_ptr->MutableTensorDesc().SetShape(GeShape(y_shape)); diff --git a/src/ge/host_kernels/identity_kernel.cc b/src/ge/host_kernels/identity_kernel.cc new file mode 100644 index 00000000..16bd3138 --- /dev/null +++ b/src/ge/host_kernels/identity_kernel.cc @@ -0,0 +1,63 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "identity_kernel.h" +#include "inc/kernel_factory.h" + +namespace { +constexpr uint32_t kInputDescIndex = 0; +constexpr uint32_t kOutputDescIndex = 0; +} // namespace + +namespace ge { +Status IdentityKernel::Compute(const ge::OpDescPtr op_desc, const std::vector &input, + std::vector &v_output) { + if (op_desc == nullptr) { + GELOGE(PARAM_INVALID, "IdentityKernel op_desc is null."); + return NOT_CHANGED; + } + if (input.empty()) { + GELOGE(PARAM_INVALID, "Node [%s] inputs is empty.", op_desc->GetName().c_str()); + return NOT_CHANGED; + } + if (op_desc->GetOutputsSize() < 1) { + GELOGE(PARAM_INVALID, "Node [%s] output is empty.", op_desc->GetName().c_str()); + return NOT_CHANGED; + } + GELOGD("IdentityKernel in: node[%s]", op_desc->GetName().c_str()); + + auto out_tensor_desc = op_desc->GetOutputDesc(kOutputDescIndex); + GeTensorPtr output_ptr = MakeShared(out_tensor_desc); + if (output_ptr == nullptr) { + GELOGE(OUT_OF_MEMORY, "Node [%s] make shared failed.", op_desc->GetName().c_str()); + return OUT_OF_MEMORY; + } + auto input_tensor_ptr = input.at(kInputDescIndex); + if (input_tensor_ptr == nullptr) { + GELOGE(PARAM_INVALID, "Node [%s] get input failed.", op_desc->GetName().c_str()); + return NOT_CHANGED; + } + if (output_ptr->SetData(input_tensor_ptr->GetData()) != GRAPH_SUCCESS) { + GELOGW("Compute: SetData failed"); + return NOT_CHANGED; + } + v_output.emplace_back(output_ptr); + GELOGD("IdentityKernel success: node[%s]", op_desc->GetName().c_str()); + + return SUCCESS; +} +REGISTER_KERNEL(IDENTITY, IdentityKernel); +} // namespace ge diff --git a/src/ge/host_kernels/identity_kernel.h b/src/ge/host_kernels/identity_kernel.h new file mode 100644 index 00000000..2164d880 --- /dev/null +++ b/src/ge/host_kernels/identity_kernel.h @@ -0,0 +1,31 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_GRAPH_PASSES_FOLDING_KERNEL_IDENTITY_KERNEL_H_ +#define GE_GRAPH_PASSES_FOLDING_KERNEL_IDENTITY_KERNEL_H_ + +#include "inc/kernel.h" +#include + +namespace ge { +class IdentityKernel : public Kernel { + public: + Status Compute(const ge::OpDescPtr op_desc_ptr, const std::vector &input, + std::vector &v_output) override; +}; +} // namespace ge + +#endif // GE_GRAPH_PASSES_FOLDING_KERNEL_IDENTITY_KERNEL_H_ diff --git a/src/ge/host_kernels/pack_kernel.cc b/src/ge/host_kernels/pack_kernel.cc index f3f64a6c..9b62a582 100644 --- a/src/ge/host_kernels/pack_kernel.cc +++ b/src/ge/host_kernels/pack_kernel.cc @@ -63,7 +63,7 @@ Status PackKernel::Compute(const ge::OpDescPtr op_desc_ptr, const std::vector &input) { if (op_desc_ptr == nullptr) { - GELOGE(PARAM_INVALID, "input opdesc is nullptr."); + GELOGW("input opdesc is nullptr."); return PARAM_INVALID; } if (!(AttrUtils::GetInt(op_desc_ptr, PACK_ATTR_NAME_NUM, n_))) { @@ -71,16 +71,15 @@ Status PackKernel::ValidateKernelParams(const ge::OpDescPtr &op_desc_ptr, GELOGD("Attr %s is not set, default value %ld is used.", PACK_ATTR_NAME_NUM.c_str(), n_); } if (!(AttrUtils::GetInt(op_desc_ptr, ATTR_NAME_AXIS, axis_))) { - GELOGE(PARAM_INVALID, "Attr %s is not exist.", ATTR_NAME_AXIS.c_str()); + GELOGW("Attr %s is not exist.", ATTR_NAME_AXIS.c_str()); return PARAM_INVALID; } if (input.empty()) { - GELOGE(PARAM_INVALID, "The number of input for Pack should be %ld, in fact it is %zu ", n_, input.size()); + GELOGW("The number of input for Pack should be %ld, in fact it is %zu ", n_, input.size()); return NOT_CHANGED; } if (input.size() != static_cast(n_)) { - GELOGE(PARAM_INVALID, "The number of input for Pack should be %d, in fact it is %ld ", static_cast(n_), - input.size()); + GELOGW("The number of input for Pack should be %d, in fact it is %ld ", static_cast(n_), input.size()); return PARAM_INVALID; } data_type_ = op_desc_ptr->GetInputDesc(0).GetDataType(); diff --git a/src/ge/host_kernels/permute_kernel.cc b/src/ge/host_kernels/permute_kernel.cc index 8263d19f..24bed54d 100644 --- a/src/ge/host_kernels/permute_kernel.cc +++ b/src/ge/host_kernels/permute_kernel.cc @@ -110,14 +110,14 @@ Status PermuteKernel::Compute(const OpDescPtr op_desc_ptr, const std::vectorGetData().data(); formats::TransResult trans_result; auto ret = formats::TransposeWithShapeCheck(src_data, src_shape, data_shape, src_data_type, perm_list, trans_result); if (ret != SUCCESS) { - GELOGE(INTERNAL_ERROR, "Failed to Transpose from %s to %s, shape %s to %s, perm_list %s, data type %s", + GELOGW("Failed to Transpose from %s to %s, shape %s to %s, perm_list %s, data type %s", TypeUtils::FormatToSerialString(src_format).c_str(), TypeUtils::FormatToSerialString(data_format).c_str(), formats::ShapeToString(src_shape).c_str(), formats::ShapeToString(data_shape).c_str(), formats::ShapeToString(perm_list).c_str(), TypeUtils::DataTypeToSerialString(src_data_type).c_str()); diff --git a/src/ge/host_kernels/rank_kernel.cc b/src/ge/host_kernels/rank_kernel.cc index faaf16b8..c8763aef 100644 --- a/src/ge/host_kernels/rank_kernel.cc +++ b/src/ge/host_kernels/rank_kernel.cc @@ -49,7 +49,7 @@ Status RankKernel::Compute(const NodePtr &node, std::vector &v_outp auto ndims = input_shape->GetShape().GetDimNum(); GeTensorDesc tensor_desc(op_desc->GetOutputDesc(0)); GeTensorPtr output_ptr; - output_ptr = MakeShared(tensor_desc, reinterpret_cast(&ndims), sizeof(ndims)); + output_ptr = MakeShared(tensor_desc, reinterpret_cast(&ndims), GetSizeByDataType(DT_INT32)); if (output_ptr == nullptr) { GELOGE(MEMALLOC_FAILED, "make_shared ge::GeTensor failed"); return MEMALLOC_FAILED; diff --git a/src/ge/host_kernels/reduce_prod_kernel.cc b/src/ge/host_kernels/reduce_prod_kernel.cc index 479b50ab..739d4b9f 100644 --- a/src/ge/host_kernels/reduce_prod_kernel.cc +++ b/src/ge/host_kernels/reduce_prod_kernel.cc @@ -51,7 +51,7 @@ Status ReduceProdKernel::ReduceProdCheck(const ge::OpDescPtr &op_desc_ptr, op_desc_ptr->GetName().c_str()); return NOT_CHANGED; } - GELOGE(PARAM_INVALID, "Unexpected ReduceProd node, node input size: %zu, node name: %s", input.size(), + GELOGW("Unexpected ReduceProd node, node input size: %zu, node name: %s", input.size(), op_desc_ptr->GetName().c_str()); return PARAM_INVALID; } @@ -60,13 +60,13 @@ Status ReduceProdKernel::ReduceProdCheck(const ge::OpDescPtr &op_desc_ptr, GE_CHECK_NOTNULL(data_tensor); GE_CHECK_NOTNULL(axis_tensor); if (axis_tensor->GetTensorDesc().GetShape().GetDimNum() > kReduceProdMaxAxisRank) { - GELOGE(PARAM_INVALID, "Axis must be at most rank 1, node node: %s", op_desc_ptr->GetName().c_str()); + GELOGW("Axis must be at most rank 1, node node: %s", op_desc_ptr->GetName().c_str()); return PARAM_INVALID; } DataType data_type = data_tensor->GetTensorDesc().GetDataType(); if (kReduceProdSupportedType.find(data_type) == kReduceProdSupportedType.end()) { - GELOGE(PARAM_INVALID, "ReduceProdKernel data type %s not support, node name: %s", + GELOGW("ReduceProdKernel data type %s not support, node name: %s", TypeUtils::DataTypeToSerialString(data_type).c_str(), op_desc_ptr->GetName().c_str()); return PARAM_INVALID; } @@ -83,7 +83,7 @@ Status ReduceProdKernel::AxisCal(const std::vector &input) int32_t *axis = const_cast(reinterpret_cast(axis_tensor->GetData().GetData())); GE_CHECK_NOTNULL(axis); if (static_cast(*axis) >= data_dim_size) { - GELOGE(PARAM_INVALID, "axis is out of rank of data_dims, axis is %d.", *axis); + GELOGW("axis is out of rank of data_dims, axis is %d.", *axis); return PARAM_INVALID; } axis_dim_ = data_dims[static_cast(*axis)]; @@ -98,13 +98,13 @@ Status ReduceProdKernel::AxisCal(const std::vector &input) // data_dims is the vector of dims, element in data_dims isn't negative. if (axis_appear) { if (data_dims[i] != 0 && end_dim_ > (INT64_MAX / data_dims[i])) { - GELOGE(INTERNAL_ERROR, "Product is overflow. multiplier 1: %ld. multiplier 2: %ld.", end_dim_, data_dims[i]); + GELOGW("Product is overflow. multiplier 1: %ld. multiplier 2: %ld.", end_dim_, data_dims[i]); return INTERNAL_ERROR; } end_dim_ *= data_dims[i]; } else { if (data_dims[i] != 0 && head_dim_ > (INT64_MAX / data_dims[i])) { - GELOGE(INTERNAL_ERROR, "Product is overflow. multiplier 1: %ld. multiplier 2: %ld.", head_dim_, data_dims[i]); + GELOGW("Product is overflow. multiplier 1: %ld. multiplier 2: %ld.", head_dim_, data_dims[i]); return INTERNAL_ERROR; } head_dim_ *= data_dims[i]; @@ -122,7 +122,7 @@ Status ReduceProdKernel::DataCal(const std::vector &input, size_t data_num = data_tensor->GetData().size() / sizeof(int32_t); unique_ptr buf(new (std::nothrow) int32_t[data_num]()); if (buf == nullptr) { - GELOGE(MEMALLOC_FAILED, "new buf failed"); + GELOGW("new buf failed"); return INTERNAL_ERROR; } @@ -190,12 +190,12 @@ Status ReduceProdKernel::ComputeNoAxis(const ge::OpDescPtr &op_desc_ptr, const s ConstGeTensorPtr data_tensor = input.at(kReduceProdDataIndex); GE_CHECK_NOTNULL(data_tensor); if (data_tensor->GetData().size() == 0) { - GELOGE(PARAM_INVALID, "ReduceProdKernel data size of inputs is 0, node node: %s", op_desc_ptr->GetName().c_str()); + GELOGW("ReduceProdKernel data size of inputs is 0, node node: %s", op_desc_ptr->GetName().c_str()); return PARAM_INVALID; } DataType data_type = data_tensor->GetTensorDesc().GetDataType(); if (kReduceProdSupportedType.find(data_type) == kReduceProdSupportedType.end()) { - GELOGE(PARAM_INVALID, "ReduceProdKernel data type %s not support, node name: %s", + GELOGW("ReduceProdKernel data type %s not support, node name: %s", TypeUtils::DataTypeToSerialString(data_type).c_str(), op_desc_ptr->GetName().c_str()); return PARAM_INVALID; } @@ -206,7 +206,7 @@ Status ReduceProdKernel::ComputeNoAxis(const ge::OpDescPtr &op_desc_ptr, const s size_t data_num = data_tensor->GetData().size() / sizeof(int32_t); unique_ptr buf(new (std::nothrow) int32_t[data_num]()); if (buf == nullptr) { - GELOGE(MEMALLOC_FAILED, "new buf failed"); + GELOGW("new buf failed"); return INTERNAL_ERROR; } @@ -235,7 +235,7 @@ Status ReduceProdKernel::Compute(const ge::OpDescPtr op_desc_ptr, const std::vec GELOGI("ReduceProdKernel in."); Status ret = ReduceProdCheck(op_desc_ptr, input); if (ret != SUCCESS && ret != NOT_CHANGED) { - GELOGE(PARAM_INVALID, "ReduceProdKernel input is invalid, failed to fold node."); + GELOGW("ReduceProdKernel input is invalid, failed to fold node."); return NOT_CHANGED; } @@ -243,7 +243,7 @@ Status ReduceProdKernel::Compute(const ge::OpDescPtr op_desc_ptr, const std::vec auto output_tensor_desc = op_desc_ptr->GetOutputDesc(0); GeTensorPtr output_ptr = MakeShared(output_tensor_desc); if (output_ptr == nullptr) { - GELOGE(MEMALLOC_FAILED, "make_shared ge::GeTensor failed, node name %s.", op_desc_ptr->GetName().c_str()); + GELOGW("make_shared ge::GeTensor failed, node name %s.", op_desc_ptr->GetName().c_str()); return NOT_CHANGED; } diff --git a/src/ge/host_kernels/reformat_kernel.cc b/src/ge/host_kernels/reformat_kernel.cc index 33a13599..c2dd1e17 100644 --- a/src/ge/host_kernels/reformat_kernel.cc +++ b/src/ge/host_kernels/reformat_kernel.cc @@ -56,7 +56,7 @@ Status ReFormatKernel::Compute(const OpDescPtr op_desc_ptr, const std::vectorGetTensorDesc().GetShape()).c_str()); return NOT_CHANGED; } GeTensorPtr output_ptr = MakeShared(op_desc_ptr->GetOutputDesc(kReformatFirstOutput)); if (output_ptr == nullptr) { - GELOGE(INTERNAL_ERROR, "Create shared ptr for GeTensor failed"); + GELOGW("Create shared ptr for GeTensor failed"); return NOT_CHANGED; } - GE_IF_BOOL_EXEC(output_ptr->SetData(input.at(0)->GetData()) != GRAPH_SUCCESS, - GELOGE(INTERNAL_ERROR, "set data failed"); + GE_IF_BOOL_EXEC(output_ptr->SetData(input.at(0)->GetData()) != GRAPH_SUCCESS, GELOGW("set data failed"); return NOT_CHANGED); v_output.emplace_back(output_ptr); GELOGD("ReFormatKernel success."); diff --git a/src/ge/host_kernels/reshape_kernel.cc b/src/ge/host_kernels/reshape_kernel.cc index 906624d2..dc7e4bb8 100644 --- a/src/ge/host_kernels/reshape_kernel.cc +++ b/src/ge/host_kernels/reshape_kernel.cc @@ -67,7 +67,7 @@ Status ReshapeKernel::Compute(const ge::OpDescPtr op_desc_ptr, const std::vector auto output_tensor_desc = op_desc_ptr->GetOutputDesc(kOutputDescFirstIndex); GeTensorPtr output_ptr = MakeShared(output_tensor_desc); if (output_ptr == nullptr) { - GELOGE(MEMALLOC_FAILED, "Failed to fold node %s, out of memory", op_desc_ptr->GetName().c_str()); + GELOGW("Failed to fold node %s, out of memory", op_desc_ptr->GetName().c_str()); return NOT_CHANGED; } diff --git a/src/ge/host_kernels/rsqrt_kernel.cc b/src/ge/host_kernels/rsqrt_kernel.cc index 3e14fd5f..56972d23 100644 --- a/src/ge/host_kernels/rsqrt_kernel.cc +++ b/src/ge/host_kernels/rsqrt_kernel.cc @@ -64,7 +64,7 @@ Status RsqrtKernel::Compute(const OpDescPtr op_desc_ptr, const std::vector 0) { unique_ptr buf(new (std::nothrow) float[data_count]()); if (buf == nullptr) { - GELOGE(MEMALLOC_FAILED, "new buf failed"); + GELOGW("new buf failed"); return NOT_CHANGED; } @@ -81,13 +81,13 @@ Status RsqrtKernel::Compute(const OpDescPtr op_desc_ptr, const std::vectorGetOutputDesc(0); GeTensorPtr output_ptr = MakeShared(output_tensor_desc); if (output_ptr == nullptr) { - GELOGE(MEMALLOC_FAILED, "MakeShared GeTensor failed, node name %s.", op_desc_ptr->GetName().c_str()); + GELOGW("MakeShared GeTensor failed, node name %s.", op_desc_ptr->GetName().c_str()); return NOT_CHANGED; } output_ptr->MutableTensorDesc().SetDataType(DT_FLOAT); GE_IF_BOOL_EXEC(output_ptr->SetData(reinterpret_cast(buf.get()), data_size) != GRAPH_SUCCESS, - GELOGE(INTERNAL_ERROR, "set data failed"); + GELOGW("set data failed"); return NOT_CHANGED); output_ptr->MutableTensorDesc().SetShape(x_shape); v_output.push_back(output_ptr); diff --git a/src/ge/host_kernels/slice_d_kernel.cc b/src/ge/host_kernels/slice_d_kernel.cc index ad0a1675..3b8fd0a0 100644 --- a/src/ge/host_kernels/slice_d_kernel.cc +++ b/src/ge/host_kernels/slice_d_kernel.cc @@ -129,7 +129,7 @@ Status SliceDKernel::Compute(const OpDescPtr op_desc_ptr, const std::vectorGetOutputDesc(0); GeTensorPtr output_ptr = MakeShared(output_tensor_desc); if (output_ptr == nullptr) { - GELOGE(MEMALLOC_FAILED, "Failed to fold node %s, out of memory", op_desc_ptr->GetName().c_str()); + GELOGW("Failed to fold node %s, out of memory", op_desc_ptr->GetName().c_str()); return NOT_CHANGED; } @@ -143,8 +143,14 @@ Status SliceDKernel::Compute(const OpDescPtr op_desc_ptr, const std::vector(const_cast(x_tensor->GetData().data())); int64_t x_data_size = x_tensor->GetTensorDesc().GetShape().GetShapeSize(); - Status ret = OpUtils::SetOutputSliceData(data, x_data_size, x_data_type, x_dims, begin_list, size_list, - output_ptr.get(), stride_list); + + Status ret = CheckOutputDims(size_list, op_desc_ptr); + if (ret != SUCCESS) { + return ret; + } + + ret = OpUtils::SetOutputSliceData(data, x_data_size, x_data_type, x_dims, begin_list, size_list, output_ptr.get(), + stride_list); if (ret != SUCCESS) { GELOGW("Set output data of SliceD failed."); return NOT_CHANGED; @@ -155,5 +161,16 @@ Status SliceDKernel::Compute(const OpDescPtr op_desc_ptr, const std::vector &output_dims, const OpDescPtr attr) { + // check dim not all less than 0 + for (auto dim : output_dims) { + if (dim > 0) { + return SUCCESS; + } + } + GELOGW("all output dim <=0, can't be processed. op_name : %s", attr->GetName().c_str()); + return NOT_CHANGED; +} + REGISTER_KERNEL(SLICED, SliceDKernel); } // namespace ge diff --git a/src/ge/host_kernels/slice_d_kernel.h b/src/ge/host_kernels/slice_d_kernel.h index 9fe35352..90ef9b8b 100644 --- a/src/ge/host_kernels/slice_d_kernel.h +++ b/src/ge/host_kernels/slice_d_kernel.h @@ -29,6 +29,7 @@ class SliceDKernel : public Kernel { private: Status SliceDCheck(const OpDescPtr &op_desc_ptr, const std::vector &input, std::vector &begin_list, std::vector &size_list); + Status CheckOutputDims(const std::vector &output_dims, const OpDescPtr attr); }; } // namespace ge diff --git a/src/ge/host_kernels/slice_kernel.cc b/src/ge/host_kernels/slice_kernel.cc index 1d7d90c2..5f72fc49 100644 --- a/src/ge/host_kernels/slice_kernel.cc +++ b/src/ge/host_kernels/slice_kernel.cc @@ -21,8 +21,8 @@ #include "common/types.h" #include "common/util.h" #include "framework/common/debug/ge_log.h" -#include "host_kernels/kernel_utils.h" #include "graph/utils/type_utils.h" +#include "host_kernels/kernel_utils.h" #include "inc/kernel_factory.h" namespace ge { diff --git a/src/ge/host_kernels/ssd_prior_box_kernel.cc b/src/ge/host_kernels/ssd_prior_box_kernel.cc index c874d732..9de5a08d 100644 --- a/src/ge/host_kernels/ssd_prior_box_kernel.cc +++ b/src/ge/host_kernels/ssd_prior_box_kernel.cc @@ -365,7 +365,7 @@ Status SsdPriorboxKernel::Compute(const NodePtr &node, std::vector // make TensorDesc GeTensorPtr output_ptr = MakeShared(output_tensor_desc); if (output_ptr == nullptr) { - GELOGE(INTERNAL_ERROR, "Create shared ptr for GeTensor failed"); + GELOGW("Create shared ptr for GeTensor failed"); return NOT_CHANGED; } GE_IF_BOOL_EXEC(output_ptr->SetData(reinterpret_cast(output_data.get()), diff --git a/src/ge/host_kernels/strided_slice_kernel.cc b/src/ge/host_kernels/strided_slice_kernel.cc index 0d70a36a..6a9a558c 100644 --- a/src/ge/host_kernels/strided_slice_kernel.cc +++ b/src/ge/host_kernels/strided_slice_kernel.cc @@ -46,31 +46,31 @@ Status StridedSliceKernel::CheckAndGetAttr(const OpDescPtr &attr, const std::vec int64_t shrink_axis_mask = 0; if (attr == nullptr) { - GELOGE(PARAM_INVALID, "input opdescptr is nullptr."); + GELOGW("input opdescptr is nullptr."); return PARAM_INVALID; } if (input.size() != kStridedSliceInputSize) { - GELOGE(PARAM_INVALID, "The number of input for strided slice must be %zu.", kStridedSliceInputSize); + GELOGW("The number of input for strided slice must be %zu.", kStridedSliceInputSize); return PARAM_INVALID; } if (!AttrUtils::GetInt(attr, STRIDE_SLICE_ATTR_BEGIN_MASK, begin_mask)) { - GELOGE(PARAM_INVALID, "get begin_mask attr failed."); + GELOGW("get begin_mask attr failed."); return PARAM_INVALID; } if (!AttrUtils::GetInt(attr, STRIDE_SLICE_ATTR_END_MASK, end_mask)) { - GELOGE(PARAM_INVALID, "get end_mask attr failed."); + GELOGW("get end_mask attr failed."); return PARAM_INVALID; } if (!AttrUtils::GetInt(attr, STRIDE_SLICE_ATTR_ELLIPSIS_MASK, ellipsis_mask)) { - GELOGE(PARAM_INVALID, "get ellipsis_mask attr failed."); + GELOGW("get ellipsis_mask attr failed."); return PARAM_INVALID; } if (!AttrUtils::GetInt(attr, STRIDE_SLICE_ATTR_NEW_AXIS_MASK, new_axis_mask)) { - GELOGE(PARAM_INVALID, "get new_axis_mask attr failed."); + GELOGW("get new_axis_mask attr failed."); return PARAM_INVALID; } if (!AttrUtils::GetInt(attr, STRIDE_SLICE_ATTR_SHRINK_AXIS_MASK, shrink_axis_mask)) { - GELOGE(PARAM_INVALID, "get shrink_axis_mask attr failed."); + GELOGW("get shrink_axis_mask attr failed."); return PARAM_INVALID; } if ((ellipsis_mask != 0) || (new_axis_mask != 0)) { @@ -98,7 +98,7 @@ Status StridedSliceKernel::CheckAndGetAttr(const OpDescPtr &attr, const std::vec ConstGeTensorPtr weight2 = input[kStridedSliceInputIndex2]; ConstGeTensorPtr weight3 = input[kStridedSliceInputIndex3]; if (CheckWeight(weight0, weight1, weight2, weight3) != SUCCESS) { - GELOGE(PARAM_INVALID, "Check And Get Attr failed."); + GELOGW("Check And Get Attr failed."); return PARAM_INVALID; } @@ -168,6 +168,17 @@ void StridedSliceKernel::GetOutputDims(uint32_t dims_size, const std::vector &output_dims, const OpDescPtr attr) { + // check dim not all less than 0 + for (auto dim : output_dims) { + if (dim > 0) { + return SUCCESS; + } + } + GELOGW("all output dim <=0, can't be processed. op_name : %s", attr->GetName().c_str()); + return NOT_CHANGED; +} + Status StridedSliceKernel::Compute(const ge::OpDescPtr attr, const std::vector &input, vector &v_output) { GELOGI("StridedSliceKernel in."); @@ -191,7 +202,7 @@ Status StridedSliceKernel::Compute(const ge::OpDescPtr attr, const std::vector(weight2->GetData().data()); const int32_t *stride = reinterpret_cast(weight3->GetData().data()); if ((begin == nullptr) || (end == nullptr) || (stride == nullptr)) { - GELOGE(PARAM_INVALID, "input weight tensor is nullptr."); + GELOGW("input weight tensor is nullptr."); return NOT_CHANGED; } @@ -237,16 +248,22 @@ Status StridedSliceKernel::Compute(const ge::OpDescPtr attr, const std::vectorGetOutputDesc(0); GeTensorPtr output_ptr = MakeShared(output_tensor_desc); if (output_ptr == nullptr) { - GELOGE(MEMALLOC_FAILED, "MakeShared GeTensor failed, node name %s.", attr->GetName().c_str()); + GELOGW("MakeShared GeTensor failed, node name %s.", attr->GetName().c_str()); return NOT_CHANGED; } void *data = reinterpret_cast(const_cast(weight0->GetData().data())); GE_CHECK_NOTNULL(data); + + ret = CheckOutputDims(output_dims, attr); + if (ret != SUCCESS) { + return ret; + } + ret = OpUtils::SetOutputSliceData(data, static_cast(data_size), args.data_type, input_dims, begin_vec, output_dims, output_ptr.get(), stride_vec); if (ret != SUCCESS) { - GELOGE(INTERNAL_ERROR, "SetOutputSliceData failed."); + GELOGW("SetOutputSliceData failed."); return NOT_CHANGED; } diff --git a/src/ge/host_kernels/strided_slice_kernel.h b/src/ge/host_kernels/strided_slice_kernel.h index e569b2d0..0ba3afbd 100644 --- a/src/ge/host_kernels/strided_slice_kernel.h +++ b/src/ge/host_kernels/strided_slice_kernel.h @@ -44,6 +44,7 @@ class StridedSliceKernel : public Kernel { int32_t &end_i, int32_t &dim_i) const; void GetOutputDims(uint32_t dims_size, const std::vector &output_dims, const Attr &args, vector &v_dims); + Status CheckOutputDims(const std::vector &output_dims, const OpDescPtr attr); }; } // namespace ge #endif // GE_GRAPH_PASSES_FOLDING_KERNEL_STRIDED_SLICE_KERNEL_H_ diff --git a/src/ge/host_kernels/sub_kernel.cc b/src/ge/host_kernels/sub_kernel.cc index ed1e5808..70a14c9f 100644 --- a/src/ge/host_kernels/sub_kernel.cc +++ b/src/ge/host_kernels/sub_kernel.cc @@ -162,7 +162,7 @@ Status SubKernel::Compute(const ge::OpDescPtr op_desc_ptr, const std::vectorGetOutputDesc(kSubFirstOutput); GeTensorPtr output_ptr = MakeShared(output_tensor_desc); if (output_ptr == nullptr) { - GELOGE(MEMALLOC_FAILED, "make_shared ge::GeTensor failed, node name %s.", op_desc_ptr->GetName().c_str()); + GELOGW("make_shared ge::GeTensor failed, node name %s.", op_desc_ptr->GetName().c_str()); return NOT_CHANGED; } diff --git a/src/ge/host_kernels/transdata_kernel.cc b/src/ge/host_kernels/transdata_kernel.cc index 5fe44fe4..c5c9da6e 100644 --- a/src/ge/host_kernels/transdata_kernel.cc +++ b/src/ge/host_kernels/transdata_kernel.cc @@ -113,7 +113,7 @@ Status TransdataKernel::Compute(const OpDescPtr op_desc_ptr, const std::vectorGetData().data(); formats::TransResult trans_result; auto ret = formats::TransposeWithShapeCheck(src_data, src_shape, data_shape, src_data_type, perm_list, trans_result); if (ret != SUCCESS) { - GELOGE(INTERNAL_ERROR, "Failed to Transpose from %s to %s, shape %s to %s, perm_list %s, data type %s", + GELOGW("Failed to Transpose from %s to %s, shape %s to %s, perm_list %s, data type %s", TypeUtils::FormatToSerialString(src_format).c_str(), TypeUtils::FormatToSerialString(data_format).c_str(), formats::ShapeToString(src_shape).c_str(), formats::ShapeToString(data_shape).c_str(), formats::ShapeToString(perm_list).c_str(), TypeUtils::DataTypeToSerialString(src_data_type).c_str()); diff --git a/src/ge/hybrid/common/npu_memory_allocator.cc b/src/ge/hybrid/common/npu_memory_allocator.cc index f432318b..1908725f 100644 --- a/src/ge/hybrid/common/npu_memory_allocator.cc +++ b/src/ge/hybrid/common/npu_memory_allocator.cc @@ -25,6 +25,11 @@ namespace hybrid { std::map> NpuMemoryAllocator::allocators_; std::mutex NpuMemoryAllocator::mu_; +AllocationAttr::AllocationAttr(int padding, void *try_reuse_addr) + : padding_(padding), try_reuse_addr_(try_reuse_addr) {} +AllocationAttr::AllocationAttr(int padding) : AllocationAttr(padding, nullptr) {} +AllocationAttr::AllocationAttr(void *try_reuse_addr) : AllocationAttr(0, try_reuse_addr) {} + NpuMemoryAllocator *NpuMemoryAllocator::GetAllocator() { int32_t device_id = 0; if (rtGetDevice(&device_id) != RT_ERROR_NONE) { @@ -38,15 +43,26 @@ NpuMemoryAllocator *NpuMemoryAllocator::GetAllocator() { NpuMemoryAllocator::NpuMemoryAllocator(uint32_t device_id) : device_id_(device_id) {} -void *NpuMemoryAllocator::Allocate(std::size_t size, void *try_reuse_addr) { - void *buffer = - MemManager::CachingInstance(RT_MEMORY_HBM).Malloc(size, reinterpret_cast(try_reuse_addr), device_id_); +void *NpuMemoryAllocator::Allocate(std::size_t size, AllocationAttr *attr) { + void *try_reuse_addr = nullptr; + size_t allocate_size = size; + if (attr != nullptr) { + try_reuse_addr = attr->try_reuse_addr_; + if (attr->padding_ != 0) { + // padding up to multiple of attr->padding, and add extra attr->padding_ + allocate_size = (size + 2 * attr->padding_ - 1) / attr->padding_ * attr->padding_; + GELOGD("Padding size %ld by %d. final size = %zu.", size, attr->padding_, allocate_size); + } + } + + void *buffer = MemManager::CachingInstance(RT_MEMORY_HBM) + .Malloc(allocate_size, reinterpret_cast(try_reuse_addr), device_id_); if (buffer == nullptr) { - GELOGE(MEMALLOC_FAILED, "Failed to malloc memory, device_id = %u, size = %zu", device_id_, size); + GELOGE(MEMALLOC_FAILED, "Failed to malloc memory, device_id = %u, size = %zu", device_id_, allocate_size); return nullptr; } - GELOGI("Allocating buffer of size %u successfully. device_id = %u, address = %p", size, device_id_, buffer); + GELOGI("Allocating buffer of size %zu successfully. device_id = %u, address = %p", allocate_size, device_id_, buffer); return buffer; } diff --git a/src/ge/hybrid/common/npu_memory_allocator.h b/src/ge/hybrid/common/npu_memory_allocator.h index 8cfeafa6..a9744540 100644 --- a/src/ge/hybrid/common/npu_memory_allocator.h +++ b/src/ge/hybrid/common/npu_memory_allocator.h @@ -26,16 +26,35 @@ namespace ge { namespace hybrid { +class AllocationAttr { + public: + explicit AllocationAttr(int padding); + explicit AllocationAttr(void *try_reuse_addr); + AllocationAttr(int padding, void *try_reuse_addr); + ~AllocationAttr() = default; + + private: + friend class NpuMemoryAllocator; + int padding_ = 0; + void *try_reuse_addr_ = nullptr; +}; + class NpuMemoryAllocator { public: ~NpuMemoryAllocator() = default; static NpuMemoryAllocator *GetAllocator(uint32_t device_id); static NpuMemoryAllocator *GetAllocator(); static void DestroyAllocator(); + static AllocationAttr *AttrWithDefaultPadding() { + static AllocationAttr attr(kDefaultPadding, nullptr); + return &attr; + } - void *Allocate(std::size_t size, void *try_reuse_addr = nullptr); + void *Allocate(std::size_t size, AllocationAttr *attr = nullptr); void Deallocate(void *data); + static constexpr int kDefaultPadding = 32; + private: explicit NpuMemoryAllocator(uint32_t device_id); uint32_t device_id_; diff --git a/src/ge/hybrid/common/tensor_value.cc b/src/ge/hybrid/common/tensor_value.cc index 9544e03a..929d3c87 100644 --- a/src/ge/hybrid/common/tensor_value.cc +++ b/src/ge/hybrid/common/tensor_value.cc @@ -24,7 +24,7 @@ namespace hybrid { TensorBuffer::TensorBuffer(NpuMemoryAllocator *allocator, void *buffer, size_t size) : allocator_(allocator), buffer_(buffer), size_(size) {} -std::unique_ptr TensorBuffer::Create(NpuMemoryAllocator *allocator, size_t size) { +std::unique_ptr TensorBuffer::Create(NpuMemoryAllocator *allocator, size_t size, AllocationAttr *attr) { void *buffer = nullptr; if (size == 0) { GELOGD("size is 0"); @@ -36,7 +36,7 @@ std::unique_ptr TensorBuffer::Create(NpuMemoryAllocator *allocator return nullptr; } - buffer = allocator->Allocate(size); + buffer = allocator->Allocate(size, attr); if (buffer == nullptr) { GELOGE(MEMALLOC_FAILED, "Failed to allocate memory. size = %zu", size); return nullptr; diff --git a/src/ge/hybrid/common/tensor_value.h b/src/ge/hybrid/common/tensor_value.h index 18e67534..db8df9e5 100644 --- a/src/ge/hybrid/common/tensor_value.h +++ b/src/ge/hybrid/common/tensor_value.h @@ -24,10 +24,12 @@ namespace ge { namespace hybrid { class NpuMemoryAllocator; +class AllocationAttr; class TensorBuffer { public: - static std::unique_ptr Create(NpuMemoryAllocator *allocator, size_t size); + static std::unique_ptr Create(NpuMemoryAllocator *allocator, size_t size, + AllocationAttr *attr = nullptr); static std::unique_ptr Create(void *buffer, size_t size); diff --git a/src/ge/hybrid/executor/hybrid_execution_context.cc b/src/ge/hybrid/executor/hybrid_execution_context.cc index bb8e0195..8144ba52 100644 --- a/src/ge/hybrid/executor/hybrid_execution_context.cc +++ b/src/ge/hybrid/executor/hybrid_execution_context.cc @@ -17,34 +17,5 @@ #include "hybrid_execution_context.h" namespace ge { -namespace hybrid { -NodeStatePtr GraphExecutionContext::GetOrCreateNodeState(const NodePtr &node) { - auto &node_state = node_states[node]; - if (node_state == nullptr) { - const NodeItem *node_item = model->GetNodeItem(node); - if (node_item == nullptr) { - return nullptr; - } - node_state.reset(new (std::nothrow) NodeState(*node_item)); - } - - return node_state; -} - -void GraphExecutionContext::OnError(Status error_code) { - GELOGE(error_code, "Error occurred while executing model"); - { - std::lock_guard lk(mu_); - this->status = error_code; - } - - compile_queue.Stop(); - execution_queue.Stop(); -} - -Status GraphExecutionContext::GetStatus() { - std::lock_guard lk(mu_); - return status; -} -} // namespace hybrid +namespace hybrid {} // namespace hybrid } // namespace ge \ No newline at end of file diff --git a/src/ge/hybrid/executor/hybrid_execution_context.h b/src/ge/hybrid/executor/hybrid_execution_context.h index 07a6fabf..96722fa9 100644 --- a/src/ge/hybrid/executor/hybrid_execution_context.h +++ b/src/ge/hybrid/executor/hybrid_execution_context.h @@ -20,6 +20,7 @@ #include #include #include "common/blocking_queue.h" +#include "framework/common/debug/ge_log.h" #include "hybrid/common/npu_memory_allocator.h" #include "hybrid/common/tensor_value.h" #include "hybrid/executor/hybrid_profiler.h" @@ -33,34 +34,26 @@ namespace hybrid { struct GraphExecutionContext { uint64_t session_id = 0; const HybridModel *model = nullptr; - NodeDoneManager cv_manager; - BlockingQueue compile_queue; - BlockingQueue execution_queue; - std::vector all_inputs; - std::vector all_outputs; - std::unordered_map node_states; rtStream_t stream = nullptr; + rtContext_t rt_context = nullptr; + rtContext_t rt_gen_context = nullptr; std::unique_ptr callback_manager; NpuMemoryAllocator *allocator = nullptr; mutable std::unique_ptr profiler; bool trace_enabled = false; - int profiling_level = 0; + long profiling_level = 0; bool dump_enabled = false; - Status status = SUCCESS; - std::mutex mu_; - - NodeStatePtr GetOrCreateNodeState(const NodePtr &node); - void OnError(Status status); - Status GetStatus(); + long iteration = 0; }; -#define RECORD_PROFILING_EVENT(context, event_type, fmt, category, node_name, ...) \ +#define RECORD_PROFILING_EVENT(context, evt_type, fmt, category, node_name, ...) \ do { \ if ((context)->profiler != nullptr) { \ if (node_name != nullptr) { \ - context->profiler->RecordEvent(event_type, "[%s] [%s] " fmt, node_name, category, ##__VA_ARGS__); \ + context->profiler->RecordEvent(evt_type, "tid:%lu [%s] [%s] " fmt, GetTid(), node_name, category, \ + ##__VA_ARGS__); \ } else { \ - context->profiler->RecordEvent(event_type, "[%s] " fmt, category, ##__VA_ARGS__); \ + context->profiler->RecordEvent(evt_type, "tid:%lu [%s] " fmt, GetTid(), category, ##__VA_ARGS__); \ } \ } \ } while (0) @@ -79,7 +72,6 @@ struct GraphExecutionContext { #define RECORD_CALLBACK_EVENT(context, name, fmt, ...) \ RECORD_PROFILING_EVENT((context), HybridProfiler::CALLBACK, fmt, "Callback", name, ##__VA_ARGS__) - } // namespace hybrid } // namespace ge #endif // GE_HYBRID_EXECUTOR_HYBRID_EXECUTION_CONTEXT_H_ diff --git a/src/ge/hybrid/executor/hybrid_model_async_executor.cc b/src/ge/hybrid/executor/hybrid_model_async_executor.cc index bd5d77f7..7f650017 100644 --- a/src/ge/hybrid/executor/hybrid_model_async_executor.cc +++ b/src/ge/hybrid/executor/hybrid_model_async_executor.cc @@ -77,19 +77,18 @@ Status HybridModelAsyncExecutor::Init() { GE_CHECK_NOTNULL(data_inputer_); GE_CHK_RT_RET(rtStreamCreate(&stream_, RT_STREAM_PRIORITY_DEFAULT)); - engine_ = std::unique_ptr(new (std::nothrow) HybridModelExecutor(model_, device_id_, stream_)); - GE_CHECK_NOTNULL(engine_); - GE_CHK_STATUS_RET(engine_->Init(), "Failed to init hybrid engine"); - + executor_ = std::unique_ptr(new (std::nothrow) HybridModelExecutor(model_, device_id_, stream_)); + GE_CHECK_NOTNULL(executor_); + GE_CHK_STATUS_RET(executor_->Init(), "Failed to init hybrid engine"); GE_CHK_STATUS_RET(InitInputTensors(), "Failed to init input tensors"); return SUCCESS; } Status HybridModelAsyncExecutor::PreRun(InputData ¤t_data) { GE_CHK_STATUS_RET(SyncVarData(), "Failed to sync var data"); - RECORD_MODEL_EXECUTION_EVENT(engine_->GetContext(), "[SyncVarData] End"); + RECORD_MODEL_EXECUTION_EVENT(executor_->GetContext(), "[SyncVarData] End"); GE_CHK_STATUS_RET(CopyInputData(current_data), "Failed to copy input data to model"); - RECORD_MODEL_EXECUTION_EVENT(engine_->GetContext(), "[CopyInputData] End"); + RECORD_MODEL_EXECUTION_EVENT(executor_->GetContext(), "[CopyInputData] End"); return SUCCESS; } @@ -119,21 +118,21 @@ Status HybridModelAsyncExecutor::RunInternal() { args.inputs[it.first] = it.second; } - RECORD_MODEL_EXECUTION_EVENT(engine_->GetContext(), "[RunInternal] [iteration = %d] Start", iterator_count_); + RECORD_MODEL_EXECUTION_EVENT(executor_->GetContext(), "[RunInternal] [iteration = %d] Start", iterator_count_); ret = PreRun(current_data); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG( - ret != SUCCESS, (void)HandleResult(ret, current_data.index, args.outputs, data_wrapper->GetOutput()); + ret != SUCCESS, (void)HandleResult(ret, current_data.index, args, data_wrapper->GetOutput()); CsaInteract::GetInstance().StoreInternalErrorCode(ret, ERROR_MODULE_FMK, JOBSUBSTATE_GRAPH_EXEC); continue, "PreRun failed."); // [No need to check value] - ret = engine_->Execute(args); - ret = HandleResult(ret, current_data.index, args.outputs, data_wrapper->GetOutput()); + ret = executor_->Execute(args); + ret = HandleResult(ret, current_data.index, args, data_wrapper->GetOutput()); if (ret != SUCCESS) { CsaInteract::GetInstance().StoreInternalErrorCode(ret, ERROR_MODULE_RUNTIME, JOBSUBSTATE_GRAPH_EXEC); continue; } - RECORD_MODEL_EXECUTION_EVENT(engine_->GetContext(), "[RunInternal] [iteration = %d] End", iterator_count_); + RECORD_MODEL_EXECUTION_EVENT(executor_->GetContext(), "[RunInternal] [iteration = %d] End", iterator_count_); iterator_count_++; GELOGI("run iterator count is %lu", iterator_count_); } @@ -143,8 +142,8 @@ Status HybridModelAsyncExecutor::RunInternal() { return SUCCESS; } -Status HybridModelAsyncExecutor::HandleResult(Status exec_ret, uint32_t data_id, - const std::vector &output_tensors, OutputData *output_data) { +Status HybridModelAsyncExecutor::HandleResult(Status exec_ret, uint32_t data_id, HybridModelExecutor::ExecuteArgs &args, + OutputData *output_data) { GELOGD("Start to handle result. model id = %u, data index = %u, execution ret = %u", model_id_, data_id, exec_ret); std::vector output_tensor_info_list; if (exec_ret == END_OF_SEQUENCE) { @@ -158,7 +157,7 @@ Status HybridModelAsyncExecutor::HandleResult(Status exec_ret, uint32_t data_id, } GE_CHECK_NOTNULL(output_data); - auto ret = CopyOutputs(output_tensors, output_data, output_tensor_info_list); + auto ret = CopyOutputs(args, output_data, output_tensor_info_list); if (ret != SUCCESS) { OnComputeDone(data_id, INTERNAL_ERROR, output_tensor_info_list); return INTERNAL_ERROR; @@ -215,9 +214,8 @@ Status HybridModelAsyncExecutor::CopyInputData(const InputData ¤t_data) { Status HybridModelAsyncExecutor::InitInputTensors() { auto allocator = NpuMemoryAllocator::GetAllocator(device_id_); GE_CHECK_NOTNULL(allocator); - for (const auto &it : model_->input_nodes_) { - auto input_index = it.first; - auto input_node = it.second; + int input_index = 0; + for (const auto &input_node : model_->GetRootGraphItem()->GetInputNodes()) { GELOGD("Init input[%u], node = %s", input_index, input_node->NodeName().c_str()); auto output_desc = input_node->op_desc->GetOutputDescPtr(kDataOutputIndex); GE_CHECK_NOTNULL(output_desc); @@ -235,6 +233,7 @@ Status HybridModelAsyncExecutor::InitInputTensors() { TensorValue tensor(shared_ptr(buffer.release())); tensor.SetName("Input_" + input_node->NodeName()); input_tensors_.emplace(input_index, tensor); + input_index += 1; } return SUCCESS; @@ -250,35 +249,33 @@ Status HybridModelAsyncExecutor::OnComputeDone(uint32_t data_index, uint32_t res return result_code; } -Status HybridModelAsyncExecutor::CopyOutputs(const std::vector &output_tensors, OutputData *output_data, +Status HybridModelAsyncExecutor::CopyOutputs(HybridModelExecutor::ExecuteArgs &args, OutputData *output_data, std::vector &outputs) { // copy output data from op to designated position - NodeItem *net_output_node = model_->net_output_node_; - GE_CHECK_NOTNULL(net_output_node); - auto all_input_desc = net_output_node->op_desc->GetAllInputsDescPtr(); - - if (all_input_desc.size() != output_tensors.size()) { + std::vector &output_tensor_desc_list = args.output_desc; + std::vector &output_tensors = args.outputs; + if (output_tensor_desc_list.size() != output_tensors.size()) { GELOGE(INTERNAL_ERROR, "Output sizes mismatch. From op_desc = %zu, and from output tensors = %zu", - all_input_desc.size(), output_tensors.size()); + output_tensor_desc_list.size(), output_tensors.size()); return INTERNAL_ERROR; } - GELOGD("Number of outputs = %zu", all_input_desc.size()); + GELOGD("Number of outputs = %zu", output_tensor_desc_list.size()); for (size_t i = 0; i < output_tensors.size(); ++i) { GELOGD("Start to process output[%zu]", i); auto &output_tensor = output_tensors[i]; - auto &tensor_desc = all_input_desc.at(i); + auto &tensor_desc = output_tensor_desc_list.at(i); GE_CHECK_NOTNULL(tensor_desc); int64_t output_size = -1; - GE_CHK_GRAPH_STATUS_RET(TensorUtils::CalcTensorMemSize(tensor_desc->MutableShape(), tensor_desc->GetFormat(), + GE_CHK_GRAPH_STATUS_RET(TensorUtils::CalcTensorMemSize(tensor_desc->GetShape(), tensor_desc->GetFormat(), tensor_desc->GetDataType(), output_size), "Failed to calc tensor size for output[%zu]. shape = [%s], type = %s, format = %s", i, - tensor_desc->MutableShape().ToString().c_str(), + tensor_desc->GetShape().ToString().c_str(), TypeUtils::DataTypeToSerialString(tensor_desc->GetDataType()).c_str(), TypeUtils::FormatToSerialString(tensor_desc->GetFormat()).c_str()); GELOGD("Got tensor size for output[%zu] successfully. shape = [%s], type = %s, format = %s, size = %ld", i, - tensor_desc->MutableShape().ToString().c_str(), + tensor_desc->GetShape().ToString().c_str(), TypeUtils::DataTypeToSerialString(tensor_desc->GetDataType()).c_str(), TypeUtils::FormatToSerialString(tensor_desc->GetFormat()).c_str(), output_size); @@ -286,7 +283,7 @@ Status HybridModelAsyncExecutor::CopyOutputs(const std::vector &out GE_CHECK_LE(output_size, UINT32_MAX); if (output_tensor.GetSize() < static_cast(output_size)) { GELOGE(INTERNAL_ERROR, "output[%zu] tensor size(%zu) is not enough for output shape [%s]", i, - output_tensor.GetSize(), tensor_desc->MutableShape().ToString().c_str()); + output_tensor.GetSize(), tensor_desc->GetShape().ToString().c_str()); return INTERNAL_ERROR; } @@ -302,7 +299,7 @@ Status HybridModelAsyncExecutor::CopyOutputs(const std::vector &out output.data = std::move(data_buf); output_data->blobs.emplace_back(data_buf.get(), static_cast(output_size), false); } else { - GELOGW("Output[%zu] is empty. shape = [%s]", i, tensor_desc->MutableShape().ToString().c_str()); + GELOGW("Output[%zu] is empty. shape = [%s]", i, tensor_desc->GetShape().ToString().c_str()); output.data = nullptr; output_data->blobs.emplace_back(nullptr, 0U, false); } @@ -310,7 +307,53 @@ Status HybridModelAsyncExecutor::CopyOutputs(const std::vector &out outputs.emplace_back(std::move(output)); GELOGD("Output[%zu] added, type = %s, shape = [%s], size = %ld", i, TypeUtils::DataTypeToSerialString(tensor_desc->GetDataType()).c_str(), - tensor_desc->MutableShape().ToString().c_str(), output_size); + tensor_desc->GetShape().ToString().c_str(), output_size); + } + + return SUCCESS; +} + +Status HybridModelAsyncExecutor::Execute(const vector &inputs, vector &outputs) { + GELOGD("Start to execute model."); + // prepare inputs + InputData input_data; + for (auto &tensor : inputs) { + DataBuffer buffer; + buffer.data = const_cast(tensor.GetData().GetData()); + buffer.length = tensor.GetData().size(); + input_data.blobs.emplace_back(buffer); + } + GE_CHK_STATUS_RET(CopyInputData(input_data), "Failed to copy input data to model"); + GELOGD("Done copying input data successfully."); + + HybridModelExecutor::ExecuteArgs args; + args.inputs.resize(input_tensors_.size()); + args.input_desc.resize(input_tensors_.size()); + for (auto &it : input_tensors_) { + args.inputs[it.first] = it.second; + args.input_desc[it.first] = MakeShared(inputs[it.first].GetTensorDesc()); + } + + GE_CHK_STATUS_RET(executor_->Execute(args), "Failed to execute model."); + + std::vector output_tensor_info_list; + OutputData output_data; + GE_CHK_STATUS_RET(CopyOutputs(args, &output_data, output_tensor_info_list), "Failed to copy outputs."); + GELOGD("Done copying output data successfully. output count = %zu", output_tensor_info_list.size()); + + int out_index = 0; + outputs.resize(output_tensor_info_list.size()); + for (auto &out_tensor_info : output_tensor_info_list) { + auto &ge_tensor = outputs[out_index]; + if (out_tensor_info.length > 0) { + GE_CHK_GRAPH_STATUS_RET(ge_tensor.SetData(out_tensor_info.data.get(), out_tensor_info.length), + "Failed to set output[%d].", out_index); + } + + ge_tensor.MutableTensorDesc() = *args.output_desc[out_index]; + GELOGD("Set output[%d], tensor size = %ld, shape = [%s]", out_index, out_tensor_info.length, + ge_tensor.MutableTensorDesc().MutableShape().ToString().c_str()); + ++out_index; } return SUCCESS; diff --git a/src/ge/hybrid/executor/hybrid_model_async_executor.h b/src/ge/hybrid/executor/hybrid_model_async_executor.h index cb440ba7..195f79a9 100644 --- a/src/ge/hybrid/executor/hybrid_model_async_executor.h +++ b/src/ge/hybrid/executor/hybrid_model_async_executor.h @@ -35,6 +35,8 @@ class HybridModelAsyncExecutor { Status Init(); + Status Execute(const vector &inputs, vector &outputs); + Status Start(const std::shared_ptr &listener); void SetDeviceId(uint32_t device_id); @@ -52,10 +54,10 @@ class HybridModelAsyncExecutor { Status SyncVarData(); - Status HandleResult(Status exec_ret, uint32_t data_id, const std::vector &output_tensors, + Status HandleResult(Status exec_ret, uint32_t data_id, HybridModelExecutor::ExecuteArgs &args, OutputData *output_data); - Status CopyOutputs(const std::vector &output_tensors, OutputData *output_data, + Status CopyOutputs(HybridModelExecutor::ExecuteArgs &args, OutputData *output_data, std::vector &outputs); Status OnComputeDone(uint32_t data_index, uint32_t result_code, std::vector &outputs); @@ -70,7 +72,7 @@ class HybridModelAsyncExecutor { uint32_t model_id_ = 0U; std::atomic_bool run_flag_; std::unique_ptr data_inputer_; - std::unique_ptr engine_; + std::unique_ptr executor_; std::future future_; uint64_t iterator_count_ = 0; diff --git a/src/ge/hybrid/executor/hybrid_model_executor.cc b/src/ge/hybrid/executor/hybrid_model_executor.cc index 856b4483..d62d7be3 100644 --- a/src/ge/hybrid/executor/hybrid_model_executor.cc +++ b/src/ge/hybrid/executor/hybrid_model_executor.cc @@ -26,17 +26,17 @@ HybridModelExecutor::HybridModelExecutor(HybridModel *model, uint32_t device_id, Status HybridModelExecutor::Init() { GELOGD("Start to init HybridGraphEngine."); GE_CHK_STATUS_RET_NOLOG(InitExecutionContext()); - infer_shape_engine_.reset(new (std::nothrow) ShapeInferenceEngine(&context_)); - compile_engine_.reset(new (std::nothrow) TaskCompileEngine(&context_)); - execute_engine_.reset(new (std::nothrow) ExecutionEngine(&context_, context_.callback_manager.get())); - GE_CHK_STATUS_RET_NOLOG(compile_engine_->Init()); GELOGD("HybridGraphEngine initialized successfully."); return SUCCESS; } Status HybridModelExecutor::Execute(HybridModelExecutor::ExecuteArgs &args) { GELOGD("Start to execute model."); - auto ret = ExecuteGraphInternal(args); + auto root_graph_item = model_->GetRootGraphItem(); + GE_CHECK_NOTNULL(root_graph_item); + + SubgraphExecutor executor(model_->GetRootGraphItem(), &context_); + auto ret = ExecuteGraphInternal(executor, args); Cleanup(); RECORD_MODEL_EXECUTION_EVENT(&context_, "[Cleanup] End"); GE_CHK_STATUS_RET(ret, "Failed to execute model"); @@ -46,24 +46,22 @@ Status HybridModelExecutor::Execute(HybridModelExecutor::ExecuteArgs &args) { context_.profiler->Reset(); } + context_.iteration += 1; return SUCCESS; } -Status HybridModelExecutor::ExecuteGraphInternal(HybridModelExecutor::ExecuteArgs &args) { +Status HybridModelExecutor::ExecuteGraphInternal(SubgraphExecutor &executor, HybridModelExecutor::ExecuteArgs &args) { RECORD_MODEL_EXECUTION_EVENT(&context_, "[InitContext] Start"); GE_CHK_STATUS_RET_NOLOG(ResetExecutionContext(context_)); RECORD_MODEL_EXECUTION_EVENT(&context_, "[InitContext] End"); - GE_CHK_STATUS_RET_NOLOG(InitInputsAndOutputs(args, context_)); - RECORD_MODEL_EXECUTION_EVENT(&context_, "[InitInputsAndOutputs] End"); - GE_CHK_STATUS_RET_NOLOG(compile_engine_->Start(pool_)); - RECORD_MODEL_EXECUTION_EVENT(&context_, "[CompileProcess] Started"); - GE_CHK_STATUS_RET_NOLOG(infer_shape_engine_->Start(pool_)); - RECORD_MODEL_EXECUTION_EVENT(&context_, "[InferShapeProcess] Started"); - GE_CHK_STATUS_RET(execute_engine_->Start(), "Run execution engine failed."); - RECORD_MODEL_EXECUTION_EVENT(&context_, "[ExecutionProcess] End"); - GE_CHK_STATUS_RET_NOLOG(Synchronize()); + + GE_CHK_STATUS_RET(executor.ExecuteAsync(args.inputs, args.input_desc), "Failed to execute partitioned call."); + RECORD_MODEL_EXECUTION_EVENT(&context_, "[ExecuteAsync] End"); + + GE_CHK_STATUS_RET(executor.Synchronize(), "Failed to sync root graph."); RECORD_MODEL_EXECUTION_EVENT(&context_, "[Synchronize] End"); - GE_CHK_STATUS_RET_NOLOG(GetOutput(args)); + + GE_CHK_STATUS_RET(executor.GetOutputs(args.outputs, args.output_desc), "Failed to get outputs"); RECORD_MODEL_EXECUTION_EVENT(&context_, "[GetOutput] End"); return SUCCESS; } @@ -71,18 +69,16 @@ Status HybridModelExecutor::ExecuteGraphInternal(HybridModelExecutor::ExecuteArg Status HybridModelExecutor::Cleanup() { GELOGD("Start to cleanup."); context_.callback_manager->Destroy(); - context_.cv_manager.Reset(); - context_.node_states.clear(); - context_.all_inputs.clear(); - context_.all_outputs.clear(); - context_.compile_queue.Clear(); - context_.execution_queue.Clear(); RuntimeInferenceContext::DestroyContext(to_string(context_.session_id)); GELOGD("Cleanup successfully."); return SUCCESS; } Status HybridModelExecutor::InitExecutionContext() { + GE_CHK_RT_RET(rtCtxGetCurrent(&context_.rt_context)); + GE_CHK_RT_RET(rtCtxCreate(&context_.rt_gen_context, RT_CTX_GEN_MODE, 0)); + GE_CHK_RT_RET(rtCtxSetCurrent(context_.rt_context)); + context_.stream = stream_; context_.model = model_; context_.session_id = ::ge::GetContext().SessionId(); @@ -94,78 +90,15 @@ Status HybridModelExecutor::InitExecutionContext() { if (IsLogEnable(GE_MODULE_NAME, DLOG_DEBUG)) { context_.trace_enabled = true; } - return SUCCESS; } Status HybridModelExecutor::ResetExecutionContext(GraphExecutionContext &context) { - auto &model = *context.model; - context.all_inputs.resize(model.TotalInputs()); - context.all_outputs.resize(model.TotalOutputs()); - context.compile_queue.Restart(); - context.execution_queue.Restart(); GE_CHK_STATUS_RET_NOLOG(context.callback_manager->Init()); - - for (auto const_node : model.GetConstNodes()) { - auto weight_tensor = model.GetWeight(const_node); - GE_CHECK_NOTNULL(weight_tensor); - for (auto &dst_aid_and_nid : const_node->outputs[0]) { - auto *dst_node_item = dst_aid_and_nid.second; - auto input_offset = dst_node_item->input_start + dst_aid_and_nid.first; - context.all_inputs[input_offset] = *weight_tensor; - } - } - string ctx_id = std::to_string(context.session_id); RuntimeInferenceContext::DestroyContext(ctx_id); GE_CHK_GRAPH_STATUS_RET(RuntimeInferenceContext::CreateContext(ctx_id), "Failed to Destroy RuntimeInferenceContext"); return SUCCESS; } - -Status HybridModelExecutor::InitInputsAndOutputs(HybridModelExecutor::ExecuteArgs &args, - GraphExecutionContext &context) { - for (const auto &it : model_->GetInputNodes()) { - uint32_t input_index = it.first; - if (input_index >= args.inputs.size()) { - GELOGE(PARAM_INVALID, "Not enough inputs. NumInputs = %zu, but input index = %u", args.inputs.size(), - input_index); - return PARAM_INVALID; - } - - auto node_item = it.second; - auto &input_tensor = args.inputs[input_index]; - GELOGD("Set input tensor[%u] to inputs with index = %d, addr = %p, size = %zu", input_index, node_item->input_start, - input_tensor.GetData(), input_tensor.GetSize()); - context.all_inputs[node_item->input_start] = input_tensor; - } - - for (size_t i = 0; i < model_->GetOutputOffsets().size(); ++i) { - auto offset = model_->GetOutputOffsets()[i]; - if (i < args.outputs.size() && args.outputs[i].GetData() != nullptr) { - GELOGD("Use user allocated output memory. output index = %zu, output offset = %d", i, offset); - context.all_outputs[offset] = args.outputs[i]; - } - } - - return SUCCESS; -} - -Status HybridModelExecutor::Synchronize() { - GE_CHK_RT_RET(rtStreamSynchronize(stream_)); - return SUCCESS; -} - -Status HybridModelExecutor::GetOutput(HybridModelExecutor::ExecuteArgs &args) { - auto &net_output_input_offsets = model_->GetNetOutputInputOffsets(); - auto num_outputs = net_output_input_offsets.size(); - args.outputs.resize(num_outputs); - for (size_t i = 0; i < num_outputs; ++i) { - auto offset = net_output_input_offsets[i]; - GELOGI("Get output[%zu] from offset %d", i, offset); - args.outputs[i] = context_.all_inputs[offset]; - } - - return SUCCESS; -} } // namespace hybrid } // namespace ge diff --git a/src/ge/hybrid/executor/hybrid_model_executor.h b/src/ge/hybrid/executor/hybrid_model_executor.h index 2bda6331..9996dbe0 100644 --- a/src/ge/hybrid/executor/hybrid_model_executor.h +++ b/src/ge/hybrid/executor/hybrid_model_executor.h @@ -20,9 +20,7 @@ #include "graph/load/new_model_manager/data_inputer.h" #include "hybrid/executor/hybrid_execution_context.h" #include "hybrid/executor/rt_callback_manager.h" -#include "hybrid/executor/worker/execution_engine.h" -#include "hybrid/executor/worker/shape_inference_engine.h" -#include "hybrid/executor/worker/task_compile_engine.h" +#include "hybrid/executor/subgraph_executor.h" namespace ge { namespace hybrid { @@ -30,7 +28,9 @@ class HybridModelExecutor { public: struct ExecuteArgs { std::vector inputs; + std::vector input_desc; std::vector outputs; + std::vector output_desc; }; HybridModelExecutor(HybridModel *model, uint32_t device_id, rtStream_t stream); @@ -44,24 +44,15 @@ class HybridModelExecutor { Status Execute(ExecuteArgs &args); private: - Status ExecuteGraphInternal(ExecuteArgs &args); + Status ExecuteGraphInternal(SubgraphExecutor &executor, ExecuteArgs &args); Status Cleanup(); Status InitExecutionContext(); static Status ResetExecutionContext(GraphExecutionContext &context); - Status InitInputsAndOutputs(ExecuteArgs &args, GraphExecutionContext &context); - Status GetOutput(ExecuteArgs &args); - - Status Synchronize(); - - ThreadPool pool_; HybridModel *model_; uint32_t device_id_; rtStream_t stream_; GraphExecutionContext context_; - std::unique_ptr infer_shape_engine_; - std::unique_ptr compile_engine_; - std::unique_ptr execute_engine_; }; } // namespace hybrid } // namespace ge diff --git a/src/ge/hybrid/executor/hybrid_profiler.cc b/src/ge/hybrid/executor/hybrid_profiler.cc index 1081a144..4c70e043 100644 --- a/src/ge/hybrid/executor/hybrid_profiler.cc +++ b/src/ge/hybrid/executor/hybrid_profiler.cc @@ -59,11 +59,10 @@ void HybridProfiler::Dump(std::ostream &output_stream) { auto first_evt = events_[0]; auto start = first_evt.timestamp; - output_stream << "Start " << first_evt.desc << std::endl; std::vector prev_timestamps; prev_timestamps.resize(kMaxEventTypes, start); - for (int i = 1; i < counter_; ++i) { + for (int i = 0; i < counter_; ++i) { auto &evt = events_[i]; auto elapsed = std::chrono::duration_cast(evt.timestamp - start).count(); auto &prev_ts = prev_timestamps[evt.event_type]; diff --git a/src/ge/hybrid/executor/node_done_manager.cc b/src/ge/hybrid/executor/node_done_manager.cc index dfeddb5b..3ec45339 100644 --- a/src/ge/hybrid/executor/node_done_manager.cc +++ b/src/ge/hybrid/executor/node_done_manager.cc @@ -15,35 +15,49 @@ */ #include "hybrid/executor/node_done_manager.h" +#include #include "framework/common/debug/ge_log.h" namespace ge { namespace hybrid { +namespace { +constexpr int kDefaultWaitTimeoutInSec = 10; +} bool NodeDoneManager::Cond::Await() { - std::unique_lock lk(mu_); - cv_.wait(lk, [&]() { return is_released_ || is_cancelled_; }); + std::unique_lock lk(cond_mu_); + if (!cv_.wait_for(lk, std::chrono::seconds(kDefaultWaitTimeoutInSec), + [&]() { return is_released_ || is_cancelled_; })) { + GELOGE(INTERNAL_ERROR, "Wait timed out."); + return false; + } + return is_released_; } void NodeDoneManager::Cond::Release() { - std::unique_lock lk(mu_); + std::unique_lock lk(cond_mu_); is_released_ = true; cv_.notify_all(); } void NodeDoneManager::Cond::Cancel() { - std::unique_lock lk(mu_); + std::unique_lock lk(cond_mu_); is_cancelled_ = true; cv_.notify_all(); } bool NodeDoneManager::Cond::IsRelease() { - std::unique_lock lk(mu_); + std::unique_lock lk(cond_mu_); return is_released_; } NodeDoneManager::Cond *NodeDoneManager::GetSubject(const NodePtr &node) { std::lock_guard lk(mu_); + if (destroyed_) { + GELOGD("Already destroyed."); + return nullptr; + } + auto it = subjects_.find(node); if (it == subjects_.end()) { return &subjects_[node]; @@ -52,8 +66,10 @@ NodeDoneManager::Cond *NodeDoneManager::GetSubject(const NodePtr &node) { return &it->second; } -void NodeDoneManager::Reset() { +void NodeDoneManager::Destroy() { + GELOGD("Start to reset NodeDoneManager."); std::lock_guard lk(mu_); + GELOGD("Cond size = %zu.", subjects_.size()); for (auto &sub : subjects_) { if (!sub.second.IsRelease()) { sub.second.Cancel(); @@ -62,15 +78,24 @@ void NodeDoneManager::Reset() { } subjects_.clear(); + destroyed_ = true; + GELOGD("Done resetting NodeDoneManager successfully."); } void NodeDoneManager::NodeDone(const NodePtr &node) { - GetSubject(node)->Release(); - GELOGD("[%s] Node released.", node->GetName().c_str()); + auto sub = GetSubject(node); + if (sub != nullptr) { + sub->Release(); + GELOGD("[%s] Node released.", node->GetName().c_str()); + } } bool NodeDoneManager::Await(const NodePtr &node) { auto sub = GetSubject(node); + if (sub == nullptr) { + return false; + } + GELOGD("[%s] Await start. is_released = %s", node->GetName().c_str(), sub->IsRelease() ? "true" : "false"); bool ret = sub->Await(); GELOGD("[%s] Await ended. is_released = %s", node->GetName().c_str(), sub->IsRelease() ? "true" : "false"); diff --git a/src/ge/hybrid/executor/node_done_manager.h b/src/ge/hybrid/executor/node_done_manager.h index ccf263d1..f1fdfbec 100644 --- a/src/ge/hybrid/executor/node_done_manager.h +++ b/src/ge/hybrid/executor/node_done_manager.h @@ -31,7 +31,7 @@ class NodeDoneManager { bool Await(const NodePtr &node); - void Reset(); + void Destroy(); private: class Cond { @@ -42,7 +42,7 @@ class NodeDoneManager { bool Await(); private: - std::mutex mu_; + std::mutex cond_mu_; std::condition_variable cv_; bool is_released_ = false; bool is_cancelled_ = false; @@ -51,6 +51,7 @@ class NodeDoneManager { Cond *GetSubject(const NodePtr &node); std::mutex mu_; std::unordered_map subjects_; + bool destroyed_ = false; }; } // namespace hybrid } // namespace ge diff --git a/src/ge/hybrid/executor/node_state.cc b/src/ge/hybrid/executor/node_state.cc index 6895f158..5368597d 100644 --- a/src/ge/hybrid/executor/node_state.cc +++ b/src/ge/hybrid/executor/node_state.cc @@ -15,13 +15,133 @@ */ #include "hybrid/executor/node_state.h" +#include +#include "framework/common/debug/log.h" #include "graph/compute_graph.h" +#include "hybrid_execution_context.h" +#include "subgraph_context.h" namespace ge { namespace hybrid { -NodeState::NodeState(const NodeItem &node_item) { - this->node_item = &node_item; - this->op_desc = node_item.node->GetOpDesc(); +namespace { +constexpr auto kMaxWaitTimeInSec = 10; +} +ShapeInferenceState::ShapeInferenceState(const NodeItem &node_item) : node_item(node_item) { + this->num_pending_shapes_ = node_item.num_inputs - node_item.num_static_input_shapes; + GELOGD("[%s] ShapeInferenceState created, pending shape count = %d", node_item.NodeName().c_str(), + this->num_pending_shapes_); +} + +void ShapeInferenceState::UpdateInputShape(uint32_t idx, const GeShape &ori_shape, const GeShape &shape) { + if (node_item.is_input_shape_static[idx]) { + GELOGD("[%s] Trying to update static shape, idx = %u. old shape = [%s], new shape = [%s]", + node_item.NodeName().c_str(), idx, node_item.op_desc->MutableInputDesc(idx)->GetShape().ToString().c_str(), + shape.ToString().c_str()); + return; + } + + GELOGD("[%s] Update input shape [%u] with Shape: [%s] and OriginalShape: [%s]", node_item.NodeName().c_str(), idx, + shape.ToString().c_str(), ori_shape.ToString().c_str()); + + std::lock_guard lk(mu_); + node_item.op_desc->MutableInputDesc(idx)->SetShape(shape); + node_item.op_desc->MutableInputDesc(idx)->SetOriginShape(ori_shape); + if (--num_pending_shapes_ == 0) { + ready_cv_.notify_all(); + } +} + +void ShapeInferenceState::UpdateInputShapeFuture(uint32_t idx, ShapeFuture &&future) { + if (node_item.is_input_shape_static[idx]) { + GELOGD("[%s] Trying to update constant shape, idx = %u", node_item.NodeName().c_str(), idx); + return; + } + + GELOGD("[%s] Update input shape [%u] with ShapeFuture.", node_item.NodeName().c_str(), idx); + std::lock_guard lk(mu_); + shape_futures.emplace_back(idx, std::move(future)); + if (--num_pending_shapes_ == 0) { + ready_cv_.notify_all(); + } +} + +Status ShapeInferenceState::AwaitShapesReady(const GraphExecutionContext &context) { + std::unique_lock lk(mu_); + if (num_pending_shapes_ > 0) { + GELOGD("[%s] Await pending shape or shape future start.", node_item.NodeName().c_str()); + if (!ready_cv_.wait_for(lk, std::chrono::seconds(kMaxWaitTimeInSec), [&]() { return num_pending_shapes_ == 0; })) { + GELOGE(INTERNAL_ERROR, "[%s] Wait for shape timeout.", node_item.NodeName().c_str()); + return INTERNAL_ERROR; + } + GELOGD("[%s] Await pending shape or shape future end.", node_item.NodeName().c_str()); + } + + for (auto &p : shape_futures) { + auto idx = p.first; + auto &future = p.second; + GeShape shape; + GeShape ori_shape; + RECORD_SHAPE_INFERENCE_EVENT(&context, node_item.NodeName().c_str(), "[AwaitShape] [idx = %u] Start", idx); + GE_CHK_STATUS_RET(future.Get(ori_shape, shape), "[%s] Get shape failed. index = %u", node_item.NodeName().c_str(), + idx); + RECORD_SHAPE_INFERENCE_EVENT(&context, node_item.NodeName().c_str(), "[AwaitShape] [idx = %u] End", idx); + + GELOGD("[%s] Update input shape [%u] with shape: [%s] and ori_shape: [%s]", node_item.NodeName().c_str(), idx, + shape.ToString().c_str(), ori_shape.ToString().c_str()); + node_item.op_desc->MutableInputDesc(idx)->SetShape(std::move(shape)); + node_item.op_desc->MutableInputDesc(idx)->SetOriginShape(ori_shape); + } + + return SUCCESS; +} + +ShapeFuture::ShapeFuture(NodePtr src_node, uint32_t src_index, SubgraphContext *subgraph_context) + : src_node_(std::move(src_node)), src_index_(src_index), subgraph_context_(subgraph_context) {} + +NodeState::NodeState(const NodeItem &node_item, SubgraphContext *subgraph_context) + : node_item_(&node_item), shape_inference_state_(node_item), subgraph_context_(subgraph_context) { + this->op_desc_ = node_item.node->GetOpDesc(); +} + +Status NodeState::AwaitInputTensors(GraphExecutionContext &context) const { + for (auto &src_node : node_item_->dependents_for_execution) { + GELOGI("[%s] Start to wait for data dependent node: [%s]", node_item_->NodeName().c_str(), + src_node->GetName().c_str()); + RECORD_EXECUTION_EVENT(&context, node_item_->NodeName().c_str(), "[AwaitNodeDone] [%s] Start", + src_node->GetName().c_str()); + if (!subgraph_context_->Await(src_node)) { + GELOGE(INTERNAL_ERROR, "[%s] Await node [%s] failed.", GetName().c_str(), src_node->GetName().c_str()); + return INTERNAL_ERROR; + } + + RECORD_EXECUTION_EVENT(&context, node_item_->NodeName().c_str(), "[AwaitNodeDone] [%s] End", + src_node->GetName().c_str()); + GELOGI("[%s] Done waiting node.", src_node->GetName().c_str()); + } + + return SUCCESS; +} + +Status NodeState::WaitForPrepareDone() { + if (prepare_future_.valid()) { + GELOGD("[%s] Start to wait for prepare future.", GetName().c_str()); + GE_CHK_STATUS_RET(prepare_future_.get(), "[%s] PreRun failed.", GetName().c_str()); + } + + return SUCCESS; +} + +Status ShapeFuture::Get(GeShape &ori_shape, GeShape &shape) { + GELOGI("Start to wait node: %s for getting shape", src_node_->GetName().c_str()); + if (!subgraph_context_->Await(src_node_)) { + GELOGE(INTERNAL_ERROR, "cancelled"); + return INTERNAL_ERROR; + } + + shape = src_node_->GetOpDesc()->MutableOutputDesc(src_index_)->MutableShape(); + ori_shape = src_node_->GetOpDesc()->MutableOutputDesc(src_index_)->GetOriginShape(); + GELOGI("Get shape from %s:%u. shape = [%s]", src_node_->GetName().c_str(), src_index_, shape.ToString().c_str()); + return SUCCESS; } } // namespace hybrid } // namespace ge \ No newline at end of file diff --git a/src/ge/hybrid/executor/node_state.h b/src/ge/hybrid/executor/node_state.h index b2811bcb..73e0f75c 100644 --- a/src/ge/hybrid/executor/node_state.h +++ b/src/ge/hybrid/executor/node_state.h @@ -17,38 +17,83 @@ #ifndef GE_HYBRID_EXECUTOR_NODE_STATE_H_ #define GE_HYBRID_EXECUTOR_NODE_STATE_H_ +#include +#include +#include +#include "external/ge/ge_api_error_codes.h" #include "hybrid/model/node_item.h" +#include "node_done_manager.h" namespace ge { namespace hybrid { - class NodeTask; +class GraphExecutionContext; +class SubgraphContext; -// 存放一些会å˜åŒ–çš„ä¿¡æ¯... -class NodeState { +class ShapeFuture { public: - NodeState() = default; - explicit NodeState(const NodeItem &node_item); + ShapeFuture(NodePtr src_node, uint32_t src_index, SubgraphContext *subgraph_context); + ~ShapeFuture() = default; + Status Get(GeShape &ori_shape, GeShape &shape); + + private: + NodePtr src_node_; + uint32_t src_index_; + SubgraphContext *subgraph_context_; +}; + +struct ShapeInferenceState { + explicit ShapeInferenceState(const NodeItem &node_item); + + void UpdateInputShape(uint32_t idx, const GeShape &ori_shape, const GeShape &shape); + + void UpdateInputShapeFuture(uint32_t idx, ShapeFuture &&future); + + Status AwaitShapesReady(const GraphExecutionContext &context); + + const NodeItem &node_item; + + private: + std::vector> shape_futures; + int num_pending_shapes_ = 0; + std::condition_variable ready_cv_; + std::mutex mu_; +}; + +// saving sth. dynamic during execution +struct NodeState { + public: + NodeState(const NodeItem &node_item, SubgraphContext *subgraph_context); ~NodeState() = default; - inline int NodeId() const { return node_item->node_id; } + OpDesc *GetOpDesc() const { return op_desc_.get(); } + + inline const NodeItem *GetNodeItem() const { return node_item_; } + + inline const string &GetName() const { return node_item_->NodeName(); } + + inline const string &GetType() const { return node_item_->NodeType(); } - inline Node *GetNode() const { return node_item->node.get(); } + ShapeInferenceState &GetShapeInferenceState() { return shape_inference_state_; } - OpDesc *GetOpDesc() const { return op_desc.get(); } + const shared_ptr &GetKernelTask() const { return kernel_task_; } - inline const NodeItem *GetNodeItem() const { return node_item; } + void SetKernelTask(const shared_ptr &kernel_task) { kernel_task_ = kernel_task; } - inline const string &GetName() const { return node_item->NodeName(); } + Status WaitForPrepareDone(); - inline const string &GetType() const { return node_item->NodeType(); } + void SetPrepareFuture(std::future &&prepare_future) { this->prepare_future_ = std::move(prepare_future); } - // private: - const NodeItem *node_item = nullptr; - std::shared_ptr kernel_task = nullptr; + Status AwaitInputTensors(GraphExecutionContext &context) const; - bool is_compiled = false; - OpDescPtr op_desc; + private: + const NodeItem *node_item_ = nullptr; + std::shared_ptr kernel_task_ = nullptr; + std::future prepare_future_; + OpDescPtr op_desc_; + ShapeInferenceState shape_inference_state_; + SubgraphContext *subgraph_context_; + std::mutex mu_; }; using NodeStatePtr = std::shared_ptr; diff --git a/src/ge/hybrid/executor/rt_callback_manager.cc b/src/ge/hybrid/executor/rt_callback_manager.cc index 6be8da31..c1c98f73 100644 --- a/src/ge/hybrid/executor/rt_callback_manager.cc +++ b/src/ge/hybrid/executor/rt_callback_manager.cc @@ -42,7 +42,6 @@ Status CallbackManager::Init() { rtContext_t ctx = nullptr; GE_CHK_RT_RET(rtCtxGetCurrent(&ctx)); ret_future_ = std::async([&](rtContext_t context) -> Status { return CallbackProcess(context); }, ctx); - if (!ret_future_.valid()) { GELOGE(INTERNAL_ERROR, "Failed to init callback manager."); return INTERNAL_ERROR; diff --git a/src/ge/hybrid/executor/subgraph_context.cc b/src/ge/hybrid/executor/subgraph_context.cc new file mode 100644 index 00000000..d5d9075d --- /dev/null +++ b/src/ge/hybrid/executor/subgraph_context.cc @@ -0,0 +1,112 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "subgraph_context.h" + +#include "common/debug/log.h" + +namespace ge { +namespace hybrid { +SubgraphContext::SubgraphContext(const GraphItem *graph_item) : graph_item_(graph_item) {} + +Status SubgraphContext::Init() { + GE_CHECK_NOTNULL(graph_item_); + GELOGD("[%s] Start to init subgraph context. total inputs = %d, total outputs = %d", graph_item_->GetName().c_str(), + graph_item_->TotalInputs(), graph_item_->TotalOutputs()); + all_inputs_.resize(graph_item_->TotalInputs()); + all_outputs_.resize(graph_item_->TotalOutputs()); + + return SUCCESS; +} + +NodeStatePtr SubgraphContext::GetOrCreateNodeState(const NodeItem *node_item) { + std::lock_guard lk(mu_); + auto &node_state = node_states_[node_item]; + if (node_state == nullptr) { + node_state.reset(new (std::nothrow) NodeState(*node_item, this)); + } + + return node_state; +} + +Status SubgraphContext::SetInput(int index, const TensorValue &tensor) { + if (static_cast(index) >= all_inputs_.size()) { + GELOGE(INTERNAL_ERROR, "output index output range. all input num = %zu, input index = %d", all_inputs_.size(), + index); + return INTERNAL_ERROR; + } + + all_inputs_[index] = tensor; + return SUCCESS; +} + +Status SubgraphContext::SetInput(const NodeItem &node_item, int input_index, const TensorValue &tensor) { + auto index = node_item.input_start + input_index; + return SetInput(index, tensor); +} + +Status SubgraphContext::SetOutput(const NodeItem &node_item, int output_index, const TensorValue &tensor) { + auto index = node_item.output_start + output_index; + if (output_index >= node_item.num_outputs || static_cast(index) >= all_outputs_.size()) { + GELOGE(INTERNAL_ERROR, "output index output range. all output num = %zu, node_item = %s, output index = %d", + all_outputs_.size(), node_item.DebugString().c_str(), output_index); + return INTERNAL_ERROR; + } + + all_outputs_[index] = tensor; + return SUCCESS; +} + +Status SubgraphContext::GetInput(int index, TensorValue &tensor) { + GE_CHECK_GE(all_inputs_.size(), index + 1U); + tensor = all_inputs_[index]; + return SUCCESS; +} + +Status SubgraphContext::GetOutputs(std::vector &outputs) { + if (graph_item_->IsDynamic()) { + GELOGD("[%s] graph is dynamic, get outputs from net output input tensors", graph_item_->GetName().c_str()); + // get from net output inputs + auto output_node = graph_item_->GetOutputNode(); + GE_CHECK_NOTNULL(output_node); + for (int i = 0; i < output_node->num_inputs; ++i) { + TensorValue tensor; + GE_CHK_STATUS_RET_NOLOG(GetInput(output_node->input_start + i, tensor)); + GELOGD("[%s] Adding output tensor by input index [%d], tensor = %s", graph_item_->GetName().c_str(), + output_node->input_start + i, tensor.DebugString().c_str()); + outputs.emplace_back(std::move(tensor)); + } + } else { + GELOGD("[%s] graph is non-dynamic, get outputs from subgraph outputs", graph_item_->GetName().c_str()); + for (auto &tensor : all_outputs_) { + GELOGD("[%s] Adding output tensor: %s", graph_item_->GetName().c_str(), tensor.DebugString().c_str()); + outputs.emplace_back(tensor); + } + } + + return SUCCESS; +} + +bool SubgraphContext::Await(const NodePtr &node) { return node_done_manager_.Await(node); } + +void SubgraphContext::OnError(Status error) { + GELOGE(error, "[%s] Error occurred while executing graph.", graph_item_->GetName().c_str()); + node_done_manager_.Destroy(); +} + +void SubgraphContext::NodeDone(const NodePtr &node) { node_done_manager_.NodeDone(node); } +} // namespace hybrid +} // namespace ge diff --git a/src/ge/hybrid/executor/subgraph_context.h b/src/ge/hybrid/executor/subgraph_context.h new file mode 100644 index 00000000..fd934d80 --- /dev/null +++ b/src/ge/hybrid/executor/subgraph_context.h @@ -0,0 +1,61 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_HYBRID_EXECUTOR_ITERATION_CONTEXT_H_ +#define GE_HYBRID_EXECUTOR_ITERATION_CONTEXT_H_ + +#include + +#include "hybrid/common/tensor_value.h" +#include "hybrid/executor/node_state.h" +#include "hybrid/executor/node_done_manager.h" +#include "hybrid/model/graph_item.h" +#include "hybrid/model/node_item.h" + +namespace ge { +namespace hybrid { +class SubgraphContext { + public: + explicit SubgraphContext(const GraphItem *graph_item); + ~SubgraphContext() = default; + + Status Init(); + NodeStatePtr GetOrCreateNodeState(const NodeItem *node_item); + + void OnError(Status error); + + Status SetInput(const NodeItem &node_item, int input_index, const TensorValue &tensor); + Status SetOutput(const NodeItem &node_item, int output_index, const TensorValue &tensor); + Status SetInput(int index, const TensorValue &tensor); + Status GetInput(int index, TensorValue &tensor); + Status GetOutputs(std::vector &outputs); + + bool Await(const NodePtr &node); + void NodeDone(const NodePtr &node); + + private: + friend class TaskContext; + const GraphItem *graph_item_; + std::mutex mu_; + std::vector all_inputs_; + std::vector all_outputs_; + NodeDoneManager node_done_manager_; + std::unordered_map node_states_; +}; +} // namespace hybrid +} // namespace ge + +#endif // GE_HYBRID_EXECUTOR_ITERATION_CONTEXT_H_ diff --git a/src/ge/hybrid/executor/subgraph_executor.cc b/src/ge/hybrid/executor/subgraph_executor.cc new file mode 100644 index 00000000..3d699970 --- /dev/null +++ b/src/ge/hybrid/executor/subgraph_executor.cc @@ -0,0 +1,373 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hybrid/executor/subgraph_executor.h" +#include "hybrid/executor/worker/task_compile_engine.h" +#include "hybrid/executor/worker/execution_engine.h" +#include "hybrid/node_executor/node_executor.h" + +namespace ge { +namespace hybrid { +namespace { +constexpr int kDefaultThreadNum = 4; +constexpr int kDataInputIndex = 0; +} // namespace + +SubgraphExecutor::SubgraphExecutor(const GraphItem *graph_item, GraphExecutionContext *context, bool force_infer_shape) + : graph_item_(graph_item), + context_(context), + force_infer_shape_(force_infer_shape), + pre_run_pool_(kDefaultThreadNum) {} + +SubgraphExecutor::~SubgraphExecutor() { GELOGD("[%s] SubgraphExecutor destroyed.", graph_item_->GetName().c_str()); } + +Status SubgraphExecutor::Init(const std::vector &inputs, + const std::vector &input_desc) { + subgraph_context_.reset(new (std::nothrow) SubgraphContext(graph_item_)); + GE_CHECK_NOTNULL(subgraph_context_); + GE_CHK_STATUS_RET(subgraph_context_->Init(), "[%s] Failed to init subgraph context.", graph_item_->GetName().c_str()); + + shape_inference_engine_.reset(new (std::nothrow) ShapeInferenceEngine(context_, subgraph_context_.get())); + GE_CHECK_NOTNULL(shape_inference_engine_); + + if (graph_item_->IsDynamic()) { + GE_CHK_STATUS_RET(InitInputsForUnknownShape(inputs, input_desc), "[%s] Failed to set inputs.", + graph_item_->GetName().c_str()); + } else { + GE_CHK_STATUS_RET(InitInputsForKnownShape(inputs), + "[%s] Failed to init subgraph executor for known shape subgraph.", + graph_item_->GetName().c_str()); + } + + return SUCCESS; +} + +Status SubgraphExecutor::InitInputsForUnknownShape(const std::vector &inputs, + const std::vector &input_desc) { + // Number of inputs of parent node should be greater or equal than that of subgraph + auto input_nodes = graph_item_->GetInputNodes(); + if (inputs.size() < input_nodes.size()) { + GELOGE(INTERNAL_ERROR, "[%s] Number of inputs [%zu] is not sufficient for subgraph which needs [%zu] inputs.", + graph_item_->GetName().c_str(), inputs.size(), input_nodes.size()); + return INTERNAL_ERROR; + } + + for (size_t i = 0; i < input_nodes.size(); ++i) { + auto &input_node = input_nodes[i]; + if (input_node == nullptr) { + GELOGD("[%s] Input[%zu] is not needed by subgraph, skip it.", graph_item_->GetName().c_str(), i); + continue; + } + + auto &input_tensor = inputs[i]; + GELOGD("[%s] Set input tensor[%zu] to inputs with index = %d, tensor = %s", graph_item_->GetName().c_str(), i, + input_node->input_start, input_tensor.DebugString().c_str()); + + GE_CHK_STATUS_RET(subgraph_context_->SetInput(*input_node, kDataInputIndex, input_tensor), + "[%s] Failed to set input tensor[%zu]", graph_item_->GetName().c_str(), i); + + if (force_infer_shape_ || input_node->is_dynamic) { + GELOGD("[%s] Start to update input[%zu] for subgraph data node.", graph_item_->GetName().c_str(), i); + GE_CHECK_LE(i + 1, input_desc.size()); + const auto &tensor_desc = input_desc[i]; + auto node_state = subgraph_context_->GetOrCreateNodeState(input_node); + GE_CHECK_NOTNULL(node_state); + node_state->GetShapeInferenceState().UpdateInputShape(0, tensor_desc->GetOriginShape(), tensor_desc->GetShape()); + } + } + + GELOGD("[%s] Done setting inputs.", graph_item_->GetName().c_str()); + return SUCCESS; +} + +Status SubgraphExecutor::InitInputsForKnownShape(const std::vector &inputs) { + auto &input_index_mapping = graph_item_->GetInputIndexMapping(); + for (size_t i = 0; i < input_index_mapping.size(); ++i) { + auto &parent_input_index = input_index_mapping[i]; + if (static_cast(parent_input_index) >= inputs.size()) { + GELOGE(INTERNAL_ERROR, + "[%s] Number of inputs [%zu] is not sufficient for subgraph which needs at lease [%d] inputs", + graph_item_->GetName().c_str(), inputs.size(), parent_input_index + 1); + + return INTERNAL_ERROR; + } + + auto &input_tensor = inputs[parent_input_index]; + subgraph_context_->SetInput(i, input_tensor); + GELOGD("[%s] Set input tensor[%zu] with inputs with index = %d, tensor = %s", graph_item_->GetName().c_str(), i, + parent_input_index, input_tensor.DebugString().c_str()); + } + + return SUCCESS; +} + +Status SubgraphExecutor::ExecuteAsync(const std::vector &inputs, + const std::vector &input_desc) { + GELOGD("[%s] is dynamic = %s", graph_item_->GetName().c_str(), graph_item_->IsDynamic() ? "true" : "false"); + GE_CHK_STATUS_RET(Init(inputs, input_desc), "[%s] Failed to init executor.", graph_item_->GetName().c_str()); + + if (!graph_item_->IsDynamic()) { + return ExecuteAsyncForKnownShape(inputs); + } + + GE_CHK_STATUS_RET(ScheduleTasks(), "[%s] Failed to execute tasks.", graph_item_->GetName().c_str()); + GELOGD("[%s] Done executing subgraph successfully.", graph_item_->GetName().c_str()); + return SUCCESS; +} + +Status SubgraphExecutor::ExecuteAsyncForKnownShape(const std::vector &inputs) { + GELOGD("[%s] subgraph is not dynamic.", graph_item_->GetName().c_str()); + if (graph_item_->GetAllNodes().size() != 1) { + GELOGE(INTERNAL_ERROR, "[%s] Invalid known shape subgraph. node size = %zu", graph_item_->GetName().c_str(), + graph_item_->GetAllNodes().size()); + return INTERNAL_ERROR; + } + + auto node_item = graph_item_->GetAllNodes()[0]; + GE_CHECK_NOTNULL(node_item); + auto node_state = subgraph_context_->GetOrCreateNodeState(node_item); + GE_CHECK_NOTNULL(node_state); + node_state->SetKernelTask(node_item->kernel_task); + + known_shape_task_context_ = TaskContext::Create(*node_item, context_, subgraph_context_.get()); + GE_CHECK_NOTNULL(known_shape_task_context_); + + GE_CHK_STATUS_RET(ExecutionEngine::ExecuteAsync(*node_state, known_shape_task_context_, *context_), + "[%s] Failed to execute node [%s] for known subgraph.", graph_item_->GetName().c_str(), + known_shape_task_context_->GetNodeName()); + + GELOGD("[%s] Done execute non-dynamic subgraph successfully.", graph_item_->GetName().c_str()); + return SUCCESS; +} + +Status SubgraphExecutor::ExecuteAsync(TaskContext &task_context) { + std::vector inputs; + std::vector input_desc; + for (int i = 0; i < task_context.NumInputs(); ++i) { + auto tensor = task_context.GetInput(i); + GE_CHECK_NOTNULL(tensor); + inputs.emplace_back(*tensor); + input_desc.emplace_back(task_context.GetInputDesc(i)); + } + + GE_CHK_STATUS_RET(ExecuteAsync(inputs, input_desc), "[%s] Failed to execute subgraph.", + graph_item_->GetName().c_str()); + + GE_CHK_STATUS_RET(SetOutputsToParentNode(task_context), "[%s] Failed to set output shapes to parent node.", + graph_item_->GetName().c_str()); + return SUCCESS; +} + +Status SubgraphExecutor::PrepareNodes() { + GELOGD("[%s] Start to prepare nodes. force infer shape = %s.", graph_item_->GetName().c_str(), + force_infer_shape_ ? "true" : "false"); + auto &all_nodes = graph_item_->GetAllNodes(); + for (size_t i = 0; i < all_nodes.size(); ++i) { + auto &node_item = *all_nodes[i]; + // for while op + if (force_infer_shape_ && !node_item.is_dynamic) { + GELOGD("[%s] Force infer shape is set, updating node to dynamic.", node_item.NodeName().c_str()); + auto &mutable_node_item = const_cast(node_item); + mutable_node_item.SetToDynamic(); + } + + GELOGD("[%s] Start to prepare node [%s].", graph_item_->GetName().c_str(), node_item.NodeName().c_str()); + auto node_state = subgraph_context_->GetOrCreateNodeState(&node_item); + GE_CHECK_NOTNULL(node_state); + auto p_node_state = node_state.get(); + + if (node_item.node_type == NETOUTPUT) { + // Wait for all inputs become valid + // after PrepareNodes returned. all output tensors and shapes are valid + GE_CHK_STATUS_RET_NOLOG(p_node_state->GetShapeInferenceState().AwaitShapesReady(*context_)); + GE_CHK_STATUS_RET_NOLOG(p_node_state->AwaitInputTensors(*context_)); + continue; + } + + // only do shape inference and compilation for nodes with dynamic shapes. + if (node_item.is_dynamic) { + auto prepare_future = pre_run_pool_.commit([this, p_node_state]() -> Status { + GE_CHK_STATUS_RET_NOLOG(InferShape(shape_inference_engine_.get(), *p_node_state)); + return PrepareForExecution(context_, *p_node_state); + }); + + p_node_state->SetPrepareFuture(std::move(prepare_future)); + } else { + GELOGD("[%s] Skipping shape inference and compilation for node with static shape.", node_item.NodeName().c_str()); + if (node_item.kernel_task == nullptr) { + GELOGW("[%s] Node of static shape got no task.", node_item.NodeName().c_str()); + GE_CHK_STATUS_RET(TaskCompileEngine::Compile(*p_node_state, context_), "[%s] Failed to create task.", + p_node_state->GetName().c_str()); + } else { + node_state->SetKernelTask(node_item.kernel_task); + } + } + + if (!ready_queue_.Push(p_node_state)) { + GELOGE(INTERNAL_ERROR, "[%s] Error occurs while launching tasks. quit from preparing nodes.", + graph_item_->GetName().c_str()); + return INTERNAL_ERROR; + } + + GELOGD("[%s] Push node [%s] to queue.", graph_item_->GetName().c_str(), node_item.NodeName().c_str()); + } + + return SUCCESS; +} + +Status SubgraphExecutor::InferShape(ShapeInferenceEngine *shape_inference_engine, NodeState &node_state) { + const auto &node_item = *node_state.GetNodeItem(); + GE_CHK_STATUS_RET(shape_inference_engine->InferShape(node_state), "[%s] Failed to InferShape.", + node_state.GetName().c_str()); + GE_CHK_STATUS_RET(shape_inference_engine->PropagateOutputShapes(node_item), "[%s] Failed to PropagateOutputShapes.", + node_state.GetName().c_str()); + return SUCCESS; +} + +Status SubgraphExecutor::PrepareForExecution(GraphExecutionContext *ctx, NodeState &node_state) { + auto &node_item = *node_state.GetNodeItem(); + if (node_item.kernel_task == nullptr) { + GE_CHK_STATUS_RET(TaskCompileEngine::Compile(node_state, ctx), "Failed to create task for node[%s]", + node_state.GetName().c_str()); + } else { + node_state.SetKernelTask(node_item.kernel_task); + } + + GELOGD("[%s] Start to invoke CalcOpRunningParam.", node_item.NodeName().c_str()); + RECORD_COMPILE_EVENT(ctx, node_item.NodeName().c_str(), "[CalcOpRunningParam] Start"); + GE_CHK_STATUS_RET(NodeExecutorManager::GetInstance().CalcOpRunningParam(*node_item.node), + "[%s] Failed to invoke CalcOpRunningParam.", node_item.NodeName().c_str()); + RECORD_COMPILE_EVENT(ctx, node_item.NodeName().c_str(), "[CalcOpRunningParam] End"); + GELOGD("[%s] Done invoking CalcOpRunningParam successfully.", node_item.NodeName().c_str()); + return SUCCESS; +} + +Status SubgraphExecutor::LaunchTasks() { + while (true) { + NodeState *node_state = nullptr; + if (!ready_queue_.Pop(node_state)) { + GELOGE(INTERNAL_ERROR, "[%s] Failed to pop node.", graph_item_->GetName().c_str()); + return INTERNAL_ERROR; + } + + if (node_state == nullptr) { + GELOGD("[%s] Got EOF from queue.", graph_item_->GetName().c_str()); + return SUCCESS; + } + + GE_CHK_STATUS_RET_NOLOG(node_state->WaitForPrepareDone()); + + GELOGD("[%s] Start to execute.", node_state->GetName().c_str()); + auto task_context = TaskContext::Create(*node_state->GetNodeItem(), context_, subgraph_context_.get()); + GE_CHECK_NOTNULL(task_context); + task_context->SetForceInferShape(force_infer_shape_); + auto shared_task_context = std::shared_ptr(task_context.release()); + GE_CHK_STATUS_RET(ExecutionEngine::ExecuteAsync(*node_state, shared_task_context, *context_), + "[%s] Execute node failed.", node_state->GetName().c_str()); + + GELOGD("[%s] Done executing node successfully.", node_state->GetName().c_str()); + } +} + +Status SubgraphExecutor::ScheduleTasks() { + GELOGD("[%s] Start to schedule prepare workers.", graph_item_->GetName().c_str()); + auto prepare_future = std::async([&]() -> Status { + auto ret = PrepareNodes(); + ready_queue_.Push(nullptr); + return ret; + }); + + GELOGD("[%s] Start to execute subgraph.", graph_item_->GetName().c_str()); + auto ret = LaunchTasks(); + if (ret != SUCCESS) { + GELOGE(ret, "[%s] Failed to execute subgraph.", graph_item_->GetName().c_str()); + subgraph_context_->OnError(ret); + ready_queue_.Stop(); + prepare_future.wait(); + return ret; + } + + GE_CHK_STATUS_RET(prepare_future.get(), "[%s] Error occurred in task preparation.", graph_item_->GetName().c_str()); + + GELOGD("[%s] Done launching all tasks successfully.", graph_item_->GetName().c_str()); + return SUCCESS; +} + +Status SubgraphExecutor::GetOutputs(vector &outputs) { return subgraph_context_->GetOutputs(outputs); } + +Status SubgraphExecutor::GetOutputs(vector &outputs, std::vector &output_desc) { + GE_CHK_STATUS_RET(GetOutputs(outputs), "[%s] Failed to get output tensors.", graph_item_->GetName().c_str()); + + // copy output data from op to designated position + std::vector output_tensor_desc_list; + GE_CHK_STATUS_RET(graph_item_->GetOutputDescList(output_desc), "[%s] Failed to get output tensor desc.", + graph_item_->GetName().c_str()); + return SUCCESS; +} + +Status SubgraphExecutor::Synchronize() { + GELOGD("[%s] Synchronize start.", graph_item_->GetName().c_str()); + GE_CHK_RT_RET(rtStreamSynchronize(context_->stream)); + GELOGD("[%s] Done synchronizing successfully.", graph_item_->GetName().c_str()); + return SUCCESS; +} + +Status SubgraphExecutor::SetOutputsToParentNode(TaskContext &task_context) { + // get output tensors and tensor desc list + std::vector outputs; + std::vector output_desc_list; + GE_CHK_STATUS_RET(subgraph_context_->GetOutputs(outputs), "[%s] Failed to get output tensors.", + graph_item_->GetName().c_str()); + GE_CHK_STATUS_RET(graph_item_->GetOutputDescList(output_desc_list), "[%s] Failed to get output tensor desc.", + graph_item_->GetName().c_str()); + + if (outputs.size() != output_desc_list.size()) { + GELOGE(INTERNAL_ERROR, "[%s] num output tensors = %zu, num output tensor desc = %zu", + graph_item_->GetName().c_str(), outputs.size(), output_desc_list.size()); + return INTERNAL_ERROR; + } + + // mapping to parent task context + for (size_t i = 0; i < outputs.size(); ++i) { + int parent_output_index = graph_item_->GetParentOutputIndex(i); + GE_CHECK_GE(parent_output_index, 0); + // update tensor + GELOGD("[%s] Updating output[%zu] to parent output[%d]", graph_item_->GetName().c_str(), i, parent_output_index); + + GELOGD("[%s] Updating output tensor, index = %d, tensor = %s", graph_item_->GetName().c_str(), parent_output_index, + outputs[i].DebugString().c_str()); + task_context.SetOutput(parent_output_index, outputs[i]); + + // updating shapes. dynamic format/dtype is not supported. + // It should be noted that even the subgraph is of known shape, it is also necessary to update parent output desc, + // for instance, IfOp may have two known-shaped subgraphs of different output shapes + const auto &output_desc = output_desc_list[i]; + auto parent_output_desc = task_context.MutableOutputDesc(parent_output_index); + GE_CHECK_NOTNULL(parent_output_desc); + GELOGD("[%s] Updating output shape[%d] from [%s] to [%s]", graph_item_->GetName().c_str(), parent_output_index, + parent_output_desc->MutableShape().ToString().c_str(), output_desc->GetShape().ToString().c_str()); + parent_output_desc->SetShape(output_desc->GetShape()); + + GELOGD("[%s] Updating output original shape[%d] from [%s] to [%s]", graph_item_->GetName().c_str(), + parent_output_index, parent_output_desc->GetOriginShape().ToString().c_str(), + output_desc->GetOriginShape().ToString().c_str()); + parent_output_desc->SetOriginShape(output_desc->GetOriginShape()); + } + + return SUCCESS; +} +} // namespace hybrid +} // namespace ge \ No newline at end of file diff --git a/src/ge/hybrid/executor/subgraph_executor.h b/src/ge/hybrid/executor/subgraph_executor.h new file mode 100644 index 00000000..7cdb2070 --- /dev/null +++ b/src/ge/hybrid/executor/subgraph_executor.h @@ -0,0 +1,101 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_HYBRID_EXECUTOR_EXECUTOR_SUBGRAPH_EXECUTOR_H_ +#define GE_HYBRID_EXECUTOR_EXECUTOR_SUBGRAPH_EXECUTOR_H_ + +#include + +#include "common/blocking_queue.h" +#include "common/thread_pool.h" +#include "hybrid/executor/subgraph_context.h" +#include "hybrid/executor/node_state.h" +#include "hybrid/executor/hybrid_execution_context.h" +#include "hybrid/executor/worker/shape_inference_engine.h" +#include "hybrid/model/graph_item.h" +#include "hybrid/node_executor/task_context.h" + +namespace ge { +namespace hybrid { +// Executor for executing a subgraph +class SubgraphExecutor { + public: + SubgraphExecutor(const GraphItem *graph_item, GraphExecutionContext *context, bool force_infer_shape = false); + ~SubgraphExecutor(); + + /** + * Execute subgraph async, output tensor address(not data) and output tensor descriptions are + * valid after this method returned + * @param inputs input tensors + * @param input_desc input tensor descriptions + * @return SUCCESS on success, error code otherwise + */ + Status ExecuteAsync(const std::vector &inputs, const std::vector &input_desc); + + /** + * Execute subgraph async, output tensor address(not data) and output tensor descriptions are + * valid after this method returned + * @param task_context instance of TaskContext + * @return SUCCESS on success, error code otherwise + */ + Status ExecuteAsync(TaskContext &task_context); + + /** + * Synchronize all tasks in the subgraph. output tensor data are valid after this method returned + * @return SUCCESS on success, error code otherwise + */ + Status Synchronize(); + + /** + * Get output tensors + * @param outputs output tensors + * @return SUCCESS on success, error code otherwise + */ + Status GetOutputs(std::vector &outputs); + + /** + * Get output tensors and output tensor descriptions + * @param outputs output tensors + * @param output_desc output tensor descriptions + * @return SUCCESS on success, error code otherwise + */ + Status GetOutputs(std::vector &outputs, std::vector &output_desc); + + private: + static Status PrepareForExecution(GraphExecutionContext *ctx, NodeState &node_state); + static Status InferShape(ShapeInferenceEngine *shape_inference_engine, NodeState &node_state); + Status Init(const std::vector &inputs, const std::vector &input_desc); + Status InitInputsForUnknownShape(const std::vector &inputs, + const std::vector &input_desc); + Status InitInputsForKnownShape(const std::vector &inputs); + Status ExecuteAsyncForKnownShape(const std::vector &inputs); + Status ScheduleTasks(); + Status PrepareNodes(); + Status LaunchTasks(); + Status SetOutputsToParentNode(TaskContext &task_context); + + const GraphItem *graph_item_; + GraphExecutionContext *context_; + std::unique_ptr subgraph_context_; + bool force_infer_shape_; + ThreadPool pre_run_pool_; + BlockingQueue ready_queue_; + std::unique_ptr shape_inference_engine_; + std::shared_ptr known_shape_task_context_; +}; +} // namespace hybrid +} // namespace ge +#endif // GE_HYBRID_EXECUTOR_EXECUTOR_SUBGRAPH_EXECUTOR_H_ diff --git a/src/ge/hybrid/executor/worker/execution_engine.cc b/src/ge/hybrid/executor/worker/execution_engine.cc index 9e656139..20da6378 100644 --- a/src/ge/hybrid/executor/worker/execution_engine.cc +++ b/src/ge/hybrid/executor/worker/execution_engine.cc @@ -15,7 +15,6 @@ */ #include "hybrid/executor/worker/execution_engine.h" -#include #include "graph/runtime_inference_context.h" #include "graph/utils/tensor_utils.h" #include "graph/utils/tensor_adapter.h" @@ -23,9 +22,38 @@ namespace ge { namespace hybrid { +namespace { +constexpr int64_t kMaxPadding = 63; + +Status LogInputs(const NodeItem &node_item, const TaskContext &task_context) { + for (auto i = 0; i < task_context.NumInputs(); ++i) { + const auto &input_tensor = task_context.GetInput(i); + GE_CHECK_NOTNULL(input_tensor); + const auto &tensor_desc = node_item.op_desc->MutableInputDesc(i); + GE_CHECK_NOTNULL(tensor_desc); + GELOGD("[%s] Print task args. input[%d] = %s, shape = [%s]", node_item.NodeName().c_str(), i, + input_tensor->DebugString().c_str(), tensor_desc->MutableShape().ToString().c_str()); + } + + return SUCCESS; +} + +Status LogOutputs(const NodeItem &node_item, const TaskContext &task_context) { + for (auto i = 0; i < task_context.NumOutputs(); ++i) { + const auto &output_tensor = task_context.GetOutput(i); + GE_CHECK_NOTNULL(output_tensor); + const auto &tensor_desc = node_item.op_desc->MutableOutputDesc(i); + GE_CHECK_NOTNULL(tensor_desc); + GELOGD("[%s] Print task args. output[%d] = %s, shape = [%s]", node_item.NodeName().c_str(), i, + output_tensor->DebugString().c_str(), tensor_desc->MutableShape().ToString().c_str()); + } + + return SUCCESS; +} +} // namespace class NodeDoneCallback { public: - NodeDoneCallback(GraphExecutionContext *graph_context, std::shared_ptr &task_context); + NodeDoneCallback(GraphExecutionContext *graph_context, std::shared_ptr task_context); ~NodeDoneCallback() = default; Status OnNodeDone(); @@ -35,8 +63,8 @@ class NodeDoneCallback { std::shared_ptr context_; }; -NodeDoneCallback::NodeDoneCallback(GraphExecutionContext *graph_context, std::shared_ptr &task_context) - : graph_context_(graph_context), context_(task_context) {} +NodeDoneCallback::NodeDoneCallback(GraphExecutionContext *graph_context, std::shared_ptr task_context) + : graph_context_(graph_context), context_(std::move(task_context)) {} Status NodeDoneCallback::PrepareConstInputs(const NodeItem &node_item) { for (auto output_idx : node_item.to_const_output_id_list) { @@ -46,17 +74,28 @@ Status NodeDoneCallback::PrepareConstInputs(const NodeItem &node_item) { auto output_tensor = context_->GetOutput(output_idx); GE_CHECK_NOTNULL(output_tensor); - vector host_buffer(output_tensor->GetSize()); - GELOGD("[%s] To cache output[%d] to host, size = %zu", node_item.NodeName().c_str(), output_idx, - output_tensor->GetSize()); - GE_CHK_RT_RET(rtMemcpy(host_buffer.data(), host_buffer.size(), output_tensor->GetData(), output_tensor->GetSize(), - RT_MEMCPY_HOST_TO_DEVICE)); Tensor tensor; - tensor.SetData(host_buffer); auto ge_tensor_desc = node_item.op_desc->MutableOutputDesc(output_idx); GE_CHECK_NOTNULL(ge_tensor_desc); tensor.SetTensorDesc(TensorAdapter::GeTensorDesc2TensorDesc(*ge_tensor_desc)); + int64_t tensor_size; + GE_CHK_GRAPH_STATUS_RET(TensorUtils::GetTensorSizeInBytes(*ge_tensor_desc, tensor_size), + "Failed to invoke GetTensorSizeInBytes"); + + if (output_tensor->GetSize() < static_cast(tensor_size)) { + GELOGE(INTERNAL_ERROR, "[%s] Tensor size is not enough. output index = %d, required size = %zu, tensor = %s", + node_item.NodeName().c_str(), output_idx, tensor_size, output_tensor->DebugString().c_str()); + return INTERNAL_ERROR; + } + + vector host_buffer(tensor_size); + GELOGD("[%s] To cache output[%d] to host, size = %zu", node_item.NodeName().c_str(), output_idx, + output_tensor->GetSize()); + GE_CHK_RT_RET( + rtMemcpy(host_buffer.data(), tensor_size, output_tensor->GetData(), tensor_size, RT_MEMCPY_DEVICE_TO_HOST)); + tensor.SetData(host_buffer); + string session_id = std::to_string(context_->GetSessionId()); RuntimeInferenceContext *runtime_infer_ctx = nullptr; GE_CHK_GRAPH_STATUS_RET(RuntimeInferenceContext::GetContext(session_id, &runtime_infer_ctx), @@ -87,115 +126,118 @@ Status NodeDoneCallback::OnNodeDone() { GE_CHK_STATUS_RET_NOLOG(PrepareConstInputs(node_item)); // PropagateOutputs for type == DEPEND_COMPUTE if (node_item.shape_inference_type == DEPEND_COMPUTE) { + if (graph_context_->trace_enabled) { + (void)LogOutputs(node_item, *context_); + } + GE_CHK_STATUS_RET(context_->PropagateOutputs(), "[%s] Failed to propagate outputs failed", node_item.NodeName().c_str()); RECORD_CALLBACK_EVENT(graph_context_, context_->GetNodeName(), "[PropagateOutputs] End"); } - // release + // release condition variable if (node_item.has_observer) { GELOGI("[%s] Notify observer. node_id = %d", node_item.NodeName().c_str(), node_item.node_id); - graph_context_->cv_manager.NodeDone(node_item.node); + context_->NodeDone(); } RECORD_CALLBACK_EVENT(graph_context_, context_->GetNodeName(), "[Callback] End"); return SUCCESS; } -ExecutionEngine::ExecutionEngine(GraphExecutionContext *context, CallbackManager *callback_manager) - : context_(context), callback_manager_(callback_manager) {} - -Status ExecutionEngine::Start() { - GE_CHK_STATUS_RET_NOLOG(ExecutionProcess()); - return SUCCESS; -} - -Status ExecutionEngine::ExecutionProcess() { - GELOGI("ExecutorEngine worker started"); - auto &ready_queue = context_->execution_queue; - while (true) { - NodeStatePtr node_state = nullptr; - if (!ready_queue.Pop(node_state)) { - GELOGE(FAILED, "Pop task failed"); - return FAILED; - } - - // EOF - if (node_state == nullptr) { - break; +Status ExecutionEngine::ExecuteAsync(NodeState &node_state, const std::shared_ptr &task_context, + GraphExecutionContext &execution_context) { + GELOGI("[%s] Node is ready for execution", task_context->GetNodeName()); + RECORD_EXECUTION_EVENT(&execution_context, task_context->GetNodeName(), "Start"); + auto cb = std::shared_ptr(new (std::nothrow) NodeDoneCallback(&execution_context, task_context)); + GE_CHECK_NOTNULL(cb); + auto callback = [&, cb]() { + auto ret = cb->OnNodeDone(); + if (ret != SUCCESS) { + task_context->OnError(ret); } + }; - RECORD_EXECUTION_EVENT(context_, node_state->GetName().c_str(), "Start"); - GELOGI("[%s] Node is ready for execution", node_state->GetName().c_str()); - auto *node_item = node_state->node_item; - auto task_context = TaskContext::Create(*node_item, context_); - GE_CHECK_NOTNULL(task_context); - auto shared_task_context = shared_ptr(task_context.release()); - - auto cb = std::shared_ptr(new (std::nothrow) NodeDoneCallback(context_, shared_task_context)); - GE_CHECK_NOTNULL(cb); - auto callback = [&, cb]() { - auto ret = cb->OnNodeDone(); - if (ret != SUCCESS) { - context_->OnError(ret); - } - }; - - GE_CHK_STATUS_RET_NOLOG(ExecuteAsync(*node_state, *shared_task_context, callback)); - GE_CHK_STATUS_RET_NOLOG(PropagateOutputs(*node_item, *shared_task_context)); - } - - GELOGI("ExecutorEngine worker ended."); + GE_CHK_STATUS_RET_NOLOG(DoExecuteAsync(node_state, *task_context, execution_context, callback)); + GE_CHK_STATUS_RET_NOLOG(PropagateOutputs(*node_state.GetNodeItem(), *task_context, execution_context)); return SUCCESS; } -Status ExecutionEngine::ExecuteAsync(NodeState &node_state, TaskContext &task_context, - const std::function &callback) { - const auto &task = node_state.kernel_task; +Status ExecutionEngine::DoExecuteAsync(NodeState &node_state, TaskContext &task_context, GraphExecutionContext &context, + const std::function &callback) { + const auto &task = node_state.GetKernelTask(); if (task == nullptr) { GELOGE(INTERNAL_ERROR, "[%s] NodeTask is null.", node_state.GetName().c_str()); return INTERNAL_ERROR; } - RECORD_EXECUTION_EVENT(context_, task_context.GetNodeName(), "[PrepareTask] Start"); - auto executor = node_state.node_item->node_executor; + // Wait for dependent nodes(DEPEND_COMPUTE), so that the input tensors are valid. + RECORD_EXECUTION_EVENT(&context, task_context.GetNodeName(), "[AwaitDependents] Start"); + GE_CHK_STATUS_RET(node_state.AwaitInputTensors(context), "[%s] Failed to wait for dependent nodes.", + node_state.GetName().c_str()); + + const auto &node_item = *node_state.GetNodeItem(); + auto executor = node_item.node_executor; + GE_CHECK_NOTNULL(executor); + RECORD_EXECUTION_EVENT(&context, task_context.GetNodeName(), "[PrepareTask] Start"); GE_CHK_STATUS_RET(executor->PrepareTask(*task, task_context), "[%s] Failed to prepare task", node_state.GetName().c_str()); - RECORD_EXECUTION_EVENT(context_, task_context.GetNodeName(), "[PrepareTask] End"); + RECORD_EXECUTION_EVENT(&context, task_context.GetNodeName(), "[PrepareTask] End"); GELOGD("[%s] Done task preparation successfully.", node_state.GetName().c_str()); - if (context_->trace_enabled) { - for (auto i = 0; i < task_context.NumInputs(); ++i) { - const auto &input_tensor = task_context.GetInput(i); - GE_CHECK_NOTNULL(input_tensor); - GELOGD("[%s] Tensor of input[%d] = %s", node_state.GetName().c_str(), i, input_tensor->DebugString().c_str()); - } - - for (auto i = 0; i < task_context.NumOutputs(); ++i) { - const auto &output_tensor = task_context.GetOutput(i); - GE_CHECK_NOTNULL(output_tensor); - GELOGD("[%s] Tensor of output[%d] = %s", node_state.GetName().c_str(), i, output_tensor->DebugString().c_str()); + if (context.trace_enabled) { + LogInputs(node_item, task_context); + if (node_item.shape_inference_type != DEPEND_COMPUTE) { + LogOutputs(node_item, task_context); } } - RECORD_EXECUTION_EVENT(context_, task_context.GetNodeName(), "[ExecuteTask] Start"); + GE_CHK_STATUS_RET(ValidateInputTensors(node_state, task_context), "Failed to validate input tensors."); + RECORD_EXECUTION_EVENT(&context, task_context.GetNodeName(), "[ValidateInputTensors] End"); + GE_CHK_STATUS_RET(executor->ExecuteTask(*task, task_context, callback), "[%s] Failed to execute task", node_state.GetName().c_str()); - RECORD_EXECUTION_EVENT(context_, task_context.GetNodeName(), "[ExecuteTask] End"); + RECORD_EXECUTION_EVENT(&context, task_context.GetNodeName(), "[ExecuteTask] End"); GELOGD("[%s] Done task launch successfully.", node_state.GetName().c_str()); return SUCCESS; } -Status ExecutionEngine::PropagateOutputs(const NodeItem &node_item, TaskContext &task_context) { +Status ExecutionEngine::ValidateInputTensors(const NodeState &node_state, const TaskContext &task_context) { + for (auto i = 0; i < task_context.NumInputs(); ++i) { + const auto &input_tensor = task_context.GetInput(i); + GE_CHECK_NOTNULL(input_tensor); + const auto &tensor_desc = node_state.GetOpDesc()->MutableInputDesc(i); + GE_CHECK_NOTNULL(tensor_desc); + int64_t expected_size; + GE_CHK_GRAPH_STATUS_RET(TensorUtils::GetTensorMemorySizeInBytes(*tensor_desc, expected_size)); + GELOGD("[%s] Input[%d] expects [%ld] bytes.", task_context.GetNodeName(), i, expected_size); + auto size_diff = expected_size - static_cast(input_tensor->GetSize()); + if (size_diff > 0) { + if (size_diff <= kMaxPadding) { + GELOGW("[%s] Input[%d]: tensor size mismatches. expected: %ld, but given %zu", task_context.GetNodeName(), i, + expected_size, input_tensor->GetSize()); + } else { + GELOGE(INTERNAL_ERROR, "[%s] Input[%d]: tensor size mismatches. expected: %ld, but given %zu", + task_context.GetNodeName(), i, expected_size, input_tensor->GetSize()); + return INTERNAL_ERROR; + } + } + } + + return SUCCESS; +} + +Status ExecutionEngine::PropagateOutputs(const NodeItem &node_item, TaskContext &task_context, + GraphExecutionContext &context) { if (node_item.shape_inference_type != DEPEND_COMPUTE) { GE_CHK_STATUS_RET(task_context.PropagateOutputs(), "[%s] Failed to propagate outputs.", node_item.NodeName().c_str()); - RECORD_EXECUTION_EVENT(context_, task_context.GetNodeName(), "[PropagateOutputs] End"); + RECORD_EXECUTION_EVENT(&context, task_context.GetNodeName(), "[PropagateOutputs] End"); + GELOGD("[%s] Done propagating outputs successfully.", node_item.NodeName().c_str()); } - GELOGD("[%s] Done propagating outputs successfully.", node_item.NodeName().c_str()); return SUCCESS; } } // namespace hybrid diff --git a/src/ge/hybrid/executor/worker/execution_engine.h b/src/ge/hybrid/executor/worker/execution_engine.h index f5f317af..56f1557d 100644 --- a/src/ge/hybrid/executor/worker/execution_engine.h +++ b/src/ge/hybrid/executor/worker/execution_engine.h @@ -17,30 +17,21 @@ #ifndef GE_HYBRID_EXECUTOR_EXECUTOR_EXECUTION_ENGINE_H_ #define GE_HYBRID_EXECUTOR_EXECUTOR_EXECUTION_ENGINE_H_ -#include "common/thread_pool.h" -#include "hybrid/common/npu_memory_allocator.h" #include "hybrid/executor/hybrid_execution_context.h" -#include "hybrid/executor/rt_callback_manager.h" #include "hybrid/node_executor/task_context.h" namespace ge { namespace hybrid { class ExecutionEngine { public: - explicit ExecutionEngine(GraphExecutionContext *context, CallbackManager *callback_manager); - ~ExecutionEngine() = default; - - Status Start(); + static Status ExecuteAsync(NodeState &node_state, const std::shared_ptr &task_context, + GraphExecutionContext &execution_context); private: - Status PropagateOutputs(const NodeItem &node_item, TaskContext &task_context); - - Status ExecutionProcess(); - - Status ExecuteAsync(NodeState &node_state, TaskContext &task_context, const std::function &callback); - - GraphExecutionContext *context_; - CallbackManager *callback_manager_; + static Status ValidateInputTensors(const NodeState &node_state, const TaskContext &task_context); + static Status PropagateOutputs(const NodeItem &node_item, TaskContext &task_context, GraphExecutionContext &context); + static Status DoExecuteAsync(NodeState &node_state, TaskContext &task_context, GraphExecutionContext &context, + const std::function &callback); }; } // namespace hybrid } // namespace ge diff --git a/src/ge/hybrid/executor/worker/shape_inference_engine.cc b/src/ge/hybrid/executor/worker/shape_inference_engine.cc index 90082fff..f600e94a 100644 --- a/src/ge/hybrid/executor/worker/shape_inference_engine.cc +++ b/src/ge/hybrid/executor/worker/shape_inference_engine.cc @@ -15,117 +15,27 @@ */ #include "hybrid/executor/worker/shape_inference_engine.h" - #include "graph/shape_refiner.h" -#include "graph/runtime_inference_context.h" #include "graph/utils/node_utils.h" #include "hybrid/node_executor/node_executor.h" namespace ge { namespace hybrid { +ShapeInferenceEngine::ShapeInferenceEngine(GraphExecutionContext *execution_context, SubgraphContext *subgraph_context) + : execution_context_(execution_context), subgraph_context_(subgraph_context) {} -ShapeInferenceEngine::ShapeInferenceEngine(GraphExecutionContext *context) : context_(context) {} - -Status ShapeInferenceEngine::Start(ThreadPool &pool) { - GELOGI("RuntimeShapeInferenceEngine start."); - pool.commit([&]() { - auto ret = this->InferShapeProcess(); - InferenceDone(ret); - }); - - return SUCCESS; -} - -Status ShapeInferenceEngine::InferShapeProcess() { - GELOGI("RuntimeShapeInferenceEngine worker start."); - const auto &root_nodes = context_->model->RootNodes(); - auto &complete_queue = context_->compile_queue; - std::queue ready_nodes; - for (auto &node_item : root_nodes) { - auto infer_state = GetOrCreateEntry(*node_item); - GE_CHECK_NOTNULL(infer_state); - ready_nodes.emplace(infer_state); - } - - while (!ready_nodes.empty()) { - InferenceState *infer_state = ready_nodes.front(); - ready_nodes.pop(); - auto node_item = infer_state->node_item; - // even for non-dynamic shape node, it is still necessary to wait for pending shapes if got any. - // which indicates that the parent node is of type 4, in which case the inputs will be valid only - // when computing is done. - GE_CHK_STATUS_RET(infer_state->AwaitShapeFutures(context_), "Await shape failed."); - GELOGI("[%s] Node is ready for shape inference.", node_item.NodeName().c_str()); - if (node_item.is_dynamic) { - // may block - RECORD_SHAPE_INFERENCE_EVENT(context_, node_item.NodeName().c_str(), "Start"); - GELOGI("[%s] Start to invoke InferShape", node_item.NodeName().c_str()); - auto ret = InferShape(*infer_state); - if (ret != SUCCESS) { - return ret; - } - - RECORD_SHAPE_INFERENCE_EVENT(context_, node_item.NodeName().c_str(), "[CalcOpRunningParam] Start"); - GE_CHK_STATUS_RET(NodeExecutorManager::GetInstance().CalcOpRunningParam(*node_item.node), - "[%s] Failed to invoke CalcOpRunningParam.", node_item.NodeName().c_str()); - RECORD_SHAPE_INFERENCE_EVENT(context_, node_item.NodeName().c_str(), "[CalcOpRunningParam] End"); - } else { - GELOGD("[%s] Skip static shape node", node_item.NodeName().c_str()); - } - - if (node_item.node_type != NETOUTPUT) { - GELOGI("[%s] Push to compile queue", node_item.NodeName().c_str()); - // may block if full - auto node_state = context_->GetOrCreateNodeState(node_item.node); - complete_queue.Push(node_state); - } - - // Propagate - RECORD_SHAPE_INFERENCE_EVENT(context_, node_item.NodeName().c_str(), "[PropagateOutputShapes] Start"); - PropagateOutputShapes(*infer_state, ready_nodes); - RECORD_SHAPE_INFERENCE_EVENT(context_, node_item.NodeName().c_str(), "[PropagateOutputShapes] End"); - } - - return SUCCESS; -} - -void ShapeInferenceEngine::InferenceDone(Status status) { - if (status != SUCCESS) { - GELOGE(status, "Error occurred while shape inference"); - context_->OnError(status); - } else { - context_->compile_queue.Push(nullptr); - } - inference_states_.clear(); - GELOGI("RuntimeShapeInferenceEngine worker END"); -} - -Status ShapeInferenceEngine::InferShape(InferenceState &entry) { - // input shapes are ready, wait for dependent data if has any - const auto &node_item = entry.node_item; - if (!node_item.dependent_node_list.empty()) { - for (auto &src_node : node_item.dependent_node_list) { - auto *src_node_item = context_->model->GetNodeItem(src_node); - GELOGI("[%s] Start to wait for data dependent node: %s", node_item.NodeName().c_str(), - src_node_item->NodeName().c_str()); - RECORD_SHAPE_INFERENCE_EVENT(context_, node_item.NodeName().c_str(), "[AwaitNodeDone] [%s] Start", - src_node->GetName().c_str()); - if (!context_->cv_manager.Await(src_node)) { - GELOGE(INTERNAL_ERROR, "[%s] Await node failed.", src_node_item->NodeName().c_str()); - return INTERNAL_ERROR; - } - - RECORD_SHAPE_INFERENCE_EVENT(context_, node_item.NodeName().c_str(), "[AwaitNodeDone] [%s] End", - src_node->GetName().c_str()); - GELOGI("[%s] Done waiting node.", src_node_item->NodeName().c_str()); - } - } +Status ShapeInferenceEngine::InferShape(NodeState &node_state) { + // Wait for all input shape become valid + GE_CHK_STATUS_RET_NOLOG(node_state.GetShapeInferenceState().AwaitShapesReady(*execution_context_)); + auto &node_item = *node_state.GetNodeItem(); + // Skip shape inference for node of type DEPEND_COMPUTE if (node_item.shape_inference_type == DEPEND_COMPUTE) { - GELOGD("[%s] Skip node with unknown shape type DEPEND_COMPUTE", node_item.NodeName().c_str()); + GELOGD("[%s] Skipping node with unknown shape type DEPEND_COMPUTE", node_item.NodeName().c_str()); return SUCCESS; } + // Clear shape range in case shape inference func forgot to do it if (node_item.shape_inference_type == DEPEND_SHAPE_RANGE) { // in case InferFunc forgot to reset output shape for (auto &output_desc : node_item.op_desc->GetAllOutputsDescPtr()) { @@ -133,13 +43,16 @@ Status ShapeInferenceEngine::InferShape(InferenceState &entry) { } } - // do shape inference - RECORD_SHAPE_INFERENCE_EVENT(context_, node_item.NodeName().c_str(), "[InferShape] Start"); + // Wait for "const input nodes" if node's shape inference function requires any. + GE_CHK_STATUS_RET_NOLOG(AwaitDependentNodes(node_state)); + + // Do shape inference GELOGD("[%s] Start to invoke InferShapeAndType", node_item.NodeName().c_str()); + RECORD_SHAPE_INFERENCE_EVENT(execution_context_, node_item.NodeName().c_str(), "[InferShapeAndType] Start"); GE_CHK_STATUS_RET(ShapeRefiner::InferShapeAndType(node_item.node), "Invoke InferShapeAndType failed."); - RECORD_SHAPE_INFERENCE_EVENT(context_, node_item.NodeName().c_str(), "[InferShape] End"); + RECORD_SHAPE_INFERENCE_EVENT(execution_context_, node_item.NodeName().c_str(), "[InferShapeAndType] End"); - // Check shape + // Check again to make sure shape is valid after shape inference if (node_item.shape_inference_type != DEPEND_SHAPE_RANGE) { bool is_unknown_shape = false; GE_CHK_STATUS_RET(NodeUtils::GetNodeUnknownShapeStatus(*node_item.node, is_unknown_shape), @@ -149,12 +62,33 @@ Status ShapeInferenceEngine::InferShape(InferenceState &entry) { node_item.NodeName().c_str()); } + GELOGD("[%s] [HybridTrace] After shape inference. Node = %s", node_item.NodeName().c_str(), + node_item.DebugString().c_str()); + GELOGD("[%s] InferShapeAndType finished successfully.", node_item.NodeName().c_str()); return SUCCESS; } -void ShapeInferenceEngine::PropagateOutputShapes(InferenceState &entry, std::queue &queue) { - auto &node_item = entry.node_item; +Status ShapeInferenceEngine::AwaitDependentNodes(NodeState &node_state) { + auto &node_item = *node_state.GetNodeItem(); + for (auto &src_node : node_item.dependents_for_shape_inference) { + GELOGI("[%s] Start to wait for data dependent node: %s", node_item.NodeName().c_str(), src_node->GetName().c_str()); + RECORD_SHAPE_INFERENCE_EVENT(execution_context_, node_item.NodeName().c_str(), "[AwaitNodeDone] [%s] Start", + src_node->GetName().c_str()); + if (!subgraph_context_->Await(src_node)) { + GELOGE(INTERNAL_ERROR, "[%s] Await node failed.", src_node->GetName().c_str()); + return INTERNAL_ERROR; + } + + RECORD_SHAPE_INFERENCE_EVENT(execution_context_, node_item.NodeName().c_str(), "[AwaitNodeDone] [%s] End", + src_node->GetName().c_str()); + GELOGI("[%s] Done waiting node.", src_node->GetName().c_str()); + } + + return SUCCESS; +} + +Status ShapeInferenceEngine::PropagateOutputShapes(const NodeItem &node_item) { // output shape will not be valid until compute is done. bool shape_is_future = node_item.shape_inference_type == DEPEND_SHAPE_RANGE || node_item.shape_inference_type == DEPEND_COMPUTE; @@ -171,88 +105,25 @@ void ShapeInferenceEngine::PropagateOutputShapes(InferenceState &entry, std::que // propagate output to all sub-inputs for (auto &dst_input_index_and_node : output_nodes) { auto &dst_node_item = dst_input_index_and_node.second; - auto inference_state = GetOrCreateEntry(*dst_node_item); + auto dst_node_state = subgraph_context_->GetOrCreateNodeState(dst_node_item); + GE_CHECK_NOTNULL(dst_node_state); + GELOGI("[%s] Update dst node [%s], input index = %d", node_item.NodeName().c_str(), dst_node_item->NodeName().c_str(), dst_input_index_and_node.first); - // in case type 3/4, shape will be valid after computing is done + // in case type 3 and 4, shape will be valid after computing is done if (shape_is_future) { - ShapeFuture future(node_item.node, i, &context_->cv_manager); - inference_state->UpdateInputShapeFuture(dst_input_index_and_node.first, std::move(future)); + ShapeFuture future(node_item.node, i, subgraph_context_); + dst_node_state->GetShapeInferenceState().UpdateInputShapeFuture(dst_input_index_and_node.first, + std::move(future)); } else { - inference_state->UpdateInputShape(dst_input_index_and_node.first, ori_shape, shape); - } - - if (inference_state->IsInputShapesReady()) { - GELOGI("[%s] Node input shape is ready, add to queue.", inference_state->node_item.NodeName().c_str()); - queue.emplace(inference_state); + dst_node_state->GetShapeInferenceState().UpdateInputShape(dst_input_index_and_node.first, ori_shape, shape); } } } GELOGD("[%s] Propagating output shapes finished successfully.", node_item.NodeName().c_str()); -} - -ShapeInferenceEngine::InferenceState *ShapeInferenceEngine::GetOrCreateEntry(const NodeItem &node_item) { - auto &node_state = inference_states_[node_item.node_id]; - if (node_state == nullptr) { - node_state.reset(new (std::nothrow) InferenceState(node_item)); - } - - return node_state.get(); -} - -ShapeInferenceEngine::InferenceState::InferenceState(const NodeItem &node_item) : node_item(node_item) { - this->num_pending_shapes = node_item.num_inputs; -} - -void ShapeInferenceEngine::InferenceState::UpdateInputShape(uint32_t idx, const GeShape &ori_shape, - const GeShape &shape) { - if (node_item.const_input_shapes.count(idx) != 0) { - GELOGD("[%s] Trying to update constant shape, idx = %u. old shape = [%s], new shape = [%s]", - node_item.NodeName().c_str(), idx, node_item.op_desc->MutableInputDesc(idx)->GetShape().ToString().c_str(), - shape.ToString().c_str()); - } - - GELOGD("[%s] Update input shape [%u] with Shape: [%s] and OriginalShape: [%s]", node_item.NodeName().c_str(), idx, - shape.ToString().c_str(), ori_shape.ToString().c_str()); - num_pending_shapes -= 1; - node_item.op_desc->MutableInputDesc(idx)->SetShape(shape); - node_item.op_desc->MutableInputDesc(idx)->SetOriginShape(ori_shape); -} - -void ShapeInferenceEngine::InferenceState::UpdateInputShapeFuture(uint32_t idx, ShapeFuture &&future) { - if (node_item.const_input_shapes.count(idx) != 0) { - GELOGE(INTERNAL_ERROR, "[%s] Trying to update constant shape, idx = %u", node_item.NodeName().c_str(), idx); - return; - } - - GELOGD("[%s] Update input shape [%u] with ShapeFuture.", node_item.NodeName().c_str(), idx); - num_pending_shapes -= 1; - shape_futures.emplace_back(idx, std::move(future)); -} - -Status ShapeInferenceEngine::InferenceState::AwaitShapeFutures(GraphExecutionContext *context) { - for (auto &p : shape_futures) { - auto idx = p.first; - auto &future = p.second; - GeShape shape; - GeShape ori_shape; - RECORD_SHAPE_INFERENCE_EVENT(context, node_item.NodeName().c_str(), "[AwaitShape] [idx = %u] Start", idx); - GE_CHK_STATUS_RET(future.Get(ori_shape, shape), "[%s] Get shape failed. index = %u", node_item.NodeName().c_str(), - idx); - RECORD_SHAPE_INFERENCE_EVENT(context, node_item.NodeName().c_str(), "[AwaitShape] [idx = %u] End", idx); - - GELOGD("[%s] Update input shape [%u] with shape: [%s] and ori_shape: [%s]", node_item.NodeName().c_str(), idx, - shape.ToString().c_str(), ori_shape.ToString().c_str()); - node_item.op_desc->MutableInputDesc(idx)->SetShape(std::move(shape)); - node_item.op_desc->MutableInputDesc(idx)->SetOriginShape(ori_shape); - } - return SUCCESS; } - -ShapeInferenceEngine::ShapeFuture::ShapeFuture(NodePtr src_node, uint32_t src_index, NodeDoneManager *node_done_manager) - : src_node_(std::move(src_node)), src_index_(src_index), node_done_manager_(node_done_manager) {} } // namespace hybrid -} // namespace ge \ No newline at end of file +} // namespace ge diff --git a/src/ge/hybrid/executor/worker/shape_inference_engine.h b/src/ge/hybrid/executor/worker/shape_inference_engine.h index b1e1c879..972f8ee1 100644 --- a/src/ge/hybrid/executor/worker/shape_inference_engine.h +++ b/src/ge/hybrid/executor/worker/shape_inference_engine.h @@ -17,75 +17,25 @@ #ifndef GE_HYBRID_EXECUTOR_INFERSHAPE_SHAPE_INFERENCE_ENGINE_H_ #define GE_HYBRID_EXECUTOR_INFERSHAPE_SHAPE_INFERENCE_ENGINE_H_ -#include -#include -#include -#include "common/thread_pool.h" #include "hybrid/executor/hybrid_execution_context.h" +#include "hybrid/executor/subgraph_context.h" namespace ge { namespace hybrid { class ShapeInferenceEngine { public: - explicit ShapeInferenceEngine(GraphExecutionContext *context); - + ShapeInferenceEngine(GraphExecutionContext *execution_context, SubgraphContext *subgraph_context); ~ShapeInferenceEngine() = default; - Status Start(ThreadPool &pool); - - private: - class ShapeFuture { - public: - ShapeFuture(NodePtr src_node, uint32_t src_index, NodeDoneManager *node_done_manager); - ~ShapeFuture() = default; - Status Get(GeShape &ori_shape, GeShape &shape) { - GELOGI("Start to wait node: %s for getting shape", src_node_->GetName().c_str()); - if (!node_done_manager_->Await(src_node_)) { - GELOGE(INTERNAL_ERROR, "cancelled"); - return INTERNAL_ERROR; - } - - shape = src_node_->GetOpDesc()->MutableOutputDesc(src_index_)->MutableShape(); - ori_shape = src_node_->GetOpDesc()->MutableOutputDesc(src_index_)->GetOriginShape(); - GELOGI("Get shape from %s:%u. shape = [%s]", src_node_->GetName().c_str(), src_index_, shape.ToString().c_str()); - return SUCCESS; - } - - private: - NodePtr src_node_; - uint32_t src_index_; - NodeDoneManager *node_done_manager_; - }; - - struct InferenceState { - explicit InferenceState(const NodeItem &node_item); - inline bool IsInputShapesReady() const { return num_pending_shapes == 0; } - - void UpdateInputShape(uint32_t idx, const GeShape &ori_shape, const GeShape &shape); - - Status AwaitShapeFutures(GraphExecutionContext *context); + Status InferShape(NodeState &node_state); - void UpdateInputShapeFuture(uint32_t idx, ShapeFuture &&future); + Status PropagateOutputShapes(const NodeItem &node_item); - const NodeItem &node_item; - - private: - std::vector> shape_futures; - int num_pending_shapes = 0; - }; - - InferenceState *GetOrCreateEntry(const NodeItem &node_item); - - Status InferShapeProcess(); - - void InferenceDone(Status status); - - Status InferShape(InferenceState &entry); - - void PropagateOutputShapes(InferenceState &entry, std::queue &queue); + private: + Status AwaitDependentNodes(NodeState &node_state); - GraphExecutionContext *context_; - std::unordered_map> inference_states_; + GraphExecutionContext *execution_context_; + SubgraphContext *subgraph_context_; }; } // namespace hybrid } // namespace ge diff --git a/src/ge/hybrid/executor/worker/task_compile_engine.cc b/src/ge/hybrid/executor/worker/task_compile_engine.cc index f6434ffa..57b19f5f 100644 --- a/src/ge/hybrid/executor/worker/task_compile_engine.cc +++ b/src/ge/hybrid/executor/worker/task_compile_engine.cc @@ -16,172 +16,22 @@ #include "hybrid/executor/worker/task_compile_engine.h" #include "init/gelib.h" -#include "framework/common/debug/log.h" #include "hybrid/node_executor/node_executor.h" namespace ge { namespace hybrid { -namespace { -uint32_t kDefaultWorkerCnt = 4; -uint32_t kDefaultDeviceId = 0; -} // namespace -TaskCompileEngine::TaskCompileEngine(GraphExecutionContext *context) : context_(context), pool_(kDefaultWorkerCnt) {} - -TaskCompileEngine::~TaskCompileEngine() { - if (rt_context_ != nullptr) { - GELOGD("To destroy compile context: %p.", rt_context_); - GE_CHK_RT(rtCtxDestroy(rt_context_)); - } -} - -Status TaskCompileEngine::Init() { - GELOGD("Start to init CompileEngine"); - rtContext_t current_ctx = nullptr; - GE_CHK_RT(rtCtxGetCurrent(¤t_ctx)); - GE_CHK_RT_RET(rtCtxCreate(&rt_context_, RT_CTX_GEN_MODE, kDefaultDeviceId)); - GELOGD("Context created for compiling. ctx = %p", rt_context_); - GE_CHK_RT_RET(rtCtxSetCurrent(current_ctx)); - return SUCCESS; -} - -void TaskCompileEngine::Reset() { - complete_queue_.Push(nullptr); // ensure iteration can stop - unique_ptr entry; - while (true) { - complete_queue_.Pop(entry); - if (entry == nullptr) { - break; - } - - if (entry->future != nullptr) { - entry->future->wait(); - } - } - - complete_queue_.Clear(); -} - -Status TaskCompileEngine::Start(ThreadPool &pool) { - pool.commit([&]() { (void)this->CompileProcess(); }); - - worker_future_ = pool_.commit([&]() -> Status { return this->DistributeCompiledTasks(); }); - - if (!worker_future_.valid()) { - GELOGE(INTERNAL_ERROR, "Failed to start worker thread"); - return INTERNAL_ERROR; - } - - return SUCCESS; -} - -Status TaskCompileEngine::CompileProcess() { - auto &compile_queue = context_->compile_queue; - while (true) { - NodeStatePtr node_state; - // Stop() will not be invoked, Pop won't failed - (void)compile_queue.Pop(node_state); - - // EOF - if (node_state == nullptr) { - GELOGD("Got EOF"); - complete_queue_.Push(unique_ptr()); - break; - } - - auto entry = unique_ptr(new (std::nothrow) ResultQueueEntry()); - GE_CHECK_NOTNULL(entry); - entry->node_state = node_state; - - auto node_item = *node_state->node_item; - if (node_item.kernel_task != nullptr) { - GELOGD("use precompiled task. node name = %s", node_item.NodeName().c_str()); - node_state->kernel_task = node_item.kernel_task; - complete_queue_.Push(std::move(entry)); - continue; - } - - auto ret = CompileAsync(*node_state->node_item, *entry); - if (ret == SUCCESS) { - complete_queue_.Push(std::move(entry)); - continue; - } - - // On Error - worker_future_.wait(); - Reset(); - return CompileDone(ret); - } - - Status ret = worker_future_.get(); - Reset(); - return CompileDone(ret); -} - -Status TaskCompileEngine::CompileDone(Status status) { - if (status != SUCCESS) { - GELOGE(status, "Error occurred while compiling node."); - context_->OnError(status); - } else { - context_->execution_queue.Push(nullptr); - } - GELOGI("CompileEngine worker END. ret = %u", status); - return status; -} - -Status TaskCompileEngine::DoCompile(const NodeItem &node_item, NodeState &node_state) { - RECORD_COMPILE_EVENT(context_, node_state.GetName().c_str(), "Start"); - GE_CHK_RT_RET(rtCtxSetCurrent(rt_context_)); - auto ret = node_item.node_executor->CompileTask(*context_->model, node_item.node, node_state.kernel_task); - RECORD_COMPILE_EVENT(context_, node_state.GetName().c_str(), "End"); +Status TaskCompileEngine::Compile(NodeState &node_state, GraphExecutionContext *context) { + const auto &node_item = *node_state.GetNodeItem(); + RECORD_COMPILE_EVENT(context, node_item.NodeName().c_str(), "Start"); + GE_CHK_RT_RET(rtCtxSetCurrent(context->rt_gen_context)); + + shared_ptr kernel_task; + auto ret = node_item.node_executor->CompileTask(*context->model, node_item.node, kernel_task); + RECORD_COMPILE_EVENT(context, node_state.GetName().c_str(), "End"); GE_CHK_STATUS_RET(ret, "Failed to create task for node: %s", node_item.NodeName().c_str()); + node_state.SetKernelTask(kernel_task); GELOGI("Compiling node %s successfully", node_state.GetName().c_str()); return SUCCESS; } - -Status TaskCompileEngine::CompileAsync(const NodeItem &node_item, ResultQueueEntry &entry) { - auto node_state = entry.node_state; - auto f = pool_.commit([this, node_item, node_state]() -> Status { return DoCompile(node_item, *node_state); }); - - if (!f.valid()) { - GELOGE(INTERNAL_ERROR, "Failed to commit compile task"); - return INTERNAL_ERROR; - } - - entry.future = unique_ptr>(new (std::nothrow) std::future(std::move(f))); - GE_CHECK_NOTNULL(entry.future); - return SUCCESS; -} - -Status TaskCompileEngine::DistributeCompiledTasks() { - GELOGD("DistributeCompiledTasks start."); - auto &execute_queue = context_->execution_queue; - unique_ptr entry; - bool ret = SUCCESS; - while (true) { - if (!complete_queue_.Pop(entry)) { - GELOGE(INTERNAL_ERROR, "Failed to pop item from queue"); - ret = INTERNAL_ERROR; - break; - } - - // EOF - if (entry == nullptr) { - break; - } - - // if has compile future - if (entry->future != nullptr) { - ret = entry->future->get(); - if (ret != SUCCESS) { - break; - } - } - - execute_queue.Push(entry->node_state); - } - - GELOGD("DistributeCompiledTasks out. ret = %u.", ret); - return ret; -} } // namespace hybrid -} // namespace ge +} // namespace ge \ No newline at end of file diff --git a/src/ge/hybrid/executor/worker/task_compile_engine.h b/src/ge/hybrid/executor/worker/task_compile_engine.h index 828a1d8c..a677cb2e 100644 --- a/src/ge/hybrid/executor/worker/task_compile_engine.h +++ b/src/ge/hybrid/executor/worker/task_compile_engine.h @@ -17,44 +17,13 @@ #ifndef GE_HYBRID_EXECUTOR_COMPILE_TASK_COMPILE_ENGINE_H_ #define GE_HYBRID_EXECUTOR_COMPILE_TASK_COMPILE_ENGINE_H_ -#include -#include -#include "common/thread_pool.h" #include "hybrid/executor/hybrid_execution_context.h" namespace ge { namespace hybrid { class TaskCompileEngine { public: - explicit TaskCompileEngine(GraphExecutionContext *context); - - ~TaskCompileEngine(); - - Status Init(); - - Status Start(ThreadPool &pool); - - private: - struct ResultQueueEntry { - NodeStatePtr node_state; - std::unique_ptr> future; - }; - - Status CompileProcess(); - - Status CompileDone(Status status); - - private: - Status DoCompile(const NodeItem &node_item, NodeState &node_state); - Status CompileAsync(const NodeItem &node_item, ResultQueueEntry &entry); - Status DistributeCompiledTasks(); - void Reset(); - - rtContext_t rt_context_ = nullptr; - GraphExecutionContext *context_; - BlockingQueue> complete_queue_; - ThreadPool pool_; - std::future worker_future_; + static Status Compile(NodeState &node_state, GraphExecutionContext *context); }; } // namespace hybrid } // namespace ge diff --git a/src/ge/hybrid/hybrid_davinci_model.cc b/src/ge/hybrid/hybrid_davinci_model.cc index 58c7d0e3..0454fa72 100644 --- a/src/ge/hybrid/hybrid_davinci_model.cc +++ b/src/ge/hybrid/hybrid_davinci_model.cc @@ -18,6 +18,7 @@ #include "hybrid_davinci_model.h" #include "hybrid/model/hybrid_model.h" #include "hybrid/executor/hybrid_model_async_executor.h" +#include "hybrid/node_executor/node_executor.h" namespace ge { namespace hybrid { @@ -25,14 +26,19 @@ class HybridDavinciModel::Impl { public: explicit Impl(GeRootModelPtr ge_model) : model_(std::move(ge_model)), executor_(&model_) {} - ~Impl() = default; + ~Impl() { NodeExecutorManager::GetInstance().FinalizeExecutors(); } Status Init() { + GE_CHK_STATUS_RET(NodeExecutorManager::GetInstance().EnsureInitialized(), "Failed to initialize executors"); GE_CHK_STATUS_RET(model_.Init(), "Failed to init model.") GE_CHK_STATUS_RET(executor_.Init(), "Failed to init model executor.") return SUCCESS; } + Status Execute(const vector &inputs, vector &outputs) { + return executor_.Execute(inputs, outputs); + } + Status ModelRunStart() { return executor_.Start(listener_); } Status ModelRunStop() { return executor_.Stop(); } @@ -76,6 +82,11 @@ Status HybridDavinciModel::Init() { return impl_->Init(); } +Status HybridDavinciModel::Execute(const vector &inputs, vector &outputs) { + GE_CHECK_NOTNULL(impl_); + return impl_->Execute(inputs, outputs); +} + Status HybridDavinciModel::ModelRunStart() { GE_CHECK_NOTNULL(impl_); return impl_->ModelRunStart(); diff --git a/src/ge/hybrid/hybrid_davinci_model.h b/src/ge/hybrid/hybrid_davinci_model.h index 866b756b..c286a222 100644 --- a/src/ge/hybrid/hybrid_davinci_model.h +++ b/src/ge/hybrid/hybrid_davinci_model.h @@ -37,6 +37,8 @@ class HybridDavinciModel { Status Init(); + Status Execute(const vector &inputs, vector &outputs); + Status ModelRunStart(); Status ModelRunStop(); diff --git a/src/ge/hybrid/hybrid_davinci_model_stub.cc b/src/ge/hybrid/hybrid_davinci_model_stub.cc index bca118f8..7bde98a3 100644 --- a/src/ge/hybrid/hybrid_davinci_model_stub.cc +++ b/src/ge/hybrid/hybrid_davinci_model_stub.cc @@ -26,6 +26,8 @@ std::unique_ptr HybridDavinciModel::Create(const GeRootModel Status HybridDavinciModel::Init() { return UNSUPPORTED; } +Status HybridDavinciModel::Execute(const vector &inputs, vector &outputs) { return UNSUPPORTED; } + Status HybridDavinciModel::ModelRunStart() { return UNSUPPORTED; } Status HybridDavinciModel::ModelRunStop() { return UNSUPPORTED; } diff --git a/src/ge/hybrid/model/graph_item.cc b/src/ge/hybrid/model/graph_item.cc new file mode 100644 index 00000000..528fc4ee --- /dev/null +++ b/src/ge/hybrid/model/graph_item.cc @@ -0,0 +1,62 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "framework/common/util.h" +#include "graph_item.h" + +namespace ge { +namespace hybrid { +namespace { +constexpr int kInvalidIndex = -1; +} // namespace +GraphItem::~GraphItem() { GELOGD("[%s] GraphItem destroyed.", name_.c_str()); } + +const vector &hybrid::GraphItem::GetAllNodes() const { return node_items_; } + +const vector &GraphItem::GetInputNodes() const { return input_nodes_; } + +Status GraphItem::GetOutputDescList(vector &output_desc_list) const { + if (is_dynamic_) { + for (auto &node_and_idx : output_edges_) { + const auto &tensor_desc = node_and_idx.first->op_desc->MutableOutputDesc(node_and_idx.second); + GE_CHECK_NOTNULL(tensor_desc); + output_desc_list.emplace_back(tensor_desc); + } + } else { + auto all_output_desc = output_node_->op_desc->GetAllOutputsDescPtr(); + for (auto &tensor_desc : output_node_->op_desc->GetAllOutputsDescPtr()) { + output_desc_list.emplace_back(tensor_desc); + } + } + + return SUCCESS; +} + +bool GraphItem::IsDynamic() const { return is_dynamic_; } + +const vector &GraphItem::GetInputIndexMapping() const { return input_index_mapping_; } + +int GraphItem::GetParentOutputIndex(size_t index) const { + if (index >= output_index_mapping_.size()) { + return kInvalidIndex; + } + + return output_index_mapping_[index]; +} + +const NodeItem *GraphItem::GetOutputNode() const { return output_node_; } +} // namespace hybrid +} // namespace ge \ No newline at end of file diff --git a/src/ge/hybrid/model/graph_item.h b/src/ge/hybrid/model/graph_item.h new file mode 100644 index 00000000..cb0fbbed --- /dev/null +++ b/src/ge/hybrid/model/graph_item.h @@ -0,0 +1,64 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_HYBRID_MODEL_SUBGRAPH_ITEM_H_ +#define GE_HYBRID_MODEL_SUBGRAPH_ITEM_H_ + +#include "external/ge/ge_api_error_codes.h" +#include "hybrid/model/node_item.h" + +namespace ge { +namespace hybrid { +class GraphItem { + public: + GraphItem() = default; + ~GraphItem(); + const vector &GetAllNodes() const; + const vector &GetInputNodes() const; + Status GetOutputDescList(std::vector &output_desc_list) const; + + int TotalInputs() const { return total_inputs_; } + + int TotalOutputs() const { return total_outputs_; } + + const std::string &GetName() const { return name_; } + + void SetName(const string &name) { name_ = name; } + + const NodeItem *GetOutputNode() const; + + bool IsDynamic() const; + int GetParentOutputIndex(size_t index) const; + const vector &GetInputIndexMapping() const; + + private: + friend class HybridModelBuilder; + std::string name_; + std::vector node_items_; + std::vector input_nodes_; + const NodeItem *output_node_ = nullptr; + // + std::vector> output_edges_; + int total_inputs_ = 0; + int total_outputs_ = 0; + + bool is_dynamic_ = true; + std::vector input_index_mapping_; + std::vector output_index_mapping_; +}; +} // namespace hybrid +} // namespace ge +#endif // GE_HYBRID_MODEL_SUBGRAPH_ITEM_H_ diff --git a/src/ge/hybrid/model/hybrid_model.cc b/src/ge/hybrid/model/hybrid_model.cc index e3726aec..0cb81aa3 100644 --- a/src/ge/hybrid/model/hybrid_model.cc +++ b/src/ge/hybrid/model/hybrid_model.cc @@ -29,6 +29,8 @@ namespace ge { namespace hybrid { HybridModel::HybridModel(GeRootModelPtr ge_model) : ge_root_model_(std::move(ge_model)) {} +HybridModel::~HybridModel() { GELOGD("[%s] HybridModel destroyed.", model_name_.c_str()); } + Status HybridModel::Init() { GELOGD("Start to init hybrid model."); GE_CHK_STATUS_RET(HybridModelBuilder(*this).Build(), "Failed to build hybrid model."); @@ -36,22 +38,6 @@ Status HybridModel::Init() { return SUCCESS; } -void HybridModel::Print() const { - for (const auto &node : node_items_) { - GELOGD("%s", node->DebugString().c_str()); - } -} - -TensorValue *HybridModel::GetWeight(const NodeItem *const_node) const { - auto it = weights_.find(const_node->node_id); - if (it == weights_.end() || it->second == nullptr) { - GELOGE(INTERNAL_ERROR, "[%s] Failed to get weight", const_node->NodeName().c_str()); - return nullptr; - } - - return it->second.get(); -} - TensorValue *HybridModel::GetVariable(const string &name) const { auto it = variable_tensors_.find(name); if (it == variable_tensors_.end()) { @@ -83,26 +69,26 @@ const std::vector *HybridModel::GetTaskDefs(const NodePtr &node) } NodeItem *HybridModel::MutableNodeItem(const NodePtr &node) { - auto node_id = node->GetOpDesc()->GetId(); - if (node_id < 0 || static_cast(node_id) > node_items_.size()) { - GELOGE(INTERNAL_ERROR, "index out of range. node_id = %ld, num_nodes = %zu", node_id, node_items_.size()); + auto it = node_items_.find(node); + if (it == node_items_.end()) { return nullptr; } - return node_items_[node_id].get(); + + return it->second.get(); } const NodeItem *HybridModel::GetNodeItem(const NodePtr &node) const { - auto node_id = node->GetOpDesc()->GetId(); - if (node_id < 0 || static_cast(node_id) > node_items_.size()) { - GELOGE(INTERNAL_ERROR, "Index out of range. node_id = %ld, num_nodes = %zu.", node_id, node_items_.size()); + auto it = node_items_.find(node); + if (it == node_items_.end()) { return nullptr; } - return node_items_[node_id].get(); + + return it->second.get(); } GeModelPtr HybridModel::GetGeModel(const NodePtr &node) const { - auto it = known_shape_sub_graphs_.find(node); - if (it == known_shape_sub_graphs_.end()) { + auto it = known_shape_sub_models_.find(node); + if (it == known_shape_sub_models_.end()) { GELOGE(INTERNAL_ERROR, "[%s] Failed to get GeModel for subgraph node.", node->GetName().c_str()); return nullptr; } @@ -110,8 +96,27 @@ GeModelPtr HybridModel::GetGeModel(const NodePtr &node) const { return it->second; } -const vector &HybridModel::GetNetOutputInputOffsets() const { return net_output_input_offsets_; } +const GraphItem *HybridModel::GetRootGraphItem() const { return root_graph_item_.get(); } + +const GraphItem *HybridModel::GetSubgraphItem(const std::string &graph_name) const { + GELOGD("To find subgraph item by name = %s", graph_name.c_str()); + auto it = subgraph_items_.find(graph_name); + if (it == subgraph_items_.end()) { + GELOGD("Subgraph item not found by node = %s", graph_name.c_str()); + return nullptr; + } + + return it->second.get(); +} + +const GraphItem *HybridModel::GetSubgraphItem(const ComputeGraphPtr &subgraph) const { + if (subgraph == nullptr) { + GELOGE(PARAM_INVALID, "subgraph is nullptr"); + return nullptr; + } -void HybridModel::SetDeviceId(uint32_t device_id) { device_id_ = device_id; } + auto subgraph_name = subgraph->GetName(); + return GetSubgraphItem(subgraph_name); +} } // namespace hybrid } // namespace ge diff --git a/src/ge/hybrid/model/hybrid_model.h b/src/ge/hybrid/model/hybrid_model.h index 007f76c6..f554752e 100644 --- a/src/ge/hybrid/model/hybrid_model.h +++ b/src/ge/hybrid/model/hybrid_model.h @@ -26,39 +26,23 @@ #include "graph/node.h" #include "hybrid/common/tensor_value.h" #include "hybrid/model/node_item.h" +#include "hybrid/model/graph_item.h" #include "model/ge_root_model.h" namespace ge { namespace hybrid { -class HybridModelAsyncExecutor; class HybridModel { public: explicit HybridModel(GeRootModelPtr ge_model); - ~HybridModel() = default; + ~HybridModel(); Status Init(); - const std::vector &RootNodes() const { return root_nodes_; } - const NodeItem *GetNodeItem(const NodePtr &node) const; - size_t NumNodes() const { return node_items_.size(); } - uint64_t GetSessionId() const { return root_runtime_param_.session_id; } - int TotalInputs() const { return total_inputs_; } - - const map &GetInputNodes() const { return input_nodes_; } - - const std::map> &GetInputOffsets() const { return input_offsets_; } - - const vector &GetNetOutputInputOffsets() const; - - const std::vector &GetOutputOffsets() const { return output_offsets_; } - - const std::vector &GetConstNodes() const { return const_nodes_; } - GeModelPtr GetGeModel(const NodePtr &node) const; NodeItem *MutableNodeItem(const NodePtr &node); @@ -67,46 +51,40 @@ class HybridModel { const uint8_t *GetVarMemBase() const { return var_mem_base_; } - void SetDeviceId(uint32_t device_id); + void SetDeviceId(uint32_t device_id) { device_id_ = device_id; } void SetModelId(uint32_t model_id) { model_id_ = model_id; } uint32_t GetModelId() const { return model_id_; } - TensorValue *GetWeight(const NodeItem *const_node) const; - TensorValue *GetVariable(const string &name) const; NodePtr GetVariableNode(const string &name) const; const std::vector *GetTaskDefs(const NodePtr &node) const; - int TotalOutputs() const { return total_outputs_; } + const GraphItem *GetRootGraphItem() const; - GeRootModelPtr GetGeRootModel() const { return ge_root_model_; } - void Print() const; + const GraphItem *GetSubgraphItem(const std::string &graph_name) const; + + const GraphItem *GetSubgraphItem(const ComputeGraphPtr &subgraph) const; private: friend class HybridModelBuilder; friend class HybridModelAsyncExecutor; + std::string model_name_; GeRootModelPtr ge_root_model_; - std::vector root_nodes_; std::map input_nodes_; - std::map> input_offsets_; - std::vector output_offsets_; - std::vector net_output_input_offsets_; - NodeItem *net_output_node_ = nullptr; - std::vector> node_items_; - std::vector const_nodes_; std::map constant_op_nodes_; std::map variable_nodes_; std::map> variable_tensors_; - std::map> weights_; std::map> task_defs_; - std::map known_shape_sub_graphs_; - int total_inputs_ = 0; - int total_outputs_ = 0; + std::map known_shape_sub_models_; + + std::unique_ptr root_graph_item_; + std::map> subgraph_items_; + std::map> node_items_; // runtime fields uint32_t device_id_ = 0; diff --git a/src/ge/hybrid/model/hybrid_model_builder.cc b/src/ge/hybrid/model/hybrid_model_builder.cc index 190890b7..841f1f15 100644 --- a/src/ge/hybrid/model/hybrid_model_builder.cc +++ b/src/ge/hybrid/model/hybrid_model_builder.cc @@ -23,7 +23,6 @@ #include "graph/manager/trans_var_data_utils.h" #include "graph/utils/graph_utils.h" #include "graph/utils/type_utils.h" -#include "framework/common/debug/log.h" #include "hybrid/common/npu_memory_allocator.h" #include "hybrid/node_executor/node_executor.h" @@ -32,6 +31,7 @@ namespace hybrid { namespace { const uint32_t kSubgraphIndex = 0U; const uint32_t kVarOutputIndex = 0U; +const uint32_t kAlignment = 32; const int kBytes = 8; int64_t CalcVarSizeInBytes(const GeTensorDesc &desc) { @@ -46,6 +46,9 @@ int64_t CalcVarSizeInBytes(const GeTensorDesc &desc) { for (size_t dim_index = 0; dim_index < dim_num; ++dim_index) { var_size *= shape.GetDim(dim_index); } + + // padding up to multiple of kAlignment, and add extra kAlignment + var_size = (var_size + kAlignment * 2 - 1) / kAlignment * kAlignment; return var_size; } } // namespace @@ -56,20 +59,19 @@ HybridModelBuilder::HybridModelBuilder(HybridModel &hybrid_model) Status HybridModelBuilder::Build() { GE_CHK_STATUS_RET(ValidateParams(), "Failed to validate GeRootModel"); - graph_name_ = ge_root_model_->GetRootGraph()->GetName(); + hybrid_model_.model_name_ = ge_root_model_->GetRootGraph()->GetName(); GELOGI("[%s] Start to build hybrid model.", GetGraphName()); GE_CHK_STATUS_RET(InitRuntimeParams(), "[%s] Failed to InitRuntimeParams", GetGraphName()); - GE_CHK_STATUS_RET(NodeExecutorManager::GetInstance().EnsureInitialized(), "Failed to initialize executors"); GE_CHK_STATUS_RET(IndexSpecialNodes(), "[%s] Failed to index nodes", GetGraphName()); GE_CHK_STATUS_RET(IndexTaskDefs(), "[%s] Failed to index task defs", GetGraphName()); GE_CHK_STATUS_RET(LoadGraph(), "[%s] Failed to load graph", GetGraphName()); + GE_CHK_STATUS_RET(AssignUninitializedConstantOps(), "[%s] Failed to assign uninitialized constants", GetGraphName()); GE_CHK_STATUS_RET(TransAllVarData(), "[%s] Failed to trans all var data", GetGraphName()); GE_CHK_STATUS_RET(CopyVarData(), "[%s] Failed to copy var data", GetGraphName()); GE_CHK_STATUS_RET(InitModelMem(), "[%s] Failed to init memory", GetGraphName()); GE_CHK_STATUS_RET(InitWeights(), "[%s] Failed to init weights", GetGraphName()); GE_CHK_STATUS_RET(InitConstantOps(), "[%s] Failed to init constant op", GetGraphName()); GE_CHK_STATUS_RET(InitVariableTensors(), "[%s] Failed to init variables", GetGraphName()); - GE_CHK_STATUS_RET(ResolveRootNodes(), "[%s] Failed to resolve root nodes", GetGraphName()); GE_CHK_STATUS_RET(LoadTasks(), "[%s] Failed to load tasks", GetGraphName()); GELOGI("[%s] Done building hybrid model successfully.", GetGraphName()); return SUCCESS; @@ -81,45 +83,17 @@ Status HybridModelBuilder::ValidateParams() { return SUCCESS; } -Status HybridModelBuilder::ResolveRootNodes() { - for (auto &node : hybrid_model_.node_items_) { - if (node->node->GetInDataNodes().empty()) { - hybrid_model_.root_nodes_.emplace_back(node.get()); - GELOGI("[%s] Root node added. node name = %s", GetGraphName(), node->NodeName().c_str()); - } - } - - if (hybrid_model_.root_nodes_.empty()) { - GELOGE(PARAM_INVALID, "[%s] Root nodes is empty.", GetGraphName()); - return PARAM_INVALID; - } - - return SUCCESS; -} - -Status HybridModelBuilder::BuildNoteItem(const NodePtr &node, NodeItem &node_item) { - GE_CHK_STATUS_RET(NodeUtils::GetNodeUnknownShapeStatus(*node, node_item.is_dynamic), - "[%s] Failed to get shape status.", node->GetName().c_str()); - +Status HybridModelBuilder::BuildNodeItem(const NodePtr &node, NodeItem &node_item) { auto op_desc = node->GetOpDesc(); vector dependencies = node->GetOpDesc()->GetOpInferDepends(); GE_CHK_STATUS_RET(ParseDependentInputNodes(node_item, dependencies), "[%s] Failed to parse node dependencies.", node_item.NodeName().c_str()); - auto it = node_ref_inputs_.find(node); - if (it != node_ref_inputs_.end()) { - for (auto &idx_and_node : it->second) { - // var and constant only have one output - node_item.const_input_shapes[idx_and_node.first] = - idx_and_node.second->GetOpDesc()->MutableOutputDesc(kVarOutputIndex); - } - } - node_item.outputs.resize(node_item.num_outputs); for (int i = 0; i < node_item.num_outputs; ++i) { auto out_data_anchor = node->GetOutDataAnchor(i); if (out_data_anchor == nullptr) { - GELOGE(INTERNAL_ERROR, "out anchor[%zu] of node %s is nullptr", i, node->GetName().c_str()); + GELOGE(INTERNAL_ERROR, "out anchor[%d] of node %s is nullptr", i, node->GetName().c_str()); return INTERNAL_ERROR; } @@ -137,27 +111,46 @@ Status HybridModelBuilder::BuildNoteItem(const NodePtr &node, NodeItem &node_ite } } + GE_CHK_STATUS_RET_NOLOG(ResolveRefIo(node_item)); return SUCCESS; } -Status HybridModelBuilder::GetOrCreateNodeItem(const NodePtr &node, NodeItem **node_item) { - auto &node_items = hybrid_model_.node_items_; - auto node_id = node->GetOpDesc()->GetId(); - if (node_id < 0 || static_cast(node_id) > node_items.size()) { - GELOGE(INTERNAL_ERROR, "[%s] Index out of range. node_id = %ld, num_nodes = %zu", node->GetName().c_str(), node_id, - node_items.size()); - return INTERNAL_ERROR; +Status HybridModelBuilder::ResolveRefIo(NodeItem &node_item) { + bool is_ref = false; + auto &op_desc = *node_item.op_desc; + (void)AttrUtils::GetBool(op_desc, ATTR_NAME_REFERENCE, is_ref); + if (!is_ref) { + return SUCCESS; + } + + auto inputs = op_desc.GetAllInputName(); + auto outputs = op_desc.GetAllOutputName(); + for (auto &output : outputs) { + for (auto &input : inputs) { + if (input.first == output.first) { + auto input_idx = static_cast(input.second); + auto output_idx = static_cast(output.second); + node_item.reuse_inputs[output_idx] = input_idx; + GELOGD("[%s] Output[%d] reuse input[%d]", node_item.NodeName().c_str(), output_idx, input_idx); + } + } } - auto &node_ptr = node_items[node_id]; - if (node_ptr != nullptr) { - *node_item = node_ptr.get(); + return SUCCESS; +} + +Status HybridModelBuilder::GetOrCreateNodeItem(const NodePtr &node, NodeItem **node_item) { + auto &node_items = hybrid_model_.node_items_; + auto it = node_items.find(node); + if (it != node_items.end()) { + *node_item = it->second.get(); return SUCCESS; } auto new_node = std::unique_ptr(new (std::nothrow) NodeItem(node)); GE_CHECK_NOTNULL(new_node); GE_CHECK_NOTNULL(new_node->op_desc); + GE_CHK_STATUS_RET(new_node->Init(), "Failed to init NodeItem [%s] .", node->GetName().c_str()); GE_CHK_STATUS_RET_NOLOG(NodeExecutorManager::GetInstance().GetExecutor(*node, &new_node->node_executor)); // we do not need L2 Buffer @@ -169,18 +162,58 @@ Status HybridModelBuilder::GetOrCreateNodeItem(const NodePtr &node, NodeItem **n int32_t unknown_shape_type_val = 0; (void)AttrUtils::GetInt(new_node->op_desc, ::ge::ATTR_NAME_UNKNOWN_SHAPE_TYPE, unknown_shape_type_val); new_node->shape_inference_type = static_cast(unknown_shape_type_val); - if (new_node->shape_inference_type == DEPEND_SHAPE_RANGE || new_node->shape_inference_type == DEPEND_COMPUTE) { - new_node->has_observer = true; + + GE_CHK_STATUS_RET(NodeUtils::GetNodeUnknownShapeStatus(*node, new_node->is_dynamic), + "[%s] Failed to get shape status.", node->GetName().c_str()); + + if (new_node->is_dynamic && (new_node->IsControlOp() || new_node->NodeType() == PARTITIONEDCALL)) { + new_node->shape_inference_type = DEPEND_COMPUTE; } + new_node->node_id = node_index; + new_node->op_desc->SetId(node_index); + node_index += 1; + *node_item = new_node.get(); - node_items[node_id] = std::move(new_node); + node_items[node] = std::move(new_node); return SUCCESS; } Status HybridModelBuilder::ParseDependentInputNodes(NodeItem &node_item, const std::vector &dependencies) { std::set dependent_input_nodes; auto &ge_node = node_item.node; + + // The input tensors become valid after computation is done for parent nodes of type DEPEND_COMPUTE. + // Wait for these parent nodes before execution. + for (const auto &in_anchor : ge_node->GetAllInDataAnchors()) { + const auto &peer_anchor = in_anchor->GetPeerOutAnchor(); + if (peer_anchor == nullptr) { + GELOGD("[%s] Input[%d] do not have peer anchor", node_item.NodeName().c_str(), in_anchor->GetIdx()); + continue; + } + + auto src_node = peer_anchor->GetOwnerNode(); + GE_CHECK_NOTNULL(src_node); + + auto src_node_item = MutableNodeItem(src_node); + GE_CHECK_NOTNULL(src_node_item); + + if (src_node_item->shape_inference_type == DEPEND_COMPUTE) { + GELOGD("[%s] Add input data dependent node [%s] due to inference type = DEPEND_COMPUTE", + node_item.NodeName().c_str(), src_node_item->NodeName().c_str()); + + src_node_item->has_observer = true; + node_item.dependents_for_execution.emplace_back(src_node); + } + + if (src_node_item->shape_inference_type == DEPEND_SHAPE_RANGE) { + GELOGD("[%s] Add input shape dependent node [%s] due to inference type = DEPEND_SHAPE_RANGE", + node_item.NodeName().c_str(), src_node_item->NodeName().c_str()); + src_node_item->has_observer = true; + dependent_input_nodes.emplace(src_node); + } + } + for (const auto &input_name : dependencies) { int input_index = node_item.op_desc->GetInputIndexByName(input_name); if (input_index < 0) { @@ -205,7 +238,7 @@ Status HybridModelBuilder::ParseDependentInputNodes(NodeItem &node_item, const s } for (const auto &dep_node : dependent_input_nodes) { - node_item.dependent_node_list.emplace_back(dep_node); + node_item.dependents_for_shape_inference.emplace_back(dep_node); } return SUCCESS; @@ -262,9 +295,14 @@ Status HybridModelBuilder::DoLinkDataAnchors(OutDataAnchorPtr &out_data_anchor, Status HybridModelBuilder::MergeInputNodes(ComputeGraph &graph) { const auto &wrapped_node = graph.GetParentNode(); + std::set root_nodes; for (const auto &node : graph.GetDirectNode()) { GE_CHECK_NOTNULL(node); if (node->GetType() != DATA_TYPE) { + if (node->GetInDataNodes().empty()) { + root_nodes.emplace(node); + } + continue; } @@ -291,12 +329,28 @@ Status HybridModelBuilder::MergeInputNodes(ComputeGraph &graph) { for (auto &out_data_anchor : node->GetAllOutDataAnchors()) { GE_CHECK_NOTNULL(out_data_anchor); for (auto &peer_in_data_anchor : out_data_anchor->GetPeerInDataAnchors()) { + auto dst_node = peer_in_data_anchor->GetOwnerNode(); + root_nodes.emplace(dst_node); GE_CHK_STATUS_RET_NOLOG(DoUnlinkDataAnchors(out_data_anchor, peer_in_data_anchor)); GE_CHK_STATUS_RET_NOLOG(DoLinkDataAnchors(src_out_anchor, peer_in_data_anchor)); } } } + // transfer in control edges to all root nodes + for (auto &root_node : root_nodes) { + auto in_nodes = root_node->GetInAllNodes(); + std::set in_node_set(in_nodes.begin(), in_nodes.end()); + for (auto &in_control_node : wrapped_node->GetInControlNodes()) { + if (in_node_set.count(in_control_node) == 0) { + GELOGD("[%s] Restore control edge to [%s]", in_control_node->GetName().c_str(), root_node->GetName().c_str()); + GE_CHECK_NOTNULL(in_control_node->GetOutControlAnchor()); + (void)in_control_node->GetOutControlAnchor()->LinkTo(root_node->GetInControlAnchor()); + } + } + } + + wrapped_node->GetInControlAnchor()->UnlinkAll(); return SUCCESS; } @@ -307,6 +361,11 @@ Status HybridModelBuilder::MergeNetOutputNode(ComputeGraph &graph) { const auto &net_output_desc = net_output_node->GetOpDesc(); GE_CHECK_NOTNULL(net_output_desc); + auto all_in_nodes = net_output_node->GetInAllNodes(); + auto all_out_nodes = parent_node->GetOutAllNodes(); + net_output_node->GetInControlAnchor()->UnlinkAll(); + parent_node->GetOutControlAnchor()->UnlinkAll(); + for (const auto &in_data_anchor : net_output_node->GetAllInDataAnchors()) { auto src_out_anchor = in_data_anchor->GetPeerOutAnchor(); GE_CHECK_NOTNULL(src_out_anchor); @@ -338,10 +397,25 @@ Status HybridModelBuilder::MergeNetOutputNode(ComputeGraph &graph) { } } + // transfer out control edges + std::set in_node_set(all_in_nodes.begin(), all_in_nodes.end()); + std::set out_node_set(all_out_nodes.begin(), all_out_nodes.end()); + for (auto &src_node : in_node_set) { + GELOGD("[%s] process in node.", src_node->GetName().c_str()); + auto out_nodes = src_node->GetOutAllNodes(); + std::set node_set(out_nodes.begin(), out_nodes.end()); + for (auto &dst_node : out_node_set) { + if (node_set.count(dst_node) == 0) { + src_node->GetOutControlAnchor()->LinkTo(dst_node->GetInControlAnchor()); + GELOGD("[%s] Restore control edge to [%s]", src_node->GetName().c_str(), dst_node->GetName().c_str()); + } + } + } + return SUCCESS; } -Status HybridModelBuilder::MergeSubgraphs(ComputeGraph &root_graph, ComputeGraphPtr &merged_graph) { +Status HybridModelBuilder::UnfoldSubgraphs(ComputeGraph &root_graph, ComputeGraphPtr &merged_graph) { merged_graph = MakeShared("MergedGraph"); for (const auto &node : root_graph.GetDirectNode()) { GE_CHECK_NOTNULL(node); @@ -371,32 +445,74 @@ Status HybridModelBuilder::MergeSubgraphs(ComputeGraph &root_graph, ComputeGraph } auto subgraph = NodeUtils::GetSubgraph(*node, kSubgraphIndex); - GE_CHK_STATUS_RET(MergeInputNodes(*subgraph), "Failed to merge data nodes for subgraph: %s", - subgraph->GetName().c_str()); - GE_CHK_STATUS_RET(MergeNetOutputNode(*subgraph), "Failed to merge net output nodes for subgraph: %s", - subgraph->GetName().c_str()); - GELOGD("Merging subgraph %s successfully.", subgraph->GetName().c_str()); - for (auto &sub_node : subgraph->GetAllNodes()) { - auto sub_op_type = sub_node->GetType(); - if (sub_op_type == DATA_TYPE || sub_op_type == NETOUTPUT) { - continue; - } + GE_CHECK_NOTNULL(subgraph); + GE_CHK_GRAPH_STATUS_RET(UnfoldSubgraph(root_graph, *merged_graph, *subgraph), "[%s] Failed to merge subgraph.", + subgraph->GetName().c_str()); + } - if (sub_op_type == CONSTANT || sub_op_type == CONSTANTOP || sub_op_type == VARIABLE) { - GELOGE(INTERNAL_ERROR, "Unexpected node in unknown subgraph. type = %s, node = %s::%s", sub_op_type.c_str(), - subgraph->GetName().c_str(), sub_node->GetName().c_str()); - return INTERNAL_ERROR; - } + // invoke before adding subgraphs. in case modify node id in known-shaped subgraphs. + GE_CHK_GRAPH_STATUS_RET(merged_graph->TopologicalSorting(), "Failed to invoke TopologicalSorting on merged graph."); + + for (auto &remained_subgraph : root_graph.GetAllSubgraphs()) { + GELOGD("Adding subgraph [%s] to merged-graph.", remained_subgraph->GetName().c_str()); + GE_CHK_GRAPH_STATUS_RET(merged_graph->AddSubgraph(remained_subgraph), "Failed to add subgraph [%s]", + remained_subgraph->GetName().c_str()); + } + + return SUCCESS; +} + +Status HybridModelBuilder::UnfoldSubgraph(ComputeGraph &root_graph, ComputeGraph &parent_graph, + ComputeGraph &sub_graph) { + auto parent_node = sub_graph.GetParentNode(); + GE_CHECK_NOTNULL(parent_node); + + GE_CHK_STATUS_RET(MergeInputNodes(sub_graph), "[%s] Failed to merge data nodes for subgraph", + sub_graph.GetName().c_str()); + GE_CHK_STATUS_RET(MergeNetOutputNode(sub_graph), "[%s] Failed to merge net output nodes for subgraph", + sub_graph.GetName().c_str()); + GELOGD("[%s] Done merging subgraph inputs and outputs successfully.", sub_graph.GetName().c_str()); + + for (auto &sub_node : sub_graph.GetDirectNode()) { + auto sub_op_type = sub_node->GetType(); + if (sub_op_type == DATA_TYPE || sub_op_type == NETOUTPUT) { + continue; + } + + if (sub_op_type == CONSTANT || sub_op_type == VARIABLE) { + GELOGE(INTERNAL_ERROR, "Unexpected node in unknown subgraph. type = %s, node = %s::%s", sub_op_type.c_str(), + sub_graph.GetName().c_str(), sub_node->GetName().c_str()); + return INTERNAL_ERROR; + } - merged_graph->AddNode(sub_node); - GELOGD("%s::%s added to merged graph.", subgraph->GetName().c_str(), sub_node->GetName().c_str()); + if (sub_op_type == PARTITIONEDCALL) { + bool is_unknown_shape = false; + GE_CHK_GRAPH_STATUS_RET(NodeUtils::GetNodeUnknownShapeStatus(*sub_node, is_unknown_shape), + "[%s] Failed to invoke GetNodeUnknownShapeStatus.", sub_node->GetName().c_str()); + if (is_unknown_shape) { + auto sub_sub_graph = NodeUtils::GetSubgraph(*sub_node, kSubgraphIndex); + GE_CHECK_NOTNULL(sub_sub_graph); + GE_CHK_STATUS_RET(UnfoldSubgraph(root_graph, parent_graph, *sub_sub_graph), "[%s] Failed to merge subgraph", + sub_sub_graph->GetName().c_str()); + continue; + } } + + parent_graph.AddNode(sub_node); + GELOGD("[%s::%s] added to parent graph: [%s].", sub_graph.GetName().c_str(), sub_node->GetName().c_str(), + parent_graph.GetName().c_str()); } + GELOGD("[%s] Done merging subgraph. remove it from root graph.", sub_graph.GetName().c_str()); + root_graph.RemoveSubgraph(sub_graph.GetName()); return SUCCESS; } -Status HybridModelBuilder::ParseNetOutput(const NodeItem &node_item) { +Status HybridModelBuilder::BuildOutputMapping(GraphItem &graph_item, const NodeItem &node_item, bool is_root_graph) { + auto output_size = node_item.op_desc->GetAllInputsSize(); + GE_CHECK_LE(output_size, UINT32_MAX); + graph_item.output_edges_.resize(output_size); + for (auto &in_data_anchor : node_item.node->GetAllInDataAnchors()) { auto peer_out_anchor = in_data_anchor->GetPeerOutAnchor(); GE_CHECK_NOTNULL(peer_out_anchor); @@ -408,11 +524,20 @@ Status HybridModelBuilder::ParseNetOutput(const NodeItem &node_item) { auto output_offset = src_node_item->output_start + peer_out_anchor->GetIdx(); GELOGI("Output[%d], node = %s, output_index = %d, output_offset = %d ", in_data_anchor->GetIdx(), src_node_item->NodeName().c_str(), peer_out_anchor->GetIdx(), output_offset); - hybrid_model_.output_offsets_.emplace_back(output_offset); + + graph_item.output_edges_[in_data_anchor->GetIdx()] = {src_node_item, peer_out_anchor->GetIdx()}; } - for (int i = 0; i < node_item.num_inputs; ++i) { - hybrid_model_.net_output_input_offsets_.emplace_back(node_item.input_start + i); + if (!is_root_graph) { + for (uint32_t i = 0; i < static_cast(output_size); ++i) { + uint32_t p_index = i; + // Net output of Subgraph of while do not have parent index + if (AttrUtils::GetInt(node_item.op_desc->GetInputDesc(i), ATTR_NAME_PARENT_NODE_INDEX, p_index)) { + GELOGD("[%s] Parent index not set for input[%u].", node_item.NodeName().c_str(), i); + } + + graph_item.output_index_mapping_.emplace_back(p_index); + } } return SUCCESS; @@ -420,82 +545,37 @@ Status HybridModelBuilder::ParseNetOutput(const NodeItem &node_item) { Status HybridModelBuilder::LoadGraph() { auto root_graph = ge_root_model_->GetRootGraph(); - GELOGI("Before merge subgraphs DirectNodesSize = %zu, GetAllNodesSize = %zu", root_graph->GetDirectNodesSize(), - root_graph->GetAllNodesSize()); - ComputeGraphPtr merged_graph; - GE_CHK_STATUS_RET_NOLOG(MergeSubgraphs(*root_graph, merged_graph)); - GELOGI("After merge subgraphs DirectNodesSize = %zu, GetAllNodesSize = %zu", merged_graph->GetDirectNodesSize(), - merged_graph->GetAllNodesSize()); - - merged_graph->SetGraphID(runtime_param_.graph_id); - GE_DUMP(merged_graph, "hybrid_merged_graph"); - int input_start = 0; - int output_start = 0; - uint32_t data_op_index = 0; - hybrid_model_.node_items_.resize(merged_graph->GetDirectNodesSize()); - - int64_t node_index = 0; - for (auto &node : merged_graph->GetDirectNode()) { - OpDescPtr op_desc = node->GetOpDesc(); - GE_CHECK_NOTNULL(op_desc); - op_desc->SetId(node_index++); - } - - for (const auto &node : merged_graph->GetDirectNode()) { - GE_CHECK_NOTNULL(node); - GE_CHECK_NOTNULL(node->GetOpDesc()); - const auto &op_type = node->GetType(); - - NodeItem *node_item = nullptr; - GE_CHK_STATUS_RET_NOLOG(GetOrCreateNodeItem(node, &node_item)); - GE_CHK_STATUS_RET_NOLOG(BuildNoteItem(node, *node_item)); - GE_CHK_STATUS_RET_NOLOG(UpdateAnchorStatus(node)); // needed by FE generate task - - node_item->input_start = input_start; - node_item->output_start = output_start; - input_start += node_item->num_inputs; - output_start += node_item->num_outputs; - - if (op_type == DATA_TYPE || op_type == AIPP_DATA_TYPE) { - auto data_index = data_op_index; - if (AttrUtils::GetInt(node->GetOpDesc(), ATTR_NAME_INDEX, data_index)) { - GELOGI("ge_train: get new index %u, old %u", data_index, data_op_index); - } - hybrid_model_.input_nodes_.emplace(data_index, node_item); - data_op_index++; - } else if (op_type == NETOUTPUT) { - hybrid_model_.net_output_node_ = node_item; - GE_CHK_STATUS_RET_NOLOG(ParseNetOutput(*node_item)); - } else if (op_type == PARTITIONEDCALL) { // known graph - GE_CHK_STATUS_RET_NOLOG(ParsePartitionedCall(*node_item)); + GE_CHK_STATUS_RET(LoadDynamicSubgraph(*root_graph, true), "Failed to load root graph."); + GELOGD("Done loading root graph successfully."); + + for (auto &sub_graph : root_graph->GetAllSubgraphs()) { + GE_CHECK_NOTNULL(sub_graph); + GELOGD("Start to load subgraph [%s]", sub_graph->GetName().c_str()); + auto parent_node = sub_graph->GetParentNode(); + GE_CHECK_NOTNULL(parent_node); + auto parent_node_item = MutableNodeItem(parent_node); + // parent node is in another known subgraph + if (parent_node_item == nullptr) { + GELOGD("[%s] Subgraph is in another known shaped subgraph, skip it.", sub_graph->GetName().c_str()); + continue; } - GELOGI("NodeItem created: %s", node_item->DebugString().c_str()); - } - - for (auto &it : hybrid_model_.input_nodes_) { - auto input_index = it.first; - auto input_node = it.second; - - if (input_node->outputs.empty()) { - GELOGE(INTERNAL_ERROR, "data output anchor is empty"); - return INTERNAL_ERROR; - } + if (sub_graph->GetGraphUnknownFlag()) { + GE_CHK_STATUS_RET(LoadDynamicSubgraph(*sub_graph, false), "Failed to load subgraph: [%s]", + sub_graph->GetName().c_str()); + } else { + GE_CHK_STATUS_RET(IdentifyVariableOutputs(*parent_node_item), "[%s] Failed to identify ref outputs.", + parent_node_item->NodeName().c_str()); - for (auto &out : input_node->outputs) { - std::vector offsets; - for (auto &dst_anchor_and_node : out) { - auto dst_node_item = dst_anchor_and_node.second; - offsets.emplace_back(dst_node_item->input_start + dst_anchor_and_node.first); + // if parent is function control op. need add a virtual partitioned call + if (parent_node_item->IsControlOp()) { + GE_CHK_STATUS_RET(LoadKnownShapedSubgraph(*sub_graph, parent_node_item), + "Failed to load function control op subgraph [%s]", sub_graph->GetName().c_str()); } - - hybrid_model_.input_offsets_.emplace(input_index, std::move(offsets)); } } - hybrid_model_.total_inputs_ = input_start; - hybrid_model_.total_outputs_ = output_start; - GELOGI("HybridGraph::LoadGraph OUT"); + GELOGI("Done loading all subgraphs successfully."); return SUCCESS; } @@ -507,7 +587,6 @@ Status HybridModelBuilder::VarNodeToTensor(const NodePtr &var_node, std::unique_ string var_name = var_node->GetName(); auto tensor_desc = var_node->GetOpDesc()->MutableOutputDesc(0); uint8_t *var_logic = nullptr; - GE_CHK_STATUS_RET(var_manager_->GetVarAddr(var_name, *tensor_desc, &var_logic), "Failed to get var addr. var_name = %s, session_id = %ld", var_name.c_str(), hybrid_model_.GetSessionId()); @@ -559,10 +638,26 @@ Status HybridModelBuilder::HandleDtString(const GeTensor &tensor, void *var_addr return SUCCESS; } +Status HybridModelBuilder::AssignUninitializedConstantOps() { + for (auto &it : hybrid_model_.constant_op_nodes_) { + const string &var_name = it.first; + const NodePtr &var_node = it.second; + auto tensor_desc = var_node->GetOpDesc()->MutableOutputDesc(0); + if (!var_manager_->IsVarExist(var_name, *tensor_desc)) { + // allocate constant + GELOGD("[%s] Constant not allocated during graph building. now allocate it.", var_name.c_str()); + GE_CHK_STATUS_RET(var_manager_->AssignVarMem(var_name, *tensor_desc, RT_MEMORY_HBM)); + GE_CHK_STATUS_RET(var_manager_->SetAllocatedGraphId(var_name, runtime_param_.graph_id)); + } + } + + return SUCCESS; +} + Status HybridModelBuilder::InitConstantOps() { for (auto &it : hybrid_model_.constant_op_nodes_) { - string var_name = it.first; - NodePtr &var_node = it.second; + const string &var_name = it.first; + const NodePtr &var_node = it.second; std::unique_ptr var_tensor; GE_CHK_STATUS_RET_NOLOG(VarNodeToTensor(var_node, var_tensor)); @@ -578,7 +673,7 @@ Status HybridModelBuilder::InitConstantOps() { if (ge_tensor->GetData().size() > 0) { GE_CHK_STATUS_RET_NOLOG(HandleDtString(*ge_tensor, v_output_addr)); - GELOGI("[IMAS]InitConstant memcpy graph_%u type[V] name[%s] output[%d] memaddr[%p] mem_size[%u] datasize[%zu]", + GELOGI("[IMAS]InitConstant memcpy graph_%u type[V] name[%s] output[%d] memaddr[%p] mem_size[%zu] datasize[%zu]", runtime_param_.graph_id, op_desc->GetName().c_str(), 0, v_output_addr, v_output_size, ge_tensor->GetData().size()); GE_CHK_RT_RET(rtMemcpy(v_output_addr, v_output_size, ge_tensor->GetData().data(), ge_tensor->GetData().size(), @@ -614,7 +709,8 @@ Status HybridModelBuilder::InitWeights() { } Status HybridModelBuilder::LoadTasks() { - for (auto &node_item : hybrid_model_.node_items_) { + for (auto &it : hybrid_model_.node_items_) { + auto &node_item = it.second; auto &node_ptr = node_item->node; if (node_item->node_type == NETOUTPUT) { continue; @@ -622,7 +718,6 @@ Status HybridModelBuilder::LoadTasks() { GELOGD("[%s] Start to build kernel task", node_ptr->GetName().c_str()); auto load_ret = node_item->node_executor->LoadTask(hybrid_model_, node_ptr, node_item->kernel_task); - if (load_ret != UNSUPPORTED && load_ret != SUCCESS) { GELOGE(load_ret, "[%s] Failed to load task", node_ptr->GetName().c_str()); return load_ret; @@ -634,6 +729,23 @@ Status HybridModelBuilder::LoadTasks() { return SUCCESS; } +Status HybridModelBuilder::LoadGeModel(ComputeGraph &sub_graph, const GeModelPtr &ge_model) { + auto parent_node = sub_graph.GetParentNode(); + GE_CHECK_NOTNULL(parent_node); + auto op_type = parent_node->GetType(); + if (op_type == IF || op_type == CASE || op_type == WHILE) { + GELOGD("Set ge_model for control op subgraph: [%s], task_size = %d", sub_graph.GetName().c_str(), + ge_model->GetModelTaskDefPtr()->task_size()); + subgraph_models_.emplace(sub_graph.GetName(), ge_model); + } else { + GELOGD("Set ge_model for subgraph: [%s], task_size = %d", sub_graph.GetName().c_str(), + ge_model->GetModelTaskDefPtr()->task_size()); + hybrid_model_.known_shape_sub_models_.emplace(sub_graph.GetParentNode(), ge_model); + } + + return SUCCESS; +} + Status HybridModelBuilder::IndexTaskDefs() { const auto &root_graph = ge_root_model_->GetRootGraph(); for (auto &it : ge_root_model_->GetSubgraphInstanceNameToModel()) { @@ -646,12 +758,9 @@ Status HybridModelBuilder::IndexTaskDefs() { continue; } - bool is_unknown_shape = false; - GE_CHK_GRAPH_STATUS_RET(NodeUtils::GetNodeUnknownShapeStatus(*sub_graph->GetParentNode(), is_unknown_shape), - "Failed to invoke GetNodeUnknownShapeStatus."); + bool is_unknown_shape = sub_graph->GetGraphUnknownFlag(); if (!is_unknown_shape) { - GELOGD("Set ge_model for subgraph: %s", sub_graph->GetName().c_str()); - hybrid_model_.known_shape_sub_graphs_.emplace(sub_graph->GetParentNode(), ge_model); + GE_CHK_STATUS_RET_NOLOG(LoadGeModel(*sub_graph, ge_model)); continue; } @@ -676,6 +785,8 @@ Status HybridModelBuilder::IndexTaskDefs() { op_index = task_def.kernel().context().op_index(); } else if (task_type == RT_MODEL_TASK_KERNEL_EX) { op_index = task_def.kernel_ex().op_index(); + } else if (task_type == RT_MODEL_TASK_HCCL) { + op_index = task_def.kernel_hccl().op_index(); } else { GELOGD("Skip task type: %d", static_cast(task_type)); continue; @@ -790,12 +901,12 @@ Status HybridModelBuilder::GetPeerNodeAcrossSubGraphs(const NodePtr &data_node, for (uint32_t i = 0; i < static_cast(input_size); ++i) { uint32_t p_index = 0; if (!AttrUtils::GetInt(net_output_desc->GetInputDesc(i), ATTR_NAME_PARENT_NODE_INDEX, p_index)) { - GELOGW("SubGraph: %s input tensor %zu attr %s not found.", src_graph->GetName().c_str(), i, + GELOGW("SubGraph: %s input tensor %u attr %s not found.", src_graph->GetName().c_str(), i, ATTR_NAME_PARENT_NODE_INDEX.c_str()); continue; } - GELOGD("NetOutput's input[%zu], parent_node_index = %u", i, p_index); + GELOGD("NetOutput's input[%u], parent_node_index = %u", i, p_index); if (p_index == out_index) { auto in_anchor = src_net_output_node->GetInDataAnchor(i); GE_CHECK_NOTNULL(in_anchor); @@ -830,7 +941,7 @@ Status HybridModelBuilder::InitRuntimeParams() { ret = ge::AttrUtils::GetInt(first_model, ATTR_MODEL_VAR_SIZE, value); runtime_param_.var_size = ret ? (uint64_t)value : 0; runtime_param_.graph_id = ge_root_model_->GetRootGraph()->GetGraphID(); - GELOGI("InitRuntimeParams(), session_id:%u, var_size:%lu. graph_id = %u", runtime_param_.session_id, + GELOGI("InitRuntimeParams(), session_id:%lu, var_size:%lu. graph_id = %u", runtime_param_.session_id, runtime_param_.var_size, runtime_param_.graph_id); var_manager_ = VarManager::Instance(runtime_param_.session_id); @@ -838,7 +949,7 @@ Status HybridModelBuilder::InitRuntimeParams() { return SUCCESS; } -Status HybridModelBuilder::ParsePartitionedCall(NodeItem &node_item) { +Status HybridModelBuilder::IdentifyVariableOutputs(NodeItem &node_item) { GELOGD("Start to parse outputs of node: %s", node_item.NodeName().c_str()); auto subgraph = NodeUtils::GetSubgraph(*node_item.node, kSubgraphIndex); GE_CHECK_NOTNULL(subgraph); @@ -847,6 +958,7 @@ Status HybridModelBuilder::ParsePartitionedCall(NodeItem &node_item) { auto net_output_desc = net_output_node->GetOpDesc(); GE_CHECK_NOTNULL(net_output_desc); + // constant/variable connected to net output for (const auto &in_data_anchor : net_output_node->GetAllInDataAnchors()) { auto src_node = GetPeerNode(in_data_anchor); GE_CHECK_NOTNULL(src_node); @@ -864,6 +976,8 @@ Status HybridModelBuilder::ParsePartitionedCall(NodeItem &node_item) { node_item.ref_outputs.emplace(parent_index, src_node); } + // Data nodes marked with REF_VAR_SRC_VAR_NAME + // Using variable tensor as data's output for (auto &node : subgraph->GetDirectNode()) { if (node->GetType() != DATA) { continue; @@ -912,6 +1026,11 @@ Status HybridModelBuilder::GetParentNodeOutputIndex(const OpDesc &op_desc, int i Status HybridModelBuilder::InitModelMem() { hybrid_model_.var_mem_base_ = var_manager_->GetVarMemoryBase(RT_MEMORY_HBM); auto total_var_size = hybrid_model_.TotalVarMemSize(); + if (total_var_size == 0 && !hybrid_model_.constant_op_nodes_.empty()) { + total_var_size = var_manager_->GetVarMemSize(RT_MEMORY_HBM) > 0 ? var_manager_->GetVarMemMaxSize() : 0; + GELOGD("Model var size = 0. but got uninitialized constant. set var size to %zu.", total_var_size); + } + if (total_var_size > 0 && hybrid_model_.var_mem_base_ == nullptr) { GE_CHK_STATUS_RET(var_manager_->MallocVarMemory(total_var_size), "Malloc Var Memory Fail."); hybrid_model_.var_mem_base_ = var_manager_->GetVarMemoryBase(RT_MEMORY_HBM); @@ -951,5 +1070,154 @@ Status HybridModelBuilder::CopyVarData() { GELOGI("CopyVarData success."); return SUCCESS; } + +Status HybridModelBuilder::LoadKnownShapedSubgraph(ComputeGraph &graph, NodeItem *parent_node_item) { + GELOGD("Start to load known shaped subgraph [%s]", graph.GetName().c_str()); + auto graph_item = std::unique_ptr(new (std::nothrow) GraphItem()); + GE_CHECK_NOTNULL(graph_item); + graph_item->is_dynamic_ = false; + auto subgraph_name = graph.GetName(); + auto wrapper_op_desc = MakeShared(subgraph_name + "_partitioned_call", PARTITIONEDCALL); + GE_CHECK_NOTNULL(wrapper_op_desc); + + for (auto &node : graph.GetDirectNode()) { + GE_CHECK_NOTNULL(node); + auto op_desc = node->GetOpDesc(); + GE_CHECK_NOTNULL(op_desc); + const auto &op_type = node->GetType(); + + if (op_type == DATA) { + int32_t data_index = 0; + if (!AttrUtils::GetInt(node->GetOpDesc(), ATTR_NAME_PARENT_NODE_INDEX, data_index)) { + GELOGE(FAILED, "[%s] Failed to get attr [%s]", node->GetName().c_str(), ATTR_NAME_PARENT_NODE_INDEX.c_str()); + return FAILED; + } + + (void)wrapper_op_desc->AddInputDesc(op_desc->GetInputDesc(0)); + graph_item->input_index_mapping_.emplace_back(data_index); + } else if (op_type == NETOUTPUT) { + int output_index = 0; + for (const auto &output_desc : op_desc->GetAllInputsDescPtr()) { + int32_t data_index = output_index++; + if (!AttrUtils::GetInt(output_desc, ATTR_NAME_PARENT_NODE_INDEX, data_index)) { + GELOGI("[%s] Failed to get attr [%s]", node->GetName().c_str(), ATTR_NAME_PARENT_NODE_INDEX.c_str()); + } + + GE_CHK_GRAPH_STATUS_RET(wrapper_op_desc->AddOutputDesc(*output_desc), + "[%s] Failed to add output desc. output index = %d", graph.GetName().c_str(), + output_index); + + graph_item->output_index_mapping_.emplace_back(data_index); + } + } + } + + auto temp_graph = MakeShared("temp"); + GE_CHECK_NOTNULL(temp_graph); + auto wrapper_node = temp_graph->AddNode(wrapper_op_desc); + GeModelPtr ge_model = subgraph_models_[subgraph_name]; + GE_CHECK_NOTNULL(ge_model); + hybrid_model_.known_shape_sub_models_.emplace(wrapper_node, ge_model); + + NodeItem *node_item = nullptr; + GE_CHK_STATUS_RET_NOLOG(GetOrCreateNodeItem(wrapper_node, &node_item)); + node_item->input_start = 0; + node_item->output_start = 0; + node_item->outputs.resize(node_item->num_outputs); + graph_item->node_items_.emplace_back(node_item); + graph_item->output_node_ = node_item; + graph_item->total_inputs_ = node_item->num_inputs; + graph_item->total_outputs_ = node_item->num_outputs; + + GELOGD("NodeItem create for known shape subgraph [%s], NodeItem = %s", graph.GetName().c_str(), + node_item->DebugString().c_str()); + + GELOGD("Done parse known shape subgraph successfully. graph = [%s]", graph.GetName().c_str()); + graph_item->SetName(graph.GetName()); + GELOGD("Done loading known shape subgraph: [%s]", graph_item->GetName().c_str()); + hybrid_model_.subgraph_items_.emplace(graph.GetName(), std::move(graph_item)); + return SUCCESS; +} + +Status HybridModelBuilder::LoadDynamicSubgraph(ComputeGraph &graph, bool is_root_graph) { + GELOGD("Start to load subgraph [%s]", graph.GetName().c_str()); + // for known partitioned call, load all nodes + auto graph_item = std::unique_ptr(new (std::nothrow) GraphItem()); + GE_CHECK_NOTNULL(graph_item); + + graph_item->is_dynamic_ = true; + graph_item->node_items_.reserve(graph.GetDirectNodesSize()); + int input_start = 0; + int output_start = 0; + std::vector data_nodes; + for (auto &node : graph.GetDirectNode()) { + GE_CHECK_NOTNULL(node); + GE_CHECK_NOTNULL(node->GetOpDesc()); + const auto &op_type = node->GetType(); + + NodeItem *node_item = nullptr; + GE_CHK_STATUS_RET_NOLOG(GetOrCreateNodeItem(node, &node_item)); + GE_CHK_STATUS_RET_NOLOG(BuildNodeItem(node, *node_item)); + GE_CHK_STATUS_RET_NOLOG(UpdateAnchorStatus(node)); // needed by FE generate task + + node_item->input_start = input_start; + node_item->output_start = output_start; + input_start += node_item->num_inputs; + output_start += node_item->num_outputs; + + if (op_type == DATA_TYPE || op_type == AIPP_DATA_TYPE) { + data_nodes.emplace_back(node_item); + } else if (op_type == NETOUTPUT) { + graph_item->output_node_ = node_item; + GE_CHK_STATUS_RET_NOLOG(BuildOutputMapping(*graph_item, *node_item, is_root_graph)); + } + + graph_item->node_items_.emplace_back(node_item); + GELOGD("NodeItem created: %s", node_item->DebugString().c_str()); + } + + graph_item->total_inputs_ = input_start; + graph_item->total_outputs_ = output_start; + GE_CHK_STATUS_RET_NOLOG(BuildInputMapping(*graph_item, data_nodes, is_root_graph)); + if (is_root_graph) { + graph_item->SetName("Root-Graph"); + GELOGD("Done loading dynamic subgraph: [%s]", graph_item->GetName().c_str()); + hybrid_model_.root_graph_item_ = std::move(graph_item); + } else { + graph_item->SetName(graph.GetName()); + GELOGD("Done loading dynamic subgraph: [%s]", graph_item->GetName().c_str()); + hybrid_model_.subgraph_items_.emplace(graph.GetName(), std::move(graph_item)); + } + + return SUCCESS; +} + +Status HybridModelBuilder::BuildInputMapping(GraphItem &graph_item, vector &data_nodes, + bool is_root_graph) { + uint32_t data_op_index = 0; + for (auto &node_item : data_nodes) { + auto node = node_item->node; + int data_index = data_op_index; + if (is_root_graph) { + if (AttrUtils::GetInt(node->GetOpDesc(), ATTR_NAME_INDEX, data_index)) { + GELOGI("ge_train: get new index %u, old %u", data_index, data_op_index); + } + data_op_index++; + } else { + if (!AttrUtils::GetInt(node->GetOpDesc(), ATTR_NAME_PARENT_NODE_INDEX, data_index)) { + GELOGE(FAILED, "[%s] Failed to get attr [%s]", node->GetName().c_str(), ATTR_NAME_PARENT_NODE_INDEX.c_str()); + return FAILED; + } + } + + if (graph_item.input_nodes_.size() <= static_cast(data_index)) { + graph_item.input_nodes_.resize(data_index + 1); + } + + graph_item.input_nodes_[data_index] = node_item; + } + + return SUCCESS; +} } // namespace hybrid } // namespace ge diff --git a/src/ge/hybrid/model/hybrid_model_builder.h b/src/ge/hybrid/model/hybrid_model_builder.h index 33cd1f03..1103aa1c 100644 --- a/src/ge/hybrid/model/hybrid_model_builder.h +++ b/src/ge/hybrid/model/hybrid_model_builder.h @@ -46,18 +46,20 @@ class HybridModelBuilder { static Status HandleDtString(const GeTensor &tensor, void *var_addr); static Status MergeInputNodes(ComputeGraph &compute_graph); static Status MergeNetOutputNode(ComputeGraph &compute_graph); - static Status MergeSubgraphs(ComputeGraph &root_graph, ComputeGraphPtr &merged_graph); + static Status UnfoldSubgraphs(ComputeGraph &root_graph, ComputeGraphPtr &merged_graph); + static Status UnfoldSubgraph(ComputeGraph &root_graph, ComputeGraph &parent_graph, ComputeGraph &sub_graph); static Status InitWeights(); - + static Status BuildInputMapping(GraphItem &graph_item, std::vector &data_nodes, bool is_root_graph); + static Status ResolveRefIo(NodeItem &node_item); + Status BuildOutputMapping(GraphItem &partitioned_call, const NodeItem &node_item, bool is_root_graph); Status ValidateParams(); Status LoadGraph(); + Status LoadGeModel(ComputeGraph &graph, const GeModelPtr &ge_model); Status LoadTasks(); - Status ParsePartitionedCall(NodeItem &node_item); - Status ParseNetOutput(const NodeItem &node_item); - Status BuildNoteItem(const NodePtr &node, NodeItem &node_item); + Status IdentifyVariableOutputs(NodeItem &node_item); + Status BuildNodeItem(const NodePtr &node, NodeItem &node_item); Status GetOrCreateNodeItem(const NodePtr &node, NodeItem **node_item); Status ParseDependentInputNodes(NodeItem &node_item, const std::vector &dependencies); - Status ResolveRootNodes(); Status IndexTaskDefs(); Status IndexSpecialNodes(); Status InitRuntimeParams(); @@ -65,19 +67,23 @@ class HybridModelBuilder { Status TransAllVarData(); Status CopyVarData(); Status VarNodeToTensor(const NodePtr &var_node, std::unique_ptr &tensor); + Status AssignUninitializedConstantOps(); Status InitConstantOps(); Status InitVariableTensors(); + Status LoadDynamicSubgraph(ComputeGraph &graph, bool is_root_graph); + Status LoadKnownShapedSubgraph(ComputeGraph &graph, NodeItem *parent_node_item); - const char *GetGraphName() const { return graph_name_.c_str(); } + const char *GetGraphName() const { return hybrid_model_.model_name_.c_str(); } const NodeItem *GetNodeItem(const NodePtr &node) const; NodeItem *MutableNodeItem(const NodePtr &node); GeRootModelPtr ge_root_model_; - std::string graph_name_; std::map> weights_; + std::map subgraph_models_; HybridModel &hybrid_model_; std::map>> node_ref_inputs_; + int node_index = 0; RuntimeParam &runtime_param_; VarManager *var_manager_ = nullptr; diff --git a/src/ge/hybrid/model/node_item.cc b/src/ge/hybrid/model/node_item.cc index b5d4fbda..e1cd7f64 100644 --- a/src/ge/hybrid/model/node_item.cc +++ b/src/ge/hybrid/model/node_item.cc @@ -16,6 +16,8 @@ #include "node_item.h" #include +#include "common/debug/log.h" +#include "hybrid/node_executor/node_executor.h" namespace ge { namespace hybrid { @@ -28,12 +30,34 @@ NodeItem::NodeItem(NodePtr node) : node(std::move(node)) { this->node_type = this->node->GetType(); } +Status NodeItem::Init() { + for (int i = 0; i < num_inputs; ++i) { + const auto &input_desc = op_desc->MutableInputDesc(i); + GE_CHECK_NOTNULL(input_desc); + if (input_desc->MutableShape().IsUnknownShape()) { + is_input_shape_static.push_back(false); + } else { + num_static_input_shapes++; + is_input_shape_static.push_back(true); + GELOGD("[%s] The shape of input[%d] is static. shape = [%s]", NodeName().c_str(), i, + input_desc->MutableShape().ToString().c_str()); + } + } + + return SUCCESS; +} + +bool NodeItem::IsControlOp() const { + auto op_type = op_desc->GetType(); + return op_type == IF || op_type == CASE || op_type == WHILE || op_type == FOR; +} + std::string NodeItem::DebugString() const { std::stringstream ss; ss << "Node: "; ss << "id = " << node_id; - ss << ", name = " << node->GetName(); - ss << ", type = " << node->GetType(); + ss << ", name = [" << node->GetName(); + ss << "], type = " << node->GetType(); ss << ", is_dynamic = " << (is_dynamic ? "True" : "False"); ss << ", unknown_shape_op_type = " << shape_inference_type; ss << ", input_start = " << input_start; @@ -41,7 +65,7 @@ std::string NodeItem::DebugString() const { ss << ", output_start = " << output_start; ss << ", num_outputs = " << num_outputs; ss << ", dependent_nodes = ["; - for (const auto &dep_node : dependent_node_list) { + for (const auto &dep_node : dependents_for_shape_inference) { ss << dep_node->GetName() << ", "; } ss << "]"; @@ -55,5 +79,18 @@ std::string NodeItem::DebugString() const { return ss.str(); } + +void NodeItem::SetToDynamic() { + num_static_input_shapes = 0; + is_dynamic = true; + for (size_t i = 0; i < is_input_shape_static.size(); ++i) { + is_input_shape_static[i] = false; + } + if (kernel_task != nullptr && !kernel_task->IsSupportDynamicShape()) { + GELOGD("[%s] Dynamic shape is not supported, clear node task.", node_name.c_str()); + kernel_task = nullptr; + } +} + } // namespace hybrid } // namespace ge \ No newline at end of file diff --git a/src/ge/hybrid/model/node_item.h b/src/ge/hybrid/model/node_item.h index b12d100b..4e6d770b 100644 --- a/src/ge/hybrid/model/node_item.h +++ b/src/ge/hybrid/model/node_item.h @@ -18,6 +18,7 @@ #define GE_HYBRID_MODEL_NODE_ITEM_H_ #include +#include "external/ge/ge_api_error_codes.h" #include "graph/node.h" #include "graph/op_desc.h" #include "framework/common/types.h" @@ -33,10 +34,16 @@ struct NodeItem { explicit NodeItem(NodePtr node); ~NodeItem() = default; + Status Init(); + const std::string &NodeName() const { return node_name; } const std::string &NodeType() const { return node_type; } + bool IsControlOp() const; + + void SetToDynamic(); + std::string DebugString() const; NodePtr node; @@ -52,17 +59,21 @@ struct NodeItem { UnknowShapeOpType shape_inference_type = DEPEND_IN_SHAPE; std::string node_name; std::string node_type; - std::vector dependent_node_list; + std::vector dependents_for_shape_inference; + std::vector dependents_for_execution; std::set to_const_output_id_list; - // src_output_id, dst_anchor_id, dst_node vector inputs; + // src_output_id, dst_anchor_id, dst_node vector>> outputs; std::shared_ptr kernel_task; const NodeExecutor *node_executor = nullptr; - std::map const_input_shapes; std::map ref_outputs; + std::map reuse_inputs; + + std::vector is_input_shape_static; + int num_static_input_shapes = 0; }; } // namespace hybrid } // namespace ge diff --git a/src/ge/hybrid/node_executor/aicore/aicore_node_executor.cc b/src/ge/hybrid/node_executor/aicore/aicore_node_executor.cc index 3f198bba..50c8e899 100644 --- a/src/ge/hybrid/node_executor/aicore/aicore_node_executor.cc +++ b/src/ge/hybrid/node_executor/aicore/aicore_node_executor.cc @@ -16,10 +16,8 @@ #include "aicore_node_executor.h" #include "cce/taskdown_common.hpp" -#include "graph/debug/ge_attr_define.h" -#include "hybrid/model/hybrid_model.h" +#include "hybrid/executor/hybrid_execution_context.h" #include "init/gelib.h" -#include "framework/common/debug/log.h" namespace ge { namespace hybrid { @@ -27,16 +25,47 @@ REGISTER_NODE_EXECUTOR_BUILDER(NodeExecutorManager::ExecutorType::AICORE, AiCore AiCoreNodeTask::AiCoreNodeTask(std::vector> &&tasks) : tasks_(std::move(tasks)) {} +Status AiCoreNodeExecutor::Initialize() { + auto ge_lib = GELib::GetInstance(); + GE_CHECK_NOTNULL(ge_lib); + if (!ge_lib->InitFlag()) { + GELOGE(GE_CLI_GE_NOT_INITIALIZED, "Ge_lib is uninitialized, failed."); + return GE_CLI_GE_NOT_INITIALIZED; + } + + auto &kernel_manager = ge_lib->OpsKernelManagerObj(); + auto aic_ops_store = kernel_manager.GetOpsKernelInfoStore("AIcoreEngine"); + GE_CHECK_NOTNULL(aic_ops_store); + + compiler_.reset(new (std::nothrow) AiCoreTaskCompiler(aic_ops_store)); + GE_CHECK_NOTNULL(compiler_); + return SUCCESS; +} + Status AiCoreNodeExecutor::LoadTask(const HybridModel &model, const NodePtr &node, shared_ptr &task) const { GE_CHECK_NOTNULL(node); - GELOGI("AiCoreNodeExecutor[%s] LoadTask Start.", node->GetName().c_str()); + GELOGI("AiCoreNodeExecutor(%s) LoadTask Start.", node->GetName().c_str()); auto *task_defs = model.GetTaskDefs(node); - Status ret = SUCCESS; - GE_IF_BOOL_EXEC(task_defs != nullptr && !task_defs->empty(), ret = CreateTask(model, *task_defs, node, task)); + if (task_defs == nullptr || task_defs->empty()) { + bool dynamic_flag = false; + if (!AttrUtils::GetBool(node->GetOpDesc(), "support_dynamicshape", dynamic_flag) || !dynamic_flag) { + GELOGD("Skip create task of node (%s) as 'support_dynamicshape' is false and cann't get task_defs.", + node->GetName().c_str()); + return SUCCESS; + } else { + GELOGE(FAILED, "Task_defs is empty for node (%s) which 'support_dynamicshape' is true, failed.", + node->GetName().c_str()); + return FAILED; + } + } - GELOGI("AiCoreNodeExecutor[%s] LoadTask End, ret[%u].", node->GetName().c_str(), ret); - return ret; + AiCoreTaskBuilder builder(node->GetOpDesc(), *task_defs); + std::unique_ptr node_task; + GE_CHK_STATUS_RET(builder.BuildTask(node_task, true), "[%s] Failed to build op tasks.", node->GetName().c_str()); + task = std::move(node_task); + GELOGI("AiCoreNodeExecutor(%s) LoadTask End.", node->GetName().c_str()); + return SUCCESS; } Status AiCoreNodeExecutor::GenNodeKey(const NodePtr &node, std::string &node_key) { @@ -47,16 +76,19 @@ Status AiCoreNodeExecutor::GenNodeKey(const NodePtr &node, std::string &node_key // make sure unique, (op_id + input_shape) is unique node_key = std::to_string(op_desc->GetId()) + "/"; node_key.append(std::to_string(op_desc->GetInputsSize())); - auto input_descs = op_desc->GetAllInputsDesc(); - for (auto input_desc : input_descs) { + auto input_descs = op_desc->GetAllInputsDescPtr(); + for (auto &input_desc : input_descs) { node_key.push_back('/'); - std::vector dims = input_desc.GetShape().GetDims(); - GE_IF_BOOL_EXEC(dims.size() == 0, continue); // scalar - for (std::size_t i = 0; i < dims.size() - 1; i++) { - node_key.append(std::to_string(dims[i])); + auto &shape = input_desc->MutableShape(); + auto num_dims = shape.GetDimNum(); + if (num_dims == 0) { + continue; + } // scalar + for (std::size_t i = 0; i < num_dims - 1; i++) { + node_key.append(std::to_string(shape.GetDim(i))); node_key.push_back(','); } - node_key.append(std::to_string(dims[dims.size() - 1])); + node_key.append(std::to_string(shape.GetDim(num_dims - 1))); } return SUCCESS; } @@ -65,8 +97,10 @@ bool AiCoreNodeTaskRegistry::AddTask(const std::string &node_key, const std::sha GE_CHECK_NOTNULL(task); std::lock_guard lock(mutex_); auto iter = reg_node_tasks_.find(node_key); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(iter != reg_node_tasks_.end(), return false, - "AiCoreNodeTaskRegistry[%s] AddTask failed, key already exist.", node_key.c_str()); + if (iter != reg_node_tasks_.end()) { + GELOGE(FAILED, "AiCoreNodeTaskRegistry(%s) AddTask failed, key already exist.", node_key.c_str()); + return false; + } auto ret = reg_node_tasks_.emplace(node_key, task); return ret.second; } @@ -80,231 +114,84 @@ std::shared_ptr AiCoreNodeTaskRegistry::GetTask(const std::string &nod Status AiCoreNodeExecutor::CompileTask(const HybridModel &model, const NodePtr &node, shared_ptr &task) const { GE_CHECK_NOTNULL(node); - GELOGI("AiCoreNodeExecutor[%s] CompileTask Start.", node->GetName().c_str()); + GELOGI("AiCoreNodeExecutor(%s) CompileTask Start.", node->GetName().c_str()); AiCoreNodeTaskRegistry ®istry = AiCoreNodeTaskRegistry::GetInstance(); std::string node_key; - GE_CHK_STATUS_RET(GenNodeKey(node, node_key), "GenNodeKey failed. op name = %s", node->GetName().c_str()); + GE_CHK_STATUS_RET(GenNodeKey(node, node_key), "GenNodeKey failed, op name = %s.", node->GetName().c_str()); + node_key = std::to_string(model.GetModelId()) + "/" + node_key; GELOGD("NodeKey for %s = %s", node->GetName().c_str(), node_key.c_str()); task = registry.GetTask(node_key); - GE_CHK_TRUE_EXEC_INFO(task != nullptr, return SUCCESS, "AiCoreNodeExecutor[%s] CompileTask Skip.", - node->GetName().c_str()); + if (task != nullptr) { + GELOGI("AiCoreNodeExecutor(%s) CompileTask Skip.", node->GetName().c_str()); + return SUCCESS; + } std::vector task_defs; - GE_CHK_STATUS_RET_NOLOG(compiler_->CompileOp(node, task_defs)); + GE_CHK_STATUS_RET(compiler_->CompileOp(node, task_defs), "Compile op(%s) failed.", node->GetName().c_str()); GELOGD("successfully generated task_defs: %s", node->GetName().c_str()); - GE_CHK_STATUS_RET_NOLOG(CreateTask(model, task_defs, node, task)); + AiCoreTaskBuilder builder(node->GetOpDesc(), task_defs); + std::unique_ptr node_task; + GE_CHK_STATUS_RET(builder.BuildTask(node_task, false), "[%s] Failed to build op tasks.", node->GetName().c_str()); + task = std::move(node_task); GELOGD("successfully created node task: %s", node->GetName().c_str()); - GE_CHK_BOOL_EXEC(registry.AddTask(node_key, task), return INTERNAL_ERROR, "Add NodeTask failed. op name = %s", - node->GetName().c_str()); // should not happen. - GELOGI("AiCoreNodeExecutor[%s] CompileTask End.", node->GetName().c_str()); - return SUCCESS; -} - -Status AiCoreNodeExecutor::BuildAiCoreTask(const domi::KernelDef &kernel_def, const OpDescPtr &op_desc, - AiCoreOpTask **task) { - GE_CHECK_NOTNULL(op_desc); - GE_CHECK_NOTNULL(task); - - const auto &context = kernel_def.context(); - auto kernel_type = static_cast(context.kernel_type()); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(kernel_type != cce::ccKernelType::TE, return UNSUPPORTED, - "Only TBE kernel is supported, but [%s] got %u", op_desc->GetName().c_str(), - context.kernel_type()); - - auto *aicore_task = new (std::nothrow) AiCoreOpTask(); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(aicore_task == nullptr, return MEMALLOC_FAILED, "Create AiCore op task failed."); - - auto builder = AiCoreTaskBuilder(op_desc, kernel_def); - auto ret = builder.BuildTask(*aicore_task); - GE_IF_BOOL_EXEC(ret != SUCCESS, delete aicore_task; aicore_task = nullptr; return ret); - - *task = aicore_task; - return SUCCESS; -} - -Status AiCoreNodeExecutor::CreateTask(const HybridModel &model, const std::vector &task_defs, - const NodePtr &node, std::shared_ptr &task) { - GE_CHECK_NOTNULL(node); - GELOGD("To CreateTask, task def size = %zu", task_defs.size()); - std::vector> aicore_op_tasks; - aicore_op_tasks.reserve(task_defs.size()); - for (size_t i = 0; i < task_defs.size(); ++i) { - const domi::TaskDef &task_def = task_defs[i]; - GELOGD("Op[%s] Task[%d], type = %u, DebugString = %s", node->GetName().c_str(), i, task_def.type(), - task_def.DebugString().c_str()); - auto task_type = static_cast(task_def.type()); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(task_type == RT_MODEL_TASK_KERNEL_EX, return UNSUPPORTED, - "BuildKernelExTask is not supported"); - GE_CHK_BOOL_TRUE_EXEC_INFO(task_type != RT_MODEL_TASK_KERNEL, continue, "Skip task type %d", - static_cast(task_type)); - - const domi::KernelDef &kernel_def = task_def.kernel(); - AiCoreOpTask *aicore_op_task = nullptr; - // not use hybrid model now - GE_CHK_STATUS_RET_NOLOG(BuildAiCoreTask(kernel_def, node->GetOpDesc(), &aicore_op_task)); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(aicore_op_task == nullptr, return FAILED, "BuildAiCoreTask[%s] failed.", - node->GetName().c_str()); - - aicore_op_tasks.emplace_back(std::unique_ptr(aicore_op_task)); + if (!registry.AddTask(node_key, task)) { + GELOGE(INTERNAL_ERROR, "Add NodeTask failed, op name = %s.", node->GetName().c_str()); + return INTERNAL_ERROR; } - if (!aicore_op_tasks.empty()) { - auto aic_task = std::shared_ptr(new AiCoreNodeTask(std::move(aicore_op_tasks))); - task = std::move(aic_task); - GELOGD("Generate AiCoreOpTask success"); - return SUCCESS; - } - - GELOGE(INTERNAL_ERROR, "Failed to build task. node = %s", node->GetName().c_str()); - return INTERNAL_ERROR; -} - -Status AiCoreNodeExecutor::Initialize() { - std::shared_ptr ge_lib = GELib::GetInstance(); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG((ge_lib == nullptr) || !ge_lib->InitFlag(), return GE_CLI_GE_NOT_INITIALIZED, - "Get ge_lib failed."); - - auto &kernel_manager = ge_lib->OpsKernelManagerObj(); - auto aic_ops_store = kernel_manager.GetOpsKernelInfoStore("AIcoreEngine"); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(aic_ops_store == nullptr, return GE_CLI_GE_NOT_INITIALIZED, - "Failed to get kernel info store for AIcoreEngine."); - - compiler_.reset(new (std::nothrow) AiCoreTaskCompiler(aic_ops_store)); - GE_CHECK_NOTNULL(compiler_); + GELOGI("AiCoreNodeExecutor(%s) CompileTask End.", node->GetName().c_str()); return SUCCESS; } -Status AiCoreNodeExecutor::Finalize() { return NodeExecutor::Finalize(); } - Status AiCoreNodeTask::ExecuteAsync(TaskContext &context, std::function done_callback) { auto op_desc = context.GetNodeItem().op_desc; GE_CHECK_NOTNULL(op_desc); - GELOGI("AiCoreNodeTask[%s] ExecuteAsync Start.", op_desc->GetName().c_str()); - for (size_t i = 0; i < tasks_.size(); i++) { - GE_CHECK_NOTNULL(tasks_[i]); - GE_CHK_STATUS_RET_NOLOG(tasks_[i]->LaunchKernel(context.GetStream())); + GELOGI("[%s] ExecuteAsync Start.", op_desc->GetName().c_str()); + for (auto &task : tasks_) { + GE_CHK_STATUS_RET_NOLOG(task->LaunchKernel(context.GetStream())); } if (done_callback != nullptr) { GE_CHK_STATUS_RET_NOLOG(context.RegisterCallback(done_callback)); } - GELOGI("AiCoreNodeTask[%s] ExecuteAsync End.", op_desc->GetName().c_str()); + GELOGD("[%s] ExecuteAsync End.", op_desc->GetName().c_str()); return SUCCESS; } -Status AiCoreNodeTask::UpdateAtomicArgs(TaskContext &context, std::unique_ptr &task) { - GE_CHECK_NOTNULL(task); +Status AiCoreNodeTask::UpdateArgs(TaskContext &context) { auto op_desc = context.GetNodeItem().op_desc; GE_CHECK_NOTNULL(op_desc); - - // refresh atomic output addr - std::vector atomic_output_indexes; // here atomic just clean output - (void)ge::AttrUtils::GetListInt(op_desc, ge::ATOMIC_ATTR_OUTPUT_INDEX, atomic_output_indexes); - GE_RETURN_WITH_LOG_IF_TRUE(atomic_output_indexes.size() > static_cast(context.NumOutputs()), - "AtomicAddrClean op's arg_size error."); - auto *arg_off = reinterpret_cast(task->args_.get()) + task->offset_; - auto *arg_base = reinterpret_cast(arg_off); - int index = 0; - for (size_t i = 0; i < atomic_output_indexes.size(); ++i) { - const auto output = context.GetOutput(atomic_output_indexes[i]); - GE_CHECK_NOTNULL(output); - arg_base[index++] = reinterpret_cast(output->GetData()); - } - - // refresh atomic workspace addr - auto workspace_sizes = op_desc->GetWorkspaceBytes(); - uint64_t ops_workspace_num = static_cast(workspace_sizes.size()); - uint64_t workspace_num = static_cast(context.NumWorkspaces()); - GE_CHK_BOOL_EXEC(ops_workspace_num == workspace_num, return PARAM_INVALID, - "The workspace_num in op_desc %lu is not equal to it %lu in context.", ops_workspace_num, - workspace_num); - GE_IF_BOOL_EXEC(workspace_num == 0, return SUCCESS); - - map> workspace_info; - workspace_info = op_desc->TryGetExtAttr(EXT_ATTR_ATOMIC_WORKSPACE_INFO, workspace_info); - if (!workspace_info.empty()) { - bool is_fusion_node = false; - (void)ge::AttrUtils::GetBool(op_desc, ATOMIC_ATTR_IS_FUSION_NODE, is_fusion_node); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(is_fusion_node, return PARAM_INVALID, - "Atomic desc[%s] shouldn't be fusion_node in AiCoreNodeTask", - op_desc->GetName().c_str()); - - for (auto iter = workspace_info.begin(); iter != workspace_info.end(); ++iter) { - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(op_desc->GetName() != iter->first, return PARAM_INVALID, - "The node name %s and the node name %s in workspace info are inconsistent.", - op_desc->GetName().c_str(), iter->first.c_str()); - GE_IF_BOOL_EXEC(iter->second.empty(), continue); - - for (auto &info_iter : iter->second) { - auto workspace_index = static_cast(info_iter.first); - - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(workspace_index >= workspace_num, return PARAM_INVALID, - "The workspace index %lu is more than the size %lu of workspace vector.", - workspace_index, workspace_num); - - const auto workspace = context.MutableWorkspace(workspace_index); - arg_base[index++] = reinterpret_cast(workspace); - } - } + GELOGI("[%s] AiCoreNodeTask UpdateArgs Start.", op_desc->GetName().c_str()); + for (auto &task : tasks_) { + GE_CHK_STATUS_RET_NOLOG(task->UpdateArgs(context)); } - + GELOGI("[%s] AiCoreNodeTask UpdateArgs End.", op_desc->GetName().c_str()); return SUCCESS; } -Status AiCoreNodeTask::UpdateAllArgs(TaskContext &context, std::unique_ptr &task) { - GE_CHECK_NOTNULL(task); - auto *arg_off = reinterpret_cast(task->args_.get()) + task->offset_; - auto *arg_base = reinterpret_cast(arg_off); - int index = 0; - for (int i = 0; i < context.NumInputs(); ++i) { - const auto input = context.GetInput(i); - GE_CHECK_NOTNULL(input); - arg_base[index++] = reinterpret_cast(input->GetData()); - } - - for (int i = 0; i < context.NumOutputs(); ++i) { - const auto output = context.GetOutput(i); - GE_CHECK_NOTNULL(output); - arg_base[index++] = reinterpret_cast(output->GetData()); - } - - auto op_desc = context.GetNodeItem().op_desc; - GE_CHECK_NOTNULL(op_desc); - auto workspace_sizes = op_desc->GetWorkspaceBytes(); - int ops_workspace_num = static_cast(workspace_sizes.size()); - int workspace_num = static_cast(context.NumWorkspaces()); - GE_CHK_BOOL_EXEC(ops_workspace_num == workspace_num, return PARAM_INVALID, - "The workspace_num in op_desc %lu is not equal to it %lu in context.", ops_workspace_num, - workspace_num); - for (int i = 0; i < workspace_num; ++i) { - const auto workspace = context.MutableWorkspace(i); - arg_base[index++] = reinterpret_cast(workspace); +Status AiCoreNodeTask::UpdateTilingData(TaskContext &context) { + GELOGD("[%s] PrepareWithShape started", context.GetNodeName()); + for (auto &task : tasks_) { + GE_CHK_STATUS_RET_NOLOG(task->PrepareWithShape(context)); } - + GELOGD("[%s] Done PrepareWithShape successfully.", context.GetNodeName()); return SUCCESS; } -Status AiCoreNodeTask::UpdateArgs(TaskContext &context) { - auto op_desc = context.GetNodeItem().op_desc; - GE_CHECK_NOTNULL(op_desc); - GELOGI("AiCoreNodeTask[%s] UpdateArgs Start.", op_desc->GetName().c_str()); - GE_IF_BOOL_EXEC(tasks_.size() == 1, return UpdateAllArgs(context, tasks_[0])); - - std::vector atomic_output_indexes; // here atomic just clean output - (void)ge::AttrUtils::GetListInt(op_desc, ge::ATOMIC_ATTR_OUTPUT_INDEX, atomic_output_indexes); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(atomic_output_indexes.empty(), return FAILED, "ATOMIC_ATTR_OUTPUT_INDEX is empty."); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(tasks_.size() != 2, return FAILED, "AtomicAddrClean op task num != 2."); - - GE_CHK_STATUS_RET_NOLOG(UpdateAtomicArgs(context, tasks_[0])); - GE_CHK_STATUS_RET_NOLOG(UpdateAllArgs(context, tasks_[1])); +bool AiCoreNodeTask::IsSupportDynamicShape() { + for (size_t i = 0; i < tasks_.size(); ++i) { + if (!tasks_[i]->IsDynamicShapeSupported()) { + GELOGD("[%s] Task does not support dynamic shape.", tasks_[i]->GetName().c_str()); + return false; + } + } - GELOGI("AiCoreNodeTask[%s] UpdateArgs End.", op_desc->GetName().c_str()); - return SUCCESS; + return true; } } // namespace hybrid } // namespace ge diff --git a/src/ge/hybrid/node_executor/aicore/aicore_node_executor.h b/src/ge/hybrid/node_executor/aicore/aicore_node_executor.h index a8b24e68..506202fa 100644 --- a/src/ge/hybrid/node_executor/aicore/aicore_node_executor.h +++ b/src/ge/hybrid/node_executor/aicore/aicore_node_executor.h @@ -25,7 +25,6 @@ namespace ge { namespace hybrid { - class AiCoreNodeTaskRegistry { public: ~AiCoreNodeTaskRegistry() = default; @@ -47,32 +46,27 @@ class AiCoreNodeTaskRegistry { class AiCoreNodeTask : public NodeTask { public: explicit AiCoreNodeTask(std::vector> &&tasks); - ~AiCoreNodeTask() = default; - Status ExecuteAsync(TaskContext &context, std::function done_callback) override; + ~AiCoreNodeTask() override = default; + bool IsSupportDynamicShape() override; + Status UpdateTilingData(TaskContext &context) override; + Status UpdateArgs(TaskContext &context) override; + Status ExecuteAsync(TaskContext &context, std::function done_callback) override; private: - static Status UpdateAllArgs(TaskContext &context, std::unique_ptr &task); - static Status UpdateAtomicArgs(TaskContext &context, std::unique_ptr &task); std::vector> tasks_; }; class AiCoreNodeExecutor : public NodeExecutor { public: Status Initialize() override; - Status Finalize() override; - Status LoadTask(const HybridModel &model, const NodePtr &node, shared_ptr &task) const override; Status CompileTask(const HybridModel &model, const NodePtr &node, std::shared_ptr &task) const override; private: - static Status CreateTask(const HybridModel &model, const std::vector &task_defs, const NodePtr &node, - std::shared_ptr &task); - static Status BuildAiCoreTask(const domi::KernelDef &kernel_def, const OpDescPtr &op_desc, AiCoreOpTask **task); static Status GenNodeKey(const NodePtr &node, std::string &node_key); std::unique_ptr compiler_; }; - } // namespace hybrid } // namespace ge #endif // GE_HYBRID_KERNEL_AICORE_NODE_EXECUTOR_H_ diff --git a/src/ge/hybrid/node_executor/aicore/aicore_op_task.cc b/src/ge/hybrid/node_executor/aicore/aicore_op_task.cc index 27256e9a..f5a4af83 100644 --- a/src/ge/hybrid/node_executor/aicore/aicore_op_task.cc +++ b/src/ge/hybrid/node_executor/aicore/aicore_op_task.cc @@ -14,19 +14,305 @@ * limitations under the License. */ -#include "aicore_op_task.h" +#include "hybrid/node_executor/aicore/aicore_op_task.h" +#include "cce/taskdown_common.hpp" #include "framework/common/debug/log.h" +#include "hybrid/executor/hybrid_execution_context.h" +#include "hybrid/node_executor/aicore/aicore_task_builder.h" + +using optiling::OpRunInfo; namespace ge { namespace hybrid { +namespace { +constexpr char const *kAttrSupportDynamicShape = "support_dynamicshape"; +constexpr char const *kAttrOpParamSize = "op_para_size"; +constexpr char const *kAttrAtomicOpParamSize = "atomic_op_para_size"; +} // namespace -Status AiCoreOpTask::LaunchKernel(rtStream_t stream) { - GELOGI("AiCoreOpTask LaunchKernel Start (task = %s, block_dim = %u).", stub_name_.c_str(), block_dim_); +Status AiCoreOpTask::Init(const OpDesc &op_desc, const domi::TaskDef &task_def) { + GE_CHK_STATUS_RET_NOLOG(InitWithTaskDef(op_desc, task_def)); + GE_CHK_STATUS_RET_NOLOG(InitTilingInfo(op_desc)); + return SUCCESS; +} + +Status AiCoreOpTask::InitWithTaskDef(const OpDesc &op_desc, const domi::TaskDef &task_def) { + GE_CHK_STATUS_RET(ValidateTaskDef(task_def), "[%s] Failed to validate task def: [%s]", op_desc.GetName().c_str(), + task_def.DebugString().c_str()); + + const domi::KernelDef &kernel_def = task_def.kernel(); + const domi::KernelContext &context = kernel_def.context(); + stub_name_ = kernel_def.stub_func(); + GE_CHK_RT_RET(rtGetFunctionByName(stub_name_.c_str(), &stub_func_)); + args_size_ = kernel_def.args_size(); + block_dim_ = kernel_def.block_dim(); + + // malloc args memory + args_.reset(new (std::nothrow) uint8_t[args_size_]); + GE_CHECK_NOTNULL(args_); + errno_t err = memcpy_s(args_.get(), args_size_, kernel_def.args().data(), args_size_); + if (err != EOK) { + GELOGE(INTERNAL_ERROR, "AiCoreTask memcpy args failed."); + return INTERNAL_ERROR; + } + + if (context.args_offset().size() < sizeof(uint16_t)) { + GELOGE(INTERNAL_ERROR, "Invalid args_offset, size = %zu.", context.args_offset().size()); + return INTERNAL_ERROR; + } + + const auto *args_offset_buffer = reinterpret_cast(context.args_offset().data()); + uint32_t offset = *args_offset_buffer; + if (offset > args_size_) { + GELOGE(INTERNAL_ERROR, "[%s] Arg offset out of range. offset = %u, arg size = %u", GetName().c_str(), offset, + args_size_); + return INTERNAL_ERROR; + } + + arg_base_ = reinterpret_cast(args_.get() + offset); + max_arg_count_ = (args_size_ - offset) / sizeof(void *); + GELOGD("[%s] Done setting kernel args successfully. stub_func = %s, block_dim = %d, arg base = %p, arg size = %u", + op_desc.GetName().c_str(), stub_name_.c_str(), block_dim_, arg_base_, args_size_); + + return SUCCESS; +} + +Status AiCoreOpTask::ValidateTaskDef(const domi::TaskDef &task_def) { + auto task_type = static_cast(task_def.type()); + if (task_type != RT_MODEL_TASK_KERNEL) { + GELOGE(INTERNAL_ERROR, "Invalid task type (%d) in AiCore CreateTask.", static_cast(task_type)); + return INTERNAL_ERROR; + } + + const domi::KernelDef &kernel_def = task_def.kernel(); + const domi::KernelContext &context = kernel_def.context(); + auto kernel_type = static_cast(context.kernel_type()); + if (kernel_type != cce::ccKernelType::TE) { + GELOGE(INTERNAL_ERROR, "Invalid kernel type(%d) in AiCore TaskDef.", static_cast(kernel_type)); + return INTERNAL_ERROR; + } + + return SUCCESS; +} + +Status AiCoreOpTask::PrepareWithShape(TaskContext &context) { + if (tiling_buffer_ != nullptr) { + return UpdateTilingInfo(context); + } + + return SUCCESS; +} + +Status AiCoreOpTask::UpdateTilingInfo(TaskContext &context) { + auto node = context.GetNodeItem().node; + GE_CHECK_NOTNULL(node); + auto op_desc = node->GetOpDesc(); + GE_CHECK_NOTNULL(op_desc); + + GELOGD("[%s] Start to update tiling info for task: [%s]", node->GetName().c_str(), stub_name_.c_str()); + OpRunInfo tiling_info; + tiling_info.block_dim = -1; // codex: Using uninitialized value + + auto execution_context = context.GetExecutionContext(); + RECORD_EXECUTION_EVENT(execution_context, context.GetNodeName(), "[CalcTilingInfo] Start"); + GE_CHK_STATUS_RET(CalcTilingInfo(node, tiling_info)); + RECORD_EXECUTION_EVENT(execution_context, context.GetNodeName(), "[CalcTilingInfo] End"); + + // update op args by tiling info + block_dim_ = static_cast(tiling_info.block_dim); + op_desc->SetWorkspaceBytes(tiling_info.workspaces); + + tiling_data_ = tiling_info.tiling_data.str(); + if (tiling_data_.empty()) { + GELOGE(INTERNAL_ERROR, "[%s] Tiling data is empty.", stub_name_.c_str()); + return INTERNAL_ERROR; + } + + if (tiling_data_.size() > tiling_buffer_->GetSize()) { + GELOGE(INTERNAL_ERROR, "[%s] Tiling data size now (%zu) shouldn't larger than we alloc before (%zu).", + stub_name_.c_str(), tiling_data_.size(), tiling_buffer_->GetSize()); + return INTERNAL_ERROR; + } + + RECORD_EXECUTION_EVENT(execution_context, context.GetNodeName(), "[CopyTilingInfo] Start"); + GE_CHK_RT_RET(rtMemcpy(tiling_buffer_->GetData(), tiling_buffer_->GetSize(), tiling_data_.c_str(), + tiling_data_.size(), RT_MEMCPY_HOST_TO_DEVICE)); + RECORD_EXECUTION_EVENT(execution_context, context.GetNodeName(), "[CopyTilingInfo] End"); + + GELOGD("[%s] Done updating tiling info for task: [%s]", node->GetName().c_str(), stub_name_.c_str()); + return SUCCESS; +} + +Status AiCoreOpTask::CalcTilingInfo(const NodePtr &node, OpRunInfo &tiling_info) { + GELOGD("[%s] Start to invoke OpParaCalculate.", node->GetName().c_str()); + GE_CHK_STATUS_RET(OpParaCalculate(*node, tiling_info), "Failed calc tiling data of node %s.", + node->GetName().c_str()); + GELOGD("[%s] Done invoking OpParaCalculate successfully.", node->GetName().c_str()); + return SUCCESS; +} + +Status AiCoreOpTask::UpdateArgs(TaskContext &task_context) { + size_t expected_arg_count = task_context.NumInputs() + task_context.NumOutputs() + task_context.NumWorkspaces(); + if (tiling_buffer_ != nullptr) { + ++expected_arg_count; + } + if (expected_arg_count > max_arg_count_) { + GELOGE(INTERNAL_ERROR, "[%s] Invalid arg memory, max arg count = %u, but expect = %zu", GetName().c_str(), + max_arg_count_, expected_arg_count); + return INTERNAL_ERROR; + } + int index = 0; + for (int i = 0; i < task_context.NumInputs(); ++i) { + const auto input = task_context.GetInput(i); + GE_CHECK_NOTNULL(input); + arg_base_[index++] = reinterpret_cast(input->GetData()); + } + + for (int i = 0; i < task_context.NumOutputs(); ++i) { + const auto output = task_context.GetOutput(i); + GE_CHECK_NOTNULL(output); + arg_base_[index++] = reinterpret_cast(output->GetData()); + } + + int workspace_num = static_cast(task_context.NumWorkspaces()); + for (int i = 0; i < workspace_num; ++i) { + const auto workspace = task_context.MutableWorkspace(i); + GE_CHECK_NOTNULL(workspace); + arg_base_[index++] = reinterpret_cast(workspace); + } + + if (tiling_buffer_ != nullptr) { + arg_base_[index++] = reinterpret_cast(tiling_buffer_->GetData()); + } + + if (task_context.IsTraceEnabled()) { + for (int i = 0; i < index; ++i) { + GELOGD("[%s] Arg[%d] = %lu", stub_name_.c_str(), i, arg_base_[i]); + } + } + + return SUCCESS; +} + +Status AiCoreOpTask::LaunchKernel(rtStream_t stream) { + GELOGD("AiCoreOpTask LaunchKernel Start (task = %s, block_dim = %u).", stub_name_.c_str(), block_dim_); GE_CHK_RT_RET(rtKernelLaunch(stub_func_, block_dim_, args_.get(), args_size_, nullptr, stream)); - GELOGI("AiCoreOpTask LaunchKernel End (task = %s, block_dim = %u).", stub_name_.c_str(), block_dim_); + GELOGD("AiCoreOpTask LaunchKernel End (task = %s, block_dim = %u).", stub_name_.c_str(), block_dim_); return SUCCESS; } +Status AiCoreOpTask::InitTilingInfo(const OpDesc &op_desc) { + bool dynamic_supported = false; + (void)AttrUtils::GetBool(op_desc, kAttrSupportDynamicShape, dynamic_supported); + if (!dynamic_supported) { + GELOGD("[%s] Dynamic shape is not supported.", op_desc.GetName().c_str()); + return SUCCESS; + } + + GELOGD("Start alloc tiling data of node %s.", op_desc.GetName().c_str()); + int64_t max_size = -1; + (void)AttrUtils::GetInt(op_desc, GetKeyForOpParamSize(), max_size); + GELOGD("Got op param size by key: %s, ret = %ld", GetKeyForOpParamSize().c_str(), max_size); + if (max_size <= 0) { + GELOGE(PARAM_INVALID, "[%s] Invalid op_param_size: %ld.", op_desc.GetName().c_str(), max_size); + return PARAM_INVALID; + } + + auto allocator = NpuMemoryAllocator::GetAllocator(); + GE_CHECK_NOTNULL(allocator); + tiling_buffer_ = TensorBuffer::Create(allocator, static_cast(max_size)); + GE_CHECK_NOTNULL(tiling_buffer_); + + GELOGD("[%s] Done allocating tiling buffer, size=%ld.", op_desc.GetName().c_str(), max_size); + return SUCCESS; +} + +bool AiCoreOpTask::IsDynamicShapeSupported() { return tiling_buffer_ != nullptr; } + +const std::string &AiCoreOpTask::GetName() const { return stub_name_; } + +std::string AiCoreOpTask::GetKeyForOpParamSize() const { return kAttrOpParamSize; } + +Status AtomicAddrCleanOpTask::Init(const OpDesc &op_desc, const domi::TaskDef &task_def) { + GE_CHK_STATUS_RET_NOLOG(AiCoreOpTask::Init(op_desc, task_def)); + return InitAtomicAddrCleanIndices(op_desc); +} + +Status AtomicAddrCleanOpTask::InitAtomicAddrCleanIndices(const OpDesc &op_desc) { + GELOGD("[%s] Start to setup AtomicAddrClean task.", op_desc.GetName().c_str()); + std::vector atomic_output_indices; + (void)ge::AttrUtils::GetListInt(op_desc, ATOMIC_ATTR_OUTPUT_INDEX, atomic_output_indices); + map> workspace_info; // op_name, ws_index, ws_offset + workspace_info = op_desc.TryGetExtAttr(EXT_ATTR_ATOMIC_WORKSPACE_INFO, workspace_info); + if (atomic_output_indices.empty() && workspace_info.empty()) { + GELOGE(INTERNAL_ERROR, "[%s] Neither ATOMIC_ATTR_OUTPUT_INDEX nor EXT_ATTR_ATOMIC_WORKSPACE_INFO is empty.", + op_desc.GetName().c_str()); + return INTERNAL_ERROR; + } + + for (auto output_index : atomic_output_indices) { + GELOGD("[%s] Adding output index [%ld]", op_desc.GetName().c_str(), output_index); + GE_CHECK_GE(output_index, 0); + GE_CHECK_LE(output_index, INT32_MAX); + atomic_output_indices_.emplace_back(static_cast(output_index)); + } + + for (auto &iter : workspace_info) { + for (auto &info_iter : iter.second) { + auto workspace_index = info_iter.first; + GELOGD("[%s] Adding workspace index [%ld]", op_desc.GetName().c_str(), workspace_index); + GE_CHECK_GE(workspace_index, 0); + GE_CHECK_LE(workspace_index, INT32_MAX); + atomic_workspace_indices_.emplace_back(static_cast(workspace_index)); + } + } + + size_t arg_count = atomic_workspace_indices_.size() + atomic_output_indices_.size(); + if (tiling_buffer_ != nullptr) { + arg_count += 1; + } + + if (arg_count > max_arg_count_) { + GELOGE(INTERNAL_ERROR, "[%s] Invalid arg memory, max arg count = %u, but expect = %zu", GetName().c_str(), + max_arg_count_, arg_count); + return INTERNAL_ERROR; + } + + return SUCCESS; +} + +std::string AtomicAddrCleanOpTask::GetKeyForOpParamSize() const { return kAttrAtomicOpParamSize; } + +Status AtomicAddrCleanOpTask::UpdateArgs(TaskContext &task_context) { + // refresh atomic output addr + int index = 0; + for (auto atomic_output_index : atomic_output_indices_) { + const auto output_tensor = task_context.GetOutput(atomic_output_index); + GE_CHECK_NOTNULL(output_tensor); + arg_base_[index++] = reinterpret_cast(output_tensor->GetData()); + } + + // refresh atomic workspace addr + for (auto atomic_ws_index : atomic_workspace_indices_) { + const auto workspace_tensor = task_context.GetOutput(atomic_ws_index); + GE_CHECK_NOTNULL(workspace_tensor); + arg_base_[index++] = reinterpret_cast(workspace_tensor->GetData()); + } + + if (tiling_buffer_ != nullptr) { + arg_base_[index++] = reinterpret_cast(tiling_buffer_->GetData()); + } else { + GELOGD("[%s] Not a dynamic op", GetName().c_str()); + } + + if (task_context.IsTraceEnabled()) { + for (int i = 0; i < index; ++i) { + GELOGD("[%s] Arg[%d] = %lu", GetName().c_str(), i, arg_base_[i]); + } + } + + return SUCCESS; +} } // namespace hybrid -} // namespace ge \ No newline at end of file +} // namespace ge diff --git a/src/ge/hybrid/node_executor/aicore/aicore_op_task.h b/src/ge/hybrid/node_executor/aicore/aicore_op_task.h index d23688a5..74876588 100644 --- a/src/ge/hybrid/node_executor/aicore/aicore_op_task.h +++ b/src/ge/hybrid/node_executor/aicore/aicore_op_task.h @@ -18,27 +18,69 @@ #define GE_HYBRID_KERNEL_AICORE_OP_TASK_H_ #include +#include #include "common/ge_inner_error_codes.h" #include "runtime/stream.h" +#include "hybrid/common/tensor_value.h" +#include "hybrid/node_executor/task_context.h" +#include "proto/task.pb.h" +#include "register/op_tiling.h" + namespace ge { namespace hybrid { class AiCoreOpTask { public: AiCoreOpTask() = default; - ~AiCoreOpTask() = default; + virtual ~AiCoreOpTask() = default; + + virtual Status Init(const OpDesc &op_desc, const domi::TaskDef &task_def); + + bool IsDynamicShapeSupported(); + + // do preparation with shape(without actual io memory) + Status PrepareWithShape(TaskContext &context); + + virtual Status UpdateArgs(TaskContext &task_context); + Status LaunchKernel(rtStream_t stream); + const std::string &GetName() const; + + protected: + Status UpdateTilingInfo(TaskContext &context); + virtual std::string GetKeyForOpParamSize() const; + virtual Status CalcTilingInfo(const NodePtr &node, optiling::OpRunInfo &tiling_info); + + std::unique_ptr tiling_buffer_ = nullptr; + std::string tiling_data_; + uintptr_t *arg_base_ = nullptr; + uint32_t max_arg_count_ = 0; + private: - friend class AiCoreTaskBuilder; - friend class AiCoreNodeTask; + static Status ValidateTaskDef(const domi::TaskDef &task_def); + Status InitWithTaskDef(const OpDesc &node, const domi::TaskDef &task_def); + Status InitTilingInfo(const OpDesc &op_desc); + std::string stub_name_; void *stub_func_ = nullptr; std::unique_ptr args_ = nullptr; uint32_t args_size_ = 0; uint32_t block_dim_ = 1; - uint16_t offset_ = 0; }; +class AtomicAddrCleanOpTask : public AiCoreOpTask { + public: + Status Init(const OpDesc &op_desc, const domi::TaskDef &task_def) override; + Status UpdateArgs(TaskContext &task_context) override; + + protected: + std::string GetKeyForOpParamSize() const override; + + private: + Status InitAtomicAddrCleanIndices(const OpDesc &op_desc); + std::vector atomic_output_indices_; + std::vector atomic_workspace_indices_; +}; } // namespace hybrid } // namespace ge #endif // GE_HYBRID_KERNEL_AICORE_OP_TASK_H_ diff --git a/src/ge/hybrid/node_executor/aicore/aicore_task_builder.cc b/src/ge/hybrid/node_executor/aicore/aicore_task_builder.cc index 5b263007..bad91806 100644 --- a/src/ge/hybrid/node_executor/aicore/aicore_task_builder.cc +++ b/src/ge/hybrid/node_executor/aicore/aicore_task_builder.cc @@ -15,76 +15,78 @@ */ #include "aicore_task_builder.h" -#include -#include "graph/op_desc.h" -#include "cce/taskdown_common.hpp" -#include "framework/common/debug/log.h" -#include "graph/debug/ge_attr_define.h" +#include "common/debug/log.h" +#include "aicore_node_executor.h" namespace ge { namespace hybrid { -std::mutex g_reg_mutex; - -AiCoreTaskBuilder::AiCoreTaskBuilder(const OpDescPtr &op_desc, const domi::KernelDef &kernel_def) - : op_desc_(op_desc), kernel_def_(kernel_def) { - std::string session_graph_id; - GE_IF_BOOL_EXEC(AttrUtils::GetStr(*op_desc_, ATTR_NAME_SESSION_GRAPH_ID, session_graph_id), - GELOGD("Get original type of session_graph_id.")); - // get bin_file_key - stub_name_ = (session_graph_id.empty()) ? op_desc_->GetName() : session_graph_id + "_" + op_desc_->GetName(); -} - -Status AiCoreTaskBuilder::SetKernelArgs(AiCoreOpTask &task) { - const domi::KernelContext &context = kernel_def_.context(); - // get kernel_type - auto kernel_type = static_cast(context.kernel_type()); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(kernel_type != cce::ccKernelType::TE, return UNSUPPORTED, - "Invalid kernel type[%d] in AiCore TaskDef.", static_cast(kernel_type)); - - task.args_size_ = kernel_def_.args_size(); - task.block_dim_ = kernel_def_.block_dim(); - - // malloc args memory - task.args_.reset(new (std::nothrow) uint8_t[task.args_size_]); - // task.args_ = std::make_unique(task.args_size_); - GE_CHECK_NOTNULL(task.args_); - errno_t err = memcpy_s(task.args_.get(), task.args_size_, kernel_def_.args().data(), task.args_size_); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(err != EOK, return INTERNAL_ERROR, "AiCoreTask memcpy failed."); - - const auto *args_offset_tmp = reinterpret_cast(const_cast(context.args_offset().data())); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(context.args_offset().size() / sizeof(uint16_t) < 1, return FAILED, - "context.args_offset().size() / sizeof(uint16_t) less than 1"); - task.offset_ = *args_offset_tmp; - return SUCCESS; +namespace { +const size_t kNumTaskWithAtomicAddrCleanTask = 2; } - const char *AiCoreKernelRegistry::GetUnique(const string &stub_key) { std::lock_guard lock(mutex_); auto it = unique_stubs_.find(stub_key); - GE_IF_BOOL_EXEC(it != unique_stubs_.end(), return it->c_str()); + if (it != unique_stubs_.end()) { + return it->c_str(); + } it = unique_stubs_.insert(unique_stubs_.end(), stub_key); return it->c_str(); } -Status AiCoreTaskBuilder::SetStub(AiCoreOpTask &task) { - AiCoreKernelRegistry ®istry = AiCoreKernelRegistry::GetInstance(); - std::lock_guard lock(g_reg_mutex); - const char *unique_key = registry.GetUnique(stub_name_); +AiCoreTaskBuilder::AiCoreTaskBuilder(const OpDescPtr &op_desc, const std::vector &task_defs) + : op_desc_(op_desc), task_defs_(task_defs) {} - GE_CHK_RT_RET(rtGetFunctionByName(unique_key, &(task.stub_func_))); - task.stub_name_ = stub_name_; +Status AiCoreTaskBuilder::BuildTask(std::unique_ptr &node_task, bool ignore_failure_on_atomic) { + GE_CHECK_NOTNULL(op_desc_); + if (task_defs_.size() > kNumTaskWithAtomicAddrCleanTask) { + GELOGE(INTERNAL_ERROR, "[%s] At most 2 task was supported, but got %zu", op_desc_->GetName().c_str(), + task_defs_.size()); + return INTERNAL_ERROR; + } - return SUCCESS; -} + std::vector> op_tasks; + if (ExpectAtomicAddrCleanTask()) { + if (task_defs_.size() != kNumTaskWithAtomicAddrCleanTask) { + if (ignore_failure_on_atomic) { + GELOGI("[%s] AtomicAddrClean task was expected, but got %zu task_defs", op_desc_->GetName().c_str(), + task_defs_.size()); + return SUCCESS; + } else { + GELOGE(INTERNAL_ERROR, "[%s] AtomicAddrClean task was expected, but got %zu task_defs", + op_desc_->GetName().c_str(), task_defs_.size()); + return INTERNAL_ERROR; + } + } -Status AiCoreTaskBuilder::BuildTask(AiCoreOpTask &task) { - GE_CHECK_NOTNULL(op_desc_); - GELOGI("AiCoreTaskBuilder[%s] BuildTask Start.", op_desc_->GetName().c_str()); - GE_CHK_STATUS_RET_NOLOG(SetKernelArgs(task)); - GE_CHK_STATUS_RET_NOLOG(SetStub(task)); - GELOGI("AiCoreTaskBuilder[%s] BuildTask End.", op_desc_->GetName().c_str()); + GELOGD("[%s] Build AtomicAddrClean task.", op_desc_->GetName().c_str()); + auto atomic_task = std::unique_ptr(new (std::nothrow) AtomicAddrCleanOpTask()); + GE_CHECK_NOTNULL(atomic_task); + GE_CHK_STATUS_RET(atomic_task->Init(*op_desc_, task_defs_.front()), "[%s] Failed to init task for AtomicAddrClean", + op_desc_->GetName().c_str()); + op_tasks.emplace_back(std::move(atomic_task)); + } + + // build aicore task + auto aicore_task = std::unique_ptr(new (std::nothrow) AiCoreOpTask()); + GE_CHECK_NOTNULL(aicore_task); + GE_CHK_STATUS_RET(aicore_task->Init(*op_desc_, task_defs_.back()), "[%s] Failed to init task for AtomicAddrClean", + op_desc_->GetName().c_str()); + op_tasks.emplace_back(std::move(aicore_task)); + + node_task.reset(new (std::nothrow) AiCoreNodeTask(std::move(op_tasks))); + GE_CHECK_NOTNULL(node_task); return SUCCESS; } +bool AiCoreTaskBuilder::ExpectAtomicAddrCleanTask() { + if (op_desc_->HasAttr(ATOMIC_ATTR_OUTPUT_INDEX)) { + GELOGD("[%s] Node has ATOMIC_ATTR_OUTPUT_INDEX", op_desc_->GetName().c_str()); + return true; + } + map> workspace_info; + workspace_info = op_desc_->TryGetExtAttr(EXT_ATTR_ATOMIC_WORKSPACE_INFO, workspace_info); + + return !workspace_info.empty(); +} } // namespace hybrid } // namespace ge diff --git a/src/ge/hybrid/node_executor/aicore/aicore_task_builder.h b/src/ge/hybrid/node_executor/aicore/aicore_task_builder.h index 18cb309c..4610e57a 100644 --- a/src/ge/hybrid/node_executor/aicore/aicore_task_builder.h +++ b/src/ge/hybrid/node_executor/aicore/aicore_task_builder.h @@ -17,14 +17,13 @@ #ifndef GE_HYBRID_KERNEL_AICORE_TASK_BUILDER_H_ #define GE_HYBRID_KERNEL_AICORE_TASK_BUILDER_H_ -#include +#include #include -#include -#include #include "aicore_op_task.h" -#include "proto/task.pb.h" +#include "framework/common/debug/ge_log.h" #include "graph/utils/attr_utils.h" #include "graph/op_kernel_bin.h" +#include "proto/task.pb.h" namespace ge { namespace hybrid { @@ -45,16 +44,16 @@ class AiCoreKernelRegistry { class AiCoreTaskBuilder { public: - AiCoreTaskBuilder(const OpDescPtr &op_desc, const domi::KernelDef &kernel_def); + AiCoreTaskBuilder(const OpDescPtr &op_desc, const std::vector &task_defs); ~AiCoreTaskBuilder() = default; - Status BuildTask(AiCoreOpTask &task); + + Status BuildTask(std::unique_ptr &node_task, bool ignore_failure_on_atomic); private: - Status SetKernelArgs(AiCoreOpTask &task); - Status SetStub(AiCoreOpTask &task); - const OpDescPtr &op_desc_; - const domi::KernelDef &kernel_def_; - std::string stub_name_; + bool ExpectAtomicAddrCleanTask(); + + OpDescPtr op_desc_; + const std::vector &task_defs_; }; } // namespace hybrid } // namespace ge diff --git a/src/ge/hybrid/node_executor/aicore/aicore_task_compiler.cc b/src/ge/hybrid/node_executor/aicore/aicore_task_compiler.cc index ac89afbd..9119bebb 100644 --- a/src/ge/hybrid/node_executor/aicore/aicore_task_compiler.cc +++ b/src/ge/hybrid/node_executor/aicore/aicore_task_compiler.cc @@ -34,7 +34,6 @@ Status AiCoreTaskCompiler::DoCompileOp(OpsKernelInfoStore &ops_store, const Node GE_CHECK_NOTNULL(node); vector node_vec; node_vec.emplace_back(node); - std::lock_guard lk(mu_); GE_CHK_STATUS_RET(ops_store.CompileOpRun(node_vec), "Failed to execute CompileOp, node = %s", node->GetName().c_str()); GE_CHK_STATUS_RET(ops_store.CalcOpRunningParam(*node), "Failed to execute CalcOpRunningParam, node = %s", @@ -44,9 +43,8 @@ Status AiCoreTaskCompiler::DoCompileOp(OpsKernelInfoStore &ops_store, const Node Status AiCoreTaskCompiler::CompileOp(const NodePtr &node, std::vector &tasks) const { GE_CHECK_NOTNULL(node); - GELOGI("AiCoreTaskCompiler[%s] CompileOp Start.", node->GetName().c_str()); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(aic_kernel_store_ == nullptr, return FAILED, - "Failed to get AiCore kernel store, node = %s", node->GetName().c_str()); + GELOGI("AiCoreTaskCompiler(%s) CompileOp Start.", node->GetName().c_str()); + GE_CHECK_NOTNULL(aic_kernel_store_); GE_CHK_STATUS_RET_NOLOG(DoCompileOp(*aic_kernel_store_, node)); GELOGD("successfully compiled op: %s", node->GetName().c_str()); @@ -58,7 +56,7 @@ Status AiCoreTaskCompiler::CompileOp(const NodePtr &node, std::vectorSetOutputOffset(output_offsets); GE_CHK_STATUS_RET_NOLOG(DoGenerateTask(*aic_kernel_store_, *node, tasks)); GELOGD("successfully generated task: %s", node->GetName().c_str()); - GELOGI("AiCoreTaskCompiler[%s] CompileOp End.", node->GetName().c_str()); + GELOGI("AiCoreTaskCompiler(%s) CompileOp End.", node->GetName().c_str()); return SUCCESS; } @@ -91,6 +89,5 @@ Status AiCoreTaskCompiler::DoGenerateTask(OpsKernelInfoStore &store, const Node GE_CHK_RT(rtModelDestroy(rt_model_)); return ret; } - } // namespace hybrid -} // namespace ge \ No newline at end of file +} // namespace ge diff --git a/src/ge/hybrid/node_executor/aicpu/aicpu_ext_info.cc b/src/ge/hybrid/node_executor/aicpu/aicpu_ext_info.cc index d5c3c03c..332675bf 100644 --- a/src/ge/hybrid/node_executor/aicpu/aicpu_ext_info.cc +++ b/src/ge/hybrid/node_executor/aicpu/aicpu_ext_info.cc @@ -199,6 +199,5 @@ void AicpuExtInfoHandler::GetShapeAndType(const AicpuShapeAndType *shape_and_typ data_type = static_cast(shape_and_type->type); shape = std::move(GeShape(dims)); } - } // namespace hybrid } // namespace ge \ No newline at end of file diff --git a/src/ge/hybrid/node_executor/aicpu/aicpu_ext_info.h b/src/ge/hybrid/node_executor/aicpu/aicpu_ext_info.h index e96d794c..a42678b1 100644 --- a/src/ge/hybrid/node_executor/aicpu/aicpu_ext_info.h +++ b/src/ge/hybrid/node_executor/aicpu/aicpu_ext_info.h @@ -24,7 +24,6 @@ namespace ge { namespace hybrid { - using AicpuShapeAndType = aicpu::FWKAdapter::ShapeAndType; using AicpuExtInfo = aicpu::FWKAdapter::ExtInfo; diff --git a/src/ge/hybrid/node_executor/aicpu/aicpu_node_executor.cc b/src/ge/hybrid/node_executor/aicpu/aicpu_node_executor.cc index 372f35f5..46d9a0aa 100644 --- a/src/ge/hybrid/node_executor/aicpu/aicpu_node_executor.cc +++ b/src/ge/hybrid/node_executor/aicpu/aicpu_node_executor.cc @@ -40,19 +40,28 @@ Status AicpuNodeTaskBase::AllocTensorBuffer(size_t size, std::unique_ptris_dynamic) { + // dynamic node must have ext info + GE_CHK_STATUS_RET(aicpu_ext_handle_.Parse(kernel_ext_info), + "Node[%s] parse kernel ext info failed, kernel_ext_info_size=%zu.", node_name_.c_str(), + kernel_ext_info.size()); + } + + // if no ext info no need copy to device. + if (kernel_ext_info.empty()) { + GELOGI("Node[%s] kernel_ext_info is empty, no need copy to device, is_dynamic=%s.", node_name_.c_str(), + node_item_->is_dynamic ? "true" : "false"); + return SUCCESS; + } // copy task args buf GE_CHK_STATUS_RET(AllocTensorBuffer(kernel_ext_info.size(), ext_info_addr_dev_), "Node[%s] alloc kernel_ext_info buf failed, size=%zu", node_name_.c_str(), kernel_ext_info.size()); - // if no input and no output(DEPEND_COMPUTE equal no output), copy once, or else copy when update args. - if (node_item_->num_inputs == 0 && ((unknown_type_ == DEPEND_COMPUTE) || (node_item_->num_outputs == 0))) { - GE_CHK_RT_RET(rtMemcpy(ext_info_addr_dev_->GetData(), ext_info_addr_dev_->GetSize(), kernel_ext_info.data(), - kernel_ext_info.size(), RT_MEMCPY_HOST_TO_DEVICE)); - } + // copy default ext info to device + GE_CHK_RT_RET(rtMemcpy(ext_info_addr_dev_->GetData(), ext_info_addr_dev_->GetSize(), kernel_ext_info.data(), + kernel_ext_info.size(), RT_MEMCPY_HOST_TO_DEVICE)); + return SUCCESS; } @@ -139,16 +148,18 @@ Status AicpuNodeTaskBase::UpdateExtInfo() { } Status AicpuNodeTaskBase::UpdateArgs(TaskContext &context) { - GELOGI("Node[%s] update args begin. unknown_type=%d", node_name_.c_str(), unknown_type_); + GELOGI("Node[%s] update args begin. is_dynamic=%s, unknown_type=%d", node_name_.c_str(), + node_item_->is_dynamic ? "true" : "false", unknown_type_); if (node_item_->num_inputs == 0 && node_item_->num_outputs == 0) { GELOGI("Node[%s] has no input and output, no need update args.", node_name_.c_str()); return SUCCESS; } GE_CHK_STATUS_RET(UpdateIoAddr(context), "Node[%s] update io addr failed.", node_name_.c_str()); - - GE_CHK_STATUS_RET(UpdateExtInfo(), "Node[%s] update ext info failed.", node_name_.c_str()); - + if (node_item_->is_dynamic) { + // dynamic node need update ext info. + GE_CHK_STATUS_RET(UpdateExtInfo(), "Node[%s] update ext info failed.", node_name_.c_str()); + } GELOGI("Node[%s] update args end.", node_name_.c_str()); return SUCCESS; } @@ -275,9 +286,12 @@ Status AicpuTfNodeTask::Init(const HybridModel &model) { fwk_op_kernel.fwkKernelBase.fwk_kernel.workspaceBaseAddr = reinterpret_cast(kernel_workspace_->GetData()); fwk_op_kernel.fwkKernelBase.fwk_kernel.inputOutputAddr = reinterpret_cast(input_output_addr_->GetData()); - // set ext info addr and ext info num - fwk_op_kernel.fwkKernelBase.fwk_kernel.extInfoAddr = reinterpret_cast(ext_info_addr_dev_->GetData()); - fwk_op_kernel.fwkKernelBase.fwk_kernel.extInfoLen = ext_info_addr_dev_->GetSize(); + + if (ext_info_addr_dev_ != nullptr) { + // set ext info addr and ext info num + fwk_op_kernel.fwkKernelBase.fwk_kernel.extInfoAddr = reinterpret_cast(ext_info_addr_dev_->GetData()); + fwk_op_kernel.fwkKernelBase.fwk_kernel.extInfoLen = ext_info_addr_dev_->GetSize(); + } fwk_op_kernel.fwkKernelBase.fwk_kernel.stepIDAddr = GetStepIdAddr(model); @@ -506,7 +520,8 @@ Status AicpuTfNodeTask::UpdateIoAddr(TaskContext &context) { io_addrs.emplace_back(reinterpret_cast(inputData->GetData())); } - if (unknown_type_ != DEPEND_COMPUTE) { + // known shape or not depend compute + if (!node_item_->is_dynamic || unknown_type_ != DEPEND_COMPUTE) { // unknown type 4 do this in call back. GE_CHK_STATUS_RET_NOLOG(context.AllocateOutputs()); for (auto j = 0; j < node_item_->num_outputs; ++j) { @@ -548,14 +563,17 @@ Status AicpuTfNodeTask::LaunchTask(TaskContext &context) { } Status AicpuTfNodeTask::TaskCallback(TaskContext &context) { - GELOGI("Node[%s] task callback start. unknown_type=%d.", node_name_.c_str(), unknown_type_); + GELOGI("Node[%s] task callback start. is_dynamic=%s, unknown_type=%d.", node_name_.c_str(), + node_item_->is_dynamic ? "true" : "false", unknown_type_); Status callback_ret = SUCCESS; - // check need update shape, call update shape. - if (unknown_type_ == DEPEND_SHAPE_RANGE) { - // check result - callback_ret = UpdateOutputShapeFromExtInfo(); - } else if (unknown_type_ == DEPEND_COMPUTE) { - callback_ret = UpdateShapeAndDataByResultSummary(context); + if (node_item_->is_dynamic) { + // check need update shape, call update shape. + if (unknown_type_ == DEPEND_SHAPE_RANGE) { + // check result + callback_ret = UpdateOutputShapeFromExtInfo(); + } else if (unknown_type_ == DEPEND_COMPUTE) { + callback_ret = UpdateShapeAndDataByResultSummary(context); + } } GELOGI("Node[%s] task callback end.", node_name_.c_str()); return callback_ret; @@ -612,8 +630,13 @@ Status AicpuNodeTask::Init(const HybridModel &model) { GE_CHK_STATUS_RET(InitExtInfo(kernel_ext_info), "Node[%s] init ext info failed.", node_name.c_str()); - aicpu_param_head->extInfoLength = ext_info_addr_dev_->GetSize(); - aicpu_param_head->extInfoAddr = reinterpret_cast(ext_info_addr_dev_->GetData()); + if (ext_info_addr_dev_ == nullptr) { + aicpu_param_head->extInfoLength = 0; + aicpu_param_head->extInfoAddr = 0; + } else { + aicpu_param_head->extInfoLength = ext_info_addr_dev_->GetSize(); + aicpu_param_head->extInfoAddr = reinterpret_cast(ext_info_addr_dev_->GetData()); + } GELOGI("Node[%s] init end.", node_name.c_str()); return SUCCESS; @@ -664,10 +687,12 @@ Status AicpuNodeTask::LaunchTask(TaskContext &context) { } Status AicpuNodeTask::TaskCallback(TaskContext &context) { - GELOGI("Node[%s] task callback start, unknown_type=%d.", node_name_.c_str(), unknown_type_); + GELOGI("Node[%s] task callback start, is_dynamic = %s, unknown_type=%d.", node_name_.c_str(), + node_item_->is_dynamic ? "true" : "false", unknown_type_); Status callback_ret = SUCCESS; + // check need update shape, call update shape. - if (unknown_type_ == DEPEND_SHAPE_RANGE) { + if (node_item_->is_dynamic && unknown_type_ == DEPEND_SHAPE_RANGE) { // check result callback_ret = UpdateOutputShapeFromExtInfo(); } else { diff --git a/src/ge/hybrid/node_executor/aicpu/aicpu_node_executor.h b/src/ge/hybrid/node_executor/aicpu/aicpu_node_executor.h index ce3f9707..8aca6ff7 100644 --- a/src/ge/hybrid/node_executor/aicpu/aicpu_node_executor.h +++ b/src/ge/hybrid/node_executor/aicpu/aicpu_node_executor.h @@ -24,7 +24,6 @@ namespace ge { namespace hybrid { - class AicpuNodeTaskBase : public NodeTask { public: AicpuNodeTaskBase(const NodeItem *node_item, const domi::TaskDef &task_def) @@ -70,8 +69,10 @@ class AicpuNodeTaskBase : public NodeTask { const std::string node_type_; + // valid when node_item_->is_dynamic is true UnknowShapeOpType unknown_type_ = DEPEND_IN_SHAPE; + // valid when node_item_->is_dynamic is true AicpuExtInfoHandler aicpu_ext_handle_; // ext info addr, device mem @@ -169,7 +170,6 @@ class AiCpuNodeExecutor : public NodeExecutor { Status PrepareTask(NodeTask &task, TaskContext &context) const override; }; - } // namespace hybrid } // namespace ge #endif // GE_HYBRID_KERNEL_AICPU_NODE_EXECUTOR_H_ diff --git a/src/ge/hybrid/node_executor/compiledsubgraph/known_node_executor.cc b/src/ge/hybrid/node_executor/compiledsubgraph/known_node_executor.cc index 81960c48..afa53724 100644 --- a/src/ge/hybrid/node_executor/compiledsubgraph/known_node_executor.cc +++ b/src/ge/hybrid/node_executor/compiledsubgraph/known_node_executor.cc @@ -26,7 +26,6 @@ namespace ge { namespace hybrid { - REGISTER_NODE_EXECUTOR_BUILDER(NodeExecutorManager::ExecutorType::COMPILED_SUBGRAPH, KnownNodeExecutor); Status KnownNodeTask::ExecuteAsync(TaskContext &context, std::function done_callback) { @@ -98,8 +97,11 @@ Status KnownNodeTask::Init(TaskContext &context) { GE_CHK_STATUS_RET(context.AllocateOutputs(), "known node task allocate output failed."); // init davinicmodel - davinci_model_->InitRuntimeParams(); - GE_CHK_STATUS_RET(davinci_model_->InitVariableMem(), "init variable mem failed."); + if (!load_flag_) { + davinci_model_->InitRuntimeParams(); + GE_CHK_STATUS_RET(davinci_model_->InitVariableMem(), "init variable mem failed."); + } + // allocate mem base void *buffer = nullptr; if (davinci_model_->TotalMemSize() != 0) { @@ -161,6 +163,5 @@ Status KnownNodeExecutor::ExecuteTask(NodeTask &task, TaskContext &context, context.GetNodeItem().NodeName().c_str()); return SUCCESS; } - } // namespace hybrid } // namespace ge diff --git a/src/ge/hybrid/node_executor/controlop/control_op_executor.cc b/src/ge/hybrid/node_executor/controlop/control_op_executor.cc new file mode 100644 index 00000000..1f18db3d --- /dev/null +++ b/src/ge/hybrid/node_executor/controlop/control_op_executor.cc @@ -0,0 +1,318 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "control_op_executor.h" +#include "graph/utils/node_utils.h" +#include "hybrid/executor/hybrid_execution_context.h" +#include "hybrid/executor/subgraph_executor.h" + +namespace ge { +namespace hybrid { +REGISTER_NODE_EXECUTOR_BUILDER(NodeExecutorManager::ExecutorType::CONTROL_OP, ControlOpNodeExecutor); + +Status ControlOpNodeTask::ExecuteSubgraph(const GraphItem *subgraph, TaskContext &task_context, + const std::function &done_callback) { + GELOGD("[%s] Start to execute subgraph.", subgraph->GetName().c_str()); + auto execution_context = const_cast(task_context.GetExecutionContext()); + auto executor = MakeShared(subgraph, execution_context, task_context.IsForceInferShape()); + GE_CHECK_NOTNULL(executor); + GE_CHK_STATUS_RET(executor->ExecuteAsync(task_context), "[%s] Failed to execute partitioned call.", + subgraph->GetName().c_str()); + + auto callback = [executor, done_callback]() mutable { + if (done_callback != nullptr) { + done_callback(); + } + // executor must outlive task context + executor.reset(); + }; + + GE_CHK_STATUS_RET_NOLOG(task_context.RegisterCallback(callback)); + GELOGD("[%s] Done executing subgraph successfully.", subgraph->GetName().c_str()); + return SUCCESS; +} + +Status ControlOpNodeTask::CopyTensorValueToHost(const TensorValue &tensor, int32_t &value) { + GE_CHECK_NOTNULL(tensor.GetData()); + GE_CHECK_GE(tensor.GetSize(), sizeof(value)); + GE_CHK_RT_RET(rtMemcpy(&value, sizeof(value), tensor.GetData(), sizeof(value), RT_MEMCPY_DEVICE_TO_HOST)); + return SUCCESS; +} + +Status ControlOpNodeTask::UpdateArgs(TaskContext &context) { + // do nothing + return SUCCESS; +} + +Status ControlOpNodeTask::ExecuteAsync(TaskContext &task_context, std::function done_callback) { + auto ret = DoExecuteAsync(task_context, done_callback); + task_context.SetStatus(ret); + + if (done_callback) { + done_callback(); + } + + return ret; +} + +Status IfOpNodeTask::Init(const NodePtr &node, const HybridModel &model) { + GELOGD("[%s] Start to init IfOpNodeTask.", node->GetName().c_str()); + auto then_subgraph = NodeUtils::GetSubgraph(*node, kThenBranchIndex); + GE_CHECK_NOTNULL(then_subgraph); + GELOGD("[%s] Adding subgraph [%s] to then-subgraph.", node->GetName().c_str(), then_subgraph->GetName().c_str()); + then_ = model.GetSubgraphItem(then_subgraph); + GE_CHECK_NOTNULL(then_); + + auto else_subgraph = NodeUtils::GetSubgraph(*node, kElseBranchIndex); + GE_CHECK_NOTNULL(else_subgraph); + GELOGD("[%s] Adding subgraph [%s] to else-subgraph.", node->GetName().c_str(), else_subgraph->GetName().c_str()); + else_ = model.GetSubgraphItem(else_subgraph); + GE_CHECK_NOTNULL(else_); + + GELOGD("[%s] Done initialization successfully.", node->GetName().c_str()); + return SUCCESS; +} + +const GraphItem *IfOpNodeTask::SelectBranch(int32_t cond) const { return cond != 0 ? then_ : else_; } + +Status IfOpNodeTask::DoExecuteAsync(TaskContext &task_context, const std::function &done_callback) const { + auto cond_tensor = task_context.GetInput(kIfCondIndex); + GE_CHECK_NOTNULL(cond_tensor); + int32_t cond_val = 0; + GE_CHK_STATUS_RET(CopyTensorValueToHost(*cond_tensor, cond_val), "[%s] Failed to get cond value.", + task_context.GetNodeName()); + + auto subgraph = SelectBranch(cond_val); + GELOGD("[%s] Taking subgraph [%s] by cond = [%d]", task_context.GetNodeName(), subgraph->GetName().c_str(), cond_val); + GE_CHK_STATUS_RET(ExecuteSubgraph(subgraph, task_context, done_callback), + "[%s] Failed to execute subgraph. cond = %d", task_context.GetNodeName(), cond_val); + + GELOGD("[%s] Done executing with cond = %d successfully.", task_context.GetNodeName(), cond_val); + return SUCCESS; +} + +Status CaseOpNodeTask::Init(const NodePtr &node, const HybridModel &model) { + size_t num_subgraphs = node->GetOpDesc()->GetSubgraphInstanceNames().size(); + GE_CHECK_LE(num_subgraphs, kMaxBranchNum); + GE_CHECK_GE(num_subgraphs, kMinBranchNum); + auto num_branches = static_cast(num_subgraphs); + GELOGD("[%s] Start to init CaseOpNodeTask with %u branches.", node->GetName().c_str(), num_branches); + + for (uint32_t i = 0; i < num_branches; ++i) { + auto sub_graph = NodeUtils::GetSubgraph(*node, i); + GE_CHECK_NOTNULL(sub_graph); + auto graph_item = model.GetSubgraphItem(sub_graph); + GE_CHECK_NOTNULL(graph_item); + GELOGD("[%s] Adding subgraph [%s] to branch %u.", node->GetName().c_str(), sub_graph->GetName().c_str(), i); + subgraphs_.emplace_back(graph_item); + } + + GELOGD("[%s] Done initialization successfully.", node->GetName().c_str()); + return SUCCESS; +} + +const GraphItem *CaseOpNodeTask::SelectBranch(int32_t branch_index) const { + // subgraphs_ is non-empty. checked int Init + if (branch_index < 0 || static_cast(branch_index) >= subgraphs_.size()) { + GELOGI("Branch index out of range. index = %d, num_subgraphs = %zu, will taking last branch.", branch_index, + subgraphs_.size()); + branch_index = subgraphs_.size() - 1; + } + + return subgraphs_[branch_index]; +} + +Status CaseOpNodeTask::DoExecuteAsync(TaskContext &task_context, const std::function &done_callback) const { + auto branch_tensor = task_context.GetInput(kCaseBranchIndex); + GE_CHECK_NOTNULL(branch_tensor); + int32_t branch_index = 0; + GE_CHK_STATUS_RET(CopyTensorValueToHost(*branch_tensor, branch_index), "[%s] Failed to get branch index.", + task_context.GetNodeName()); + + const GraphItem *subgraph = SelectBranch(branch_index); + GELOGI("[%s] Taking subgraph [%s] by branch = [%d]", task_context.GetNodeName(), subgraph->GetName().c_str(), + branch_index); + + std::vector inputs; + std::vector outputs; + for (int i = 0; i < task_context.NumInputs(); ++i) { + auto input_tensor = task_context.GetInput(i); + GE_CHECK_NOTNULL(input_tensor); + inputs.emplace_back(*input_tensor); + } + + GE_CHK_STATUS_RET(ExecuteSubgraph(subgraph, task_context, done_callback), "[%s] Failed to execute else-subgraph.", + task_context.GetNodeName()); + + GELOGD("[%s] Done executing subgraph[%d] successfully.", task_context.GetNodeName(), branch_index); + return SUCCESS; +} + +Status WhileOpNodeTask::Init(const NodePtr &node, const HybridModel &model) { + GELOGD("[%s] Start to init WhileOpNodeTask.", node->GetName().c_str()); + auto cond_subgraph = NodeUtils::GetSubgraph(*node, kCondBranchIndex); + GE_CHECK_NOTNULL(cond_subgraph); + GELOGD("[%s] Adding subgraph [%s] to cond-subgraph.", node->GetName().c_str(), cond_subgraph->GetName().c_str()); + cond_ = model.GetSubgraphItem(cond_subgraph); + GE_CHECK_NOTNULL(cond_); + + auto body_subgraph = NodeUtils::GetSubgraph(*node, kBodyBranchIndex); + GE_CHECK_NOTNULL(body_subgraph); + GELOGD("[%s] Adding subgraph [%s] to body-subgraph.", node->GetName().c_str(), body_subgraph->GetName().c_str()); + body_ = model.GetSubgraphItem(body_subgraph); + GE_CHECK_NOTNULL(body_); + + GELOGD("[%s] Done initialization successfully.", node->GetName().c_str()); + return SUCCESS; +} + +Status WhileOpNodeTask::DoExecuteAsync(TaskContext &task_context, const std::function &done_callback) const { + if (task_context.NumInputs() != task_context.NumOutputs()) { + GELOGE(INTERNAL_ERROR, "[%s] Invalid while args. num_inputs = %d, num_outputs = %d", task_context.GetNodeName(), + task_context.NumInputs(), task_context.NumOutputs()); + return INTERNAL_ERROR; + } + + // graph build can not set accurate flag unknown_shape_status by now. + // Treating all nodes in while scope as unknown shape. + task_context.SetForceInferShape(true); + + int iteration = 0; + while (true) { + bool is_continue = false; + GELOGD("[%s] Start to execute, iteration = %d", task_context.GetNodeName(), iteration); + GE_CHK_STATUS_RET(ExecuteOneLoop(task_context, is_continue), "[%s] Failed to execute iteration %d.", + task_context.GetNodeName(), iteration); + + if (!is_continue) { + GELOGD("[%s] Quit from loop. current iteration = %d", task_context.GetNodeName(), iteration); + break; + } + + ++iteration; + } + + return SUCCESS; +} + +Status WhileOpNodeTask::ExecuteCond(TaskContext &task_context, bool &is_continue) const { + std::vector inputs; + std::vector input_desc; + std::vector output_desc; + for (int i = 0; i < task_context.NumInputs(); ++i) { + auto input_tensor = task_context.GetInput(i); + GE_CHECK_NOTNULL(input_tensor); + inputs.emplace_back(*input_tensor); + input_desc.emplace_back(task_context.GetInputDesc(i)); + } + + auto execution_context = const_cast(task_context.GetExecutionContext()); + auto executor = MakeShared(cond_, execution_context, task_context.IsForceInferShape()); + GE_CHECK_NOTNULL(executor); + GELOGD("[%s] Start to execute cond-subgraph.", task_context.GetNodeName()); + GE_CHK_STATUS_RET(executor->ExecuteAsync(inputs, input_desc), "Failed to execute partitioned call."); + GELOGD("[%s] Done executing cond-subgraph successfully.", cond_->GetName().c_str()); + GE_CHK_STATUS_RET_NOLOG(task_context.RegisterCallback([executor]() mutable { executor.reset(); })); + + // get cond output + GE_CHK_STATUS_RET(executor->Synchronize(), "[%s] Failed to sync cond-subgraph result.", cond_->GetName().c_str()); + std::vector cond_outputs; + GE_CHK_STATUS_RET(executor->GetOutputs(cond_outputs), "[%s] Failed to get cond-output.", cond_->GetName().c_str()); + if (cond_outputs.empty()) { + GELOGE(INTERNAL_ERROR, "[%s] Cond output is empty.", task_context.GetNodeName()); + return INTERNAL_ERROR; + } + + int cond_val = 0; + GE_CHK_STATUS_RET(CopyTensorValueToHost(cond_outputs[0], cond_val), "[%s] Failed to get cond result.", + task_context.GetNodeName()); + is_continue = cond_val != 0; + return SUCCESS; +} + +Status WhileOpNodeTask::MoveOutputs2Inputs(TaskContext &task_context) { + // set outputs to inputs for next iteration + for (int i = 0; i < task_context.NumInputs(); ++i) { + auto input_tensor = task_context.MutableInput(i); + auto output_tensor = task_context.MutableOutput(i); + GE_CHECK_NOTNULL(input_tensor); + GE_CHECK_NOTNULL(output_tensor); + *input_tensor = *output_tensor; + output_tensor->Destroy(); + + auto output_tensor_desc = task_context.MutableOutputDesc(i); + GE_CHECK_NOTNULL(output_tensor_desc); + GELOGD("[%s] To update input shape[%d] by output shape. from [%s] to [%s]", task_context.GetNodeName(), i, + task_context.MutableInputDesc(i)->GetShape().ToString().c_str(), + output_tensor_desc->GetShape().ToString().c_str()); + *task_context.MutableInputDesc(i) = *output_tensor_desc; + } + + return SUCCESS; +} + +Status WhileOpNodeTask::ExecuteOneLoop(TaskContext &task_context, bool &is_continue) const { + GE_CHK_STATUS_RET(ExecuteCond(task_context, is_continue), "[%s] Failed to execute cond-subgraph", + task_context.GetNodeName()); + if (!is_continue) { + for (int i = 0; i < task_context.NumInputs(); ++i) { + auto input_tensor = task_context.GetInput(i); + GE_CHECK_NOTNULL(input_tensor); + task_context.SetOutput(i, *input_tensor); + } + return SUCCESS; + } + + GELOGD("[%s] Start to execute body-subgraph.", task_context.GetNodeName()); + GE_CHK_STATUS_RET(ExecuteSubgraph(body_, task_context, nullptr), "[%s] Failed to execute cond-subgraph", + task_context.GetNodeName()); + GELOGD("[%s] Done executing body-subgraph successfully.", task_context.GetNodeName()); + + // set outputs to inputs for next iteration + GE_CHK_STATUS_RET(MoveOutputs2Inputs(task_context), "[%s] Failed to move outputs to inputs", + task_context.GetNodeName()); + + return SUCCESS; +} + +Status ControlOpNodeExecutor::LoadTask(const HybridModel &model, const NodePtr &node, + shared_ptr &task) const { + auto node_item = model.GetNodeItem(node); + GE_CHECK_NOTNULL(node_item); + + unique_ptr node_task; + auto node_type = node->GetType(); + if (node_type == IF) { + node_task.reset(new (std::nothrow) IfOpNodeTask()); + } else if (node_type == CASE) { + node_task.reset(new (std::nothrow) CaseOpNodeTask()); + } else if (node_type == WHILE) { + node_task.reset(new (std::nothrow) WhileOpNodeTask()); + } else { + GELOGE(PARAM_INVALID, "[%s] Unsupported type: %s", node->GetName().c_str(), node_type.c_str()); + return PARAM_INVALID; + } + + GE_CHECK_NOTNULL(node_task); + GE_CHK_STATUS_RET(node_task->Init(node, model), "[%s] Failed to init ControlOpNodeTask.", node->GetName().c_str()); + + task = std::move(node_task); + return SUCCESS; +} + +Status ControlOpNodeExecutor::PrepareTask(NodeTask &task, TaskContext &context) const { return SUCCESS; } +} // namespace hybrid +} // namespace ge \ No newline at end of file diff --git a/src/ge/hybrid/node_executor/controlop/control_op_executor.h b/src/ge/hybrid/node_executor/controlop/control_op_executor.h new file mode 100644 index 00000000..0619c6a0 --- /dev/null +++ b/src/ge/hybrid/node_executor/controlop/control_op_executor.h @@ -0,0 +1,100 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_HYBRID_CONTROLOP_CONTROL_OP_EXECUTOR_H_ +#define GE_HYBRID_CONTROLOP_CONTROL_OP_EXECUTOR_H_ + +#include +#include "hybrid/node_executor/node_executor.h" +#include "hybrid/model/graph_item.h" + +namespace ge { +namespace hybrid { +class ControlOpNodeTask : public NodeTask { + public: + virtual Status Init(const NodePtr &node, const HybridModel &model) = 0; + Status UpdateArgs(TaskContext &context) override; + + Status ExecuteAsync(TaskContext &task_context, std::function done_callback) override; + + protected: + virtual Status DoExecuteAsync(TaskContext &task_context, const std::function &done_callback) const = 0; + static Status CopyTensorValueToHost(const TensorValue &tensor_value, int32_t &value); + static Status ExecuteSubgraph(const GraphItem *subgraph, TaskContext &task_context, + const std::function &done_callback); +}; + +class IfOpNodeTask : public ControlOpNodeTask { + public: + Status Init(const NodePtr &node, const HybridModel &model) override; + + protected: + const GraphItem *SelectBranch(int32_t cond) const; + Status DoExecuteAsync(TaskContext &task_context, const std::function &done_callback) const override; + + private: + static constexpr int kIfCondIndex = 0; + static constexpr int kThenBranchIndex = 0; + static constexpr int kElseBranchIndex = 1; + + const GraphItem *then_ = nullptr; + const GraphItem *else_ = nullptr; +}; + +class CaseOpNodeTask : public ControlOpNodeTask { + public: + Status Init(const NodePtr &node, const HybridModel &model) override; + + protected: + const GraphItem *SelectBranch(int32_t branch_index) const; + Status DoExecuteAsync(TaskContext &task_context, const std::function &done_callback) const override; + + private: + static constexpr int kCaseBranchIndex = 0; + static constexpr size_t kMaxBranchNum = INT32_MAX; + static constexpr size_t kMinBranchNum = 1; + + std::vector subgraphs_; +}; + +class WhileOpNodeTask : public ControlOpNodeTask { + public: + Status Init(const NodePtr &node, const HybridModel &model) override; + + protected: + Status DoExecuteAsync(TaskContext &task_context, const std::function &done_callback) const override; + Status ExecuteCond(TaskContext &task_context, bool &is_continue) const; + + static Status MoveOutputs2Inputs(TaskContext &task_context); + + Status ExecuteOneLoop(TaskContext &task_context, bool &is_continue) const; + + private: + static constexpr int kCondBranchIndex = 0; + static constexpr int kBodyBranchIndex = 1; + + const GraphItem *cond_ = nullptr; + const GraphItem *body_ = nullptr; +}; + +class ControlOpNodeExecutor : public NodeExecutor { + public: + Status LoadTask(const HybridModel &model, const NodePtr &node, shared_ptr &task) const override; + Status PrepareTask(NodeTask &task, TaskContext &context) const override; +}; +} // namespace hybrid +} // namespace ge +#endif // GE_HYBRID_CONTROLOP_CONTROL_OP_EXECUTOR_H_ diff --git a/src/ge/hybrid/node_executor/hccl/hccl_node_executor.cc b/src/ge/hybrid/node_executor/hccl/hccl_node_executor.cc new file mode 100644 index 00000000..e86c0cb0 --- /dev/null +++ b/src/ge/hybrid/node_executor/hccl/hccl_node_executor.cc @@ -0,0 +1,207 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hybrid/node_executor/hccl/hccl_node_executor.h" +#include "graph/manager/util/hcom_util.h" +#include "framework/common/debug/ge_log.h" +#include "framework/common/fmk_error_codes.h" +#include "common/ge/ge_util.h" +#include "common/ge/plugin_manager.h" +#include "graph/attr_value.h" +#include "graph/debug/ge_attr_define.h" +#include "hccl/hcom.h" + +namespace ge { +namespace hybrid { + +REGISTER_NODE_EXECUTOR_BUILDER(NodeExecutorManager::ExecutorType::HCCL, HcclNodeExecutor); + +Status HcclNodeTask::ExecuteAsync(TaskContext &context, std::function done_callback) { + GELOGI("[%s] HcclNodeTask::ExecuteAsync in.", context.GetNodeName()); + if (context.handle_ == nullptr) { + GELOGE(FAILED, "hccl handle is nullptr! "); + return FAILED; + } + auto EnqueueHcomOpertion = (hcclResult_t(*)(HcomOpertion, std::function))dlsym( + context.handle_, "EnqueueHcomOpertion"); + if (EnqueueHcomOpertion == nullptr) { + GELOGE(FAILED, "Failed to invoke EnqueueHcomOpertion hcom unknown node function."); + if (dlclose(context.handle_) != 0) { + GELOGW("Failed to close handle %s", dlerror()); + } + return FAILED; + } + + vector inputs; + for (int i = 0; i < context.NumInputs(); ++i) { + TensorValue *tv = context.MutableInput(i); + GE_CHECK_NOTNULL(tv); + inputs.emplace_back(tv->MutableData()); + } + + vector outputs; + for (int i = 0; i < context.NumOutputs(); ++i) { + TensorValue *tv = context.MutableOutput(i); + GE_CHECK_NOTNULL(tv); + outputs.emplace_back(tv->MutableData()); + } + + const NodeItem &node_item = context.GetNodeItem(); + const OpDescPtr op_desc = MakeShared(*(node_item.op_desc)); + GE_CHECK_NOTNULL(op_desc); + + HcomOpertion op_info; + op_info.hcclType = op_desc->GetType(); + op_info.inputPtr = inputs.empty() ? nullptr : inputs[0]; + op_info.outputPtr = outputs.empty() ? nullptr : outputs[0]; + ge::DataType src_data_type = op_desc->GetInputDescPtr(0)->GetDataType(); + auto iter = kConstOpHcclDataType.find(static_cast(src_data_type)); + if (iter == kConstOpHcclDataType.end()) { + GELOGE(PARAM_INVALID, "kConstOpHcclDataType find failed."); + return PARAM_INVALID; + } + op_info.dataType = iter->second; + hcclRedOp_t op_type = HCCL_REP_OP_SUM; + if (op_desc->GetType() == HCOMALLREDUCE || op_desc->GetType() == HCOMREDUCESCATTER || + op_desc->GetType() == HVDCALLBACKALLREDUCE) { + GE_CHK_STATUS_RET(HcomOmeUtil::GetHcclOperationType(op_desc, op_type), "GetHcclOperationType failed"); + op_info.opType = op_type; + } + int64_t root_id = 0; + if (op_desc->GetType() == HCOMBROADCAST) { + GE_CHK_STATUS_RET(HcomOmeUtil::GetHcclRootId(op_desc, root_id), "GetHcclRootId failed"); + } + op_info.root = root_id; + auto callback = [this](hcclResult_t status) { + if (status != HCCL_SUCCESS) { + GELOGE(HCCL_E_INTERNAL, "Call HcomExcutorInitialize failed, ret: 0x%X", status); + } + std::lock_guard lock(this->hccl_mutex_); + this->cond_.notify_all(); + GELOGI("hccl callback success."); + }; + int32_t count = 0; + GE_CHK_STATUS_RET(HcomOmeUtil::GetHcomCount(op_desc, static_cast(op_info.dataType), false, count), + "GetHcomCount failed"); + GELOGI("[%s] HcclNodeTask::ExecuteAsync hccl_type %s, count %d, data_type %d, op_type %d, root %d.", + context.GetNodeName(), op_info.hcclType.c_str(), count, op_info.dataType, op_info.opType, op_info.root); + op_info.count = count; + + hcclResult_t hccl_ret = EnqueueHcomOpertion(op_info, callback); + if (hccl_ret != HCCL_SUCCESS) { + GELOGE(HCCL_E_INTERNAL, "Call HcomExcutorInitialize failed, ret: 0x%X", hccl_ret); + return HCCL_E_INTERNAL; + } + + // pending until hccl finished + std::unique_lock ulock(hccl_mutex_); + cond_.wait(ulock); + + context.RegisterCallback(done_callback); + GELOGI("[%s] HcclNodeTask::ExecuteAsync success.", context.GetNodeName()); + return SUCCESS; +} + +Status HcclNodeTask::UpdateArgs(TaskContext &context) { return SUCCESS; } + +Status HcclNodeTask::Init(TaskContext &context) { + GELOGI("[%s] HcclNodeExecutor::Init success.", context.GetNodeName()); + return SUCCESS; +} + +Status HcclNodeExecutor::PrepareTask(NodeTask &task, TaskContext &context) const { + GELOGI("[%s] HcclNodeExecutor::PrepareTask in.", context.GetNodeName()); + + GE_CHK_STATUS_RET(task.Init(context), "hccl node load hccl so failed."); + // allocate output mem + GE_CHK_STATUS_RET(context.AllocateOutputs(), "hccl node task allocate output failed."); + + GE_CHK_STATUS_RET(task.UpdateArgs(context), "hccl node task update args failed."); + GELOGI("[%s] HcclNodeExecutor::PrepareTask success.", context.GetNodeName()); + return SUCCESS; +} + +Status HcclNodeExecutor::LoadTask(const HybridModel &model, const NodePtr &node, shared_ptr &task) const { + GELOGI("[%s] HcclNodeExecutor::LoadTask in.", node->GetName().c_str()); + GE_CHECK_NOTNULL(node); + + task = MakeShared(); + GE_CHECK_NOTNULL(task); + GELOGI("[%s] HcclNodeExecutor::LoadTask success.", node->GetName().c_str()); + return SUCCESS; +} + +Status HcclNodeExecutor::ExecuteTask(NodeTask &task, TaskContext &context, + const std::function &callback) const { + context.handle_ = handle_; + GE_CHK_STATUS_RET(task.ExecuteAsync(context, callback), "Failed to execute task. node = %s", + context.GetNodeItem().NodeName().c_str()); + return SUCCESS; +} + +Status HcclNodeExecutor::Initialize() { + std::string file_name = "libhccl.so"; + std::string path = PluginManager::GetPath(); + path.append(file_name); + string canonical_path = RealPath(path.c_str()); + if (canonical_path.empty()) { + GELOGW("failed to get realpath of %s", path.c_str()); + return FAILED; + } + + GELOGI("FileName:%s, Path:%s.", file_name.c_str(), canonical_path.c_str()); + handle_ = dlopen(canonical_path.c_str(), RTLD_NOW | RTLD_GLOBAL); + if (handle_ == nullptr) { + GELOGE(GE_PLGMGR_SO_NOT_EXIST, "Failed in dlopen %s! ", dlerror()); + return FAILED; + } + auto HcomExcutorInitialize = (hcclResult_t(*)())dlsym(handle_, "HcomExcutorInitialize"); + if (HcomExcutorInitialize == nullptr) { + GELOGE(FAILED, "Failed to invoke HcomExcutorInitialize hcom unknown node function."); + return FAILED; + } + hcclResult_t hccl_ret = HcomExcutorInitialize(); + if (hccl_ret == HCCL_E_PTR) { + GELOGI("Hccl comm is null, hcom executor initialize is not required."); + } else if (hccl_ret == HCCL_SUCCESS) { + GELOGI("Hcom executor initialize success."); + } else { + GELOGE(FAILED, "Call HcomExcutorInitialize failed, ret: 0x%X", hccl_ret); + return FAILED; + } + return SUCCESS; +} + +Status HcclNodeExecutor::Finalize() { + auto HcomExcutorFinalize = (hcclResult_t(*)())dlsym(handle_, "HcomExcutorFinalize"); + if (HcomExcutorFinalize == nullptr) { + GELOGE(FAILED, "Failed to invoke HcomExcutorFinalize hcom unknown node function."); + return FAILED; + } + hcclResult_t hccl_ret = HcomExcutorFinalize(); + if (hccl_ret != HCCL_SUCCESS) { + GELOGE(FAILED, "Call HcomExcutorFinalize failed, ret: 0x%X", hccl_ret); + return FAILED; + } + // dlclose file handle + if (dlclose(handle_) != 0) { + GELOGW("Failed to close handle %s", dlerror()); + } + GELOGI("Hcom executor finalize success."); + return SUCCESS; +} +} // namespace hybrid +} // namespace ge diff --git a/src/ge/hybrid/node_executor/hccl/hccl_node_executor.h b/src/ge/hybrid/node_executor/hccl/hccl_node_executor.h new file mode 100644 index 00000000..8791c4e3 --- /dev/null +++ b/src/ge/hybrid/node_executor/hccl/hccl_node_executor.h @@ -0,0 +1,59 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HYBRID_HCCL_NODE_EXECUTOR_H_ +#define HYBRID_HCCL_NODE_EXECUTOR_H_ +#include "hybrid/node_executor/node_executor.h" +#include "hybrid/model/hybrid_model.h" +#include "graph/op_desc.h" + +namespace ge { +namespace hybrid { +class HybridModel; + +class HcclNodeTask : public NodeTask { + public: + HcclNodeTask() {} + + ~HcclNodeTask() {} + + Status UpdateArgs(TaskContext &context) override; + Status ExecuteAsync(TaskContext &context, std::function done_callback) override; + Status Init(TaskContext &context) override; + + private: + std::shared_ptr davinci_model_ = nullptr; + bool load_flag_ = false; + std::mutex hccl_mutex_; + std::condition_variable cond_; +}; + +class HcclNodeExecutor : public NodeExecutor { + public: + Status LoadTask(const HybridModel &model, const NodePtr &node, shared_ptr &task) const; + Status PrepareTask(NodeTask &task, TaskContext &context) const; + Status ExecuteTask(NodeTask &task, TaskContext &context, const std::function &callback) const; + Status Initialize() override; + Status Finalize() override; + ~HcclNodeExecutor() {} + + private: + void *handle_; +}; +} // namespace hybrid +} // namespace ge + +#endif // HYBRID_HCCL_NODE_EXECUTOR_H_ diff --git a/src/ge/hybrid/node_executor/hostcpu/ge_local_node_executor.cc b/src/ge/hybrid/node_executor/hostcpu/ge_local_node_executor.cc index c3bc9a41..d353dff1 100644 --- a/src/ge/hybrid/node_executor/hostcpu/ge_local_node_executor.cc +++ b/src/ge/hybrid/node_executor/hostcpu/ge_local_node_executor.cc @@ -17,14 +17,12 @@ #include "hybrid/node_executor/hostcpu/ge_local_node_executor.h" #include "graph/debug/ge_attr_define.h" #include "framework/common/util.h" -#include "framework/common/types.h" +#include "hybrid/model/hybrid_model.h" #include "inc/kernel.h" #include "inc/kernel_factory.h" -#include "common/ge/ge_util.h" namespace ge { namespace hybrid { - REGISTER_NODE_EXECUTOR_BUILDER(NodeExecutorManager::ExecutorType::GE_LOCAL, GeLocalNodeExecutor); const std::unordered_map> RefInputTask::out_ref_input_index_ = { @@ -132,7 +130,7 @@ Status DependInputShapeTask::Execute(TaskContext &context) { } // alloc output - GE_CHK_STATUS_RET_NOLOG(context.AllocateOutputs()); + GE_CHK_STATUS_RET_NOLOG(context.AllocateOutputs(NpuMemoryAllocator::AttrWithDefaultPadding())); // copy data to output for (auto i = 0; i < output_num; ++i) { @@ -194,6 +192,16 @@ Status GeLocalNodeExecutor::LoadTask(const HybridModel &model, const NodePtr &no node_type.c_str()); return MEMALLOC_FAILED; } + } else if (node_type == CONSTANTOP || node_type == VARIABLE) { + GELOGI("node %s type %s, use ConstantNodeTask.", node->GetName().c_str(), node_type.c_str()); + auto tensor = model.GetVariable(node->GetName()); + if (tensor == nullptr) { + GELOGE(INTERNAL_ERROR, "Failed to get tensor by name: %s", node->GetName().c_str()); + return INTERNAL_ERROR; + } + + task = MakeShared(tensor); + GE_CHECK_NOTNULL(task); } else { GELOGE(UNSUPPORTED, "node %s type %s is not support in GeLocalNodeExecutor now.", node->GetName().c_str(), node_type.c_str()); @@ -202,5 +210,20 @@ Status GeLocalNodeExecutor::LoadTask(const HybridModel &model, const NodePtr &no return SUCCESS; } +ConstantNodeTask::ConstantNodeTask(const TensorValue *tensor) : tensor_(tensor) {} + +Status ConstantNodeTask::UpdateArgs(TaskContext &context) { return SUCCESS; } + +Status ConstantNodeTask::ExecuteAsync(TaskContext &context, std::function done_callback) { + GELOGD("[%s] Start execute.", context.GetNodeName()); + GE_CHK_STATUS_RET(context.SetOutput(0, *tensor_), "[%s] Failed to set output.", context.GetNodeName()); + if (done_callback) { + GELOGD("[%s] Start invoke callback.", context.GetNodeName()); + done_callback(); + } + + GELOGD("[%s] Done execute successfully.", context.GetNodeName()); + return SUCCESS; +} } // namespace hybrid } // namespace ge \ No newline at end of file diff --git a/src/ge/hybrid/node_executor/hostcpu/ge_local_node_executor.h b/src/ge/hybrid/node_executor/hostcpu/ge_local_node_executor.h index beb1f50d..0195e76c 100644 --- a/src/ge/hybrid/node_executor/hostcpu/ge_local_node_executor.h +++ b/src/ge/hybrid/node_executor/hostcpu/ge_local_node_executor.h @@ -23,7 +23,6 @@ namespace ge { namespace hybrid { - class RefInputTask : public NodeTask { public: explicit RefInputTask(const NodePtr &node) : node_name_(node->GetName()), node_type_(node->GetType()) {} @@ -68,6 +67,18 @@ class DependInputShapeTask : public NodeTask { static const std::unordered_set depend_input_shape_ops_; }; +class ConstantNodeTask : public NodeTask { + public: + explicit ConstantNodeTask(const TensorValue *tensor); + ~ConstantNodeTask() = default; + Status UpdateArgs(TaskContext &context) override; + + Status ExecuteAsync(TaskContext &context, std::function done_callback) override; + + private: + const TensorValue *tensor_; +}; + class GeLocalNodeExecutor : public NodeExecutor { public: Status PrepareTask(NodeTask &task, TaskContext &context) const override; diff --git a/src/ge/hybrid/node_executor/node_executor.cc b/src/ge/hybrid/node_executor/node_executor.cc index f3b86948..016ec6ef 100644 --- a/src/ge/hybrid/node_executor/node_executor.cc +++ b/src/ge/hybrid/node_executor/node_executor.cc @@ -16,6 +16,7 @@ #include "hybrid/node_executor/node_executor.h" #include "framework/common/debug/log.h" +#include "graph/utils/node_utils.h" #include "init/gelib.h" #include "hybrid/model/hybrid_model.h" @@ -25,9 +26,11 @@ namespace { const char *const kEngineNameAiCore = "AIcoreEngine"; const char *const kEngineNameGeLocal = "DNN_VM_GE_LOCAL_OP_STORE"; const char *const kEngineNameAiCpu = "aicpu_kernel"; +const char *const kEngineNameHccl = "ops_kernel_info_hccl"; } // namespace Status NodeExecutor::PrepareTask(NodeTask &task, TaskContext &context) const { GE_CHK_STATUS_RET_NOLOG(context.AllocateOutputs()); + GE_CHK_STATUS_RET_NOLOG(task.UpdateTilingData(context)); // update op_desc before alloc ws GE_CHK_STATUS_RET_NOLOG(context.AllocateWorkspaces()); GE_CHK_STATUS_RET_NOLOG(task.UpdateArgs(context)); return SUCCESS; @@ -48,6 +51,7 @@ Status NodeExecutor::CompileTask(const HybridModel &model, const NodePtr &node, } Status NodeExecutorManager::EnsureInitialized() { + GE_CHK_STATUS_RET(InitializeExecutors()); std::lock_guard lk(mu_); if (initialized_) { return SUCCESS; @@ -56,6 +60,7 @@ Status NodeExecutorManager::EnsureInitialized() { engine_mapping_.emplace(kEngineNameAiCore, NodeExecutorManager::ExecutorType::AICORE); engine_mapping_.emplace(kEngineNameGeLocal, NodeExecutorManager::ExecutorType::GE_LOCAL); engine_mapping_.emplace(kEngineNameAiCpu, NodeExecutorManager::ExecutorType::AICPU_TF); + engine_mapping_.emplace(kEngineNameHccl, NodeExecutorManager::ExecutorType::HCCL); std::shared_ptr instance_ptr = GELib::GetInstance(); if ((instance_ptr == nullptr) || (!instance_ptr->InitFlag())) { @@ -69,22 +74,6 @@ Status NodeExecutorManager::EnsureInitialized() { kernel_stores_.emplace(it.first, it.second); } - GELOGI("Start to Initialize NodeExecutors"); - for (auto &it : builders_) { - auto engine_type = it.first; - auto build_fn = it.second; - GE_CHECK_NOTNULL(build_fn); - auto executor = std::unique_ptr(build_fn()); - if (executor == nullptr) { - GELOGE(INTERNAL_ERROR, "Failed to create executor for engine type = %d", engine_type); - return INTERNAL_ERROR; - } - - GELOGD("Executor of engine type = %d was created successfully", engine_type); - GE_CHK_STATUS_RET(executor->Initialize(), "Failed to initialize NodeExecutor of type = %d", engine_type); - executors_.emplace(engine_type, std::move(executor)); - } - initialized_ = true; GELOGI("Initializing NodeExecutors successfully"); return SUCCESS; @@ -93,6 +82,11 @@ Status NodeExecutorManager::EnsureInitialized() { NodeExecutorManager::ExecutorType NodeExecutorManager::ResolveExecutorType(Node &node) const { auto op_type = node.GetType(); if (op_type == PARTITIONEDCALL) { + bool is_dynamic = false; + (void)NodeUtils::GetNodeUnknownShapeStatus(node, is_dynamic); + if (is_dynamic) { + return ExecutorType::DYNAMIC_SUBGRAPH; + } return ExecutorType::COMPILED_SUBGRAPH; } @@ -101,6 +95,10 @@ NodeExecutorManager::ExecutorType NodeExecutorManager::ResolveExecutorType(Node return ExecutorType::GE_LOCAL; } + if (op_type == IF || op_type == CASE || op_type == WHILE) { + return ExecutorType::CONTROL_OP; + } + auto op_desc = node.GetOpDesc(); // checked before const auto &lib_name = op_desc->GetOpKernelLibName(); auto it = engine_mapping_.find(lib_name); @@ -116,10 +114,11 @@ Status NodeExecutorManager::GetExecutor(Node &node, const NodeExecutor **executo auto executor_type = ResolveExecutorType(node); const auto it = executors_.find(executor_type); if (it == executors_.end()) { - GELOGE(INTERNAL_ERROR, "Failed to get executor by type: %d", executor_type); + GELOGE(INTERNAL_ERROR, "Failed to get executor by type: %d.", executor_type); return INTERNAL_ERROR; } + GELOGD("[%s] Set node executor by type: %d.", node.GetName().c_str(), executor_type); *executor = it->second.get(); return SUCCESS; } @@ -132,6 +131,11 @@ void NodeExecutorManager::RegisterExecutorBuilder(NodeExecutorManager::ExecutorT Status NodeExecutorManager::CalcOpRunningParam(Node &node) const { auto op_desc = node.GetOpDesc(); GE_CHECK_NOTNULL(op_desc); + if (op_desc->GetType() == PARTITIONEDCALL) { + GELOGD("[%s] Skipping CalcOpRunningParam for PartitionedCall.", node.GetName().c_str()); + return SUCCESS; + } + auto it = kernel_stores_.find(op_desc->GetOpKernelLibName()); if (it == kernel_stores_.end()) { GELOGE(INTERNAL_ERROR, "Failed to get OpKernelStore. libName = %s, node = %s", @@ -139,9 +143,91 @@ Status NodeExecutorManager::CalcOpRunningParam(Node &node) const { return INTERNAL_ERROR; } + // calc hccl output size independent, hccl ops kernel manager should GetSize for + // input which is the output size of input-op, but sometimes return error + // when multi-thread + if (op_desc->GetOpKernelLibName() == kEngineNameHccl) { + for (size_t i = 0; i < op_desc->GetOutputsSize(); ++i) { + GeTensorDesc output_tensor = op_desc->GetOutputDesc(static_cast(i)); + Format format = output_tensor.GetFormat(); + DataType data_type = output_tensor.GetDataType(); + GeShape output_shape = output_tensor.GetShape(); + int64_t output_mem_size = 0; + GE_CHK_STATUS_RET(TensorUtils::CalcTensorMemSize(output_shape, format, data_type, output_mem_size), + "hccl calc tensor mem size failed."); + output_mem_size = + ((output_mem_size + MEMORY_ALIGN_RATIO * MEMORY_ALIGN_SIZE - 1) / MEMORY_ALIGN_SIZE) * MEMORY_ALIGN_SIZE; + TensorUtils::SetSize(output_tensor, output_mem_size); + GE_CHK_STATUS_RET(op_desc->UpdateOutputDesc(static_cast(i), output_tensor), + "hccl update output size failed."); + GELOGD("%s output desc[%u], dim_size: %zu, mem_size: %ld.", node.GetName().c_str(), i, + output_tensor.GetShape().GetDimNum(), output_mem_size); + } + return SUCCESS; + } return it->second->CalcOpRunningParam(node); } +Status NodeExecutorManager::InitializeExecutors() { + std::lock_guard lk(mu_); + if (executor_initialized_) { + ++ref_count_; + GELOGI("Executor is already initialized. add ref count to [%d]", ref_count_); + return SUCCESS; + } + + GELOGI("Start to Initialize NodeExecutors"); + for (auto &it : builders_) { + auto engine_type = it.first; + auto build_fn = it.second; + GE_CHECK_NOTNULL(build_fn); + auto executor = std::unique_ptr(build_fn()); + if (executor == nullptr) { + GELOGE(INTERNAL_ERROR, "Failed to create executor for engine type = %d", engine_type); + return INTERNAL_ERROR; + } + + GELOGD("Executor of engine type = %d was created successfully", engine_type); + auto ret = executor->Initialize(); + if (ret != SUCCESS) { + GELOGE(ret, "Failed to initialize NodeExecutor of type = %d, clear executors", engine_type); + for (auto &executor_it : executors_) { + executor_it.second->Finalize(); + } + executors_.clear(); + return ret; + } + + executors_.emplace(engine_type, std::move(executor)); + } + + ++ref_count_; + executor_initialized_ = true; + GELOGI("Initializing NodeExecutors successfully."); + return SUCCESS; +} + +void NodeExecutorManager::FinalizeExecutors() { + std::lock_guard lk(mu_); + if (!executor_initialized_) { + GELOGD("No need for finalizing for not initialized."); + return; + } + + if (--ref_count_ > 0) { + GELOGD("Ref count = %d, do not finalize executors.", ref_count_); + return; + } + + GELOGD("Start to invoke Finalize on executors."); + for (auto &it : executors_) { + it.second->Finalize(); + } + executors_.clear(); + executor_initialized_ = false; + GELOGD("Done invoking Finalize successfully."); +} + NodeExecutorRegistrar::NodeExecutorRegistrar(NodeExecutorManager::ExecutorType executor_type, NodeExecutor *(*builder)()) { NodeExecutorManager::GetInstance().RegisterExecutorBuilder(executor_type, builder); diff --git a/src/ge/hybrid/node_executor/node_executor.h b/src/ge/hybrid/node_executor/node_executor.h index 613c0bb1..cc456fa3 100644 --- a/src/ge/hybrid/node_executor/node_executor.h +++ b/src/ge/hybrid/node_executor/node_executor.h @@ -14,70 +14,182 @@ * limitations under the License. */ -#ifndef GE_HYBRID_KERNEL_NODE_EXECUTOR_H_ -#define GE_HYBRID_KERNEL_NODE_EXECUTOR_H_ +#ifndef GE_HYBRID_NODE_EXECUTOR_NODE_EXECUTOR_H_ +#define GE_HYBRID_NODE_EXECUTOR_NODE_EXECUTOR_H_ #include "external/ge/ge_api_error_codes.h" #include "common/opskernel/ops_kernel_info_store.h" #include "graph/node.h" -#include "proto/task.pb.h" #include "task_context.h" namespace ge { +const uint32_t MEMORY_ALIGN_RATIO = 2; +const uint32_t MEMORY_ALIGN_SIZE = 32; namespace hybrid { class HybridModel; - +// Base class of Node Task class NodeTask { public: NodeTask() = default; virtual ~NodeTask() = default; + + /** + * Update tiling data + * @param context instance of TaskContext + * @return SUCCESS on success, error code otherwise + */ + virtual Status UpdateTilingData(TaskContext &context) { return SUCCESS; } + + /** + * Init + * @param context instance of TaskContext + * @return SUCCESS on success, error code otherwise + */ + virtual Status Init(TaskContext &context) { return SUCCESS; } + + /** + * Whether this task supports dynamic shape + * @return true if this task supports dynamic shape, false otherwise + */ + virtual bool IsSupportDynamicShape() { return true; } + + /** + * Update args for execution + * @param context instance of TaskContext + * @return SUCCESS on success, error code otherwise + */ virtual Status UpdateArgs(TaskContext &context) = 0; + + /** + * Execute task async + * @param context instance of TaskContext + * @param done_callback callback function, will be invoked after task is done + * @return SUCCESS on success, error code otherwise + */ virtual Status ExecuteAsync(TaskContext &context, std::function done_callback) = 0; - virtual Status Init(TaskContext &context) { return SUCCESS; } }; +// Node executor class NodeExecutor { public: NodeExecutor() = default; virtual ~NodeExecutor() = default; + /** + * Initialize node executor + * @return SUCCESS on success, error code otherwise + */ virtual Status Initialize() { return SUCCESS; } + /** + * Finalize node executor + * @return SUCCESS on success, error code otherwise + */ virtual Status Finalize() { return SUCCESS; } + /** + * Load task in load stage + * @param model instance of HybridModel + * @param node node + * @param task generated node task + * @return SUCCESS on success, error code otherwise + */ virtual Status LoadTask(const HybridModel &model, const NodePtr &node, std::shared_ptr &task) const; + /** + * Compile task in run stage + * @param model instance of HybridModel + * @param node node + * @param task generated node task + * @return SUCCESS on success, error code otherwise + */ virtual Status CompileTask(const HybridModel &model, const NodePtr &node, std::shared_ptr &task) const; + /** + * Preparation actions before execution + * @param task instance of NodeTask + * @param context instance of TaskContext + * @return SUCCESS on success, error code otherwise + */ virtual Status PrepareTask(NodeTask &task, TaskContext &context) const; + + /** + * Execute task + * @param task instance of NodeTask + * @param context instance of TaskContext + * @param callback callback function which will be invoked after computation is done + * @return SUCCESS on success, error code otherwise + */ virtual Status ExecuteTask(NodeTask &task, TaskContext &context, const std::function &callback) const; }; class NodeExecutorManager { public: - enum class ExecutorType { AICORE, GE_LOCAL, AICPU_TF, AICPU_CUSTOM, COMPILED_SUBGRAPH, HCCL, RESERVED }; + enum class ExecutorType { + AICORE, + AICPU_TF, + AICPU_CUSTOM, + COMPILED_SUBGRAPH, + DYNAMIC_SUBGRAPH, + GE_LOCAL, + CONTROL_OP, + HCCL, + RESERVED + }; static NodeExecutorManager &GetInstance() { static NodeExecutorManager instance; return instance; } - Status CalcOpRunningParam(Node &node) const; - + /** + * Register build of executor + * @param executor_type type of executor + * @param builder build function + */ void RegisterExecutorBuilder(ExecutorType executor_type, const std::function &builder); + /** + * Initialize executor if needed + * @return SUCCESS on success, error code otherwise + */ Status EnsureInitialized(); + Status InitializeExecutors(); + + void FinalizeExecutors(); + + /** + * CalcOpRunningParam + * @param node node + * @return SUCCESS on success, error code otherwise + */ + Status CalcOpRunningParam(Node &node) const; + + /** + * Get executor by node + * @param node node + * @param executor executor + * @return SUCCESS on success, error code otherwise + */ Status GetExecutor(Node &node, const NodeExecutor **executor) const; + /** + * Resolve executor type by node + * @param node node + * @return executor type + */ ExecutorType ResolveExecutorType(Node &node) const; + private: std::map> executors_; std::map> builders_; std::map> kernel_stores_; std::map engine_mapping_; std::mutex mu_; bool initialized_ = false; + bool executor_initialized_ = false; + int ref_count_ = 0; }; class NodeExecutorRegistrar { @@ -99,4 +211,4 @@ class NodeExecutorRegistrar { ::ge::hybrid::NodeExecutorRegistrar( \ engine_type, []() -> ::ge::hybrid::NodeExecutor * { return new (std::nothrow) executor(); }) -#endif // GE_HYBRID_KERNEL_NODE_EXECUTOR_H_ +#endif // GE_HYBRID_NODE_EXECUTOR_NODE_EXECUTOR_H_ diff --git a/src/ge/hybrid/node_executor/partitioned_call/partitioned_call_node_executor.cc b/src/ge/hybrid/node_executor/partitioned_call/partitioned_call_node_executor.cc new file mode 100644 index 00000000..cda9a275 --- /dev/null +++ b/src/ge/hybrid/node_executor/partitioned_call/partitioned_call_node_executor.cc @@ -0,0 +1,81 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "partitioned_call_node_executor.h" +#include "graph/utils/node_utils.h" + +namespace ge { +namespace hybrid { +REGISTER_NODE_EXECUTOR_BUILDER(NodeExecutorManager::ExecutorType::DYNAMIC_SUBGRAPH, PartitionedCallNodeExecutor); + +PartitionedCallNodeTask::PartitionedCallNodeTask(const GraphItem *graph_item) : graph_item_(graph_item) {} + +PartitionedCallNodeTask::~PartitionedCallNodeTask() { + GELOGD("[%s] PartitionedCallNodeTask destroyed.", graph_item_->GetName().c_str()); +} + +Status PartitionedCallNodeTask::Init(TaskContext &context) { + auto execution_context = const_cast(context.GetExecutionContext()); + subgraph_executor_.reset(new (std::nothrow) SubgraphExecutor(graph_item_, execution_context)); + GE_CHECK_NOTNULL(subgraph_executor_); + return SUCCESS; +} + +Status PartitionedCallNodeTask::ExecuteAsync(TaskContext &context, std::function done_callback) { + GE_CHK_STATUS_RET(subgraph_executor_->ExecuteAsync(context), "[%s] Failed to set inputs", + graph_item_->GetName().c_str()); + + auto callback = [=]() { Callback(done_callback); }; + + GE_CHK_STATUS_RET(context.RegisterCallback(callback), "[%s] Failed to register callback", + graph_item_->GetName().c_str()); + GELOGD("[%s] Done executing subgraph successfully.", graph_item_->GetName().c_str()); + return SUCCESS; +} + +Status PartitionedCallNodeTask::Callback(const std::function &done_callback) { + GELOGD("[%s] On subgraph callback", graph_item_->GetName().c_str()); + if (done_callback != nullptr) { + done_callback(); + } + + GELOGD("[%s] To release sub graph tensors.", graph_item_->GetName().c_str()); + subgraph_executor_.reset(); + GELOGD("[%s] Done releasing sub graph tensors.", graph_item_->GetName().c_str()); + return SUCCESS; +} + +Status PartitionedCallNodeTask::UpdateArgs(TaskContext &context) { return SUCCESS; } + +Status PartitionedCallNodeExecutor::LoadTask(const ge::hybrid::HybridModel &model, const ge::NodePtr &node, + std::shared_ptr &task) const { + GELOGD("Load dynamic partitioned call: [%s]", node->GetName().c_str()); + auto subgraph = NodeUtils::GetSubgraph(*node, 0); + GE_CHECK_NOTNULL(subgraph); + auto partitioned_call = model.GetSubgraphItem(subgraph); + GE_CHECK_NOTNULL(partitioned_call); + task.reset(new (std::nothrow) PartitionedCallNodeTask(partitioned_call)); + GE_CHECK_NOTNULL(task); + GELOGD("Done loading dynamic partitioned call: [%s]", node->GetName().c_str()); + return SUCCESS; +} + +Status PartitionedCallNodeExecutor::PrepareTask(NodeTask &task, TaskContext &context) const { + GE_CHK_STATUS_RET(task.Init(context), "[%s] Failed to init task.", context.GetNodeName()); + return SUCCESS; +} +} // namespace hybrid +} // namespace ge diff --git a/src/ge/hybrid/node_executor/partitioned_call/partitioned_call_node_executor.h b/src/ge/hybrid/node_executor/partitioned_call/partitioned_call_node_executor.h new file mode 100644 index 00000000..fd87d6c1 --- /dev/null +++ b/src/ge/hybrid/node_executor/partitioned_call/partitioned_call_node_executor.h @@ -0,0 +1,54 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_HYBRID_NODE_EXECUTOR_SUBGRAPH_SUBGRAPH_EXECUTOR_H_ +#define GE_HYBRID_NODE_EXECUTOR_SUBGRAPH_SUBGRAPH_EXECUTOR_H_ + +#include "hybrid/node_executor/node_executor.h" +#include "hybrid/model/hybrid_model.h" +#include "hybrid/executor/node_state.h" +#include "hybrid/executor/subgraph_executor.h" +#include "common/thread_pool.h" + +namespace ge { +namespace hybrid { +class PartitionedCallNodeTask : public NodeTask { + public: + explicit PartitionedCallNodeTask(const GraphItem *graph_item); + ~PartitionedCallNodeTask() override; + + Status Init(TaskContext &context) override; + + Status UpdateArgs(TaskContext &context) override; + + Status ExecuteAsync(TaskContext &context, std::function done_callback) override; + + private: + Status Callback(const std::function &done_callback); + + const GraphItem *graph_item_; + std::unique_ptr subgraph_executor_; + GraphExecutionContext *context_ = nullptr; +}; + +class PartitionedCallNodeExecutor : public NodeExecutor { + public: + Status LoadTask(const HybridModel &model, const NodePtr &node, shared_ptr &task) const override; + Status PrepareTask(NodeTask &task, TaskContext &context) const override; +}; +} // namespace hybrid +} // namespace ge +#endif // GE_HYBRID_NODE_EXECUTOR_SUBGRAPH_SUBGRAPH_EXECUTOR_H_ diff --git a/src/ge/hybrid/node_executor/task_context.cc b/src/ge/hybrid/node_executor/task_context.cc index 42c653be..ee35bffa 100644 --- a/src/ge/hybrid/node_executor/task_context.cc +++ b/src/ge/hybrid/node_executor/task_context.cc @@ -19,12 +19,16 @@ #include "framework/common/debug/log.h" #include "graph/utils/tensor_utils.h" #include "hybrid/executor/hybrid_execution_context.h" +#include "hybrid/executor/subgraph_executor.h" namespace ge { namespace hybrid { -TaskContext::TaskContext(GraphExecutionContext *execution_context) : execution_context_(execution_context) {} +TaskContext::TaskContext(GraphExecutionContext *execution_context, const NodeItem *node_item, + SubgraphContext *subgraph_context) + : node_item_(node_item), execution_context_(execution_context), subgraph_context_(subgraph_context) {} + TaskContext::~TaskContext() { - GELOGD("To execute ~TaskContext(). node = %s", node_item_->NodeName().c_str()); + GELOGD("[%s] TaskContext destroyed.", node_item_->NodeName().c_str()); for (auto ws_addr : workspaces_) { execution_context_->allocator->Deallocate(ws_addr); } @@ -38,19 +42,28 @@ TaskContext::~TaskContext() { } } -std::unique_ptr TaskContext::Create(const NodeItem &node_item, GraphExecutionContext *graph_context) { - GELOGI("To create task context for node %s, input start = %d, num_inputs = %d, output start = %d, num_outputs = %d", +std::unique_ptr TaskContext::Create(const NodeItem &node_item, GraphExecutionContext *execution_context, + SubgraphContext *subgraph_context) { + GELOGI("[%s] To create task context, input start = %d, num_inputs = %d, output start = %d, num_outputs = %d.", node_item.NodeName().c_str(), node_item.input_start, node_item.num_inputs, node_item.output_start, node_item.num_outputs); - auto task_context = std::unique_ptr(new (std::nothrow) TaskContext(graph_context)); + if (node_item.input_start < 0 || node_item.output_start < 0) { + GELOGE(INTERNAL_ERROR, "NodeItem not property initialized. input_start = %d, output_start = %d", + node_item.input_start, node_item.output_start); + return nullptr; + } + + auto task_context = + std::unique_ptr(new (std::nothrow) TaskContext(execution_context, &node_item, subgraph_context)); if (task_context == nullptr) { - GELOGE(MEMALLOC_FAILED, "Failed to create instance of TaskContext. node = %s", node_item.NodeName().c_str()); + GELOGE(MEMALLOC_FAILED, "[%s] Failed to create instance of TaskContext.", node_item.NodeName().c_str()); return nullptr; } task_context->node_item_ = &node_item; - task_context->inputs_start_ = graph_context->all_inputs.data() + node_item.input_start; - task_context->outputs_start_ = graph_context->all_outputs.data() + node_item.output_start; + task_context->inputs_start_ = subgraph_context->all_inputs_.data() + node_item.input_start; + task_context->outputs_start_ = subgraph_context->all_outputs_.data() + node_item.output_start; + task_context->iteration_ = execution_context->iteration; return task_context; } @@ -59,7 +72,7 @@ int TaskContext::NumInputs() const { return node_item_->num_inputs; } int TaskContext::NumOutputs() const { return node_item_->num_outputs; } TensorValue *TaskContext::MutableInput(int index) { - if (index < 0 || index > node_item_->num_inputs) { + if (index < 0 || index >= node_item_->num_inputs) { GELOGE(PARAM_INVALID, "Index out of range. index = %d, num_inputs = %d", index, node_item_->num_inputs); return nullptr; } @@ -68,7 +81,7 @@ TensorValue *TaskContext::MutableInput(int index) { } const TensorValue *TaskContext::GetOutput(int index) const { - if (index < 0 || index > node_item_->num_outputs) { + if (index < 0 || index >= node_item_->num_outputs) { GELOGE(PARAM_INVALID, "Index out of range. index = %d, num_outputs = %d", index, node_item_->num_outputs); return nullptr; } @@ -77,7 +90,7 @@ const TensorValue *TaskContext::GetOutput(int index) const { } TensorValue *TaskContext::MutableOutput(int index) { - if (index < 0 || index > node_item_->num_outputs) { + if (index < 0 || index >= node_item_->num_outputs) { GELOGE(PARAM_INVALID, "Index out of range. index = %d, num_outputs = %d", index, node_item_->num_outputs); return nullptr; } @@ -97,7 +110,7 @@ void *TaskContext::MutableWorkspace(int index) { } const TensorValue *TaskContext::GetInput(int index) const { - if (index < 0 || index > node_item_->num_inputs) { + if (index < 0 || index >= node_item_->num_inputs) { GELOGE(PARAM_INVALID, "Index out of range. index = %d, num_inputs = %d", index, node_item_->num_inputs); return nullptr; } @@ -120,7 +133,14 @@ Status TaskContext::AllocateWorkspaces() { } Status TaskContext::RegisterCallback(const std::function &callback_fun) const { - return execution_context_->callback_manager->RegisterCallback(callback_fun); + auto ret = execution_context_->callback_manager->RegisterCallback(callback_fun); + if (ret != SUCCESS) { + GELOGE(ret, "[%s] Failed to register callback", GetNodeName()); + execution_context_->callback_manager->Destroy(); + return ret; + } + + return SUCCESS; } string TaskContext::TensorDesc2String(const GeTensorDesc &desc) { @@ -137,7 +157,7 @@ string TaskContext::TensorDesc2String(const GeTensorDesc &desc) { return ss.str(); } -Status TaskContext::AllocateTensor(const GeTensorDesc &tensor_desc, TensorValue &tensor) { +Status TaskContext::AllocateTensor(const GeTensorDesc &tensor_desc, TensorValue &tensor, AllocationAttr *attr) { int64_t size = 0; if (ge::TensorUtils::GetSize(tensor_desc, size) != GRAPH_SUCCESS) { GELOGE(INTERNAL_ERROR, "Failed to get tensor size"); @@ -148,13 +168,14 @@ Status TaskContext::AllocateTensor(const GeTensorDesc &tensor_desc, TensorValue GELOGW("size from tensor_desc == 0"); } - auto buffer = TensorBuffer::Create(execution_context_->allocator, size); + auto buffer = TensorBuffer::Create(execution_context_->allocator, size, attr); GE_CHECK_NOTNULL(buffer); tensor = TensorValue(shared_ptr(buffer.release())); return SUCCESS; } -Status TaskContext::AllocateOutput(int index, const GeTensorDesc &tensor_desc, TensorValue **tensor) { +Status TaskContext::AllocateOutput(int index, const GeTensorDesc &tensor_desc, TensorValue **tensor, + AllocationAttr *attr) { GELOGI("To allocate output for node: %s. index = %d, tensor desc = %s", node_item_->NodeName().c_str(), index, TensorDesc2String(tensor_desc).c_str()); @@ -178,9 +199,29 @@ Status TaskContext::AllocateOutput(int index, const GeTensorDesc &tensor_desc, T GE_CHECK_NOTNULL(ref_tensor); outputs_start_[index] = *ref_tensor; } else { - GE_CHK_STATUS_RET_NOLOG(AllocateTensor(tensor_desc, outputs_start_[index])); - GELOGD("Allocating output successfully. node: %s. index = %d, size = %zu", node_item_->NodeName().c_str(), index, - outputs_start_[index].GetSize()); + auto reuse_input = node_item_->reuse_inputs.find(index); + if (reuse_input != node_item_->reuse_inputs.end()) { + GELOGD("[%s] Output[%d] is referenced to input[%d]", GetNodeName(), index, reuse_input->second); + outputs_start_[index] = inputs_start_[reuse_input->second]; + } else { + GE_CHK_STATUS_RET_NOLOG(AllocateTensor(tensor_desc, outputs_start_[index], attr)); + GELOGD("Allocating output successfully. node: %s. index = %d, size = %zu", node_item_->NodeName().c_str(), index, + outputs_start_[index].GetSize()); + } + } + + // Temp modification + if (node_item_->node_type == "UnsortedSegmentSum" || node_item_->node_type == "UnsortedSegmentSumD" || + node_item_->node_type == "ScatterNd") { + auto &out_tensor = outputs_start_[index]; + GELOGD("[%s] clear output tensor: %s", GetNodeName(), out_tensor.DebugString().c_str()); + auto *ctx = GetExecutionContext(); + string name = "rtMemsetAsync" + node_item_->node_name; + RegisterCallback([ctx, name]() { RECORD_CALLBACK_EVENT(ctx, name.c_str(), "[Compute] Start"); }); + RECORD_EXECUTION_EVENT(GetExecutionContext(), node_item_->node_name.c_str(), "[rtMemsetAsync] Start"); + GE_CHK_RT_RET(rtMemsetAsync(out_tensor.MutableData(), out_tensor.GetSize(), 0, out_tensor.GetSize(), GetStream())); + RECORD_EXECUTION_EVENT(GetExecutionContext(), node_item_->node_name.c_str(), "[rtMemsetAsync] End"); + RegisterCallback([ctx, name]() { RECORD_CALLBACK_EVENT(ctx, name.c_str(), "[Compute] End"); }); } if (execution_context_->trace_enabled) { @@ -194,11 +235,11 @@ Status TaskContext::AllocateOutput(int index, const GeTensorDesc &tensor_desc, T return SUCCESS; } -Status TaskContext::AllocateOutputs() { +Status TaskContext::AllocateOutputs(AllocationAttr *attr) { for (int i = 0; i < node_item_->num_outputs; ++i) { const auto &output_desc = node_item_->op_desc->MutableOutputDesc(i); GE_CHECK_NOTNULL(output_desc); - GE_CHK_STATUS_RET_NOLOG(AllocateOutput(i, *output_desc, nullptr)); + GE_CHK_STATUS_RET_NOLOG(AllocateOutput(i, *output_desc, nullptr, attr)); } return SUCCESS; @@ -230,7 +271,7 @@ Status TaskContext::SetOutput(int index, const TensorValue &tensor) { rtStream_t TaskContext::GetStream() { return execution_context_->stream; } -int64_t TaskContext::GetSessionId() { return execution_context_->session_id; } +int64_t TaskContext::GetSessionId() const { return execution_context_->session_id; } Status TaskContext::GetStatus() const { return status_; } @@ -238,7 +279,13 @@ void TaskContext::SetStatus(Status status) { status_ = status; } Status TaskContext::AllocateWorkspace(size_t size, void **buffer, void *ori_addr) { GE_CHECK_NOTNULL(buffer); - *buffer = execution_context_->allocator->Allocate(size, ori_addr); + if (ori_addr == nullptr) { + *buffer = execution_context_->allocator->Allocate(size, nullptr); + } else { + AllocationAttr attr(ori_addr); + *buffer = execution_context_->allocator->Allocate(size, &attr); + } + if (*buffer == nullptr) { GELOGE(MEMALLOC_FAILED, "Failed to allocate workspace of size = %zu", size); return MEMALLOC_FAILED; @@ -261,16 +308,21 @@ Status TaskContext::PropagateOutputs() { for (auto &dst_input_index_and_node : output_nodes) { auto dst_input_idx = dst_input_index_and_node.first; auto dst_node_item = dst_input_index_and_node.second; + auto input_offset = dst_node_item->input_start + dst_input_idx; GELOGI( "Propagate output of node %s, output index = %d, dst node = %s, " - "dst_input_index = %d, dst_input_offset = %d, addr = %p", - node_item_->NodeName().c_str(), i, dst_node_item->NodeName().c_str(), dst_input_idx, - dst_node_item->input_start + dst_input_idx, - execution_context_->all_inputs.data() + dst_node_item->input_start + dst_input_idx); - execution_context_->all_inputs[dst_node_item->input_start + dst_input_idx] = *tensor; + "dst_input_index = %d, dst_input_offset = %d.", + node_item_->NodeName().c_str(), i, dst_node_item->NodeName().c_str(), dst_input_idx, input_offset); + + if (subgraph_context_->all_inputs_.size() <= static_cast(input_offset)) { + GELOGE(INTERNAL_ERROR, "[%s] input index out of range. index = %d, total input num = %zu", GetNodeName(), + input_offset, subgraph_context_->all_inputs_.size()); + return INTERNAL_ERROR; + } + + subgraph_context_->all_inputs_[input_offset] = *tensor; if (execution_context_->trace_enabled) { - execution_context_->all_inputs[dst_node_item->input_start + dst_input_idx].SetName(node_item_->NodeName() + - "_in_" + std::to_string(i)); + subgraph_context_->all_inputs_[input_offset].SetName(node_item_->NodeName() + "_in_" + std::to_string(i)); } } } @@ -289,5 +341,37 @@ void TaskContext::ReleaseInput(int index) { GELOGD("[%s] Tensor of input[%d] released", GetNodeName(), index); } } + +ConstGeTensorDescPtr TaskContext::GetOutputDesc(int index) { + return node_item_->op_desc->MutableOutputDesc(static_cast(index)); +} + +ConstGeTensorDescPtr TaskContext::GetInputDesc(int index) { + return node_item_->op_desc->MutableInputDesc(static_cast(index)); +} + +GeTensorDescPtr TaskContext::MutableInputDesc(int index) { + return node_item_->op_desc->MutableInputDesc(static_cast(index)); +} + +GeTensorDescPtr TaskContext::MutableOutputDesc(int index) { + return node_item_->op_desc->MutableOutputDesc(static_cast(index)); +} + +bool TaskContext::IsForceInferShape() const { return force_infer_shape_; } + +void TaskContext::SetForceInferShape(bool force_infer_shape) { force_infer_shape_ = force_infer_shape; } + +void TaskContext::NodeDone() { subgraph_context_->NodeDone(node_item_->node); } + +void TaskContext::OnError(Status error) { subgraph_context_->OnError(error); } + +bool TaskContext::IsTraceEnabled() const { return execution_context_->trace_enabled; } + +TensorValue *TaskContext::GetVariable(const std::string &name) { return execution_context_->model->GetVariable(name); } + +uint64_t TaskContext::GetIterationNumber() const { return iteration_; } + +bool TaskContext::IsDumpEnabled() const { return execution_context_->dump_enabled; } } // namespace hybrid } // namespace ge diff --git a/src/ge/hybrid/node_executor/task_context.h b/src/ge/hybrid/node_executor/task_context.h index 841dcb17..5c42a347 100644 --- a/src/ge/hybrid/node_executor/task_context.h +++ b/src/ge/hybrid/node_executor/task_context.h @@ -22,16 +22,19 @@ #include #include "external/ge/ge_api_error_codes.h" #include "hybrid/common/tensor_value.h" +#include "hybrid/common/npu_memory_allocator.h" #include "hybrid/executor/rt_callback_manager.h" #include "hybrid/model/node_item.h" namespace ge { namespace hybrid { class GraphExecutionContext; +class SubgraphContext; class TaskContext { public: - static std::unique_ptr Create(const NodeItem &node_item, GraphExecutionContext *graph_context); + static std::unique_ptr Create(const NodeItem &node_item, GraphExecutionContext *execution_context, + SubgraphContext *subgraph_context); ~TaskContext(); @@ -41,19 +44,33 @@ class TaskContext { const NodeItem &GetNodeItem() const; const char *GetNodeName() const; TensorValue *MutableInput(int index); + ConstGeTensorDescPtr GetInputDesc(int index); + ConstGeTensorDescPtr GetOutputDesc(int index); + GeTensorDescPtr MutableInputDesc(int index); + GeTensorDescPtr MutableOutputDesc(int index); void ReleaseInput(int index); const TensorValue *GetInput(int index) const; const TensorValue *GetOutput(int index) const; TensorValue *MutableOutput(int index); + TensorValue *GetVariable(const std::string &name); rtStream_t GetStream(); - int64_t GetSessionId(); + int64_t GetSessionId() const; + uint64_t GetIterationNumber() const; + + void NodeDone(); + void OnError(Status error); Status SetOutput(int index, const TensorValue &tensor); - Status AllocateOutput(int index, const GeTensorDesc &tensor_desc, TensorValue **tensor); - Status AllocateOutputs(); + Status AllocateOutput(int index, const GeTensorDesc &tensor_desc, TensorValue **tensor, + AllocationAttr *attr = nullptr); + Status AllocateOutputs(AllocationAttr *attr = nullptr); Status AllocateWorkspaces(); Status AllocateWorkspace(size_t size, void **buffer, void *ori_addr = nullptr); + bool IsTraceEnabled() const; + + bool IsDumpEnabled() const; + const GraphExecutionContext *GetExecutionContext() { return execution_context_; } Status AllocateTemp(size_t size, TensorValue &tensor); @@ -68,17 +85,25 @@ class TaskContext { void SetStatus(Status status); + bool IsForceInferShape() const; + void SetForceInferShape(bool force_infer_shape); + void *handle_ = nullptr; + private: - explicit TaskContext(GraphExecutionContext *execution_context); - TensorValue *inputs_start_ = nullptr; - TensorValue *outputs_start_ = nullptr; + TaskContext(GraphExecutionContext *execution_context, const NodeItem *node_item, SubgraphContext *subgraph_context); + static string TensorDesc2String(const GeTensorDesc &desc); - Status AllocateTensor(const GeTensorDesc &tensor_desc, TensorValue &tensor); + Status AllocateTensor(const GeTensorDesc &tensor_desc, TensorValue &tensor, AllocationAttr *attr); - GraphExecutionContext *execution_context_; const NodeItem *node_item_ = nullptr; + bool force_infer_shape_ = false; + GraphExecutionContext *execution_context_; + SubgraphContext *subgraph_context_; + TensorValue *inputs_start_ = nullptr; + TensorValue *outputs_start_ = nullptr; Status status_ = SUCCESS; std::vector workspaces_; + uint64_t iteration_ = 0; }; } // namespace hybrid } // namespace ge diff --git a/src/ge/inc/kernel_factory.h b/src/ge/inc/kernel_factory.h index c0624e14..61455836 100644 --- a/src/ge/inc/kernel_factory.h +++ b/src/ge/inc/kernel_factory.h @@ -103,5 +103,5 @@ class KernelFactory { return ptr; \ } \ KernelFactory::Registerar g_##type##_Kernel_Creator(type, Creator_##type##_Kernel) -}; // end namespace ge +} // namespace ge #endif // GE_INC_KERNEL_FACTORY_H_ diff --git a/src/ge/init/gelib.cc b/src/ge/init/gelib.cc index 5fcb0cd7..f7740a3c 100644 --- a/src/ge/init/gelib.cc +++ b/src/ge/init/gelib.cc @@ -37,6 +37,7 @@ #include "graph/load/new_model_manager/model_manager.h" #include "graph/manager/graph_mem_allocator.h" #include "graph/manager/graph_var_manager.h" +#include "graph/common/ge_call_wrapper.h" #include "omm/csa_interact.h" #include "runtime/kernel.h" @@ -46,6 +47,9 @@ namespace ge { namespace { const int kDecimal = 10; const int kSocVersionLen = 50; +const uint32_t kAicoreOverflow = (0x1 << 0); +const uint32_t kAtomicOverflow = (0x1 << 1); +const uint32_t kAllOverflow = (kAicoreOverflow | kAtomicOverflow); } // namespace static std::shared_ptr instancePtr_ = nullptr; @@ -75,7 +79,7 @@ Status GELib::Initialize(const map &options) { instancePtr_ = nullptr; return ret; } - GE_TIMESTAMP_END(Init, "GELib::Initialize"); + GE_TIMESTAMP_EVENT_END(Init, "GELib::Initialize"); return SUCCESS; } @@ -126,16 +130,6 @@ Status GELib::InnerInitialize(const map &options) { return initSmStatus; } - GELOGI("memoryMallocSize initial."); - GE_TIMESTAMP_START(SetMemoryMallocSize); - Status initMemStatus = VarManager::Instance(0)->SetMemoryMallocSize(options); - GE_TIMESTAMP_END(SetMemoryMallocSize, "InnerInitialize::SetMemoryMallocSize"); - if (initMemStatus != SUCCESS) { - GELOGE(initMemStatus, "failed to set malloc size"); - RollbackInit(); - return initMemStatus; - } - GELOGI("Start to initialize HostCpuEngine"); GE_TIMESTAMP_START(HostCpuEngineInitialize); Status initHostCpuEngineStatus = HostCpuEngine::GetInstance().Initialize(); @@ -160,37 +154,6 @@ Status GELib::SystemInitialize(const map &options) { } } - iter = options.find(HEAD_STREAM); - head_stream_ = (iter != options.end()) ? std::strtol(iter->second.c_str(), nullptr, kDecimal) : false; - - iter = options.find(OPTION_EXEC_ENABLE_DUMP); - if (iter != options.end()) { - int32_t enable_dump_flag = 1; - auto path_iter = options.find(OPTION_EXEC_DUMP_PATH); - if (iter->second == std::to_string(enable_dump_flag) && path_iter != options.end()) { - std::string dump_path = path_iter->second; - if (!dump_path.empty() && dump_path[dump_path.size() - 1] != '/') { - dump_path = dump_path + "/" + CurrentTimeInStr() + "/"; - } - - PropertiesManager::Instance().AddDumpPropertyValue(DUMP_ALL_MODEL, {}); - GELOGD("Get dump path %s successfully", dump_path.c_str()); - PropertiesManager::Instance().SetDumpOutputPath(dump_path); - } - auto step_iter = options.find(OPTION_EXEC_DUMP_STEP); - if (step_iter != options.end()) { - std::string dump_step = step_iter->second; - GELOGD("Get dump step %s successfully", dump_step.c_str()); - PropertiesManager::Instance().SetDumpStep(dump_step); - } - auto mode_iter = options.find(OPTION_EXEC_DUMP_MODE); - if (mode_iter != options.end()) { - std::string dump_mode = mode_iter->second; - GELOGD("Get dump mode %s successfully", dump_mode.c_str()); - PropertiesManager::Instance().SetDumpMode(dump_mode); - } - } - // In train and infer, profiling is always needed. InitOptions(options); InitProfiling(this->options_); diff --git a/src/ge/init/gelib.h b/src/ge/init/gelib.h index 0dfec391..b5621dfd 100644 --- a/src/ge/init/gelib.h +++ b/src/ge/init/gelib.h @@ -62,9 +62,6 @@ class GELib { // get TrainMode flag bool isTrainMode() { return is_train_mode_; } - // add head stream to model - bool HeadStream() const { return head_stream_; } - // get incre build flag bool IsIncreBuild() const { return is_incre_build_; } @@ -86,6 +83,8 @@ class GELib { Status SetRTSocVersion(const map &options, map &new_options); void RollbackInit(); void InitOptions(const map &options); + void SetDumpModelOptions(const map &options); + void SetOpDebugOptions(const map &options); DNNEngineManager engineManager_; OpsKernelManager opsManager_; @@ -98,7 +97,6 @@ class GELib { bool is_shutdown = false; bool is_use_hcom = false; bool is_incre_build_ = false; - bool head_stream_ = false; std::string incre_build_cache_path_; }; } // namespace ge diff --git a/src/ge/ir_build/atc_ir_common.cc b/src/ge/ir_build/atc_ir_common.cc index 12c85bc0..352e5dc2 100644 --- a/src/ge/ir_build/atc_ir_common.cc +++ b/src/ge/ir_build/atc_ir_common.cc @@ -32,8 +32,29 @@ const int64_t kDynamicImageSizeNum = 2; // datatype/formats from user to GE, Unified to util interface file later const std::map kOutputTypeSupportDatatype = { {"FP32", ge::DT_FLOAT}, {"FP16", ge::DT_FLOAT16}, {"UINT8", ge::DT_UINT8}}; -const std::set kBufferOptimizeSupportOption = {"l2_optimize", "off_optimize"}; +const char *const kOutputTypeSupport = "only support FP32, FP16, UINT8"; +const std::set kBufferOptimizeSupportOption = {"l1_optimize", "l2_optimize", "off_optimize", + "l1_and_l2_optimize"}; +// The function is incomplete. Currently, only l2_optimize, off_optimize is supported. +const char *const kBufferOptimizeSupport = "only support l2_optimize, off_optimize"; const std::string IR_OPTION_OP_SELECT_IMPLMODE_DEFAULT = "high_performance"; +const char *const kInputShapeSample1 = "\"input_name1:n1,c1,h1,w1\""; +const char *const kInputShapeSample2 = "\"input_name1:1,3,224,224\""; +const char *const kSplitError1 = "size not equal to 2 split by \":\""; +const char *const kEmptyError = "can not be empty"; +const char *const kFloatNumError = "exist float number"; +const char *const kDigitError = "is not digit"; +const char *const kCompressWeightError = "it must be appointed when appoint parameter[--optypelist_for_implmode]"; + +vector SplitInputShape(const std::string &input_shape) { + vector shape_pair_vec; + size_t pos = input_shape.rfind(":"); + if (pos != std::string::npos) { + shape_pair_vec.emplace_back(input_shape.substr(0, pos)); + shape_pair_vec.emplace_back(input_shape.substr(pos + 1, input_shape.size() - pos)); + } + return shape_pair_vec; +} } // namespace bool CheckDynamicBatchSizeInputShapeValid(unordered_map> shape_map, @@ -42,7 +63,7 @@ bool CheckDynamicBatchSizeInputShapeValid(unordered_map> for (auto iter = shape_map.begin(); iter != shape_map.end(); ++iter) { vector shape = iter->second; if (shape.size() < 1) { - ErrorManager::GetInstance().ATCReportErrMessage("E10017"); + ErrorManager::GetInstance().ATCReportErrMessage("E10012"); GELOGE(ge::PARAM_INVALID, "--input_shape's shape size can not be less than 1 when set --dynamic_batch_size."); return false; } @@ -61,14 +82,14 @@ bool CheckDynamicBatchSizeInputShapeValid(unordered_map> } if (size == 0) { - ErrorManager::GetInstance().ATCReportErrMessage("E10043"); + ErrorManager::GetInstance().ATCReportErrMessage("E10031"); GELOGE(ge::PARAM_INVALID, "At least one batch n must be equal to -1 when set --dynamic_batch_size."); return false; } for (char c : dynamic_batch_size) { if (!isdigit(c) && (c != ',') && (c != ' ')) { - ErrorManager::GetInstance().ATCReportErrMessage("E10047", {"value"}, {dynamic_batch_size}); + ErrorManager::GetInstance().ATCReportErrMessage("E10033", {"value"}, {dynamic_batch_size}); GELOGE(ge::PARAM_INVALID, "Input parameter[--dynamic_batch_size]'s value[%s] is invalid.", dynamic_batch_size.c_str()); return false; @@ -169,7 +190,7 @@ Status CheckDynamicBatchSizeOrImageSizeParamValid(std::string &dynamic_batch_siz vector>> user_shape_map; is_dynamic_input = true; if (input_shape.empty()) { - ErrorManager::GetInstance().ATCReportErrMessage("E10000", {"parameter"}, {"input_shape"}); + ErrorManager::GetInstance().ATCReportErrMessage("E10004", {"parameter"}, {"input_shape"}); GELOGE(ge::PARAM_INVALID, "The input_shape can not be empty in dynamic batchsize scenario."); return ge::PARAM_INVALID; } @@ -200,21 +221,19 @@ bool ParseInputShape(const string &input_shape, unordered_map shape_vec = StringUtils::Split(input_shape, ';'); const int DEFAULT_SHAPE_PAIR_SIZE = 2; for (const auto &shape : shape_vec) { - vector shape_pair_vec = StringUtils::Split(shape, ':'); + vector shape_pair_vec = SplitInputShape(shape); if (shape_pair_vec.size() != DEFAULT_SHAPE_PAIR_SIZE) { - ErrorManager::GetInstance().ATCReportErrMessage("E10010", {"shape"}, {shape}); - GELOGW( - "Input parameter[--input_shape]’s shape is [%s], " - "correct sample is input_name1:n1,c1,h1,w1", - shape.c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E10002", {"shape", "reason", "sample"}, + {shape, kSplitError1, kInputShapeSample1}); + GELOGW("Parse input parameter [--input_shape]'s shape[%s] failed, reason: %s, correct sample is %s.", + shape.c_str(), kSplitError1, kInputShapeSample1); return false; } if (shape_pair_vec[1].empty()) { - ErrorManager::GetInstance().ATCReportErrMessage("E10011", {"shape"}, {shape}); - GELOGW( - "Input parameter[--input_shape]’s shape is [%s], can not empty, " - "correct sample is input_name1:n1,c1,h1,w1", - shape.c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E10002", {"shape", "reason", "sample"}, + {shape, kEmptyError, kInputShapeSample1}); + GELOGW("Parse input parameter [--input_shape]'s shape[%s] failed, reason: %s, correct sample is %s.", + shape.c_str(), kEmptyError, kInputShapeSample1); return false; } @@ -223,34 +242,48 @@ bool ParseInputShape(const string &input_shape, unordered_map caffe_support_input_format = {"NCHW", "ND"}; static std::set tf_support_input_format = {"NCHW", "NHWC", "ND", "NCDHW", "NDHWC"}; static std::set onnx_support_input_format = {"NCHW", "ND"}; +static const char *const kCaffeFormatSupport = "only support NCHW, ND in Caffe model"; +static const char *const kTFFormatSupport = "only support NCHW, NHWC, ND, NCDHW, NDHWC in TF model"; +static const char *const kONNXFormatSupport = "only support NCHW, ND in ONNX model"; static std::map input_format_str_to_geformat = { {"ND", domi::DOMI_TENSOR_ND}, {"NCHW", domi::DOMI_TENSOR_NCHW}, {"NHWC", domi::DOMI_TENSOR_NHWC}, diff --git a/src/ge/ir_build/ge_ir_build.cc b/src/ge/ir_build/ge_ir_build.cc index 0be75b51..a64591da 100644 --- a/src/ge/ir_build/ge_ir_build.cc +++ b/src/ge/ir_build/ge_ir_build.cc @@ -296,7 +296,6 @@ graphStatus Impl::BuildModel(const Graph &graph, const std::map(model.data.get()), static_cast(model.length)); } - } // namespace ge diff --git a/src/ge/model/ge_model.h b/src/ge/model/ge_model.h index 6305211a..be4b65bc 100644 --- a/src/ge/model/ge_model.h +++ b/src/ge/model/ge_model.h @@ -87,6 +87,6 @@ class GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY GeModel : public AttrHolder uint8_t platform_type_ = {0}; uint32_t model_id_ = INVALID_MODEL_ID; }; -}; // namespace ge +} // namespace ge using GeModelPtr = std::shared_ptr; #endif // GE_MODEL_GE_MODEL_H_ diff --git a/src/ge/offline/main.cc b/src/ge/offline/main.cc index f77f006d..fad4134c 100644 --- a/src/ge/offline/main.cc +++ b/src/ge/offline/main.cc @@ -66,6 +66,10 @@ static bool is_dynamic_input = false; // 310 limited 8G size const char *const kGraphMemoryManagerMallocMaxSize = "8*1024*1024*1024"; +const char *const kModeSupport = + "only support 0(model to framework model), " + "1(framework model to json), 3(only pre-check), 5(pbtxt to json)"; +const char *const kModelToJsonSupport = "only support 0(Caffe) 3(TensorFlow)"; DEFINE_string(model, "", "The model file."); DEFINE_string(output, "", "The output file path&name."); @@ -138,10 +142,6 @@ DEFINE_string(optypelist_for_implmode, "", "Optional; Nodes need use implmode selected in op_select_implmode " "Format:\"node_name1,node_name2\""); -DEFINE_string(head_stream, "0", - "Optional; Is need head stream, default is not need." - "Format: \"0: no head stream; 1: add head stream;\""); - DEFINE_string(singleop, "", "Optional; If set, generate single op model with the given json file."); DEFINE_int32(disable_reuse_memory, 0, "Optional; If set to 1, disable reuse memory when generating if."); @@ -173,7 +173,8 @@ DEFINE_string(dynamic_image_size, "", DEFINE_string(enable_small_channel, "0", "Optional; If set to 1, small channel is enabled."); -DEFINE_bool(enable_compress_weight, false, "Optional; enable compress weight. true: enable; false(default): disable"); +DEFINE_string(enable_compress_weight, "false", + "Optional; enable compress weight. true: enable; false(default): disable"); DEFINE_string(compress_weight_conf, "", "Optional; the config file to compress weight"); @@ -183,6 +184,10 @@ DEFINE_string(log, "default", "Optional; generate atc log. Support debug, info, DEFINE_string(dump_mode, "0", "Optional; generate infershape json,only support 1 , 0."); +DEFINE_int32(op_debug_level, 0, + "Optional; configure debug level of compiler. 0(default): close debug;" + "1: open TBE compiler, export ccec file and TBE instruction mapping file; 2: open ccec compiler"); + class GFlagUtils { public: /** @@ -235,7 +240,7 @@ class GFlagUtils { "\"check_result.json\"\n" " --disable_reuse_memory The switch of reuse memory. Default value is : 0." "0 means reuse memory, 1 means do not reuse memory.\n" - " --input_fp16_nodes Input node datatype is fp16 and format is NCHW. Separate multiple nodes with semicolons " + " --input_fp16_nodes Input node datatype is fp16. Separate multiple nodes with semicolons " "(;)." "Use double quotation marks (\") to enclose each argument." "E.g.: \"node_name1;node_name2\"\n" @@ -255,7 +260,6 @@ class GFlagUtils { " --optypelist_for_implmode Appoint which op to use op_select_implmode, used with op_select_implmode ." "Separate multiple nodes with commas (,). Use double quotation marks (\") to enclose each argument." "E.g.: \"node_name1,node_name2\"\n" - " --head_stream Add head stream. 0(default): disable; 1: enable\n" " --soc_version The soc version. E.g.: \"Ascend310\"\n" " --core_type Set core type AiCore or VectorCore. VectorCore: use vector core. " "Default value is: AiCore\n" @@ -283,7 +287,7 @@ class GFlagUtils { static Status CheckDumpInfershapeJsonFlags() { Status ret = CheckFrameWorkValid(FLAGS_framework, FLAGS_weight); GE_CHK_BOOL_EXEC(ret == domi::SUCCESS, return domi::FAILED, "check custom aicpu run so failed!"); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(FLAGS_weight != "" && !ge::CheckInputPathValid(FLAGS_weight, "weight"), + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(FLAGS_weight != "" && !ge::CheckInputPathValid(FLAGS_weight, "--weight"), return domi::FAILED, "Input parameter[--weight]'s value[%s] is invalid!", FLAGS_weight.c_str()); return domi::SUCCESS; @@ -292,7 +296,7 @@ class GFlagUtils { static Status CheckFlags() { // No model file information passed in GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(FLAGS_model == "", - ErrorManager::GetInstance().ATCReportErrMessage("E10000", {"parameter"}, {"model"}); + ErrorManager::GetInstance().ATCReportErrMessage("E10004", {"parameter"}, {"model"}); return domi::PARAM_INVALID, "Input parameter[--model]'s value is empty!"); // check param disable_reuse_memory GE_CHK_BOOL_EXEC(ge::CheckDisableReuseMemoryParamValid(to_string(FLAGS_disable_reuse_memory)) == ge::SUCCESS, @@ -304,7 +308,7 @@ class GFlagUtils { return ge::FAILED, "check optypelist_for_implmode and op_select_implmode failed!"); // No output file information passed in GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(FLAGS_mode == GEN_OM_MODEL && FLAGS_output == "", - ErrorManager::GetInstance().ATCReportErrMessage("E10000", {"parameter"}, {"output"}); + ErrorManager::GetInstance().ATCReportErrMessage("E10004", {"parameter"}, {"output"}); return domi::PARAM_INVALID, "Input parameter[--output]'s value is empty!"); Status ret = CheckFrameWorkValid(FLAGS_framework, FLAGS_weight); @@ -323,16 +327,16 @@ class GFlagUtils { GELOGI("domi will run with encrypt!"); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(!ge::CheckInputPathValid(FLAGS_encrypt_key), return domi::FAILED, - "encrypt_key file %s not found!!", FLAGS_encrypt_key.c_str()); + "encrypt_key file not found!!"); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(!ge::CheckInputPathValid(FLAGS_certificate), return domi::FAILED, - "certificate file %s not found!!", FLAGS_certificate.c_str()); + "certificate file not found!!"); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(!ge::CheckInputPathValid(FLAGS_hardware_key), return domi::FAILED, - "hardware_key file %s not found!!", FLAGS_hardware_key.c_str()); + "hardware_key file not found!!"); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(!ge::CheckInputPathValid(FLAGS_private_key), return domi::FAILED, - "private_key file %s not found!!", FLAGS_private_key.c_str()); + "private_key file not found!!"); } else { // No encryption GELOGI("domi will run without encrypt!"); } @@ -341,43 +345,37 @@ class GFlagUtils { /** * Check the validity of the I / O file path */ - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(!ge::CheckInputPathValid(FLAGS_model, "model"), return domi::FAILED, + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(!ge::CheckInputPathValid(FLAGS_model, "--model"), return domi::FAILED, "model file %s not found!!", FLAGS_model.c_str()); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(FLAGS_weight != "" && !ge::CheckInputPathValid(FLAGS_weight, "weight"), + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(FLAGS_weight != "" && !ge::CheckInputPathValid(FLAGS_weight, "--weight"), return domi::FAILED, "weight file %s not found!!", FLAGS_weight.c_str()); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(FLAGS_cal_conf != "" && !ge::CheckInputPathValid(FLAGS_cal_conf, "cal_conf"), + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(FLAGS_cal_conf != "" && !ge::CheckInputPathValid(FLAGS_cal_conf, "--cal_conf"), return domi::FAILED, "calibration config file %s not found!!", FLAGS_cal_conf.c_str()); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG( - FLAGS_op_name_map != "" && !ge::CheckInputPathValid(FLAGS_op_name_map, "op_name_map"), return domi::FAILED, + FLAGS_op_name_map != "" && !ge::CheckInputPathValid(FLAGS_op_name_map, "--op_name_map"), return domi::FAILED, "op config file %s not found!!", FLAGS_op_name_map.c_str()); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG( - FLAGS_head_stream != "" && FLAGS_head_stream != "0" && FLAGS_head_stream != "1", - ErrorManager::GetInstance().ATCReportErrMessage("E10006", {"parameter"}, {"head_stream"}); - return domi::FAILED, "Input parameter[--head_stream] must be 0 or 1!!"); - GE_CHK_BOOL_EXEC(ge::CheckInsertOpConfParamValid(std::string(FLAGS_insert_op_conf)) == ge::SUCCESS, return ge::FAILED, "check insert op conf failed!"); GE_CHK_BOOL_EXEC( - ge::CheckCompressWeightParamValid(FLAGS_enable_compress_weight ? std::string("true") : std::string("false"), - FLAGS_compress_weight_conf) == ge::SUCCESS, + ge::CheckCompressWeightParamValid(FLAGS_enable_compress_weight, FLAGS_compress_weight_conf) == ge::SUCCESS, return ge::FAILED, "check compress weight failed!"); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(!ge::CheckOutputPathValid(FLAGS_check_report, "check_report"), return domi::FAILED, + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(!ge::CheckOutputPathValid(FLAGS_check_report, "--check_report"), return domi::FAILED, "check_report file %s not found!!", FLAGS_check_report.c_str()); - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG( - FLAGS_mode == GEN_OM_MODEL && (!ge::CheckOutputPathValid(FLAGS_output) || !CheckPathWithName(FLAGS_output)), - return domi::FAILED, "output path %s is not valid!!", FLAGS_output.c_str()); + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(FLAGS_mode == GEN_OM_MODEL && (!ge::CheckOutputPathValid(FLAGS_output, "--output") || + !CheckPathWithName(FLAGS_output)), + return domi::FAILED, "output path %s is not valid!!", FLAGS_output.c_str()); GE_CHK_BOOL_TRUE_EXEC_WITH_LOG( FLAGS_save_original_model != "" && FLAGS_save_original_model != "true" && FLAGS_save_original_model != "false", - ErrorManager::GetInstance().ATCReportErrMessage("E10033", {"parameter", "value"}, + ErrorManager::GetInstance().ATCReportErrMessage("E10005", {"parameter", "value"}, {"save_original_model", FLAGS_save_original_model}); return domi::FAILED, "Input parameter[--save_original_model]'s value[%s] must be true or false.", FLAGS_save_original_model.c_str()); @@ -398,18 +396,18 @@ class GFlagUtils { static Status CheckConverJsonParamFlags() { // No model path passed in GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(FLAGS_om == "", - ErrorManager::GetInstance().ATCReportErrMessage("E10000", {"parameter"}, {"om"}); + ErrorManager::GetInstance().ATCReportErrMessage("E10004", {"parameter"}, {"om"}); return domi::PARAM_INVALID, "Input parameter[--om]'s value is empty!!"); // JSON path not passed in GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(FLAGS_json == "", - ErrorManager::GetInstance().ATCReportErrMessage("E10000", {"parameter"}, {"json"}); + ErrorManager::GetInstance().ATCReportErrMessage("E10004", {"parameter"}, {"json"}); return domi::PARAM_INVALID, "Input parameter[--json]'s value is empty!!"); // Check if the model path is valid - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(!ge::CheckInputPathValid(FLAGS_om, "om"), return domi::PARAM_INVALID, + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(!ge::CheckInputPathValid(FLAGS_om, "--om"), return domi::PARAM_INVALID, "model file path is invalid: %s.", FLAGS_om.c_str()); // Check whether the JSON path is valid - GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(!ge::CheckOutputPathValid(FLAGS_json, "om"), return domi::PARAM_INVALID, + GE_CHK_BOOL_TRUE_EXEC_WITH_LOG(!ge::CheckOutputPathValid(FLAGS_json, "--json"), return domi::PARAM_INVALID, "json file path is invalid: %s.", FLAGS_json.c_str()); return domi::SUCCESS; @@ -446,7 +444,8 @@ class GFlagUtils { if (framework != (int32_t)domi::CAFFE && framework != (int32_t)domi::TENSORFLOW && framework != (int32_t)domi::MINDSPORE && framework != (int32_t)domi::ONNX) { // No framework information was passed in or the entered framework is illegal - ErrorManager::GetInstance().ATCReportErrMessage("E10007", {"parameter"}, {"framework"}); + ErrorManager::GetInstance().ATCReportErrMessage("E10007", {"parameter", "support"}, + {"framework", "0(Caffe) or 1(MindSpore) or 3(TensorFlow)"}); DOMI_LOGE( "Input parameter[--framework] is mandatory and it's value must be: " "0(Caffe) or 1(MindSpore) or 3(TensorFlow)."); @@ -519,31 +518,29 @@ static bool CheckInputFormat() { if (ge::caffe_support_input_format.find(FLAGS_input_format) != ge::caffe_support_input_format.end()) { return true; } - ErrorManager::GetInstance().ATCReportErrMessage("E10031", {"value"}, {FLAGS_input_format}); // only support NCHW ND - GELOGE(ge::FAILED, - "Input parameter[--input_format]'s value[%s] is wrong, " - "only support NCHW, ND in Caffe model.", - FLAGS_input_format.c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E10001", {"parameter", "value", "reason"}, + {"--input_format", FLAGS_input_format, ge::kCaffeFormatSupport}); + GELOGE(ge::FAILED, "Invalid value for --input_format[%s], %s.", FLAGS_input_format.c_str(), + ge::kCaffeFormatSupport); return false; } else if ((FLAGS_framework == static_cast(domi::TENSORFLOW))) { // tf if (ge::tf_support_input_format.find(FLAGS_input_format) != ge::tf_support_input_format.end()) { return true; } - ErrorManager::GetInstance().ATCReportErrMessage("E10032", {"value"}, {FLAGS_input_format}); // only support NCHW NHWC ND NCDHW NDHWC - GELOGE(ge::FAILED, - "Input parameter[--input_format]'s value[%s] is wrong, " - "only support NCHW, NHWC, ND, NCDHW, NDHWC in tf model", - FLAGS_input_format.c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E10001", {"parameter", "value", "reason"}, + {"--input_format", FLAGS_input_format, ge::kTFFormatSupport}); + GELOGE(ge::FAILED, "Invalid value for --input_format[%s], %s.", FLAGS_input_format.c_str(), ge::kTFFormatSupport); return false; } else if (FLAGS_framework == static_cast(domi::ONNX)) { if (ge::onnx_support_input_format.find(FLAGS_input_format) != ge::onnx_support_input_format.end()) { return true; } // only support NCHW ND - GELOGE(ge::FAILED, "Input parameter[--input_format]'s value[%s] is error, Only support NCHW, ND in onnx model", - FLAGS_input_format.c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E10001", {"parameter", "value", "reason"}, + {"--input_format", FLAGS_input_format, ge::kONNXFormatSupport}); + GELOGE(ge::FAILED, "Invalid value for --input_format[%s], %s.", FLAGS_input_format.c_str(), ge::kONNXFormatSupport); return false; } return true; @@ -625,8 +622,7 @@ void LoadModelParserLib(std::string caffe_parser_path) { return; } -void LoadCustomOpLib() { - OpRegistry::Instance()->registrationDatas.clear(); +void LoadCustomOpLib(bool need_load_ops_plugin) { std::string plugin_path; GetCustomOpPath(plugin_path); @@ -642,7 +638,11 @@ void LoadCustomOpLib() { } LoadModelParserLib(caffe_parser_path); - + if (!need_load_ops_plugin) { + GELOGI("No need to load ops plugin so."); + return; + } + OpRegistry::Instance()->registrationDatas.clear(); // load other so files except lib_caffe_parser.so in the plugin so path for (auto elem : fileList) { ge::StringUtils::Trim(elem); @@ -657,17 +657,21 @@ void LoadCustomOpLib() { std::vector registrationDatas = OpRegistry::Instance()->registrationDatas; for (OpRegistrationData reg_data : registrationDatas) { - bool ret = ge::OpRegistrationTbe::Instance()->Finalize(reg_data); - if (ret) { - OpRegistry::Instance()->Register(reg_data); - } + (void)ge::OpRegistrationTbe::Instance()->Finalize(reg_data); + (void)OpRegistry::Instance()->Register(reg_data); } } void SaveCustomCaffeProtoPath() { GELOGI("Enter save custom caffe proto path."); - string customop_path; + std::string path_base = ge::GELib::GetPath(); + GELOGI("path_base is %s", path_base.c_str()); + path_base = path_base.substr(0, path_base.rfind('/')); + path_base = path_base.substr(0, path_base.rfind('/') + 1); + ge::GetParserContext().caffe_proto_path = path_base + "include/proto/"; + + string customop_path; const char *path_env = std::getenv("ASCEND_OPP_PATH"); if (path_env != nullptr) { std::string path = path_env; @@ -676,10 +680,6 @@ void SaveCustomCaffeProtoPath() { ge::GetParserContext().custom_proto_path = customop_path; return; } - std::string path_base = ge::GELib::GetPath(); - GELOGI("path_base is %s", path_base.c_str()); - path_base = path_base.substr(0, path_base.rfind('/')); - path_base = path_base.substr(0, path_base.rfind('/') + 1); customop_path = path_base + "ops/framework/custom/caffe/"; ge::GetParserContext().custom_proto_path = customop_path; return; @@ -723,15 +723,6 @@ Status CreateInputsForInference(const ge::Graph &graph, vector &in return ge::SUCCESS; } -void ChangeStringToBool(std::string &arg_s, bool arg_b) { - if (arg_s == "true") { - arg_b = true; - } else { - arg_b = false; - } - return; -} - domi::Status GenerateInfershapeJson() { if (!CheckInputFormat()) { GELOGE(ge::FAILED, "Check input_format failed"); @@ -740,8 +731,6 @@ domi::Status GenerateInfershapeJson() { Status ret = GFlagUtils::CheckDumpInfershapeJsonFlags(); GE_CHK_BOOL_EXEC(ret == domi::SUCCESS, return domi::FAILED, "Check flags failed!"); - // Load custom operator Library - LoadCustomOpLib(); ge::GeGenerator ge_generator; std::map options; ge::Status geRet = ge_generator.Initialize(options); @@ -783,24 +772,25 @@ static Status ConvertModelToJson(int fwk_type, const string &model_file, const s return ret; } - if ((fwk_type != domi::TENSORFLOW) && (fwk_type != domi::CAFFE)) { - ErrorManager::GetInstance().ATCReportErrMessage( - "E10068", {"param", "value", "supports"}, - {"framework", std::to_string(fwk_type), "only support 0(Caffe) 3(TensorFlow)"}); - GELOGE(ge::FAILED, "Input parameter[--framework] is mandatory and it's value must be: 0(Caffe) 3(TensorFlow)."); + if ((fwk_type != domi::TENSORFLOW) && (fwk_type != domi::CAFFE) && (fwk_type != domi::ONNX)) { + ErrorManager::GetInstance().ATCReportErrMessage("E10001", {"parameter", "value", "reason"}, + {"--framework", std::to_string(fwk_type), kModelToJsonSupport}); + GELOGE(ge::FAILED, "Invalid value for --framework[%d], %s.", fwk_type, kModelToJsonSupport); return ge::FAILED; } - // Since the Caffe model's conversion to JSON file depends on lib_caffe_parser.so, loadcustomoplib is called here. - LoadCustomOpLib(); - if (FLAGS_dump_mode == "0") { + // Caffe or tf model to json depend on lib_caffe_parser.so or libfmk_tensorflow_parser.so. + LoadCustomOpLib(false); ret = ge::ConvertFwkModelToJson((domi::FrameworkType)fwk_type, model_file.c_str(), json_file.c_str()); return ret; } else if (FLAGS_dump_mode == "1") { + // Caffe or tf model to json depend on lib_caffe_parser.so or libfmk_tensorflow_parser.so and ops plugin so. + LoadCustomOpLib(true); ret = GenerateInfershapeJson(); return ret; } else { + ErrorManager::GetInstance().ATCReportErrMessage("E10006", {"parameter"}, {"dump_mode"}); GELOGE(ge::FAILED, "Input parameter[--dump_mode]'s value must be 1 or 0."); return ge::FAILED; } @@ -831,7 +821,7 @@ domi::Status GenerateModel(std::map &options, std::string output ge::Model load_model = ge::Model("loadmodel", "version2"); auto ret1 = load_model.LoadFromFile(FLAGS_model); if (ret1 != ge::GRAPH_SUCCESS) { - ErrorManager::GetInstance().ATCReportErrMessage("E10056", {"parameter"}, {FLAGS_model}); + ErrorManager::GetInstance().ATCReportErrMessage("E10041", {"parameter"}, {FLAGS_model}); DOMI_LOGE( "Load model from %s failed, please check model file or " "input parameter[--framework] is correct", @@ -934,10 +924,11 @@ static void SetEnvForSingleOp(std::map &options) { options.emplace(ge::OPTYPELIST_FOR_IMPLMODE, FLAGS_optypelist_for_implmode); options.emplace(ge::AUTO_TUNE_MODE, FLAGS_auto_tune_mode); options.emplace(ge::GRAPH_MEMORY_MAX_SIZE, kGraphMemoryManagerMallocMaxSize); + options.emplace(ge::OP_DEBUG_LEVEL, to_string(FLAGS_op_debug_level)); } domi::Status GenerateSingleOp(const std::string &json_file_path) { - if (!FLAGS_output.empty() && !ge::CheckOutputPathValid(FLAGS_output)) { + if (!FLAGS_output.empty() && !ge::CheckOutputPathValid(FLAGS_output, "--output")) { DOMI_LOGE("output path %s is not valid!", FLAGS_output.c_str()); return domi::FAILED; } @@ -1003,7 +994,7 @@ domi::Status GenerateOmModel() { "quotation marks (\") to enclose each argument such as out_nodes, input_shape, dynamic_image_size"); #if !defined(__ANDROID__) && !defined(ANDROID) // Load custom operator Library - LoadCustomOpLib(); + LoadCustomOpLib(true); SaveCustomCaffeProtoPath(); @@ -1041,8 +1032,6 @@ domi::Status GenerateOmModel() { options.insert(std::pair(ge::INPUT_FP16_NODES, FLAGS_input_fp16_nodes)); } - options.insert(std::pair(string(ge::HEAD_STREAM), FLAGS_head_stream)); - options.insert(std::pair(string(ge::AUTO_TUNE_MODE), FLAGS_auto_tune_mode)); options.insert( @@ -1060,7 +1049,7 @@ domi::Status GenerateOmModel() { options.insert(std::pair(string(ge::FUSION_SWITCH_FILE), FLAGS_fusion_switch_file)); - options.insert(std::pair(string(ge::ENABLE_COMPRESS_WEIGHT), FLAGS_enable_compress_weight + options.insert(std::pair(string(ge::ENABLE_COMPRESS_WEIGHT), (FLAGS_enable_compress_weight == "true") ? ge::kEnableCompressWeightTrue : ge::kEnableCompressWeightFalse)); @@ -1075,6 +1064,8 @@ domi::Status GenerateOmModel() { options.insert(std::pair(string(ge::ORIGINAL_MODEL_FILE), FLAGS_output + "_original.om")); } + options.insert(std::pair(string(ge::OP_DEBUG_LEVEL), to_string(FLAGS_op_debug_level))); + // print atc option map ge::PrintOptionMap(options, "atc option"); @@ -1098,8 +1089,8 @@ domi::Status ConvertModelToJson() { return domi::SUCCESS; } -bool CheckRet(domi::Status ret, ge::Status geRet) { - if (ret != domi::SUCCESS || geRet != ge::SUCCESS) { +bool CheckRet(domi::Status ret) { + if (ret != domi::SUCCESS) { if (FLAGS_mode == ONLY_PRE_CHECK) { GELOGW("ATC precheck failed."); } else if (FLAGS_mode == GEN_OM_MODEL) { @@ -1148,7 +1139,7 @@ int init(int argc, char *argv[]) { int ret = -1; const std::set log_level = {"default", "null", "debug", "info", "warning", "error"}; if (log_level.count(FLAGS_log) == 0) { - std::cout << "E10016: invalid value for --log:" << FLAGS_log << ", only support debug, info, warning, error, null" + std::cout << "E10010: invalid value for --log:" << FLAGS_log << ", only support debug, info, warning, error, null" << std::endl; return ret; } @@ -1158,12 +1149,18 @@ int init(int argc, char *argv[]) { return ret; } + std::string path_base = ge::GELib::GetPath(); + ret = ErrorManager::GetInstance().Init(path_base); + if (ret != 0) { + DOMI_LOGE("ErrorManager init fail !"); + return ret; + } + return 0; } int main(int argc, char *argv[]) { Status ret = domi::SUCCESS; - ge::Status geRet = ge::SUCCESS; std::cout << "ATC start working now, please wait for a moment." << std::endl; try { // Initialize @@ -1188,12 +1185,9 @@ int main(int argc, char *argv[]) { GE_CHK_BOOL_EXEC(ConvertPbtxtToJson() == domi::SUCCESS, ret = domi::FAILED; break, "ATC convert pbtxt to json execute failed!!"); } else { - ErrorManager::GetInstance().ATCReportErrMessage("E10048", {"value"}, {std::to_string(FLAGS_mode)}); - DOMI_LOGE( - "Invalid value for --mode[%d], only support " - "0(model to framework model), 1(framework model to json), 3(only pre-check), " - "5(pbtxt to json)!", - FLAGS_mode); + ErrorManager::GetInstance().ATCReportErrMessage("E10001", {"parameter", "value", "reason"}, + {"--mode", std::to_string(FLAGS_mode), kModeSupport}); + GELOGE(ge::PARAM_INVALID, "Invalid value for --mode[%d], %s.", FLAGS_mode, kModeSupport); ret = domi::FAILED; break; } @@ -1208,8 +1202,12 @@ int main(int argc, char *argv[]) { std::cout << "ATC run failed, some exceptions occur !" << std::endl; } - if (!CheckRet(ret, geRet)) { + if (!CheckRet(ret)) { std::cout << "ATC run failed, Please check the detail log, Try \'atc --help\' for more information" << std::endl; + int result = ErrorManager::GetInstance().OutputErrMessage(STDOUT_FILENO); + if (result != 0) { + DOMI_LOGE("ErrorManager outputErrMessage fail !"); + } return ret; } else { std::cout << "ATC run success, welcome to the next use." << std::endl; diff --git a/src/ge/offline/single_op_parser.cc b/src/ge/offline/single_op_parser.cc index 4d589565..b8947a65 100644 --- a/src/ge/offline/single_op_parser.cc +++ b/src/ge/offline/single_op_parser.cc @@ -200,13 +200,13 @@ bool SingleOpParser::Validate(const SingleOpDesc &op_desc) { for (auto &tensor_desc : op_desc.input_desc) { if (tensor_desc.type == DT_UNDEFINED) { ErrorManager::GetInstance().ATCReportErrMessage("E10027", {"input", "index"}, {"input", std::to_string(index)}); - GELOGE(false, "Input index[%d]'s dataType is invalid", index); + GELOGE(false, "Input's dataType is invalid when the index is %d", index); return false; } if (tensor_desc.format == FORMAT_RESERVED) { ErrorManager::GetInstance().ATCReportErrMessage("E10028", {"input", "index"}, {"input", std::to_string(index)}); - GELOGE(PARAM_INVALID, "Input index[%d]'s format is invalid", index); + GELOGE(PARAM_INVALID, "Input's format is invalid when the index is %d", index); return false; } ++index; @@ -216,13 +216,13 @@ bool SingleOpParser::Validate(const SingleOpDesc &op_desc) { for (auto &tensor_desc : op_desc.output_desc) { if (tensor_desc.type == DT_UNDEFINED) { ErrorManager::GetInstance().ATCReportErrMessage("E10027", {"input", "index"}, {"output", std::to_string(index)}); - GELOGE(PARAM_INVALID, "Output[%d] dataType is invalid", index); + GELOGE(PARAM_INVALID, "Output's dataType is invalid when the index is %d", index); return false; } if (tensor_desc.format == FORMAT_RESERVED) { ErrorManager::GetInstance().ATCReportErrMessage("E10028", {"input", "index"}, {"output", std::to_string(index)}); - GELOGE(PARAM_INVALID, "Output[%d] format is invalid", index); + GELOGE(PARAM_INVALID, "Output's format is invalid when the index is %d", index); return false; } ++index; @@ -316,17 +316,15 @@ Status SingleOpParser::ParseSingleOpList(const std::string &file, std::vector &options_const) { GetExternalEnginePath(extern_engine_path); GELOGI("OPTION_EXEC_EXTERN_PLUGIN_PATH=%s.", extern_engine_path.c_str()); + op_tiling_manager_.LoadSo(); + ret = plugin_manager_.LoadSo(extern_engine_path, func_check_list); if (ret == SUCCESS) { initialize_ = options; diff --git a/src/ge/opskernel_manager/ops_kernel_manager.h b/src/ge/opskernel_manager/ops_kernel_manager.h index 8d98ad3f..1d464201 100644 --- a/src/ge/opskernel_manager/ops_kernel_manager.h +++ b/src/ge/opskernel_manager/ops_kernel_manager.h @@ -24,6 +24,7 @@ #include "common/debug/log.h" #include "common/ge/plugin_manager.h" +#include "common/ge/op_tiling_manager.h" #include "common/ge_inner_error_codes.h" #include "common/opskernel/ops_kernel_info_store.h" #include "common/optimizer/graph_optimizer.h" @@ -105,6 +106,7 @@ class OpsKernelManager { Status InitGraphOptimizerPriority(); PluginManager plugin_manager_; + OpTilingManager op_tiling_manager_; // opsKernelInfoStore map ops_kernel_store_{}; // graph_optimizer diff --git a/src/ge/session/inner_session.cc b/src/ge/session/inner_session.cc index 74495e82..b97862e1 100644 --- a/src/ge/session/inner_session.cc +++ b/src/ge/session/inner_session.cc @@ -29,6 +29,34 @@ #include "runtime/mem.h" namespace ge { +namespace { +Status CheckReuseMemoryOption(const std::map &options) { + const int kDecimal = 10; + auto dump_op_env = std::getenv("DUMP_OP"); + int dump_op_flag = (dump_op_env != nullptr) ? std::strtol(dump_op_env, nullptr, kDecimal) : 0; + auto iter = options.find(OPTION_EXEC_DISABLE_REUSED_MEMORY); + if (iter != options.end()) { + if (iter->second == "0") { + GELOGD("%s=0, reuse memory is open", OPTION_EXEC_DISABLE_REUSED_MEMORY); + if (dump_op_flag) { + GELOGW("Will dump incorrect op data with ge option %s=0", OPTION_EXEC_DISABLE_REUSED_MEMORY); + } + } else if (iter->second == "1") { + GELOGD("%s=1, reuse memory is close", OPTION_EXEC_DISABLE_REUSED_MEMORY); + } else { + GELOGE(PARAM_INVALID, "option %s=%s is invalid", OPTION_EXEC_DISABLE_REUSED_MEMORY, iter->second.c_str()); + return FAILED; + } + } else { + if (dump_op_flag) { + GELOGW("Will dump incorrect op data with default reuse memory"); + } + } + + return SUCCESS; +} +} // namespace + static std::mutex mutex_; // BuildGraph and RunGraph use InnerSession::InnerSession(uint64_t session_id, const std::map &options) @@ -39,13 +67,36 @@ Status InnerSession::Initialize() { GELOGW("[InnerSession:%lu] session already initialize.", session_id_); return SUCCESS; } + + // If the global options and the session options are duplicated, the session options is preferred. + auto all_options = options_; + all_options.insert(GetMutableGlobalOptions().begin(), GetMutableGlobalOptions().end()); + + Status ret = CheckReuseMemoryOption(all_options); + if (ret != SUCCESS) { + GELOGE(ret, "[InnerSession:%lu] check reuse memory option failed.", session_id_); + return ret; + } + UpdateThreadContext(std::map{}); GE_CHK_RT_RET(rtSetDevice(GetContext().DeviceId())); - Status ret = graph_manager_.Initialize(options_); + PropertiesManager::Instance().GetDumpProperties(session_id_).InitByOptions(); + + ret = graph_manager_.Initialize(options_); if (ret != SUCCESS) { GELOGE(ret, "[InnerSession:%lu] initialize failed.", session_id_); + PropertiesManager::Instance().RemoveDumpProperties(session_id_); + return ret; + } + + ret = VarManager::Instance(session_id_)->SetMemoryMallocSize(all_options); + if (ret != SUCCESS) { + GELOGE(ret, "failed to set malloc size"); + (void)graph_manager_.Finalize(); + PropertiesManager::Instance().RemoveDumpProperties(session_id_); + GE_CHK_RT(rtDeviceReset(static_cast(GetContext().DeviceId()))); return ret; } @@ -55,6 +106,7 @@ Status InnerSession::Initialize() { ret = VarManager::Instance(session_id_)->Init(version, session_id_, DEFAULT_DEVICE_ID, DEFAULT_JOB_ID); if (ret != SUCCESS) { GELOGE(ret, "failed to init session instance"); + PropertiesManager::Instance().RemoveDumpProperties(session_id_); } init_flag_ = true; return SUCCESS; @@ -78,6 +130,9 @@ Status InnerSession::Finalize() { // release var memory GELOGI("VarManager free var memory."); (void)VarManager::Instance(session_id_)->FreeVarMemory(); + + PropertiesManager::Instance().RemoveDumpProperties(session_id_); + GE_CHK_RT(rtDeviceReset(static_cast(GetContext().DeviceId()))); return ret; @@ -223,6 +278,7 @@ void InnerSession::UpdateThreadContext(const std::map GetThreadLocalContext().SetGlobalOption(GetMutableGlobalOptions()); GetThreadLocalContext().SetSessionOption(options_); GetThreadLocalContext().SetGraphOption(options); + GetContext().SetSessionId(session_id_); } void InnerSession::UpdateThreadContext(uint32_t graph_id) { diff --git a/src/ge/session/omg.cc b/src/ge/session/omg.cc index 4754f9b9..26103063 100644 --- a/src/ge/session/omg.cc +++ b/src/ge/session/omg.cc @@ -65,6 +65,9 @@ namespace ge { namespace { const std::string kGraphDefaultName = "domi_default"; const std::string kScopeIdAttr = "fusion_scope"; +const char *const kOutputTypeSample = "correct sample is \"opname:index:dtype\""; +const char *const kOutputTypeSupport = "only support FP32, FP16, UINT8"; +const char *const kOutputTypeError = "The multiple out nodes set in output_type must be found in out_nodes."; } // namespace // When the model is converted to a JSON file, the following operator attributes in the blacklist will be ignored @@ -78,7 +81,7 @@ static bool CheckInputTrueOrFalse(const std::string &s, const std::string &atc_p if ((s == "true") || (s == "false")) { return true; } else { - ErrorManager::GetInstance().ATCReportErrMessage("E10033", {"parameter", "value"}, {atc_param, s}); + ErrorManager::GetInstance().ATCReportErrMessage("E10005", {"parameter", "value"}, {atc_param, s}); GELOGE(PARAM_INVALID, "Input parameter[--%s]'s value[%s] must be true or false.", atc_param.c_str(), s.c_str()); return false; } @@ -97,12 +100,12 @@ static Status CheckInputShapeNode(const ComputeGraphPtr &graph) { std::string node_name = it.first; ge::NodePtr node = graph->FindNode(node_name); if (node == nullptr) { - ErrorManager::GetInstance().ATCReportErrMessage("E10034", {"parameter", "opname"}, {"input_shape", node_name}); + ErrorManager::GetInstance().ATCReportErrMessage("E10016", {"parameter", "opname"}, {"input_shape", node_name}); GELOGE(PARAM_INVALID, "Input parameter[--input_shape]'s opname[%s] is not exist in model", node_name.c_str()); return PARAM_INVALID; } if (node->GetType() != DATA) { - ErrorManager::GetInstance().ATCReportErrMessage("E10035", {"parameter", "opname"}, {"input_shape", node_name}); + ErrorManager::GetInstance().ATCReportErrMessage("E10017", {"parameter", "opname"}, {"input_shape", node_name}); GELOGE(PARAM_INVALID, "Input parameter[--input_shape]'s opname[%s] is not a input opname", node_name.c_str()); return PARAM_INVALID; } @@ -133,18 +136,19 @@ static Status CheckInputFp16Nodes(const ComputeGraphPtr &graph, const string &in for (uint32_t i = 0; i < input_fp16_nodes_vec.size(); ++i) { ge::NodePtr node = graph->FindNode(input_fp16_nodes_vec[i]); if (node == nullptr) { - ErrorManager::GetInstance().ATCReportErrMessage("E10034", {"parameter", "opname"}, + ErrorManager::GetInstance().ATCReportErrMessage("E10016", {"parameter", "opname"}, {"input_fp16_nodes", input_fp16_nodes_vec[i]}); - GELOGE(PARAM_INVALID, "Can not find node [%s] in graph, please check input_fp16_nodes param", + GELOGE(PARAM_INVALID, "Input parameter[--input_fp16_nodes]'s opname[%s] is not exist in model", input_fp16_nodes_vec[i].c_str()); return PARAM_INVALID; } auto op_desc = node->GetOpDesc(); GE_CHECK_NOTNULL(op_desc); if (op_desc->GetType() != DATA) { - ErrorManager::GetInstance().ATCReportErrMessage("E10035", {"parameter", "opname"}, + ErrorManager::GetInstance().ATCReportErrMessage("E10017", {"parameter", "opname"}, {"input_fp16_nodes", input_fp16_nodes_vec[i]}); - GELOGE(PARAM_INVALID, "input_fp16_nodes: %s is not a input node name", input_fp16_nodes_vec[i].c_str()); + GELOGE(PARAM_INVALID, "Input parameter[--input_fp16_nodes]'s opname[%s] is not a input opname", + input_fp16_nodes_vec[i].c_str()); return PARAM_INVALID; } if (ge::AttrUtils::SetBool(op_desc, "input_fp16", true)) { @@ -302,14 +306,32 @@ Status SetOutFormatAndDataTypeAttr(ge::OpDescPtr op_desc, const ge::Format forma return domi::SUCCESS; } +bool CheckDigitStr(std::string &str) { + for (char c : str) { + if (!isdigit(c)) { + GELOGE(domi::FAILED, "value[%s] is not positive integer", str.c_str()); + return false; + } + } + return true; +} + Status StringToInt(std::string &str, int32_t &value) { try { + if (!CheckDigitStr(str)) { + GELOGE(PARAM_INVALID, "Invalid of digit string: %s ", str.c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E10001", {"parameter", "value", "reason"}, + {"--output_type", str, "is not positive integer"}); + return PARAM_INVALID; + } value = stoi(str); } catch (std::invalid_argument &) { - GELOGE(PARAM_INVALID, "Invalid of out_nodes: %s ", str.c_str()); + GELOGE(PARAM_INVALID, "Invalid of digit string: %s, catch invalid_argument.", str.c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E10014", {"parameter", "value"}, {"output_type", str}); return PARAM_INVALID; } catch (std::out_of_range &) { - GELOGE(PARAM_INVALID, "Invalid of out_nodes: %s ", str.c_str()); + GELOGE(PARAM_INVALID, "Invalid of digit string: %s, catch out_of_range.", str.c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E10013", {"parameter", "value"}, {"output_type", str}); return PARAM_INVALID; } return SUCCESS; @@ -325,8 +347,9 @@ Status VerifyOutputTypeAndOutNodes(std::vector &out_type_vec) { } for (uint32_t i = 0; i < out_type_vec.size(); ++i) { if (out_nodes_info.find(out_type_vec[i]) == out_nodes_info.end()) { - ErrorManager::GetInstance().ATCReportErrMessage("E10059", {"value"}, {out_type_vec[i]}); - GELOGE(domi::FAILED, "Can not find this node (%s) in out_nodes.", out_type_vec[i].c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E10001", {"parameter", "value", "reason"}, + {"--output_type", out_type_vec[i], kOutputTypeError}); + GELOGE(domi::FAILED, "Invalid value for --output_type[%s], %s.", out_type_vec[i].c_str(), kOutputTypeError); return domi::FAILED; } } @@ -339,9 +362,9 @@ Status ParseOutputType(const std::string &output_type, std::map node_index_type_v = StringUtils::Split(node, ':'); if (node_index_type_v.size() != 3) { // The size must be 3. - ErrorManager::GetInstance().ATCReportErrMessage("E10058", {"value"}, {node}); - GELOGE(PARAM_INVALID, - "The param of output_type is invalid, the correct format is [opname:index:dtype]," - "while the actual input is %s.", - node.c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E10001", {"parameter", "value", "reason"}, + {"--output_type", node, kOutputTypeSample}); + GELOGE(PARAM_INVALID, "Invalid value for --output_type[%s], %s.", node.c_str(), kOutputTypeSample); return domi::FAILED; } ge::DataType tmp_dt; @@ -363,13 +384,15 @@ Status ParseOutputType(const std::string &output_type, std::mapsecond; @@ -396,6 +419,22 @@ Status ParseOutputType(const std::string &output_type, std::mapGetOutputsSize(); + if (index < 0 || index >= out_size) { + GELOGE(domi::FAILED, + "out_node [%s] output index:%d must be smaller " + "than node output size:%d and can not be negative!", + op_desc->GetName().c_str(), index, out_size); + std::string fail_reason = "output index:" + to_string(index) + + " must be smaller than output size:" + to_string(out_size) + " and can not be negative!"; + ErrorManager::GetInstance().ATCReportErrMessage("E10003", {"parameter", "value", "reason"}, + {"out_nodes", op_desc->GetName(), fail_reason}); + return domi::FAILED; + } + return domi::SUCCESS; +} + Status SetOutputNodeInfo(ge::Graph &graph, const std::string &output_type, const std::string &output) { ge::ComputeGraphPtr compute_graph = ge::GraphUtils::GetComputeGraph(graph); GE_CHECK_NOTNULL(compute_graph); @@ -404,7 +443,6 @@ Status SetOutputNodeInfo(ge::Graph &graph, const std::string &output_type, const std::vector output_formats = domi::GetContext().output_formats; std::vector> output_nodes_info; std::vector output_nodes_name; - std::map> out_type_index_map; std::map> out_type_dt_map; if (!output_type.empty()) { @@ -423,6 +461,10 @@ Status SetOutputNodeInfo(ge::Graph &graph, const std::string &output_type, const } auto op_desc = out_node->GetOpDesc(); GE_CHECK_NOTNULL(op_desc); + if (CheckOutNode(op_desc, user_out_nodes[i].second) != SUCCESS) { + GELOGE(domi::FAILED, "Check out node (%s) fail.", user_out_nodes[i].first.c_str()); + return domi::FAILED; + } if (i < output_formats.size()) { if (output_formats[i] == domi::DOMI_TENSOR_NC1HWC0) { GELOGI("The output node [%s] should be set NC1HWC0", user_out_nodes[i].first.c_str()); @@ -445,18 +487,43 @@ Status SetOutputNodeInfo(ge::Graph &graph, const std::string &output_type, const if (user_out_nodes.empty()) { for (ge::NodePtr node : compute_graph->GetDirectNode()) { if (!node->GetInDataNodes().empty() && node->GetOutDataNodes().empty()) { - Status ret = GetOutputLeaf(node, output_nodes_info, output_nodes_name); + Status ret = GetOutputLeaf(node, output_nodes_info); GE_CHK_BOOL_RET_STATUS(ret == SUCCESS, ret, "find leaf fail."); } } } + GetOutputNodesNameAndIndex(output_nodes_info, output_nodes_name); compute_graph->SetGraphOutNodesInfo(output_nodes_info); domi::GetContext().net_out_nodes = output_nodes_name; return domi::SUCCESS; } -Status GetOutputLeaf(NodePtr node, std::vector> &output_nodes_info, - std::vector &output_nodes_name) { +void GetOutputNodesNameAndIndex(std::vector> &output_nodes_info, + std::vector &output_nodes_name) { + output_nodes_name.clear(); + if (domi::GetContext().out_top_names.empty()) { + // tf process, no top name. + for (const auto output_node_info : output_nodes_info) { + std::string node_name = output_node_info.first->GetName(); + int32_t index = output_node_info.second; + output_nodes_name.push_back(node_name + ":" + std::to_string(index)); + } + return; + } + // caffe process, need add top name after node_name:index + for (size_t i = 0; i < output_nodes_info.size(); ++i) { + std::string node_name = output_nodes_info[i].first->GetName(); + int32_t index = output_nodes_info[i].second; + if (i < domi::GetContext().out_top_names.size()) { + output_nodes_name.push_back(node_name + ":" + std::to_string(index) + ":" + domi::GetContext().out_top_names[i]); + } else { + GELOGW("Get top name of node [%s] fail.", node_name.c_str()); + output_nodes_name.push_back(node_name + ":" + std::to_string(index)); + } + } +} + +Status GetOutputLeaf(NodePtr node, std::vector> &output_nodes_info) { ge::OpDescPtr tmpDescPtr = node->GetOpDesc(); if (tmpDescPtr == nullptr) { GELOGE(domi::FAILED, "Get outnode op desc fail."); @@ -466,7 +533,6 @@ Status GetOutputLeaf(NodePtr node, std::vector> if (node->GetType() != NETOUTPUT) { for (size_t index = 0; index < size; ++index) { output_nodes_info.push_back(std::make_pair(node, index)); - output_nodes_name.push_back(node->GetName() + ":" + std::to_string(index)); } } else { const auto in_anchors = node->GetAllInDataAnchors(); @@ -478,7 +544,6 @@ Status GetOutputLeaf(NodePtr node, std::vector> } auto out_node = out_anchor->GetOwnerNode(); output_nodes_info.push_back(std::make_pair(out_node, out_anchor->GetIdx())); - output_nodes_name.push_back(out_node->GetName() + ":" + std::to_string(out_anchor->GetIdx())); } } return SUCCESS; @@ -538,8 +603,9 @@ Status ParseOutNodes(const string &out_nodes) { for (const string &node : nodes_v) { vector key_value_v = StringUtils::Split(node, ':'); if (key_value_v.size() != 2) { // The size must be 2. - ErrorManager::GetInstance().ATCReportErrMessage("E10069", {"param", "value", "supports"}, - {"out_nodes", node, "opname:index"}); + ErrorManager::GetInstance().ATCReportErrMessage( + "E10001", {"parameter", "value", "reason"}, + {"--out_nodes", node, "the correct format is \"node_name1:0;node_name1:1;node_name2:0\""}); GELOGE(PARAM_INVALID, "The input format of --out_nodes is invalid, the correct format is " "\"node_name1:0;node_name1:1;node_name2:0\", while the actual input is %s.", @@ -548,6 +614,12 @@ Status ParseOutNodes(const string &out_nodes) { } auto iter = domi::GetContext().out_nodes_map.find(key_value_v[0]); // stoi: The method may throw an exception: invalid_argument/out_of_range + if (!CheckDigitStr(key_value_v[1])) { + ErrorManager::GetInstance().ATCReportErrMessage("E10001", {"parameter", "value", "reason"}, + {"--out_nodes", out_nodes, "is not positive integer"}); + GELOGE(PARAM_INVALID, "This str must be digit string, while the actual input is %s", out_nodes.c_str()); + return PARAM_INVALID; + } int32_t index = stoi(StringUtils::Trim(key_value_v[1])); if (iter != domi::GetContext().out_nodes_map.end()) { iter->second.emplace_back(index); @@ -561,9 +633,11 @@ Status ParseOutNodes(const string &out_nodes) { } } catch (std::invalid_argument &) { GELOGE(PARAM_INVALID, "Invalid of out_nodes: %s ", out_nodes.c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E10014", {"parameter", "value"}, {"out_nodes", out_nodes}); return PARAM_INVALID; } catch (std::out_of_range &) { GELOGE(PARAM_INVALID, "Invalid of out_nodes: %s ", out_nodes.c_str()); + ErrorManager::GetInstance().ATCReportErrMessage("E10013", {"parameter", "value"}, {"out_nodes", out_nodes}); return PARAM_INVALID; } @@ -575,7 +649,7 @@ Status ParseOutNodes(const string &out_nodes) { /// @param [in] graph Input network graph /// @return SUCCESS: Input parameters are correct; PARAM_INVALID: Input parameters are incorrect /// -static Status CheckOpNameMap(const ComputeGraphPtr &graph) { +static Status CheckOpNameMap(const ComputeGraphPtr &graph, const std::string &op_conf) { GE_CHECK_NOTNULL(graph); unordered_map graphNodeTypes; for (const NodePtr &node : graph->GetAllNodes()) { @@ -590,7 +664,9 @@ static Status CheckOpNameMap(const ComputeGraphPtr &graph) { GE_RT_PARAM_INVALID_WITH_LOG_IF_TRUE(propertiesMap.empty(), "op_name_map file is empty, please check file!"); for (auto iter = propertiesMap.begin(); iter != propertiesMap.end(); iter++) { GE_IF_BOOL_EXEC(graphNodeTypes.find(iter->second) == graphNodeTypes.end(), - ErrorManager::GetInstance().ATCReportErrMessage("E10060", {"parameter"}, {"op_name_map"}); + ErrorManager::GetInstance().ATCReportErrMessage( + "E10003", {"parameter", "value", "reason"}, + {"op_name_map", op_conf, "type[" + iter->second + "] is not found in model"}); GELOGE(PARAM_INVALID, "Invalid parameter for op_name_map."); return PARAM_INVALID;); } return SUCCESS; @@ -647,7 +723,8 @@ FMK_FUNC_HOST_VISIBILITY Status ParseGraph(ge::Graph &graph, const std::mapCreateModelParser(framework); GE_CHK_BOOL_RET_STATUS(model_parser != nullptr, FAILED, "ATC create model parser ret fail, framework:%d.", framework); return model_parser->ToJson(model_file, json_file); } - ErrorManager::GetInstance().ATCReportErrMessage("E10045", {"parameter"}, {"model"}); + ErrorManager::GetInstance().ATCReportErrMessage( + "E10001", {"parameter", "value", "reason"}, + {"--framework", std::to_string(framework), "only support 0(Caffe) 3(TensorFlow)"}); GELOGE(PARAM_INVALID, "Input parameter[--framework] is mandatory and it's value must be: 0(Caffe) 3(TensorFlow)."); return PARAM_INVALID; } diff --git a/src/ge/session/session_manager.cc b/src/ge/session/session_manager.cc index c3439b0b..68a8aa70 100644 --- a/src/ge/session/session_manager.cc +++ b/src/ge/session/session_manager.cc @@ -51,11 +51,11 @@ Status SessionManager::Finalize() { return SUCCESS; } -Status SessionManager::SetrtContext(rtContext_t rt_context) { +Status SessionManager::SetRtContext(SessionId session_id, rtContext_t rt_context) { GELOGI("set rt_context RT_CTX_NORMAL_MODE, device id:%u.", GetContext().DeviceId()); GE_CHK_RT_RET(rtCtxCreate(&rt_context, RT_CTX_NORMAL_MODE, static_cast(GetContext().DeviceId()))); GE_CHK_RT_RET(rtCtxSetCurrent(rt_context)); - RtContextUtil::GetInstance().AddrtContext(rt_context); + RtContextUtil::GetInstance().AddRtContext(session_id, rt_context); return SUCCESS; } @@ -85,7 +85,7 @@ Status SessionManager::CreateSession(const std::map &o session_id = next_session_id; // create a context - ret = SetrtContext(rtContext_t()); + ret = SetRtContext(session_id, rtContext_t()); return ret; } @@ -106,7 +106,7 @@ Status SessionManager::DestroySession(SessionId session_id) { } // Unified destruct rt_context - RtContextUtil::GetInstance().DestroyrtContexts(); + RtContextUtil::GetInstance().DestroyRtContexts(session_id); SessionPtr innerSession = it->second; Status ret = innerSession->Finalize(); @@ -300,4 +300,4 @@ bool SessionManager::IsGraphNeedRebuild(SessionId session_id, uint32_t graph_id) } return innerSession->IsGraphNeedRebuild(graph_id); } -}; // namespace ge +} // namespace ge diff --git a/src/ge/session/session_manager.h b/src/ge/session/session_manager.h index 5cce5214..5cdb849f 100644 --- a/src/ge/session/session_manager.h +++ b/src/ge/session/session_manager.h @@ -33,7 +33,6 @@ class SessionManager { friend class GELib; public: - Status SetrtContext(rtContext_t rtContext); /// /// @ingroup ge_session /// @brief create session @@ -163,10 +162,12 @@ class SessionManager { Status GetNextSessionId(SessionId &next_session_id); + Status SetRtContext(SessionId session_id, rtContext_t rtContext); + std::map session_manager_map_; std::mutex mutex_; bool init_flag_ = false; }; -}; // namespace ge +} // namespace ge #endif // GE_SESSION_SESSION_MANAGER_H_ diff --git a/src/ge/single_op/single_op.cc b/src/ge/single_op/single_op.cc index 9578471a..e2d756df 100644 --- a/src/ge/single_op/single_op.cc +++ b/src/ge/single_op/single_op.cc @@ -50,9 +50,13 @@ Status SingleOp::ValidateArgs(const std::vector &inputs, const std:: for (size_t i = 0; i < num_inputs; ++i) { // preventing from read out of bound size_t aligned_size = GetAlignedSize(inputs[i].length); + GELOGI("Input [%zu], aligned_size:%zu, inputs.length:%u, input_sizes_:%u", i, aligned_size, inputs[i].length, + input_sizes_[i]); if (aligned_size < input_sizes_[i]) { - GELOGE(PARAM_INVALID, "Input size mismatch. index = %zu, model expect %zu, but given %zu(after align)", i, - input_sizes_[i], aligned_size); + GELOGE(PARAM_INVALID, + "Input size mismatch. index = %zu, model expect %zu," + " but given %zu(after align)", + i, input_sizes_[i], aligned_size); return PARAM_INVALID; } } @@ -66,9 +70,13 @@ Status SingleOp::ValidateArgs(const std::vector &inputs, const std:: for (size_t i = 0; i < num_outputs; ++i) { // preventing from write out of bound size_t aligned_size = GetAlignedSize(outputs[i].length); + GELOGI("Output [%zu], aligned_size:%zu, outputs.length:%u, output_sizes_:%u", i, aligned_size, outputs[i].length, + output_sizes_[i]); if (aligned_size < output_sizes_[i]) { - GELOGE(PARAM_INVALID, "Output size mismatch. index = %zu, model expect %zu, but given %zu(after align)", i, - output_sizes_[i], aligned_size); + GELOGE(PARAM_INVALID, + "Output size mismatch. index = %zu, model expect %zu," + "but given %zu(after align)", + i, output_sizes_[i], aligned_size); return PARAM_INVALID; } } @@ -81,23 +89,11 @@ Status SingleOp::GetArgs(const std::vector &inputs, const std::vecto if (use_physical_addr_) { for (auto &input : inputs) { auto *addr = reinterpret_cast(input.data); - size_t aligned_size = GetAlignedSize(input.length); - auto ret = ModelUtils::ConvertVirtualAddressToPhysical(addr, aligned_size, addr); - if (ret != SUCCESS) { - GELOGE(ret, "ConvertVirtualAddressToPhysical failed. Arg index = %zu", arg_index); - return ret; - } args_[arg_index++] = reinterpret_cast(addr); } for (auto &output : outputs) { auto *addr = reinterpret_cast(output.data); - size_t aligned_size = GetAlignedSize(output.length); - auto ret = ModelUtils::ConvertVirtualAddressToPhysical(addr, aligned_size, addr); - if (ret != SUCCESS) { - GELOGE(ret, "ConvertVirtualAddressToPhysical failed. Arg index = %zu", arg_index); - return ret; - } args_[arg_index++] = reinterpret_cast(addr); } } else { @@ -117,6 +113,7 @@ Status SingleOp::UpdateArgs(const std::vector &inputs, const std::ve if (ret != SUCCESS) { return ret; } + // update tbe task args size_t num_args = arg_table_.size(); for (size_t i = 0; i < num_args; ++i) { std::vector &ptr_to_arg_in_tasks = arg_table_[i]; @@ -129,18 +126,34 @@ Status SingleOp::UpdateArgs(const std::vector &inputs, const std::ve *arg_addr = args_[i]; } } + // update aicpu_TF or aicpu_CC args for (auto &task : tasks_) { + size_t io_addr_num = args_.size(); if (task->GetOpTaskType() == OP_TASK_AICPU) { - GELOGD("Update aicpu task args"); + GELOGD("Update aicpu_TF task args"); AiCpuTask *task_aicpu = dynamic_cast(task); GE_CHECK_NOTNULL(task_aicpu); - auto *dstIOAddr = const_cast(reinterpret_cast(task_aicpu->GetIOAddr())); - auto rt_ret = rtMemcpyAsync(dstIOAddr, sizeof(uint64_t) * args_.size(), &args_[0], + auto *dst_io_addr = const_cast(reinterpret_cast(task_aicpu->GetIOAddr())); + GE_CHECK_NOTNULL(dst_io_addr); + auto rt_ret = rtMemcpyAsync(dst_io_addr, sizeof(uint64_t) * args_.size(), &args_[0], sizeof(uint64_t) * args_.size(), RT_MEMCPY_HOST_TO_DEVICE_EX, stream_); if (rt_ret != RT_ERROR_NONE) { GELOGE(RT_FAILED, "rtMemcpyAsync addresses failed, ret = %d", rt_ret); return RT_FAILED; } + } else if (task->GetOpTaskType() == OP_TASK_AICPUCC) { + GELOGD("Update aicpu_CC task args"); + AiCpuCCTask *task_aicpu_cc = dynamic_cast(task); + GE_CHECK_NOTNULL(task_aicpu_cc); + const uintptr_t *task_io_addr = reinterpret_cast(task_aicpu_cc->GetIOAddr()); + GE_CHECK_NOTNULL(task_io_addr); + auto io_addr = reinterpret_cast(const_cast(task_io_addr)); + for (size_t i = 0; i < io_addr_num; ++i) { + io_addr[i] = reinterpret_cast(args_[i]); + } + } else { + GELOGW("Only TF_kernel aicpu and aicpu_CC are supported, but got %u", task->GetOpTaskType()); + continue; } } return SUCCESS; @@ -164,6 +177,7 @@ FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY Status SingleOp::ExecuteAsync(c return ret; } } + return ret; } diff --git a/src/ge/single_op/single_op_model.cc b/src/ge/single_op/single_op_model.cc index 9decdf75..b72a41fc 100644 --- a/src/ge/single_op/single_op_model.cc +++ b/src/ge/single_op/single_op_model.cc @@ -28,6 +28,7 @@ #include "graph/utils/tensor_utils.h" #include "runtime/rt.h" #include "task/aicpu_task_builder.h" +#include "task/aicpu_kernel_task_builder.h" #include "task/tbe_task_builder.h" using domi::TaskDef; @@ -198,11 +199,6 @@ Status SingleOpModel::SetInputsAndOutputs(SingleOp &single_op) { int arg_index = 0; for (size_t i = 0; i < input_offset_list_.size(); ++i) { auto *addr = model_params_.mem_base + input_offset_list_[i]; - auto ret = ModelUtils::ConvertVirtualAddressToPhysical(addr, input_sizes_[i], addr); - if (ret != SUCCESS) { - GELOGE(ret, "ConvertVirtualAddressToPhysical failed. Input index = %zu", i); - return ret; - } model_params_.addr_mapping_.emplace(reinterpret_cast(addr), arg_index++); single_op.input_sizes_.emplace_back(input_sizes_[i]); single_op.input_addr_list_.emplace_back(addr); @@ -210,11 +206,6 @@ Status SingleOpModel::SetInputsAndOutputs(SingleOp &single_op) { for (size_t i = 0; i < output_offset_list_.size(); ++i) { auto *addr = model_params_.mem_base + output_offset_list_[i]; - auto ret = ModelUtils::ConvertVirtualAddressToPhysical(addr, output_sizes_[i], addr); - if (ret != SUCCESS) { - GELOGE(ret, "ConvertVirtualAddressToPhysical failed. Output index = %zu", i); - return ret; - } model_params_.addr_mapping_.emplace(reinterpret_cast(addr), arg_index++); single_op.output_sizes_.emplace_back(output_sizes_[i]); single_op.output_addr_list_.emplace_back(addr); @@ -234,16 +225,31 @@ Status SingleOpModel::BuildTaskList(SingleOp &single_op) { task_def.DebugString().c_str()); auto task_type = static_cast(task_def.type()); if (task_type == RT_MODEL_TASK_KERNEL) { - GELOGD("Building TBE task"); - OpTask *task = nullptr; - auto ret = BuildKernelTask(task_def.kernel(), single_op, &task); - if (ret != SUCCESS) { - return ret; + const domi::KernelDef &kernel_def = task_def.kernel(); + const auto &context = kernel_def.context(); + auto kernel_type = static_cast(context.kernel_type()); + if (kernel_type == cce::ccKernelType::TE) { + GELOGD("Building TBE task"); + OpTask *task = nullptr; + auto ret = BuildKernelTask(task_def.kernel(), single_op, &task); + if (ret != SUCCESS) { + return ret; + } + single_op.tasks_.emplace_back(task); + } else if (kernel_type == cce::ccKernelType::AI_CPU) { + GELOGD("Building AICPU_CC task"); + OpTask *task = nullptr; + auto ret = BuildCpuKernelTask(task_def.kernel(), &task); + if (ret != SUCCESS) { + return ret; + } + single_op.tasks_.emplace_back(task); + } else { + GELOGE(UNSUPPORTED, "Only TBE kernel and AI_CPU kernek are supported, but got %u", context.kernel_type()); + return UNSUPPORTED; } - - single_op.tasks_.emplace_back(task); } else if (task_type == RT_MODEL_TASK_KERNEL_EX) { - GELOGD("Building AICPU task"); + GELOGD("Building AICPU_TF task"); OpTask *task = nullptr; auto ret = BuildKernelExTask(task_def.kernel_ex(), single_op, &task); if (ret != SUCCESS) { @@ -281,12 +287,6 @@ void SingleOpModel::ParseArgTable(TbeOpTask *task, SingleOp &op) { Status SingleOpModel::BuildKernelTask(const domi::KernelDef &kernel_def, SingleOp &single_op, OpTask **task) { GE_CHECK_NOTNULL(task); const auto &context = kernel_def.context(); - auto kernel_type = static_cast(context.kernel_type()); - if (kernel_type != cce::ccKernelType::TE) { - GELOGE(UNSUPPORTED, "Only TBE kernel is supported, but got %u", context.kernel_type()); - return UNSUPPORTED; - } - auto iter = op_list_.find(context.op_index()); if (iter == op_list_.end()) { GELOGE(INTERNAL_ERROR, "op desc not found. op index = %u", context.op_index()); @@ -323,13 +323,13 @@ Status SingleOpModel::BuildKernelExTask(const domi::KernelExDef &kernel_def, Sin std::unique_ptr aicpu_task(new (std::nothrow) AiCpuTask()); if (aicpu_task == nullptr) { - GELOGE(MEMALLOC_FAILED, "create aicpu op task failed"); + GELOGE(MEMALLOC_FAILED, "create aicpu_TF op task failed"); return MEMALLOC_FAILED; } auto builder = AiCpuTaskBuilder(iter->second, kernel_def); auto ret = builder.BuildTask(*aicpu_task, model_params_); if (ret != SUCCESS) { - GELOGE(ret, "build aicpu op task failed"); + GELOGE(ret, "build aicpu_TF op task failed"); return ret; } @@ -337,6 +337,24 @@ Status SingleOpModel::BuildKernelExTask(const domi::KernelExDef &kernel_def, Sin return SUCCESS; } +Status SingleOpModel::BuildCpuKernelTask(const domi::KernelDef &kernel_def, OpTask **task) { + std::unique_ptr aicpucc_task(new (std::nothrow) AiCpuCCTask()); + if (aicpucc_task == nullptr) { + GELOGE(MEMALLOC_FAILED, "create aicpu_CC op task failed"); + return MEMALLOC_FAILED; + } + + auto builder = AiCpuCCTaskBuilder(kernel_def); + auto ret = builder.BuildTask(*aicpucc_task); + if (ret != SUCCESS) { + GELOGE(ret, "build aicpu_CC op task failed"); + return ret; + } + + *task = aicpucc_task.release(); + return SUCCESS; +} + Status SingleOpModel::BuildOp(StreamResource &resource, SingleOp &single_op) { auto ret = InitModelMem(resource); if (ret != SUCCESS) { diff --git a/src/ge/single_op/single_op_model.h b/src/ge/single_op/single_op_model.h index 4d8aae30..3b8c2616 100644 --- a/src/ge/single_op/single_op_model.h +++ b/src/ge/single_op/single_op_model.h @@ -64,6 +64,7 @@ class SingleOpModel { Status BuildTaskList(SingleOp &single_op); Status BuildKernelTask(const domi::KernelDef &kernel_def, SingleOp &single_op, OpTask **task); Status BuildKernelExTask(const domi::KernelExDef &kernel_def, SingleOp &single_op, OpTask **task); + Status BuildCpuKernelTask(const domi::KernelDef &kernel_def, OpTask **task); static void ParseOpModelParams(ModelHelper &model_helper, SingleOpModelParam ¶m); void ParseArgTable(TbeOpTask *task, SingleOp &op); diff --git a/src/ge/single_op/task/aicpu_kernel_task_builder.cc b/src/ge/single_op/task/aicpu_kernel_task_builder.cc new file mode 100644 index 00000000..936c7b67 --- /dev/null +++ b/src/ge/single_op/task/aicpu_kernel_task_builder.cc @@ -0,0 +1,56 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "single_op/task/aicpu_kernel_task_builder.h" + +namespace ge { +AiCpuCCTaskBuilder::AiCpuCCTaskBuilder(const domi::KernelDef &kernel_def) : kernel_def_(kernel_def) {} + +Status AiCpuCCTaskBuilder::SetKernelArgs(AiCpuCCTask &task) { + size_t aicpu_arg_size = kernel_def_.args_size(); + if (aicpu_arg_size <= 0) { + GELOGE(RT_FAILED, "aicpu_arg_size is invalid, value = %zu", aicpu_arg_size); + return RT_FAILED; + } + void *aicpu_args = malloc(aicpu_arg_size); + if (aicpu_args == nullptr) { + GELOGE(RT_FAILED, "malloc failed, size = %zu", aicpu_arg_size); + return RT_FAILED; + } + + task.SetKernelArgs(aicpu_args, aicpu_arg_size); + auto err = memcpy_s(aicpu_args, aicpu_arg_size, kernel_def_.args().data(), aicpu_arg_size); + if (err != EOK) { + GELOGE(RT_FAILED, "memcpy_s args failed, size = %zu, err = %d", aicpu_arg_size, err); + return RT_FAILED; + } + + task.SetIoAddr(static_cast(aicpu_args) + sizeof(aicpu::AicpuParamHead)); + return SUCCESS; +} + +Status AiCpuCCTaskBuilder::BuildTask(AiCpuCCTask &task) { + auto ret = SetKernelArgs(task); + if (ret != SUCCESS) { + return ret; + } + const std::string &so_name = kernel_def_.so_name(); + const std::string &kernel_name = kernel_def_.kernel_name(); + task.SetSoName(so_name); + task.SetkernelName(kernel_name); + return SUCCESS; +} +} // namespace ge \ No newline at end of file diff --git a/src/ge/single_op/task/aicpu_kernel_task_builder.h b/src/ge/single_op/task/aicpu_kernel_task_builder.h new file mode 100644 index 00000000..c445132e --- /dev/null +++ b/src/ge/single_op/task/aicpu_kernel_task_builder.h @@ -0,0 +1,40 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_SINGLE_OP_TASK_AICPU_KERNEL_TASK_BUILDER_H_ +#define GE_SINGLE_OP_TASK_AICPU_KERNEL_TASK_BUILDER_H_ + +#include +#include "aicpu/common/aicpu_task_struct.h" +#include "single_op/single_op.h" +#include "single_op/single_op_model.h" +#include "runtime/mem.h" + +namespace ge { +class AiCpuCCTaskBuilder { + public: + explicit AiCpuCCTaskBuilder(const domi::KernelDef &kernel_def); + ~AiCpuCCTaskBuilder() = default; + + Status BuildTask(AiCpuCCTask &task); + + private: + Status SetKernelArgs(AiCpuCCTask &task); + const domi::KernelDef &kernel_def_; +}; +} // namespace ge + +#endif // GE_SINGLE_OP_TASK_AICPUCC_TASK_BUILDER_H_ \ No newline at end of file diff --git a/src/ge/single_op/task/aicpu_task_builder.cc b/src/ge/single_op/task/aicpu_task_builder.cc index 1a4c37ca..bc2c76f6 100644 --- a/src/ge/single_op/task/aicpu_task_builder.cc +++ b/src/ge/single_op/task/aicpu_task_builder.cc @@ -129,7 +129,8 @@ Status AiCpuTaskBuilder::BuildTask(ge::AiCpuTask &task, const SingleOpModelParam task.task_info_ = kernel_def_.task_info(); task.workspace_addr_ = ws_addr_vec[0]; + auto debug_info = BuildTaskUtils::GetTaskInfo(op_desc_); + GELOGI("[TASK_INFO] %s %s", task.task_info_.c_str(), debug_info.c_str()); return SUCCESS; } - } // namespace ge diff --git a/src/ge/single_op/task/build_task_utils.cc b/src/ge/single_op/task/build_task_utils.cc index 82b77031..9e97ee57 100644 --- a/src/ge/single_op/task/build_task_utils.cc +++ b/src/ge/single_op/task/build_task_utils.cc @@ -19,7 +19,9 @@ #include "runtime/rt.h" #include "graph/load/new_model_manager/model_utils.h" #include "graph/manager/graph_var_manager.h" +#include "graph/utils/type_utils.h" #include "framework/common/debug/ge_log.h" +#include "framework/common/types.h" namespace ge { namespace { @@ -27,7 +29,7 @@ const uint64_t kSessionId = UINT64_MAX; uint8_t *kVarBase = nullptr; const uint64_t kLogicVarBase = 0; const uint64_t kVarSize = 0; -} +} // namespace std::vector> BuildTaskUtils::GetAddresses(const OpDescPtr &op_desc, const SingleOpModelParam ¶m) { @@ -58,9 +60,46 @@ std::vector BuildTaskUtils::JoinAddresses(const std::vector BuildTaskUtils::GetKernelArgs(const OpDescPtr &op_desc, - const SingleOpModelParam ¶m) { +std::vector BuildTaskUtils::GetKernelArgs(const OpDescPtr &op_desc, const SingleOpModelParam ¶m) { auto addresses = GetAddresses(op_desc, param); return JoinAddresses(addresses); } + +std::string BuildTaskUtils::GetTaskInfo(const OpDescPtr &op_desc) { + std::stringstream ss; + if (op_desc != nullptr) { + auto op_type = op_desc->GetType(); + if (op_type == ge::NETOUTPUT || op_type == ge::DATA) { + return ss.str(); + } + // Conv2D IN[DT_FLOAT16 NC1HWC0[256, 128, 7, 7, 16],DT_FLOAT16 FRACTAL_Z[128, 32, 16, 16]] + // OUT[DT_FLOAT16 NC1HWC0[256, 32, 7, 7, 16]] + ss << op_type << " IN["; + for (uint32_t idx = 0; idx < op_desc->GetInputsSize(); idx++) { + const GeTensorDescPtr &input = op_desc->MutableInputDesc(idx); + ss << TypeUtils::DataTypeToSerialString(input->GetDataType()) << " "; + ss << TypeUtils::FormatToSerialString(input->GetFormat()); + ss << VectorToString(input->GetShape().GetDims()); + if (idx < op_desc->GetInputsSize() - 1) { + ss << ","; + } + } + ss << "] OUT["; + + for (uint32_t idx = 0; idx < op_desc->GetOutputsSize(); idx++) { + const GeTensorDescPtr &output = op_desc->MutableOutputDesc(idx); + ss << TypeUtils::DataTypeToSerialString(output->GetDataType()) << " "; + Format out_format = output->GetFormat(); + const GeShape &out_shape = output->GetShape(); + const auto &dims = out_shape.GetDims(); + ss << TypeUtils::FormatToSerialString(out_format); + ss << VectorToString(dims); + if (idx < op_desc->GetOutputsSize() - 1) { + ss << ","; + } + } + ss << "]\n"; + } + return ss.str(); +} } // namespace ge diff --git a/src/ge/single_op/task/build_task_utils.h b/src/ge/single_op/task/build_task_utils.h index a5030e69..f5885fd2 100644 --- a/src/ge/single_op/task/build_task_utils.h +++ b/src/ge/single_op/task/build_task_utils.h @@ -18,6 +18,7 @@ #define GE_SINGLE_OP_TASK_BUILD_TASK_UTILS_H_ #include +#include #include "graph/op_desc.h" #include "single_op/single_op.h" @@ -31,6 +32,21 @@ class BuildTaskUtils { static std::vector> GetAddresses(const OpDescPtr &op_desc, const SingleOpModelParam ¶m); static std::vector JoinAddresses(const std::vector> &addresses); static std::vector GetKernelArgs(const OpDescPtr &op_desc, const SingleOpModelParam ¶m); + static std::string GetTaskInfo(const OpDescPtr &op_desc); + template + static std::string VectorToString(const std::vector &values) { + std::stringstream ss; + ss << '['; + auto size = values.size(); + for (size_t i = 0; i < size; ++i) { + ss << values[i]; + if (i != size - 1) { + ss << ", "; + } + } + ss << ']'; + return ss.str(); + } }; } // namespace ge #endif // GE_SINGLE_OP_TASK_BUILD_TASK_UTILS_H_ diff --git a/src/ge/single_op/task/op_task.cc b/src/ge/single_op/task/op_task.cc index e93fad71..19e8b6a4 100644 --- a/src/ge/single_op/task/op_task.cc +++ b/src/ge/single_op/task/op_task.cc @@ -16,10 +16,18 @@ #include "single_op/task/op_task.h" +#include +#include + #include "runtime/rt.h" #include "framework/common/debug/ge_log.h" namespace ge { +namespace { +constexpr int kLaunchRetryTimes = 1000; +constexpr int kSleepTime = 10; +} // namespace + void TbeOpTask::SetStubFunc(const std::string &name, const void *stub_func) { this->stub_name_ = name; this->stub_func_ = stub_func; @@ -53,12 +61,20 @@ Status TbeOpTask::LaunchKernel(rtStream_t stream) { GELOGD("To invoke rtKernelLaunch. task = %s, block_dim = %u", this->stub_name_.c_str(), block_dim_); auto *sm_desc = reinterpret_cast(sm_desc_); auto ret = rtKernelLaunch(stub_func_, block_dim_, args_, static_cast(arg_size_), sm_desc, stream); + int retry_times = 0; + while (ret != RT_ERROR_NONE && retry_times < kLaunchRetryTimes) { + retry_times++; + GELOGW("Retry after %d ms, retry_times: %d", kSleepTime, retry_times); + std::this_thread::sleep_for(std::chrono::milliseconds(kSleepTime)); + ret = rtKernelLaunch(stub_func_, block_dim_, args_, arg_size_, sm_desc, stream); + } + if (ret != RT_ERROR_NONE) { GELOGE(RT_FAILED, "Invoke rtKernelLaunch failed. ret = %d, task = %s", ret, this->stub_name_.c_str()); return RT_FAILED; } - GELOGD("Invoke rtKernelLaunch succeeded. task = %s", this->stub_name_.c_str()); + GELOGI("[TASK_INFO] %s", this->stub_name_.c_str()); return SUCCESS; } @@ -88,8 +104,49 @@ Status AiCpuTask::LaunchKernel(rtStream_t stream) { GELOGE(RT_FAILED, "Invoke rtKernelLaunch failed. ret = %d, task = %s", ret, this->op_type_.c_str()); return RT_FAILED; } + GELOGI("[TASK_INFO] %s", this->task_info_.c_str()); + return SUCCESS; +} + +void AiCpuCCTask::SetKernelArgs(void *args, size_t arg_size) { + args_ = args; + arg_size_ = arg_size; + // the blockdim value is defult "1" for rtCpuKernelLaunch + block_dim_ = 1; +} + +void AiCpuCCTask::SetSoName(const std::string &so_name) { so_name_ = so_name; } + +void AiCpuCCTask::SetkernelName(const std::string &kernel_Name) { kernel_name_ = kernel_Name; } + +void AiCpuCCTask::SetIoAddr(void *io_addr) { io_addr_ = io_addr; } + +const void *AiCpuCCTask::GetIOAddr() const { return io_addr_; } + +const void *AiCpuCCTask::GetArgs() const { return args_; } + +size_t AiCpuCCTask::GetArgSize() const { return arg_size_; } + +AiCpuCCTask::~AiCpuCCTask() { + if (args_ != nullptr) { + free(args_); + args_ = nullptr; + } +} - GELOGD("Invoke rtKernelLaunch succeeded. task = %s", this->op_type_.c_str()); +Status AiCpuCCTask::LaunchKernel(rtStream_t stream) { + GELOGI("To invoke rtCpuKernelLaunch. block_dim = %u, so_name is %s, kernel_name is %s", block_dim_, so_name_.data(), + kernel_name_.data()); + // sm_desc is nullptr, because l2 buffer does not support + auto *sm_desc = reinterpret_cast(sm_desc_); + auto ret = + rtCpuKernelLaunch(static_cast(so_name_.data()), static_cast(kernel_name_.data()), + block_dim_, args_, static_cast(arg_size_), sm_desc, stream); + if (ret != RT_ERROR_NONE) { + GELOGE(RT_FAILED, "Invoke rtCpuKernelLaunch failed. ret = %d", ret); + return RT_FAILED; + } + GELOGD("Invoke rtCpuKernelLaunch succeeded"); return SUCCESS; } } // namespace ge diff --git a/src/ge/single_op/task/op_task.h b/src/ge/single_op/task/op_task.h index 168a71b3..fd4cc96f 100644 --- a/src/ge/single_op/task/op_task.h +++ b/src/ge/single_op/task/op_task.h @@ -28,6 +28,7 @@ namespace ge { enum OpTaskType { OP_TASK_TBE = 0, OP_TASK_AICPU, + OP_TASK_AICPUCC, OP_TASK_INVALID, }; @@ -79,6 +80,34 @@ class AiCpuTask : public OpTask { std::string op_type_; void *io_addr_ = nullptr; }; + +class AiCpuCCTask : public OpTask { + public: + AiCpuCCTask() = default; + ~AiCpuCCTask() override; + AiCpuCCTask(const AiCpuCCTask &) = delete; + AiCpuCCTask &operator=(const AiCpuCCTask &) = delete; + + Status LaunchKernel(rtStream_t stream) override; + OpTaskType GetOpTaskType() override { return OP_TASK_AICPUCC; } + const void *GetIOAddr() const; + const void *GetArgs() const; + void SetKernelArgs(void *args, size_t arg_size); + void SetSoName(const std::string &so_name); + void SetkernelName(const std::string &kernel_Name); + void SetIoAddr(void *io_addr); + size_t GetArgSize() const; + + private: + friend class AiCpuCCTaskBuilder; + std::string so_name_; + std::string kernel_name_; + void *args_ = nullptr; + size_t arg_size_ = 0; + uint32_t block_dim_ = 1; + void *sm_desc_ = nullptr; + void *io_addr_ = nullptr; +}; } // namespace ge #endif // GE_SINGLE_OP_TASK_OP_TASK_H_ diff --git a/src/ge/single_op/task/tbe_task_builder.cc b/src/ge/single_op/task/tbe_task_builder.cc index c0f6877f..a422fb96 100644 --- a/src/ge/single_op/task/tbe_task_builder.cc +++ b/src/ge/single_op/task/tbe_task_builder.cc @@ -290,6 +290,8 @@ Status TbeTaskBuilder::BuildTask(TbeOpTask &task, const SingleOpModelParam ¶ if (ret != SUCCESS) { return ret; } + auto task_info = BuildTaskUtils::GetTaskInfo(op_desc_); + GELOGI("[TASK_INFO] %s %s", stub_name_.c_str(), task_info.c_str()); void *stub_func = nullptr; auto rtRet = rtGetFunctionByName(stub_name_.c_str(), &stub_func); diff --git a/tests/depends/cce/src/op_kernel_registry.cc b/tests/depends/cce/src/op_kernel_registry.cc index 9bb32a31..5ccd1391 100644 --- a/tests/depends/cce/src/op_kernel_registry.cc +++ b/tests/depends/cce/src/op_kernel_registry.cc @@ -1,19 +1,3 @@ -/** - * Copyright 2019 Huawei Technologies Co., Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - #include "register/op_kernel_registry.h" namespace ge { diff --git a/third_party/fwkacllib/inc/hccl/base.h b/third_party/fwkacllib/inc/hccl/base.h index 74163baf..1d83d7bf 100644 --- a/third_party/fwkacllib/inc/hccl/base.h +++ b/third_party/fwkacllib/inc/hccl/base.h @@ -102,6 +102,11 @@ typedef enum tagHcclDataType { HCCL_DATA_TYPE_RESERVED /**< reserved */ } hcclDataType_t; +constexpr u32 HCCL_UNIQUE_ID_BYTES = 2060; // 2060: unique id length +using hcclUniqueId = struct hcclUniqueIdDef { + char internal[HCCL_UNIQUE_ID_BYTES]; +}; + const u32 HCCL_MAX_SEGMENT_NUM = 8; // The max number of gradient segments. /** @@ -120,6 +125,12 @@ enum GradSplitForceMode { FORCE_RESERVED /**< reserved */ }; +enum OriginalGraphShapeType { + KNOWN_SHAPE, + UNKNOWN_SHAPE, + SHAPE_RESERVED /**< reserved */ +}; + /** * @brief stream handle. */ diff --git a/third_party/fwkacllib/inc/hccl/hcom.h b/third_party/fwkacllib/inc/hccl/hcom.h index a448d411..19bf4fb3 100644 --- a/third_party/fwkacllib/inc/hccl/hcom.h +++ b/third_party/fwkacllib/inc/hccl/hcom.h @@ -22,7 +22,6 @@ #ifndef HCOM_H_ #define HCOM_H_ -#include #include #ifdef __cplusplus @@ -246,8 +245,9 @@ hcclResult_t hcom_receive(const char *tag, void *outputPtr, u64 count, hcclDataT * @param segmentIdx A list identifying the index of end gradient in each segment. * @return hcclResult_t */ -hcclResult_t hcom_get_split_strategy(const char *group, const struct model_feature *feature, - u32 maxSegmentNum, u32 *segmentNum, u32 *segmentIdx, GradSplitForceMode force = FORCE_NONE); +hcclResult_t hcom_get_split_strategy(const char *group, const struct model_feature *feature, u32 maxSegmentNum, + u32 *segmentNum, u32 *segmentIdx, GradSplitForceMode force = FORCE_NONE, + OriginalGraphShapeType shapeType = KNOWN_SHAPE); /** * @brief Set the gradient split strategy with in the group, according to gradient index. diff --git a/third_party/fwkacllib/inc/mmpa/sub_inc/mmpa_linux.h b/third_party/fwkacllib/inc/mmpa/sub_inc/mmpa_linux.h index ce83d143..6ac8f8f6 100644 --- a/third_party/fwkacllib/inc/mmpa/sub_inc/mmpa_linux.h +++ b/third_party/fwkacllib/inc/mmpa/sub_inc/mmpa_linux.h @@ -344,6 +344,8 @@ extern INT32 mmRealPath(const CHAR *path, CHAR *realPath, INT32 realPathLen); extern INT32 mmDup2(INT32 oldFd, INT32 newFd); +extern INT32 mmDup(INT32 fd); + extern INT32 mmUnlink(const CHAR *filename); extern INT32 mmChmod(const CHAR *filename, INT32 mode); diff --git a/third_party/fwkacllib/inc/mmpa/sub_inc/mmpa_win.h b/third_party/fwkacllib/inc/mmpa/sub_inc/mmpa_win.h index ef15f371..68a70c27 100644 --- a/third_party/fwkacllib/inc/mmpa/sub_inc/mmpa_win.h +++ b/third_party/fwkacllib/inc/mmpa/sub_inc/mmpa_win.h @@ -378,6 +378,7 @@ _declspec(dllexport) INT32 mmGetRealPath(CHAR *path, CHAR *realPath); _declspec(dllexport) INT32 mmRealPath(const CHAR *path, CHAR *realPath, INT32 realPathLen); _declspec(dllexport) INT32 mmDup2(INT32 oldFd, INT32 newFd); +_declspec(dllexport) INT32 mmDup(INT32 fd); _declspec(dllexport) INT32 mmUnlink(const CHAR *filename); _declspec(dllexport) INT32 mmChmod(const CHAR *filename, INT32 mode); _declspec(dllexport) INT32 mmFileno(FILE *stream); diff --git a/third_party/fwkacllib/inc/ops/all_ops.h b/third_party/fwkacllib/inc/ops/all_ops.h index 031e955c..c30bf32b 100644 --- a/third_party/fwkacllib/inc/ops/all_ops.h +++ b/third_party/fwkacllib/inc/ops/all_ops.h @@ -31,7 +31,9 @@ #include "functional_ops.h" #include "get_data_ops.h" #include "hcom_ops.h" +#include "hvd_ops.h" #include "image_ops.h" +#include "internal_ops.h" #include "linalg_ops.h" #include "logging_ops.h" #include "lookup_ops.h" diff --git a/third_party/fwkacllib/inc/ops/array_ops.h b/third_party/fwkacllib/inc/ops/array_ops.h index 0d2a05a3..7c6f9b2c 100644 --- a/third_party/fwkacllib/inc/ops/array_ops.h +++ b/third_party/fwkacllib/inc/ops/array_ops.h @@ -1084,6 +1084,43 @@ REG_OP(TransShape) .ATTR(outShape,ListInt ,{}) .OP_END_FACTORY_REG(TransShape); +/** +*@brief Computes the (possibly normalized) Levenshtein Edit Distance. + +*@par Inputs: +*@li hypothesis_indices: The indices of the hypothesis list SparseTensor.\n +This is an N x R int64 matrix. +*@li hypothesis_shape: The values of the hypothesis list SparseTensor.\n +This is an N-length vector. +*@li hypothesis_shape: The shape of the hypothesis list SparseTensor.\n +This is an R-length vector. +*@li truth_indices: The indices of the truth list SparseTensor.\n +This is an M x R int64 matrix. +*@li truth_shape: The values of the truth list SparseTensor.\n +This is an M-length vector. +*@li truth_shape: The shape of the truth list SparseTensor.\n +This is an R-length vector + +*@par Attributes: +*@li normalize: boolean (if true, edit distances are normalized by length of truth). + +*@par Outputs: +*@li output: A dense float tensor with rank R - 1. + +*@par Third-party framework compatibility +* Compatible with TensorFlow EditDistance operator. +*/ +REG_OP(EditDistance) + .INPUT(hypothesis_indices, TensorType({DT_INT64})) + .INPUT(hypothesis_values, TensorType::BasicType()) + .INPUT(hypothesis_shape, TensorType({DT_INT64})) + .INPUT(truth_indices, TensorType({DT_INT64})) + .INPUT(truth_values, TensorType::BasicType()) + .INPUT(truth_shape, TensorType({DT_INT64})) + .ATTR(normalize, Bool, true) + .OUTPUT(output, TensorType({DT_FLOAT})) + .OP_END_FACTORY_REG(EditDistance) + } // namespace ge #endif // GE_OP_ARRAY_OPS_H_ diff --git a/third_party/fwkacllib/inc/ops/ctc_ops.h b/third_party/fwkacllib/inc/ops/ctc_ops.h index 00485a14..74b797f3 100644 --- a/third_party/fwkacllib/inc/ops/ctc_ops.h +++ b/third_party/fwkacllib/inc/ops/ctc_ops.h @@ -50,7 +50,6 @@ If not specified, defaults to true *@par Third-party framework compatibility * Compatible with TensorFlow CTCLoss operator. */ - REG_OP(CTCLoss) .INPUT(inputs, TensorType({DT_FLOAT, DT_DOUBLE})) .INPUT(labels_indices, TensorType({DT_INT64})) @@ -63,6 +62,77 @@ REG_OP(CTCLoss) .ATTR(ignore_longer_outputs_than_inputs, Bool, false) .OP_END_FACTORY_REG(CTCLoss) +/** +*@brief Performs greedy decoding on the logits given in inputs. + +*@par Inputs: +*@li inputs: 3-D, shape: `(max_time x batch_size x num_classes)`, the logits. +*@li sequence_length: A vector containing sequence lengths, size `(batch_size)`. + +*@par Attributes: +*@li merge_repeated: If True, merge repeated classes in output. + +*@par Outputs: +*@li decoded_indices: Indices matrix, size `(total_decoded_outputs x 2)`,\n +of a `SparseTensor`. The rows store: [batch, time]. +*@li decoded_values: Values vector, size: `(total_decoded_outputs)`,\n +of a `SparseTensor`. The vector stores the decoded classes. +*@li decoded_shape: Shape vector, size `(2)`, of the decoded SparseTensor.\n +Values are: `[batch_size, max_decoded_length]`. +*@li log_probability: Matrix, size `(batch_size x 1)`, containing sequence\n +log-probabilities. + +*@par Third-party framework compatibility +* Compatible with TensorFlow CTCGreedyDecoder operator. +*/ +REG_OP(CTCGreedyDecoder) + .INPUT(inputs, TensorType({DT_FLOAT, DT_DOUBLE})) + .INPUT(sequence_length, TensorType({DT_INT32})) + .ATTR(merge_repeated, Bool, false) + .OUTPUT(decoded_indices, TensorType({DT_INT64})) + .OUTPUT(decoded_values, TensorType({DT_INT64})) + .OUTPUT(decoded_shape, TensorType({DT_INT64})) + .OUTPUT(log_probability, TensorType({DT_FLOAT, DT_DOUBLE})) + .OP_END_FACTORY_REG(CTCGreedyDecoder) + +/** +*@brief Performs beam search decoding on the logits given in input. + +*@par Inputs: +*@li inputs: 3-D, shape: `(max_time x batch_size x num_classes)`, the logits. +*@li sequence_length: A vector containing sequence lengths, size `(batch_size)`. + +*@par Attributes: +*@li merge_repeated: If True, merge repeated classes in output. + +*@par Outputs: +*@li decoded_indices: A list (length: top_paths) of indices matrices. Matrix j,\n +size `(total_decoded_outputs[j] x 2)`, has indices of a\n +`SparseTensor`. The rows store: [batch, time]. +*@li decoded_values: A list (length: top_paths) of values vectors. Vector j,\n +size `(length total_decoded_outputs[j])`, has the values of a\n +`SparseTensor`. The vector stores the decoded classes for beam j. +*@li decoded_shape: A list (length: top_paths) of shape vector. Vector j,\n +size `(2)`, stores the shape of the decoded `SparseTensor[j]`.\n +Its values are: `[batch_size, max_decoded_length[j]]`. +*@li log_probability: A matrix, shaped: `(batch_size x top_paths)`. The\n +sequence log-probabilities. + +*@par Third-party framework compatibility +* Compatible with TensorFlow CTCBeamSearchDecoder operator. +*/ +REG_OP(CTCBeamSearchDecoder) + .INPUT(inputs, TensorType({DT_FLOAT, DT_DOUBLE})) + .INPUT(sequence_length, TensorType({DT_INT32})) + .REQUIRED_ATTR(beam_width, Int) + .REQUIRED_ATTR(top_paths, Int) + .ATTR(merge_repeated, Bool, true) + .DYNAMIC_OUTPUT(decoded_indices, TensorType({DT_INT64})) + .DYNAMIC_OUTPUT(decoded_values, TensorType({DT_INT64})) + .DYNAMIC_OUTPUT(decoded_shape, TensorType({DT_INT64})) + .OUTPUT(log_probability, TensorType({DT_FLOAT, DT_DOUBLE})) + .OP_END_FACTORY_REG(CTCBeamSearchDecoder) + } // namespace ge #endif //GE_OP_CTC_OPS_H \ No newline at end of file diff --git a/third_party/fwkacllib/inc/ops/elewise_calculation_ops.h b/third_party/fwkacllib/inc/ops/elewise_calculation_ops.h index 04e1cea3..378eee38 100644 --- a/third_party/fwkacllib/inc/ops/elewise_calculation_ops.h +++ b/third_party/fwkacllib/inc/ops/elewise_calculation_ops.h @@ -483,9 +483,9 @@ REG_OP(Equal) *x: A Tensor. Must be one of the following types: float16, float32, double, complex64, complex128. *@par Attributes: -*@li base: An optional attribute of type float32, specifying the base gamma. Defaults to "-1". -*@li scale: An optional attribute of type float32, specifying the scale alpha. Defaults to "1". -*@li shift: An optional attribute of type float32, specifying the shift beta. Defaults to "0". +*@li base: An optional attribute of type float32, specifying the base gamma. Defaults to "-1.0". +*@li scale: An optional attribute of type float32, specifying the scale alpha. Defaults to "1.0". +*@li shift: An optional attribute of type float32, specifying the shift beta. Defaults to "0.0". *@par Outputs: *y: A Tensor of the same type as "x". @@ -1016,17 +1016,17 @@ REG_OP(BesselI1e) * y = log_base(shift + scale * x), with "base" > 0. * @par Inputs: -* @li x: A Tensor of type UnaryDataType. +* @li x: A Tensor of type complex64, complex128, float16, float32 or double. * @par Attributes: -* @li base: An optional float32, specifying the base "e". Defaults to "-1" +* @li base: An optional float32, specifying the base "e". Defaults to "-1.0" * @li scale: An optional float32, specifying the scale of input "x". Defaults -* to "1" -* @li shift: An optional float32, specifying the shift. Defaults to "0" +* to "1.0" +* @li shift: An optional float32, specifying the shift. Defaults to "0.0" * @par Outputs: -* y: A Tensor of type UnaryDataType. +* y: A Tensor has same type as "x". * @attention Constraints: * @li "base" is supposed to be greater than 0. Retaining the default @@ -2262,7 +2262,7 @@ REG_OP(ArgMinD) *dtype: The output type, either "int32" or "int64". Defaults to "int64". *@par Outputs: -*y: A multi-dimensional Tensor of type int32, specifying the index with the largest value. The dimension is one less than that of "x". +*y: A multi-dimensional Tensor of type int32 or int64, specifying the index with the largest value. The dimension is one less than that of "x". *@attention Constraints: *@li x: If there are multiple maximum values, the index of the first maximum value is used. @@ -2398,8 +2398,8 @@ REG_OP(ArgMinWithValue) *y: A Tensor. Has the same type and format as "x". *@par Attributes: -*@li N: A required attribute. the number of input x, max size is 32. -*@li model: An optional attribute. Defaults to "1". +*@li N: A required attribute. the number of input x, max size is 32. Type is int. +*@li model: An optional attribute. Type is int. Defaults to "1". * "0": product, "1": sum, "2": max. *@li coeff: A required attribute. Must met all of following rules: * size of "coeff" must be equal to len("x") or is null. @@ -2692,6 +2692,86 @@ REG_OP(AdamApplyOne) .OUTPUT(output2, TensorType({DT_FLOAT16,DT_FLOAT})) .OP_END_FACTORY_REG(AdamApplyOne) +/** +*@brief A fusion operator for bert lamb. + +*@par Inputs: +*Eleven inputs, including: +* @li input0: A Tensor. Must be one of the following types: float16, float32. +* @li input1: A Tensor. Must be one of the following types: float16, float32. +* @li input2: A Tensor. Must be one of the following types: float16, float32. +* @li input3: A Tensor. Must be one of the following types: float16, float32. +* @li input4: A Tensor. Must be one of the following types: float16, float32. +* @li mul0_x: A Tensor. Must be one of the following types: float16, float32. +* @li mul1_x: A Tensor. Must be one of the following types: float16, float32. +* @li mul2_x: A Tensor. Must be one of the following types: float16, float32. +* @li mul3_x: A Tensor. Must be one of the following types: float16, float32. +* @li mul4_x: A Tensor. Must be one of the following types: float16, float32. +* @li add2_y: A Tensor. Must be one of the following types: float16, float32. + +*@par Outputs: +*Three outputs, including: +* @li output0: A Tensor. Must be one of the following types: float16, float32. +* @li output1: A Tensor. Must be one of the following types: float16, float32. +* @li output2: A Tensor. Must be one of the following types: float16, float32. + +*/ +REG_OP(AdamApplyOneWithDecayAssign) + .INPUT(input0, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(input1, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(input2, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(input3, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(input4, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(mul0_x, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(mul1_x, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(mul2_x, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(mul3_x, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(mul4_x, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(add2_y, TensorType({DT_FLOAT16,DT_FLOAT})) + .OUTPUT(output0, TensorType({DT_FLOAT16,DT_FLOAT})) + .OUTPUT(output1, TensorType({DT_FLOAT16,DT_FLOAT})) + .OUTPUT(output2, TensorType({DT_FLOAT16,DT_FLOAT})) + .OP_END_FACTORY_REG(AdamApplyOneWithDecayAssign) + +/** +*@brief A fusion operator for bert lamb. + +*@par Inputs: +*Ten inputs, including: +* @li input0: A Tensor. Must be one of the following types: float16, float32. +* @li input1: A Tensor. Must be one of the following types: float16, float32. +* @li input2: A Tensor. Must be one of the following types: float16, float32. +* @li input3: A Tensor. Must be one of the following types: float16, float32. +* @li input4: A Tensor. Must be one of the following types: float16, float32. +* @li mul0_x: A Tensor. Must be one of the following types: float16, float32. +* @li mul1_x: A Tensor. Must be one of the following types: float16, float32. +* @li mul2_x: A Tensor. Must be one of the following types: float16, float32. +* @li mul3_x: A Tensor. Must be one of the following types: float16, float32. +* @li add2_y: A Tensor. Must be one of the following types: float16, float32. + +*@par Outputs: +*Three outputs, including: +* @li output0: A Tensor. Must be one of the following types: float16, float32. +* @li output1: A Tensor. Must be one of the following types: float16, float32. +* @li output2: A Tensor. Must be one of the following types: float16, float32. + +*/ +REG_OP(AdamApplyOneAssign) + .INPUT(input0, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(input1, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(input2, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(input3, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(input4, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(mul0_x, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(mul1_x, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(mul2_x, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(mul3_x, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(add2_y, TensorType({DT_FLOAT16,DT_FLOAT})) + .OUTPUT(output0, TensorType({DT_FLOAT16,DT_FLOAT})) + .OUTPUT(output1, TensorType({DT_FLOAT16,DT_FLOAT})) + .OUTPUT(output2, TensorType({DT_FLOAT16,DT_FLOAT})) + .OP_END_FACTORY_REG(AdamApplyOneAssign) + /** *@brief Confuse select, maximum, greater and sqrt. @@ -3042,6 +3122,22 @@ REG_OP(KLDiv) .OUTPUT(y, TensorType({DT_FLOAT16, DT_FLOAT, DT_DOUBLE})) .OP_END_FACTORY_REG(KLDiv) +/** +*@brief copy data from x to y.. + +*@par Inputs: +*One inputs, including: +* @li x: A Tensor. Must be one of the following types: float16, float32, int8, uint8, int32, bool. + +*@par Outputs: +*y: A Tensor. Has the same type as "x". + +*@par Third-party framework compatibility +*/ +REG_OP(TensorMove) + .INPUT(x, TensorType({DT_FLOAT16, DT_FLOAT, DT_INT32, DT_INT8, DT_UINT8, DT_BOOL})) + .OUTPUT(y, TensorType({DT_FLOAT16, DT_FLOAT, DT_INT32, DT_INT8, DT_UINT8, DT_BOOL})) + .OP_END_FACTORY_REG(TensorMove) } // namespace ge diff --git a/third_party/fwkacllib/inc/ops/hvd_ops.h b/third_party/fwkacllib/inc/ops/hvd_ops.h new file mode 100644 index 00000000..09748b8e --- /dev/null +++ b/third_party/fwkacllib/inc/ops/hvd_ops.h @@ -0,0 +1,77 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_OP_HVD_OPS_H_ +#define GE_OP_HVD_OPS_H_ + +#include "graph/operator_reg.h" + +namespace ge { +/** + * @brief Outputs a tensor gathering all input tensors. + * @par Inputs: + * x: A tensor. Must be one of the following types: uint8, int8, uint16, int16, int32, + * int64, float16, bool. + * @par Attributes: + * @li rank_size: A required integer identifying the number of ranks + * participating in the op. + * @par Outputs: + * y: A Tensor. Has the same type as "x". + */ +REG_OP(HorovodAllgather) + // GE not support float64 currently + .INPUT(x, TensorType({DT_UINT8, DT_INT8, DT_UINT16, DT_INT16, DT_INT32, DT_INT64, DT_FLOAT16, DT_FLOAT, DT_BOOL})) + .OUTPUT(y, TensorType({DT_UINT8, DT_INT8, DT_UINT16, DT_INT16, DT_INT32, DT_INT64, DT_FLOAT16, DT_FLOAT, DT_BOOL})) + // add rank_size attr + .REQUIRED_ATTR(rank_size, Int) + .OP_END_FACTORY_REG(HorovodAllgather) + +/** + * @brief Outputs a tensor containing the reduction across all input tensors + * passed to op. + * @par Inputs: + * x: A tensor. Must be one of the following types: int32, int64, float16, float32 + * @par Attributes: + * @li reduce_op: A required int identifying the reduction operation to + * perform.The supported operation are: "sum", "max", "min", "prod". + * @par Outputs: + * y: A Tensor. Has the same type as "x". + */ +REG_OP(HorovodAllreduce) + .INPUT(x, TensorType({DT_INT32, DT_INT64, DT_FLOAT16, DT_FLOAT})) + .OUTPUT(y, TensorType({DT_INT32, DT_INT64, DT_FLOAT16, DT_FLOAT})) + .REQUIRED_ATTR(reduce_op, Int) + .OP_END_FACTORY_REG(HorovodAllreduce) + +/** + * @brief Broadcasts the input tensor in root rank to all ranks. + * @par Inputs: + * x: A list of dynamic input tensor. Must be one of the following types: + * int8, int32, float16, float32. + * @par Attributes: + * @li root_rank: A required integer identifying the root rank in the op + * input of this rank will be broadcast to other ranks. + * @par Outputs: + * y: A list of dynamic output tensor. Has the same type and length as "x". + */ +REG_OP(HorovodBroadcast) + .INPUT(x, TensorType({DT_UINT8, DT_INT8, DT_UINT16, DT_INT16, DT_INT32, DT_INT64, DT_FLOAT16, DT_FLOAT, DT_BOOL})) + .OUTPUT(y, TensorType({DT_UINT8, DT_INT8, DT_UINT16, DT_INT16, DT_INT32, DT_INT64, DT_FLOAT16, DT_FLOAT, DT_BOOL})) + .REQUIRED_ATTR(root_rank, Int) + .OP_END_FACTORY_REG(HorovodBroadcast) + +} // namespace ge +#endif // GE_OP_HVD_OPS_H_ diff --git a/third_party/fwkacllib/inc/ops/internal_ops.h b/third_party/fwkacllib/inc/ops/internal_ops.h new file mode 100644 index 00000000..e3caa45f --- /dev/null +++ b/third_party/fwkacllib/inc/ops/internal_ops.h @@ -0,0 +1,48 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GE_OP_INTERNAL_OPS_H_ +#define GE_OP_INTERNAL_OPS_H_ + +#include "graph/operator_reg.h" +#include "graph/operator.h" + +namespace ge { + +/** +*@brief aicpu assit help op for auxiliary matrix generation. + +*@par Inputs: +*The input is dynamic for attribute func_name \n + +*@par Attributes: +*@li func_name:An required param, for example "topkv2". \n + +*@par Outputs: +*The output is dynamic for attribute func_name. +*/ + +REG_OP(AssistHelp) + .DYNAMIC_INPUT(x, TensorType({ DT_FLOAT, DT_FLOAT16, DT_INT8, DT_INT16, DT_UINT16, + DT_UINT8, DT_INT32, DT_INT64, DT_UINT32, DT_UINT64, DT_BOOL, DT_DOUBLE })) + .DYNAMIC_OUTPUT(y, TensorType({ DT_FLOAT, DT_FLOAT16, DT_INT8, DT_INT16, DT_UINT16, + DT_UINT8, DT_INT32, DT_INT64, DT_UINT32, DT_UINT64, DT_BOOL, DT_DOUBLE})) + . REQUIRED_ATTR (func_name, String) + . OP_END_FACTORY_REG(AssistHelp) + +} // namespace ge + +#endif // GE_OP_INTERNAL_OPS_H_ diff --git a/third_party/fwkacllib/inc/ops/math_ops.h b/third_party/fwkacllib/inc/ops/math_ops.h index 5d34804c..b0c35c28 100644 --- a/third_party/fwkacllib/inc/ops/math_ops.h +++ b/third_party/fwkacllib/inc/ops/math_ops.h @@ -29,9 +29,9 @@ namespace ge { * x: A Tensor of type float16 or float32. *@par Attributes: -*@li power: Optional. Defaults to 1.0. -*@li scale: Optional. Defaults to 1.0. -*@li shift: Optional. Defaults to 0.0. +*@li power: Optional. Must be one of the following types: float32. Defaults to 1.0. +*@li scale: Optional. Must be one of the following types: float32. Defaults to 1.0. +*@li shift: Optional. Must be one of the following types: float32. Defaults to 0.0. *@par Outputs: * y: A Tensor. Has the same type and shape as "x". diff --git a/third_party/fwkacllib/inc/ops/matrix_calculation_ops.h b/third_party/fwkacllib/inc/ops/matrix_calculation_ops.h index 29cf0df3..7cfd762f 100644 --- a/third_party/fwkacllib/inc/ops/matrix_calculation_ops.h +++ b/third_party/fwkacllib/inc/ops/matrix_calculation_ops.h @@ -698,6 +698,45 @@ REG_OP(FullyConnection) .ATTR(offset_x, Int, 0) .OP_END_FACTORY_REG(FullyConnection) +/** +*@brief Also known as a "fully-connected-compress" layer, computes an inner product with a set of learned weights, and (optionally) adds biases. + +*@par Inputs: +* Four inputs, including: +*@li x: A Tensor of type uint8, int8. +*@li w: A weight matrix of type int8, int8. +*@li w: A compress index matrix of type int8, int8. +*@li b: A Tensor of type float16, int32, int32. +*@li offset_w: A Tensor of type int8.i + +*@par Attributes: +*@li num_output: Reserved. +*@li transpose: A bool, specifying whether to transpose, either "true" or "false". Defaults to "false". +*@li axis: Reserved. +*@li offset_x: Reserved. + +*@par Outputs: +*y: The result tensor of type int32. + +*@par Third-party framework compatibility +* Compatible with the Caffe operator InnerProduct. + +*@par Quantization supported or not +* Yes +*/ +REG_OP(FullyConnectionCompress) + .INPUT(x, TensorType({DT_UINT8, DT_INT8})) + .INPUT(w, TensorType({DT_INT8})) + .INPUT(comress_index, TensorType({DT_INT8})) + .OPTIONAL_INPUT(b, TensorType({DT_INT32})) + .OPTIONAL_INPUT(offset_w, TensorType({DT_INT8})) + .OUTPUT(y, TensorType({DT_INT32})) + .REQUIRED_ATTR(num_output, Int) + .ATTR(transpose, Bool, false) + .ATTR(axis, Int, 1) + .ATTR(offset_x, Int, 0) + .OP_END_FACTORY_REG(FullyConnectionCompress) + /** *@brief Computes the confusion matrix from predictions and labels. diff --git a/third_party/fwkacllib/inc/ops/nn_batch_norm_ops.h b/third_party/fwkacllib/inc/ops/nn_batch_norm_ops.h index e8eb4769..39aaa993 100644 --- a/third_party/fwkacllib/inc/ops/nn_batch_norm_ops.h +++ b/third_party/fwkacllib/inc/ops/nn_batch_norm_ops.h @@ -33,12 +33,12 @@ namespace ge { * @li variance: A Tensor. Must be one of the following types: float32. *@par Attributes: -* @li mode: A Tensor. Must be one of the following types: int. -* @li epsilon: A Tensor. Must be one of the following types: float32. -* @li momentum: A Tensor. Must be one of the following types: float32. -* @li is_training: A Tensor. Must be one of the following types: bool. -* @li is_training_fusion: A Tensor. Must be one of the following types: bool. -* @li moving_average_fraction: A Tensor. Must be one of the following types: float32. +* @li mode: A Tensor. Must be one of the following types: int. defaults: 1. +* @li epsilon: A Tensor. Must be one of the following types: float32. Defaults to 0.000001. +* @li momentum: A Tensor. Must be one of the following types: float32. Defaults to 0.9. +* @li is_training: A Tensor. Must be one of the following types: bool. Defaults to true. +* @li is_training_fusion: A Tensor. Must be one of the following types: bool. Defaults to true. +* @li moving_average_fraction: A Tensor. Must be one of the following types: float32. Defaults to 0.00300002098. *@par Outputs: *Three outputs, including: @@ -83,8 +83,8 @@ REG_OP(FusedBatchNorm) * @li save_inv_variance1: A Tensor. Must be one of the following types: float32. *@par Attributes: -* @li epsilon: A Tensor. Must be one of the following types: float32. -* @li momentum: A Tensor. Must be one of the following types: float32. +* @li epsilon: A Tensor. Must be one of the following types: float32. Defaults to 0.0. +* @li momentum: A Tensor. Must be one of the following types: float32. Defaults to 0.0. *@par Outputs: *Three outputs, including: @@ -361,14 +361,14 @@ REG_OP(BatchNormGradExt2) *@par Inputs: *@li x: A 4D or 5D Tensor of type float16 or float32, with format NHWC or NCHW for 4D or NC1HWC0 for 5D. *@li mean: A Tensor of type float32 or float16. Must be 1D if input "x" Specifies the mean used for inference. -*@li variance: A Tensor of type float32 or float16. Must be 1D if input "x" Specifies the variance used for inference. -*@li momentum: A Tensor of type float32 or float16, represents the mean and the variance's scale factor +*@li variance: A Tensor of type float32 or float16 . Must be 1D if input "x" Specifies the variance used for inference. +*@li momentum: A Tensor,represents the mean and the variance's scale factor *@li scale: An optional tensor of type float16 or float32, no use *@li offset: An optional tensor of type float16 or float32, no use *@par Attributes: *@li epsilon: An optional float32, specifying the small value added to variance to avoid dividing by zero. Defaults to "0.00001". *@li use_global_stats: mean inference mode , only can be "True". -*@li mode: An optional attr, not use +*@li mode: An optional input, not use *@par Outputs:\n *@li y: A 4D or 5D Tensor of type float16 or float32 for the normalized "x" */ @@ -391,11 +391,11 @@ REG_OP(BNInference) *@li mean: A Tensor of type float32 or float16. Must be 1D if input "x" Specifies the mean used for inference. *@li variance: A Tensor of type float32 or float16 . Must be 1D if input "x" Specifies the variance used for inference. -*@li momentum: A Tensor of type float32 or float16, the mean and the variance's Scale factor +*@li momentum: An optional float, mean and variance's Scale factor *@par Attributes: *@li epsilon: An optional float32, specifying the small value added to variance to avoid dividing by zero. Defaults to "0.00001". *@li use_global_stats: mean inference mode , only can be "True". -*@li mode: An optional inpout, not use +*@li mode: An optional attr, not use *@par Outputs: *@li alpha: A Tensor of type float16 or float32 for the cpu calculate mean *@li beta: A Tensor of type float16 or float32 for the cpu calculate variance @@ -418,8 +418,8 @@ REG_OP(BnHost) *@par Inputs: *@li x: A 4D or 5D Tensor of type float16 or float32, with format NHWC or NCHW for 4D or NC1HWC0 for 5D. -*@li mean: A Tensor of type float32 or float16. Must be 1D if input "x" Specifies the mean used for inference. -*@li variance: A Tensor of type float32 or float16 . Must be 1D if input "x" Specifies the variance used for inference. +*@li mean: A Tensor of type float32 or float16. Must be 1D if input "x" Specifies the mean used for inference. +*@li variance: A Tensor of type float32 or float16 . Must be 1D if input "x" Specifies the variance used for inference. *@li scale: An optional tensor of type float16 or float32, no use *@li offset: An optional tensor of type float16 or float32, no use *@par Attributes: diff --git a/third_party/fwkacllib/inc/ops/nn_calculation_ops.h b/third_party/fwkacllib/inc/ops/nn_calculation_ops.h index 5d4e6bff..5818e14b 100644 --- a/third_party/fwkacllib/inc/ops/nn_calculation_ops.h +++ b/third_party/fwkacllib/inc/ops/nn_calculation_ops.h @@ -143,31 +143,29 @@ REG_OP(DepthwiseConv2DBackpropFilterD) * @par Inputs: * Three inputs include: \n * @li input_size: 4D shape of input tensor [N, C, H, W] or [N, H, W, C], -* support int32 -* @li filter: 4D filter tensor with shape of [H, W, C, K], support float16, -* float32, double +* support int32, int64 +* @li filter: 4D filter tensor with shape of [H, W, C, K], support float16. * @li out_backprop: 4D tensor with shape [N, C, H, W] or [N, H, W, C]. -* Must be one of the following types: float16, float32, double. +* Must be one of the following types: float16. * @par Attributes: -* @li strides: A required list or tuple. The stride of the sliding window for +* @li strides: A required list or tuple of int32. The stride of the sliding window for * height and width of input "x" of the convolution. * Must be with shape [1, 1, stride_height, stride_width] or [1, stride_height, * stride_width, 1]. -* @li dilations: An optional list or tuple. The dilation factor for each -* dimension of input "x". +* @li dilations: An optional list or tuple of int32. The dilation factor for each +* dimension of input "x". Defaults to "[1, 1, 1, 1]". * If set to k > 1, there will be k-1 skipped cells between each filter element * on that dimension. Must be with shape [1, 1, dilation_height, dilation_width] * or [1, dilation_height, dilation_width, 1]. -* @li pads: A required list or tuple. Padding added to each dimension of the +* @li pads: A required list or tuple of int32. Padding added to each dimension of the * input. * @li data_format: An optional string. Input data format, either "NHWC" or -* "NCHW". +* "NCHW". Defaults to "NHWC". * @par Outputs: * input_grad: Gradient of the deep convolution relative to the input with shape -* [N, C, H, W] or [N, H, W, C] Must be one of the following types: float16, -* float32, double. +* [N, C, H, W] or [N, H, W, C] Must be one of the following types: float16. * @attention Constraints:\n * The feature map is 4D with shape [N, C, Hi, Wi] or [N, Hi, Wi, C], but @@ -259,8 +257,8 @@ REG_OP(DepthwiseConv2DBackpropInputD) *@par Inputs: *Two required inputs and two optional inputs, including: \n -* @li x: A 4D tensor of type float16, with shape [N, C, H, W] or [N, H, W, C] -* @li filter: A 4D tensor of type float16, with shape [H, W, C, K] +* @li x: A 4D tensor of type float16 or int8, with shape [N, C, H, W] or [N, H, W, C] +* @li filter: A 4D tensor of type float16 or int8, with shape [H, W, C, K] * @li bias: An optional tensor of type float16 or int32 * @li offset_w: An optional float16 or int8, used for quantized inference @@ -273,8 +271,8 @@ REG_OP(DepthwiseConv2DBackpropInputD) * dimension of input "x". * If set to k > 1, there will be k-1 skipped cells between each filter element * on that dimension. Must be with shape [1, 1, dilation_height, dilation_width] -* or [1, dilation_height, dilation_width, 1]. -* @li pads: A required list or tuple. Padding added to each dimension of the +* or [1, dilation_height, dilation_width, 1]. Defaults to "[1, 1, 1, 1]". +* @li pads: A required list or tuple of int32. Padding added to each dimension of the * input. * @li data_format: An optional string. Input data format, either "NHWC" or * "NCHW". Defaults to "NHWC". @@ -282,7 +280,7 @@ REG_OP(DepthwiseConv2DBackpropInputD) * Defaults to 0. * @par Outputs: -* y: 4D tensor of type float16, with shape [N, C, H, W] or [N, H, W, C] +* y: 4D tensor of type float16 or int32, with shape [N, C, H, W] or [N, H, W, C] * @attention Constraints:\n * The feature map is 4D with shape [N, C, Hi, Wi] or [N, Hi, Wi, C], but @@ -462,24 +460,24 @@ REG_OP(Conv2DBackpropInputD) * @li x: A Tensor. Must have the same type as "filter". 4D with shape * [batch, out_channels, out_height, out_width]. Gradients with respect * to the output of the convolution. - * @li filter: A Tensor of type float16. + * @li filter: A Tensor of type float16, float32, double or int8. * 4D with shape [out_channels, in_channel, filter_height, filter_width].\n * Two optional inputs: - * @li bias: An optional tensor of type float16 - * @li offset_w: An optional 1D tensor for quantized deconvolution. Reserved.\n + * @li bias: An optional tensor of type float16, float32, int32 or int64. + * @li offset_w: An optional 1D tensor for quantized deconvolution. Type is int8. Reserved.\n *@par Attributes: * Six attributes: * @li strides: A tuple or list of 2 integers. The stride of the sliding window - * for H/W dimension. + * for H/W dimension. Defaults to [1, 1, 1, 1]. * @li pads: A tuple or list of 4 integers. The [top, bottom, left, right] - * padding on the feature map + * padding on the feature map. Defaults to [0, 0, 0, 0]. * @li dilations: A tuple or list of 4 integers. The dilation factor for each * dimension of input. Must be [1, 1, 1, 1]. * @li groups: Number of blocked connections from input channels to - * output channels. - * @li data_format: An optional string from: "NCHW". Defaults to "NCHW".\n + output channels. Defaults to "1". + * @li data_format: An optional string from: "NCHW". Defaults to "NCHW". \n Specify the data format of the input and output data. - * @li offset_x: An optional integer for quantized deconvolution. + * @li offset_x: An optional integer for quantized deconvolution. Defaults to "0". *@par Outputs: * y: A Tensor. Has the same type as "filter". 4D tensor with shape * [batch, channels, height, width]. @@ -577,19 +575,19 @@ REG_OP(Conv2DBackpropFilterD) * * The input and output tensor attributes are listed as follows: * @verbatim - Tensor | x | filter | bias | offset_w | y + |Tensor | x | filter | bias | offset_w | y -----------|---------|---------|---------|----------|-------- - Data Type | float16 | float16 | float16 | _ | float16 - |---------|---------|---------|----------|-------- - | float32 | float32 | float32 | _ | float32 - |---------|---------|---------|----------|-------- - | float64 | float64 | float64 | _ | float64 - |---------|---------|---------|----------|-------- - | int8 | int8 | int32 | int8 | int32 + |Data Type | float16 | float16 | float16 | _ | float16 + | |---------|---------|---------|----------|-------- + | | float32 | float32 | float32 | _ | float32 + | |---------|---------|---------|----------|-------- + | | float64 | float64 | float64 | _ | float64 + | |---------|---------|---------|----------|-------- + | | int8 | int8 | int32 | int8 | int32 -----------|---------|---------|---------|----------|-------- - Format | NCHW | NCHW | ND | ND | NCHW - | NHWC | NHWC | | | NHWC - | | HWCN | | | + |Format | NCHW | NCHW | ND | ND | NCHW + | | NHWC | NHWC | | | NHWC + | | | HWCN | | | @endverbatim * It should be noted that the data types must correspond to each other, but the * format does not need to. @@ -604,10 +602,10 @@ REG_OP(Conv2DBackpropFilterD) * for dilated convolution. Has the same dimension order and value as "strides". * @li groups: Number of blocked connections from input channels to output * channels. Input channels and output channels must both be divisible by -* "groups". Must be set to 1. -* @li offset_x: An optional integer for quantized convolution. +* "groups".Type is int32. Must be set to 1. +* @li offset_x: An optional integer for quantized convolution. Type is int32. Defaults to "0". * @li data_format: An optional string from: "NHWC", "NCHW". Specifying the -* data format of the input and output images. Reserved. +* data format of the input and output images. Type is string. Defaults to "NHWC". Reserved. *@par Outputs: * @li y: A 4D Tensor of output images. @@ -615,23 +613,23 @@ REG_OP(Conv2DBackpropFilterD) *@attention * @li The parameter scope is listed as follows: * @verbatim - Name | Field | Scope + |Name | Field | Scope ------------------|--------------|---------- - Input Image Size | H dimension | [1, 4096] - | W dimension | [1, 4096] + |Input Image Size | H dimension | [1, 4096] + | | W dimension | [1, 4096] ------------------|--------------|---------- - Filter Size | H dimension | [1, 255] - | W dimension | [1, 255] + |Filter Size | H dimension | [1, 255] + | | W dimension | [1, 255] ------------------|--------------|---------- - Stride Size | H dimension | [1, 63] - | W dimension | [1, 63] + |Stride Size | H dimension | [1, 63] + | | W dimension | [1, 63] ------------------|--------------|---------- - Padding Size | top side | [0, 255] - | bottom side | [0, 255] - | left side | [0, 255] - | right side | [0, 255] + |Padding Size | top side | [0, 255] + | | bottom side | [0, 255] + | | left side | [0, 255] + | | right side | [0, 255] ------------------|--------------|---------- - Dilation Size | H dimension | [1, 255] + |Dilation Size | H dimension | [1, 255] | W dimension | [1, 255] @endverbatim @@ -712,8 +710,8 @@ REG_OP(Conv3D) .INPUT(filter, TensorType({DT_FLOAT16, DT_FLOAT, DT_DOUBLE})) .OPTIONAL_INPUT(bias, TensorType({DT_FLOAT16, DT_FLOAT, DT_DOUBLE})) .OUTPUT(y, TensorType({DT_FLOAT16, DT_FLOAT, DT_DOUBLE})) - .ATTR(strides, ListInt, {1, 1, 1, 1, 1}) - .ATTR(pads, ListInt, {0, 0, 0, 0, 0, 0}) + .REQUIRED_ATTR(strides, ListInt) + .REQUIRED_ATTR(pads, ListInt) .ATTR(data_format, String, "NDHWC") .ATTR(dilations, ListInt, {1, 1, 1, 1, 1}) .OP_END_FACTORY_REG(Conv3D) @@ -744,7 +742,7 @@ REG_OP(Conv3DBackpropInput) .INPUT(grads, TensorType({DT_FLOAT16, DT_FLOAT, DT_DOUBLE})) .OUTPUT(y, TensorType({DT_FLOAT16, DT_FLOAT, DT_DOUBLE})) .REQUIRED_ATTR(strides, ListInt) - .ATTR(pads, ListInt, {0, 0, 0, 0, 0, 0}) + .REQUIRED_ATTR(pads, ListInt) .ATTR(data_format, String, "NDHWC") .ATTR(dilations, ListInt, {1, 1, 1, 1, 1}) .OP_END_FACTORY_REG(Conv3DBackpropInput) @@ -773,7 +771,7 @@ REG_OP(Conv3DBackpropInputD) .OUTPUT(y, TensorType({DT_FLOAT16})) .REQUIRED_ATTR(input_size, ListInt) .REQUIRED_ATTR(strides, ListInt) - .ATTR(pads, ListInt, {0, 0, 0, 0, 0, 0}) + .REQUIRED_ATTR(pads, ListInt) .ATTR(data_format, String, "NDHWC") .ATTR(dilations, ListInt, {1, 1, 1, 1, 1}) .OP_END_FACTORY_REG(Conv3DBackpropInputD) diff --git a/third_party/fwkacllib/inc/ops/nn_detect_ops.h b/third_party/fwkacllib/inc/ops/nn_detect_ops.h index 5dca8a9d..ceb92f7a 100644 --- a/third_party/fwkacllib/inc/ops/nn_detect_ops.h +++ b/third_party/fwkacllib/inc/ops/nn_detect_ops.h @@ -187,14 +187,15 @@ REG_OP(ROIAlignGrad) *@li features: A 5HD Tensor of type float32 or float16. *@li rois: ROI position. A 2D Tensor of float32 or float16 with shape (N, 5). "N" indicates the number of ROIs, the value "5" indicates the indexes of images where the ROIs are located, * "x0", "y0", "x1", and "y1". -*@li rois_n: An optional input, specifying the number of valid ROIs. This parameter is reserved. +*@li rois_n: An optional input of type int32, specifying the number of valid ROIs. This parameter is reserved. *@par Attributes: -*@li spatial_scale: A required attribute of type float, specifying the scaling ratio of "features" to the original image. -*@li pooled_height: A required attribute of type int, specifying the H dimension. -*@li pooled_width: A required attribute of type int, specifying the W dimension. -*@li sample_num: An optional attribute of type int, specifying the horizontal and vertical sampling frequency of each output. If this attribute is set to "0", +*@li spatial_scale: A required attribute of type float32, specifying the scaling ratio of "features" to the original image. +*@li pooled_height: A required attribute of type int32, specifying the H dimension. +*@li pooled_width: A required attribute of type int32, specifying the W dimension. +*@li sample_num: An optional attribute of type int32, specifying the horizontal and vertical sampling frequency of each output. If this attribute is set to "0", * the sampling frequency is equal to the rounded up value of "rois", which is a floating point number. Defaults to "2". +*@li roi_end_mode: An optional attribute of type int32. Defaults to "1". *@par Outputs: * output: Outputs the feature sample of each ROI position. The format is 5HD Tensor of type float32 or float16. The axis N is the number of input ROIs. Axes H, W, and C are consistent @@ -362,15 +363,15 @@ REG_OP(PSROIPooling) *@li im_info: An ND tensor of type float16 or float32, specifying the Image information. *@li actual_rois_num: An optional NCHW tensor of type int32, specifying the number of valid boxes per batch. *@par Attributes: -*@li batch_rois: An optional int32, specifying the number of images to be predicted. +*@li batch_rois: An optional int32, specifying the number of images to be predicted. Defaults to "1". *@li num_classes: An required int32, specifying the number of classes to be predicted. The value must be greater than 0. *@li score_threshold: An required float32, specifying the threshold for box filtering. The value range is [0.0, 1.0]. *@li iou_threshold: An required float32, specifying the confidence threshold for box filtering, which is the output "obj" of operator Region. The value range is (0.0, 1.0). *@par Outputs: -*@li box: An NCHW tensor of type float16 or float32, describing the information of each output box, including the coordinates, class, and confidence. -Proposal of actual output, with output shape [batch, numBoxes,8], 8 means [x1, y1, x2, y2, score, label, batchID, NULL], the maximum value of numBoxes is 1024. +*@li box: A tensor of type float16 or float32 for proposal of actual output, with output shape [batch, numBoxes,8]. +* 8 means [x1, y1, x2, y2, score, label, batchID, NULL], the maximum value of numBoxes is 1024. That is, take min (the maximum number of input boxes, 1024) -*@li actual_bbox_num: An NCHW tensor of type int32 With shape [bacth, num_classes], specifying the number of output boxes. +*@li actual_bbox_num: A tensor of type int32 With shape [bacth, num_classes], specifying the number of output boxes. *@attention Constraints:\n *@li totalnum < max_rois_num * batch_rois. @@ -414,9 +415,9 @@ REG_OP(FSRDetectionOutput) *@li confidence_threshold: An optional float32, specify the topk filter threshold. Only consider detections with confidence greater than the threshold *@li kernel_name: An optional string, specifying the operator name. Defaults to "ssd_detection_output". *@par Outputs: -*@li out_boxnum: An NCHW tensor of type int32, specifying the number of output boxes. -*@li y: An NCHW tensor of type float16 or float32 with shape [batch,keep_top_k, 8], describing the information of each output box, including the coordinates, -* class, and confidence. In output shape, 8 means (batchID, label(classID), score (class probability), xmin, ymin, xmax, ymax, null) +*@li out_boxnum: A tensor of type int32, specifying the number of output boxes. +*@li y: A tensor of type float16 or float32 with shape [batch,keep_top_k, 8], describing the information of each output box. +* In output shape, 8 means (batchID, label(classID), score (class probability), xmin, ymin, xmax, ymax, null) * It is a custom operator. It has no corresponding operator in Caffe. */ REG_OP(SSDDetectionOutput) @@ -447,10 +448,10 @@ REG_OP(SSDDetectionOutput) *@li boxes: A required int32, specifying the number of anchor boxes. Defaults to "5" for V2 or "3" for V3. *@li coords: An int32, specifying the number of parameters required for locating an object. The value is fixed at "4", corresponding to (x,y,w,h). *@li classes: An int32, specifying the number of prediction classes. Defaults to "80". The value range is [1, 1024]. -*@li yolo_version: A string, specifying the YOLO version, either "V2" or "V3". -*@li softmax: A bool, specifying whether to perform softmax, valid only when "yolo_version = V2". -*@li background: A bool, specifying the operation types of the obj and classes, used in conjunction with "softmax" and valid only when "yolo_version = V2". -*@li softmaxtree: A bool, Fixed to False, defined in Lite, but not used. +*@li yolo_version: A string, specifying the YOLO version, either "V2" or "V3".Defaults to "V3" +*@li softmax: A bool, specifying whether to perform softmax, valid only when "yolo_version = V2". Defaults to "false". +*@li background: A bool, specifying the operation types of the obj and classes, used in conjunction with "softmax" and valid only when "yolo_version = V2". Defaults to "false". +*@li softmaxtree: A bool, Fixed to False, defined in Lite, but not used. Defaults to "false". *@par Outputs: *@li coord_data: A float16 or float32 with shape [N, boxes*coords, ceilx(height*width*2+32, 32)/2], where "ceil" indicates that a detected box is aligned upwards with the second parameter. Specifies the coordinates of a detected box. @@ -501,10 +502,10 @@ and the actual image height and width. *@li pre_nms_topn: An optional int, specifying the number of boxes for non-maximum suppression (NMS). Defaults to "512". * *@par Outputs: -*@li boxout: An NCHW tensor of type float16 or float32 with shape [batch,6,post_nms_topn]. describing the information of each output box, including the coordinates, class, -and confidence. In output shape, 6 means x1, y1, x2, y2, score, label(class). Output by the number of box_out_num. -*@li boxoutnum: An NCHW tensor of type int32 with shape [batch,8,1,1], specifying the number of output boxes. It means only the first one of the 8 numbers is valid, -the number of valid boxes in each batch, the maximum number of valid boxes in each batch is 1024 +*@li boxout: A tensor of type float16 or float32 with shape [batch,6,post_nms_topn]. describing the information of each output box, +* In output shape, 6 means x1, y1, x2, y2, score, label(class). Output by the number of box_out_num. +*@li boxoutnum: A tensor of type int32 with shape [batch,8,1,1], specifying the number of output boxes. It means only the first one of the 8 numbers is valid, +* the number of valid boxes in each batch, the maximum number of valid boxes in each batch is 1024 * *@attention Constraints:\n *@li This operator applies only to the YOLO v2 network. @@ -561,10 +562,10 @@ and the actual image height and width. *@li pre_nms_topn: An optional int, specifying the number of boxes for non-maximum suppression (NMS). Defaults to "512". * *@par Outputs: -*@li boxout: An NCHW tensor of type float16, describing the information of each output box, including the coordinates, class, and confidence. -With shape [batch,6,post_nms_topn], 6 means x1, y1, x2, y2, score, label(class). Output by the number of box_out_num. -*@li boxoutnum: An NCHW tensor of type int32, specifying the number of output boxes. -With shape [batch,8,1,1], means only the first one of the 8 numbers is valid, the number of valid boxes in each batch, the maximum number of valid boxes in each batch is 1024 +*@li boxout: A tensor of type float16 or float32 with shape [batch,6,post_nms_topn]. describing the information of each output box, +* In output shape, 6 means x1, y1, x2, y2, score, label(class). Output by the number of box_out_num. +*@li boxoutnum: A tensor of type int32 with shape [batch,8,1,1], specifying the number of output boxes. It means only the first one of the 8 numbers is valid, +* the number of valid boxes in each batch, the maximum number of valid boxes in each batch is 1024 * *@attention Constraints:\n *@li This operator applies only to the YOLO v2 network. @@ -621,11 +622,11 @@ and the actual image height and width. *@li pre_nms_topn: An optional int, specifying the number of boxes for non-maximum suppression (NMS). Defaults to "512". * *@par Outputs: -*@li boxout: An NCHW tensor of type float16 or float32 with shape [batch,6,post_nms_topn], describing the information of each output box, including the coordinates, class, and confidence. -In output shape, 6 means x1, y1, x2, y2, score, label(class). Output by the number of box_out_num. -*@li boxoutnum: An NCHW tensor of type int32 with shape [batch,8,1,1], specifying the number of output boxes. -The output shape means only the first one of the 8 numbers is valid, the number of valid boxes in each batch, the maximum number of valid boxes in each batch is 1024 - +*@li boxout: A tensor of type float16 or float32 with shape [batch,6,post_nms_topn], describing the information of each output box. +* In output shape, 6 means x1, y1, x2, y2, score, label(class). Output by the number of box_out_num. +*@li boxoutnum: A tensor of type int32 with shape [batch,8,1,1], specifying the number of output boxes. +* The output shape means only the first one of the 8 numbers is valid, the number of valid boxes in each batch, the maximum number of valid boxes in each batch is 1024 +* *@attention Constraints:\n *@li This operator applies only to the YOLO v3 network. *@li The preceding layer of operator Yolov3DetectionOutput must be three Yolo operators. @@ -688,12 +689,11 @@ and the actual image height and width. *@li pre_nms_topn: An optional int, specifying the number of boxes for non-maximum suppression (NMS). Defaults to "512". * *@par Outputs: -*@li boxout: An NCHW tensor of type float16, describing the information of each output box, including the coordinates, class, and confidence. -With shape [batch,6,post_nms_topn], 6 means x1, y1, x2, y2, score, label(class). Output by the number of box_out_num. -*@li boxoutnum: An NCHW tensor of type int32, specifying the number of output boxes. -With shape [batch,8,1,1], means only the first one of the 8 numbers is valid, the number of valid boxes in each batch, the maximum number of valid boxes in each batch is 1024 +*@li boxout: A tensor of type float16 or float32 with shape [batch,6,post_nms_topn], describing the information of each output box. +* In output shape, 6 means x1, y1, x2, y2, score, label(class). Output by the number of box_out_num. +*@li boxoutnum: A tensor of type int32 with shape [batch,8,1,1], specifying the number of output boxes. +* The output shape means only the first one of the 8 numbers is valid, the number of valid boxes in each batch, the maximum number of valid boxes in each batch is 1024 * - *@attention Constraints:\n *@li This operator applies only to the YOLO v3 network. *@li The preceding layer of operator Yolov3DetectionOutput must be three Yolo operators. diff --git a/third_party/fwkacllib/inc/ops/nn_norm_ops.h b/third_party/fwkacllib/inc/ops/nn_norm_ops.h index d4db7cf0..d18a4fa4 100644 --- a/third_party/fwkacllib/inc/ops/nn_norm_ops.h +++ b/third_party/fwkacllib/inc/ops/nn_norm_ops.h @@ -291,8 +291,8 @@ REG_OP(BinaryCrossEntropyGrad) * double. Should be a Variable Tensor. *@par Attributes: -*axes: A list of ints. The dimension softmax would be performed on. Defaults -* to "{-1}". +*axes: A list of int. The dimension softmax would be performed on. Defaults +* to "[-1]". *@par Outputs: *y: A Tensor. Has the same dimensionality and shape as the "x" with values in @@ -632,7 +632,7 @@ REG_OP(DropOutDoMask) * Three inputs, including: *@li x: An ND tensor of type float16 or float32. *@li scale: An ND tensor of type float16 or float32. -*@li bias: An ND tensor of type float16 or float32. +*@li bias: An optional ND tensor of type float16 or float32. *@par Attributes: *@li axis: An optional int32 used to compute the shape of scale and bias input from the online bottoms. Defaults to "1". @@ -679,9 +679,9 @@ REG_OP(Scale) * depth_radius = (local_size - 1) / 2. local_size is the number of channels to sum over (for ACROSS_CHANNELS) * or the side length of the square region to sum over (for WITHIN_CHANNEL). *@li bias: An optional float32. An offset, usually > 0 to avoid dividing by 0. -* Defaults to "1". +* Defaults to "1.0". *@li alpha: An optional float32. A scaling factor, usually positive. -* Defaults to "1". +* Defaults to "1.0". *@li beta: An optional float32. An exponent. Defaults to "0.75" for the caffe framework, Defaults to "0.5" for others. *@li norm_region: An optional string. A mode option. "ACROSS_CHANNELS":0, "WITHIN_CHANNEL":1. Defaults to "ACROSS_CHANNELS". @@ -836,6 +836,56 @@ REG_OP(GroupNorm) .ATTR(num_groups, Int, 2) .OP_END_FACTORY_REG(GroupNorm) +/** +*@brief Performs instance normalization. + +*@par Inputs:\n +* Five inputs, including: (NC1HWC0, supported) +*@li x: A 5D Tensor of type float16 or float32, NC1HWC0. +*@li gamma: A Tensor of type float32. +A 5D Tensor for scaling factor, to scale the normalized x. +*@li beta: A Tensor of type float32. +A 5D Tensor for offset, to shift to the normalized x. +*@li mean: A Tensor of type float32. +A 5D Tensor Specifies the mean used for inference. Reserved. +*@li variance: A Tensor of type float32. +A 5D Tensor Specifies the variance used for inference. Reserved. + +*@par Attributes: +*@li is_training: An optional bool, specifying if the operation is used for \n +training or inference. Defaults to "True". +*@li momentum: An optional float32, \n +the value used for the running_mean and running_var computation. Default: "0.1". +*@li epsilon: An optional float32, specifying the small value added to \n +variance to avoid dividing by zero. Defaults to "0.00001". + +*@par Outputs:\n +* Three outputs, including: (NHWC, NCHW NC1HWC0 supported) +*@li y: A 5D tensor of type float16 or float32 for the normalized "x", \n +*@li batch_mean: A Tensor of type float32. +Specifies the mean of "x". +*@li batch_variance: A Tensor of type float32. +Specifies the variance of "x". + +*@par Third-party framework compatibility +*@li Compatible with the PyTorch operator InstanceNorm. +*/ +REG_OP(InstanceNormV2) + .INPUT(x, TensorType({DT_FLOAT16, DT_FLOAT})) + .OPTIONAL_INPUT(gamma, TensorType({DT_FLOAT})) + .OPTIONAL_INPUT(beta, TensorType({DT_FLOAT})) + .OPTIONAL_INPUT(mean, TensorType({DT_FLOAT})) + .OPTIONAL_INPUT(variance, TensorType({DT_FLOAT})) + + .OUTPUT(y, TensorType({DT_FLOAT16, DT_FLOAT})) + .OUTPUT(batch_mean, TensorType({DT_FLOAT})) + .OUTPUT(batch_variance, TensorType({DT_FLOAT})) + + .ATTR(is_training, Bool, true) + .ATTR(momentum, Float, 0.1) + .ATTR(epsilon, Float, 0.00001) + .OP_END_FACTORY_REG(InstanceNormV2) + } // namespace ge #endif //GE_OP_NN_NORM_OPS_H diff --git a/third_party/fwkacllib/inc/ops/nn_pooling_ops.h b/third_party/fwkacllib/inc/ops/nn_pooling_ops.h index 5eb11445..693e51d1 100644 --- a/third_party/fwkacllib/inc/ops/nn_pooling_ops.h +++ b/third_party/fwkacllib/inc/ops/nn_pooling_ops.h @@ -101,6 +101,42 @@ REG_OP(AvgPool) .ATTR(data_format, String, "NHWC") .OP_END_FACTORY_REG(AvgPool) +/** +*@brief Performs average pooling on the input. + +*@par Inputs: +*x: A 5-D Tensor of shape [batch, depth, height, width, channels] and type float16, float32, double. + +*@par Attributes: +*@li ksize: List of ints that has length 1, 3 or 5. The size of the window for each dimension of the input tensor. +*@li strides:List of ints that has length 1, 3 or 5. The stride of the sliding window for each dimension of the input tensor. +*@li pads: List of ints, implicit zero paddings on both sides of the input. +*@li ceil_mode: When true, will use ceil instead of floor in the formula to compute the output shape. +*@li count_include_pad: When true, will include the zero-padding in the averaging calculation. +*@li divisor_override: if specified, it will be used as divisor, otherwise size of the pooling region will be used. +*@li data_format: A string, format of input data. + +*@par Outputs: +*y: The average pooled output tensor. + +*@attention Constraints: +*@li "ksize" is in the range [1, 255]. "strides" is in the range [1, 63] + +*@par Third-party framework compatibility +* Compatible with the TensorFlow operator AvgPool3D. +*/ +REG_OP(AvgPool3D) + .INPUT(x, TensorType({DT_FLOAT16, DT_FLOAT32, DT_DOUBLE})) + .OUTPUT(y, TensorType({DT_FLOAT16, DT_FLOAT32, DT_DOUBLE})) + .REQUIRED_ATTR(ksize, ListInt) + .REQUIRED_ATTR(strides, ListInt) + .REQUIRED_ATTR(pads, ListInt) + .ATTR(ceil_mode, Bool, false) + .ATTR(count_include_pad, Bool, true) + .ATTR(divisor_override, Int, 0) + .ATTR(data_format, String, "NDHWC") + .OP_END_FACTORY_REG(AvgPool3D) + /** *@brief Performs max_pool_ext2 on the input. @@ -184,17 +220,62 @@ REG_OP(MaxPool) .OP_END_FACTORY_REG(MaxPool) REG_OP(MaxPool3D) - .INPUT(x, TensorType({DT_FLOAT16})) - .OUTPUT(y, TensorType({DT_FLOAT16})) + .INPUT(x, TensorType({DT_FLOAT16, DT_FLOAT32, DT_DOUBLE})) + .OUTPUT(y, TensorType({DT_FLOAT16, DT_FLOAT32, DT_DOUBLE})) .REQUIRED_ATTR(ksize, ListInt) .REQUIRED_ATTR(strides, ListInt) .REQUIRED_ATTR(padding, String) .ATTR(pads, ListInt, {0,0,0}) - .ATTR(dilation, ListInt, {0,0,0}) + .ATTR(dilation, ListInt, {1,1,1}) .ATTR(ceil_mode, Int, 0) .ATTR(data_format, String, "NDHWC") .OP_END_FACTORY_REG(MaxPool3D) + +/** +* @brief Computes second-order gradients of the maxpooling3d function. + +* @par Inputs: +* @li orig_x: Original forward input tensor(NDC1HWC0) of type float16 +* @li orig_y: Original forward output tensor(NDC1HWC0) of type float16 +* @li grads: Gradient tensor(NDC1HWC0) of type float16 +* @li assist: Assist tensor(NDC1HWC0) of type float16 + +* @par Attributes: +* @li ksize: A required list or tuple, +* specifying the size of the sliding window. +* @li strides: A required list or tuple, +* specifying the stride of the sliding window. +* @li pads: A required list or tuple +* @li padding: A required string, window sliding mode. Either SAME or VALID. +* @li data_format: An optional string. +* Format of the original input, either NCDHW or NDHWC. Defaults to NDHWC. + +* @attention Constraints: +* @li Only the Ascend 910 platform is supported. +* @li "orig_x" and "grads" must have the same shape. +* @li "orig_y" and "y" must have the same shape. Otherwise, an error is reported. +* @li "orig_x", "orig_y", "grads", and "y" must be NDC1HWC0 tensors. + +* @par Outputs: +* @li y: Result tensor of type float16 + +* @par Third-party framework compatibility +* @li Compatible with the TensorFlow operator MaxPool3DGradGrad. +*/ + +REG_OP(MaxPool3DGradGrad) + .INPUT(orig_x, TensorType::RealNumberType()) + .INPUT(orig_y, TensorType::RealNumberType()) + .INPUT(grads, TensorType::RealNumberType()) + .OUTPUT(y, TensorType::RealNumberType()) + .REQUIRED_ATTR(ksize, ListInt) + .REQUIRED_ATTR(strides, ListInt) + .REQUIRED_ATTR(pads, ListInt) + .ATTR(data_format, String, "NDHWC") + .OP_END_FACTORY_REG(MaxPool3DGradGrad) + + /** * @brief Computes gradients of the maxpooling function. @@ -239,9 +320,10 @@ REG_OP(MaxPoolGrad) * @brief Computes second-order gradients of the maxpooling function. * @par Inputs: -* @li x1: Original forward input tensor of type RealNumberType -* @li x2: Original forward output tensor of type RealNumberType -* @li grad: Gradient tensor of type RealNumberType +* @li x1: Original forward input tensor. Supported type:float, double, int32, + * uint8, int16, int8, int64, uint16, half, uint32, uint64. +* @li x2: Has the same type and format as input "x1". +* @li grad:Has the same type and format as input "x1". * @par Attributes: * @li ksize: A required list or tuple, @@ -262,7 +344,7 @@ REG_OP(MaxPoolGrad) * @li Other dimensions of ksize and strides is 1. * @par Outputs: -* @li y: Result tensor of type RealNumberType +* @li y: Has the same type and format as input "x1". * @par Third-party framework compatibility * @li Compatible with the TensorFlow operator MaxPoolGradGrad. @@ -397,19 +479,56 @@ REG_OP(MaxPoolGradWithArgmax) .REQUIRED_ATTR(padding, String) .OP_END_FACTORY_REG(MaxPoolGradWithArgmax) +/** +*@brief Performs transform mask to argmax. + +*@par Inputs: +* Two input: +*x: An NC1HWC0 Tensor of type float16. +*mask: An NC1HWC0 Tensor of type uint16. + +*@par Attributes: +*@li ksize: A required list of int8, int16, int32, or int64 values, specifying the size of the window for each dimension of the input tensor. No default value. +*@li strides: A required list of int8, int16, int32, or int64 values, specifying the stride of the sliding window for each dimension of the input tensor. No default value. +*@li padding: A required string. No default value. + +*@par Outputs: +*argmax: An NC1HWC0 Tensor of type int32. + +*@attention Constraints: +*@li "ksize" is a list that has length 4: ksize[0] = 1 or ksize[3] = 1, ksize[1] * ksize[2] <= 255. +*@li "stride is a list that has length 4: strides[0] = 1 or strides[3] = 1, strides[1] <= 63, strides[0] >= 1, strides[2] <= 63, strides[2] >= 1. +*@li "padding" is either "SAME" or "VALID". + +*@par Third-party framework compatibility +* Compatible with the TensorFlow operator Mask2Argmax. +*/ +REG_OP(Mask2Argmax) + .INPUT(x, TensorType::RealNumberType()) + .INPUT(mask, TensorType::IndexNumberType()) + .OUTPUT(argmax, TensorType::IndexNumberType()) + .REQUIRED_ATTR(ksize, ListInt) + .REQUIRED_ATTR(strides, ListInt) + .REQUIRED_ATTR(padding, String) + .REQUIRED_ATTR(originshape, ListInt) + .OP_END_FACTORY_REG(Mask2Argmax) + /** * @brief Computes second-order gradients of the maxpooling function. * @par Inputs: -* @li x: Original forward input tensor of type RealNumberType -* @li grad: Gradient tensor of type RealNumberType -* @li argmax: An tensor of type IndexNumberType +* @li x: Original forward input tensor. Supported type: float, double, int32, + * uint8, int16, int8, int64, uint16, half, uint32, uint64. +* @li grad: Gradient tensor. Supported type: float, double, int32, + * uint8, int16, int8, int64, uint16, half, uint32, uint64. +* @li argmax: An tensor of type int32 or int64. * @par Attributes: * @li ksize: A required list, specifying the size of the sliding window. * @li strides: A required list, specifying the stride of the sliding window. * @li padding: A required string, window sliding mode. Either SAME or VALID. * @par Outputs: -* @li y:Result tensor of type RealNumberType +* @li y:Result tensor. Supported type: float, double, int32, + * uint8, int16, int8, int64, uint16, half, uint32, uint64 * @attention Constraints: * @li Only the cloud platform is supported. @@ -531,7 +650,7 @@ REG_OP(MaxPoolGradWithArgmaxCCE) * one input, including: *@li x: A tensor of type float16 or float32. *@par Attributes: -*@li scale: A optional float, scale factor of x. Defaults to "1.0". +*@li scale: A optional float32, scale factor of x. Defaults to "1.0". *@li stride_h: An optional int32, broadcast the axis of h. Defaults to "2". *@li stride_w: An optional int32, broadcast the axis of w. Defaults to "2". *@par Outputs: @@ -749,7 +868,186 @@ REG_OP(DataFormatVecPermute) .ATTR(dst_format, String, "NCHW") .OP_END_FACTORY_REG(DataFormatVecPermute) +/** +* @brief Computes gradients of the MaxPool3D function. +* @par Inputs: +* @li orig_x: A mutable NDC1HWC0 tensor of type float16. +* @li orig_y: A mutable NDC1HWC0 tensor of type float16. +* @li grads: A mutable NDC1HWC0 tensor of type float16. + +* @par Attributes: +* @li ksize: A required tuple or list, specifying the size of the window for +* each dimension of the input tensor. +* @li strides: A required tuple or list, specifying the stride of the sliding +* window for each dimension of the input tensor. +* @li pads: A list of 6 ints. Supports only padding along the D, +* H and W dimensions in sequence of head, tail, top, bottom, left and right. +* to use. +* @li data_format: An optional string, Specify the data format of the input and +* output data. With the default format "NDHWC". + +* @par Outputs: +* y: A mutable tensor. Has the same shape as "orig_x", but type is float32. + +* @par Third-party framework compatibility +* Compatible with the TensorFlow operator MaxPool3DGrad. +*/ +REG_OP(MaxPool3DGrad) + .INPUT(orig_x, TensorType::RealNumberType()) + .INPUT(orig_y, TensorType::RealNumberType()) + .INPUT(grads, TensorType::RealNumberType()) + .OUTPUT(y, TensorType::RealNumberType()) + .REQUIRED_ATTR(ksize, ListInt) + .REQUIRED_ATTR(strides, ListInt) + .REQUIRED_ATTR(pads, ListInt) + .ATTR(data_format, String, "NDHWC") + .OP_END_FACTORY_REG(MaxPool3DGrad) + +/** +*@brief Performs AvgPool1D on the input. + +*@par Inputs: +*x: A Tensor. Must be one of the following types: int8, uint8, int16, int32, int64, float16, float32, float64. + +*@par Attributes: +*@li ksize: An required int, specifying the size of the window. +*@li strides: An required int. +*@li pads: A required tuple or list. +*@li ceil_mode: An optional bool. Defaults to False. +*@li count_include_pad: An optional bool. Defaults to False. + +*@par Outputs: +*y: A Tensor. Has the same type as x. + +*@par Third-party framework compatibility +*@li compatible with pytorch AvgPool1D operator. +*/ +REG_OP(AvgPool1D) + .INPUT(x, TensorType({DT_INT8, DT_UINT8, DT_INT16, DT_INT32, DT_INT64, DT_FLOAT16, DT_FLOAT, DT_DOUBLE})) + .OUTPUT(y, TensorType({DT_INT8, DT_UINT8, DT_INT16, DT_INT32, DT_INT64, DT_FLOAT16, DT_FLOAT, DT_DOUBLE})) + .REQUIRED_ATTR(ksize, Int) + .REQUIRED_ATTR(strides, Int) + .REQUIRED_ATTR(pads, ListInt) + .ATTR(ceil_mode, Bool, false) + .ATTR(count_include_pad, Bool, false) + .OP_END_FACTORY_REG(AvgPool1D) + +/** +*@brief Performs AvgPool1D on the input. + +*@par Inputs: +*x: A Tensor. Must be one of the following types: int8, uint8, int16, int32, int64, float16, float32, float64. + +*@par Attributes: +*@li ksize: An required int, specifying the size of the window. +*@li strides: An required int. +*@li pads: A required tuple or list. +*@li ceil_mode: An optional bool. Defaults to False. +*@li count_include_pad: An optional bool. Defaults to False. + +*@par Outputs: +*y: A Tensor. Has the same type as x. + +*@par Third-party framework compatibility +*@li compatible with pytorch AvgPool1D operator. +*/ +REG_OP(AvgPool1DD) + .INPUT(x, TensorType({DT_INT8, DT_UINT8, DT_INT16, DT_INT32, DT_INT64, DT_FLOAT16, DT_FLOAT, DT_DOUBLE})) + .INPUT(assist_matrix, TensorType({DT_INT8, DT_UINT8, DT_INT16, DT_INT32, DT_INT64, DT_FLOAT16, DT_FLOAT, DT_DOUBLE})) + .OUTPUT(y, TensorType({DT_INT8, DT_UINT8, DT_INT16, DT_INT32, DT_INT64, DT_FLOAT16, DT_FLOAT, DT_DOUBLE})) + .REQUIRED_ATTR(ksize, Int) + .REQUIRED_ATTR(strides, Int) + .REQUIRED_ATTR(pads, ListInt) + .ATTR(ceil_mode, Bool, false) + .ATTR(count_include_pad, Bool, false) + .OP_END_FACTORY_REG(AvgPool1DD) +/** +*@brief Performs max pooling on the input and outputs both max values and indices. + +*@par Inputs: +* One input: +*x: An NC1HWC0 Tensor of type float16. +*@par Attributes: +*@li ksize: A required list of int8, int16, int32, or int64 values, specifying the size of the window for +* each dimension of the input tensor. No default value. +*@li strides: A required list of int8, int16, int32, or int64 values, specifying the stride of the sliding window for +* each dimension of the input tensor. No default value. +*@li pads: A required string. No default value. +*@li dtype: A optional int. default value is 3. +*@li dilation: A optional list of int8, int16, int32, or int64 values. +*@li ceil_mode: A optional bool. default value is false. + +*@par Outputs: +*y: A Tensor. Has the same type and format as input "x". +*argmax: A Tensor. type:uint16, format:NC1HWC0. +*@attention Constraints: +*@li "ksize" is a list that has length 4: ksize[0] = 1 or ksize[3] = 1, ksize[1] * ksize[2] <= 255. +*@li "strides is a list that has length 4: strides[0] = 1 or strides[3] = 1, strides[1] <= 63, strides[0] >= 1, +* strides[2] <= 63, strides[2] >= 1. +*@li "dilation" is a list that has length 4. +*@li "ceil_mode" is a bool, default is false. + +*@par Third-party framework compatibility +* Compatible with the TensorFlow operator MaxPoolWithArgmax. +*/ +REG_OP(MaxPoolWithArgmaxV2) + .INPUT(x, TensorType({DT_FLOAT16})) + .OUTPUT(y, TensorType({DT_FLOAT16})) + .OUTPUT(argmax, TensorType({DT_UINT16})) + .REQUIRED_ATTR(ksize, ListInt) + .REQUIRED_ATTR(strides, ListInt) + .REQUIRED_ATTR(pads, ListInt) + .ATTR(dtype, Int, 3) + .ATTR(dilation, ListInt, {1, 1, 1, 1}) + .ATTR(ceil_mode, Bool, false) + .OP_END_FACTORY_REG(MaxPoolWithArgmaxV2) + +/** +*@brief Performs the backpropagation of MaxPoolWithArgmaxV2. + +*@par Inputs: +* Three inputs, including: +*@li x: An NC1HWC0 tensor of type float16. +*@li grad: An NC1HWC0 tensor of type float16. +*@li argmx: An NC1HWC0 tensor of type uint16 or int64. + +*@par Attributes: +*@li ksize: A required list of int8, int16, int32, or int64 values, specifying the size of the window for + * each dimension of the input tensor. No default value. +*@li strides: A required list of int8, int16, int32, or int64 values, specifying the stride of the sliding window for + * each dimension of the input tensor. No default value. +*@li pads: A required string. No default value. +*@li dtype: A optional int. default value is 3. +*@li dilation: A optional list of int8, int16, int32, or int64 values. +*@li ceil_mode: A optional bool. default value is false. + +*@par Outputs: +*y: A Tensor. Has the same type and format as input "x". + +*@attention Constraints: +*@li "ksize" is a list that has length 4: ksize[0] = 1 or ksize[3] = 1, ksize[1] * ksize[2] <= 255. +*@li "strides" is a list that has length 4: strides[0] = 1 or strides[3] = 1 +*@li "dilation" is a list that has length 4. +*@li "ceil_mode" is a bool, default is false. + +*@see max_pool_grad_with_argmaxv2 +*@par Third-party framework compatibility +* Compatible with the TensorFlow operator MaxPoolGradWithArgmaxV2. +*/ + +REG_OP(MaxPoolGradWithArgmaxV2) + .INPUT(x, TensorType({DT_FLOAT16})) + .INPUT(grad, TensorType({DT_FLOAT16})) + .INPUT(argmax, TensorType({DT_UINT16})) + .OUTPUT(y, TensorType({DT_FLOAT16})) + .REQUIRED_ATTR(ksize, ListInt) + .REQUIRED_ATTR(strides, ListInt) + .REQUIRED_ATTR(pads, ListInt) + .ATTR(dtype, Int, 3) + .ATTR(dilation, ListInt, {1,1,1,1}) + .ATTR(ceil_mode, Bool, false) + .OP_END_FACTORY_REG(MaxPoolGradWithArgmaxV2) } // namespace ge #endif // GE_OP_NN_POOLING_OPS_H diff --git a/third_party/fwkacllib/inc/ops/nn_training_ops.h b/third_party/fwkacllib/inc/ops/nn_training_ops.h index 1c9aa516..368054f5 100644 --- a/third_party/fwkacllib/inc/ops/nn_training_ops.h +++ b/third_party/fwkacllib/inc/ops/nn_training_ops.h @@ -1508,7 +1508,7 @@ REG_OP(ApplyProximalAdagradD) *@par Attributes: *use_locking: An optional bool. Defaults to "False".\n * If "True", updating of the var and accum tensors will be protected by a lock; \n -* If "False", the behavior is undefined, but may exhibit less contention. +* If "False", the behavior is undefined, but may exhibit less contention. *@par Outputs: *var: A mutable Tensor. Has the same type as "var". diff --git a/third_party/fwkacllib/inc/ops/nonlinear_fuc_ops.h b/third_party/fwkacllib/inc/ops/nonlinear_fuc_ops.h index 1405fdb7..a01073cf 100644 --- a/third_party/fwkacllib/inc/ops/nonlinear_fuc_ops.h +++ b/third_party/fwkacllib/inc/ops/nonlinear_fuc_ops.h @@ -83,7 +83,7 @@ REG_OP(TanhGrad) *@par Inputs: *One input: -*x: A Tensor. Must be one of the following types: float16, float32, complex64, complex128, int32, int64 +*x: A Tensor. Must be one of the following types: float16, float32, complex64, complex128, double. *@par Outputs: *y: A Tensor. Has the same type as "x". @@ -184,7 +184,7 @@ REG_OP(Relu6Grad) * @brief Compute sigmoid of "x" element-wise. * @par Inputs: -* A Tensor of type UnaryDataType. +* A Tensor of type complex64, complex128, float16, float32 or double. * @par Outputs: * A Tensor. Has the same type as "x". @@ -220,7 +220,7 @@ REG_OP(SigmoidGrad) *if x>0, x+log(1+exp(-x)); otherwise log(1+exp(x)). *@par Inputs: -*x: A Tensor of type float16 or float32. +*x: A Tensor of type double, float16 or float32. *@par Outputs: *y: A tensor. Has the same type and format as input "x". @@ -442,7 +442,7 @@ REG_OP(PReluGrad) *x: A float16, float32 or double, for the input data type. *@par Attributes: -*alpha: A float. Defines at which negative value the ELU saturates. Defaults to "1.0". +*alpha: A float32. Defines at which negative value the ELU saturates. Defaults to "1.0". *@par Outputs: *y: A float16, float32 or double, for the normalized result. diff --git a/third_party/fwkacllib/inc/ops/reduce_ops.h b/third_party/fwkacllib/inc/ops/reduce_ops.h index 8819d2d5..a8aed058 100644 --- a/third_party/fwkacllib/inc/ops/reduce_ops.h +++ b/third_party/fwkacllib/inc/ops/reduce_ops.h @@ -673,7 +673,7 @@ REG_OP(ReduceAnyD) *@par Attributes: *@li operation: An optional int32 from 1(SUM), 2(ASUM), 3(SUMSQ), and 4(MEAN), -*specifying the reduction algorithm. Defaults to 1. +*specifying the reduction algorithm. Defaults to "1". *@li axis: An optional int32, specifying the first axis to reduce. Defaults to "0". *The value range is [-N, N-1], where N is the input tensor rank. *@li coeff: An optional float32, specifying the scale coefficient. Defaults to "1.0". @@ -745,7 +745,190 @@ REG_OP(EuclideanNormD) .ATTR(keep_dims, Bool, false) .OP_END_FACTORY_REG(EuclideanNormD) -} //namespace ge +/** +*@brief Performs instance normalization for inference. + +*@par Inputs:\n +* Five inputs, including: (NC1HWC0 supported) +*@li x: A Tensor of type float16 or float32. +*@li gamma: A [N, C1, 1, 1, C0] Tensor of type float32, for the scaling gamma. +*@li beta: A [N, C1, 1, 1, C0] Tensor of type float32, for the scaling beta. +*@li mean: A [N, C1, 1, 1, C0] ensor of type float32, for the mean. +*@li variance: A [N, C1, 1, 1, C0] Tensor of type float32, for the variance. + +*@par Attributes: +*epsilon: An optional float32, specifying the small value added to variance to avoid dividing by zero. +Defaults to "0.00001". + +*@par Outputs:\n +*y: A Tensor of type float16 or float32 for the normalized "x". +*batch_mean: A Tensor of type float32 for the result mean. +*batch_ variance: A Tensor of type float32 for the result variance. + +*@attention Constraints: +*For Ascend 310, the result accuracy fails to reach 1‰ due to the square root instruction. +*/ +REG_OP(INInferV2) + .INPUT(x, TensorType({DT_FLOAT16,DT_FLOAT})) + .OPTIONAL_INPUT(gamma, TensorType({DT_FLOAT})) + .OPTIONAL_INPUT(beta, TensorType({DT_FLOAT})) + .OPTIONAL_INPUT(mean, TensorType({DT_FLOAT})) + .OPTIONAL_INPUT(variance, TensorType({DT_FLOAT})) + .ATTR(epsilon, Float, 0.00001) + .OUTPUT(y, TensorType({DT_FLOAT16,DT_FLOAT})) + .OUTPUT(batch_mean, TensorType({DT_FLOAT})) + .OUTPUT(batch_variance, TensorType({DT_FLOAT})) + .OP_END_FACTORY_REG(INInferV2) + +/** +*@brief Performs reduced instance normalization. + +*@par Inputs:\n +*x: A Tensor of type float16 or float32, with format NC1HWC0. + +*@par Outputs: +*@li sum: A Tensor of type float32 for SUM reduced "x". +*@li square_sum: A Tensor of type float32 for SUMSQ reduced "x". + +*@attention Constraints:\n +* This operator is a InstanceNorm fusion operator for updating the moving averages for training. \n +* This operator is used in conjunction with INTrainingUpdateV2. +*/ +REG_OP(INTrainingReduceV2) + .INPUT(x, TensorType({DT_FLOAT16,DT_FLOAT})) + .OUTPUT(sum, TensorType({DT_FLOAT})) + .OUTPUT(square_sum, TensorType({DT_FLOAT})) + .OP_END_FACTORY_REG(INTrainingReduceV2) + + +/** +*@brief Performs update instance normalization. + +*@par Inputs:\n +* Seven inputs, including: (NC1HWC0supported) +*@li x: A Tensor of type float16 or float32. +*@li sum: A T [N, C1, 1, 1, C0] ensor of type float32 for the output of operator INTrainingReduceV2. +*@li square_sum: A [N, C1, 1, 1, C0] Tensor of type float32 for the output of operator INTrainingReduceV2. +*@li gamma: A [N, C1, 1, 1, C0] Tensor of type float32, for the scaling gamma. +*@li beta: A [N, C1, 1, 1, C0] Tensor of type float32, for the scaling beta. +*@li mean: A [N, C1, 1, 1, C0] Tensor of type float32, for the updated mean. +*@li variance: A [N, C1, 1, 1, C0] Tensor of type float32, for the updated variance. + +*@par Attributes: +*@li momentum: A required float32, specifying the momentum to update mean and var. +*@li epsilon: A required float32, specifying the small value added to variance to avoid dividing by zero. + +*@par Outputs:\n +* Three outputs, including: (NC1HWC0 supported) +*@li y: A Tensor of type float16 or float32, for normalized "x". +*@li batch_mean: A Tensor of type float32, for the updated mean. +*@li batch_variance: A Tensor of type float32, for the updated variance. + +*@attention Constraints: +*@li This operator is a InstanceNorm fusion operator for updating the moving averages for training. \n +* This operator is used in conjunction with INTrainingReduceV2. +*@li For Ascend 310, the result accuracy fails to reach 1‰ due to the square root instruction. +*/ +REG_OP(INTrainingUpdateV2) + .INPUT(x, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(sum, TensorType({DT_FLOAT})) + .INPUT(square_sum, TensorType({DT_FLOAT})) + .OPTIONAL_INPUT(gamma, TensorType({DT_FLOAT})) + .OPTIONAL_INPUT(beta, TensorType({DT_FLOAT})) + .OPTIONAL_INPUT(mean, TensorType({DT_FLOAT})) + .OPTIONAL_INPUT(variance, TensorType({DT_FLOAT})) + .ATTR(momentum, Float, 0.1) + .ATTR(epsilon, Float, 0.00001) + .OUTPUT(y, TensorType({DT_FLOAT16,DT_FLOAT})) + .OUTPUT(batch_mean, TensorType({DT_FLOAT})) + .OUTPUT(batch_variance, TensorType({DT_FLOAT})) + .OP_END_FACTORY_REG(INTrainingUpdateV2) + + +/** +*@brief Performs reduced group normalization. + +*@par Inputs:\n +*x: A Tensor of type float16 or float32, with format NCHW NHWC. + +*@par Outputs: +*@li sum: A Tensor of type float32 for SUM reduced "x". +*@li square_sum: A Tensor of type float32 for SUMSQ reduced "x". + + +*@par Attributes: +*@li num_groups: Int, specifying the num of groups. required, same to GNTrainingUpdate. + +*@attention Constraints:\n +* This operator is a GroupNorm fusion operator for updating the moving averages for training. \n +* This operator is used in conjunction with GNTrainingUpdate. +*/ +REG_OP(GNTrainingReduce) + .INPUT(x, TensorType({DT_FLOAT16,DT_FLOAT})) + .OUTPUT(sum, TensorType({DT_FLOAT})) + .OUTPUT(square_sum, TensorType({DT_FLOAT})) + .ATTR(num_groups, Int, 2) + .OP_END_FACTORY_REG(GNTrainingReduce) + + +/** +*@brief Performs update group normalization. + +*@par Inputs:\n +* Eight inputs, including: (NCHW NHWC supported) +*@li x: A Tensor of type float16 or float32. +*@li sum: A 5D Tensor of type float32, +shape is [N, G, D, 1, 1] for NCHW, [N, 1, 1, G, D] for NHWC +for the output of operator GNTrainingReduce. +*@li square_sum: A 5D Tensor of type float32, +shape is [N, G, D, 1, 1] for NCHW, [N, 1, 1, G, D] for NHWC +for the output of operator GNTrainingReduce. +*@li scale: A 5D Tensor of type float32, +shape is [1, G, D, 1, 1] for NCHW, [1, 1, 1, G, D] for NHWC +is for the scaling gamma. +*@li offset: A 5D Tensor of type float32, +shape is [1, G, D, 1, 1] for NCHW, [1, 1, 1, G, D] for NHWC +for the scaling beta. +*@li mean: A 5D Tensor of type float32, +shape is [N, G, D, 1, 1] for NCHW, [N, 1, 1, G, D] for NHWC +for the updated mean. +*@li variance: A 5D Tensor of type float32, +shape is [N, G, D, 1, 1] for NCHW, [N, 1, 1, G, D] for NHWC +for the updated variance. + + +*@par Attributes: +*@li epsilon: A float32, specifying the small value added to variance to avoid dividing by zero. +*@li num_groups: Int, specifying the num of groups. required, same to GNTrainingReduce + +*@par Outputs:\n +* Three outputs, including: (NC1HWC0 supported) +*@li y: A Tensor of type float16 or float32, for normalized "x". +*@li batch_mean: A Tensor of type float32, for the updated mean. +*@li batch_variance: A Tensor of type float32, for the updated variance. + +*@attention Constraints: +*@li This operator is a InstanceNorm fusion operator for updating the moving averages for training. \n +* This operator is used in conjunction with GNTrainingUpdate. +*@li For Ascend 310, the result accuracy fails to reach 1‰ due to the square root instruction. +*/ +REG_OP(GNTrainingUpdate) + .INPUT(x, TensorType({DT_FLOAT16,DT_FLOAT})) + .INPUT(sum, TensorType({DT_FLOAT})) + .INPUT(square_sum, TensorType({DT_FLOAT})) + .OPTIONAL_INPUT(scale, TensorType({DT_FLOAT})) + .OPTIONAL_INPUT(offset, TensorType({DT_FLOAT})) + .OPTIONAL_INPUT(mean, TensorType({DT_FLOAT})) + .OPTIONAL_INPUT(variance, TensorType({DT_FLOAT})) + .ATTR(num_groups, Int, 2) + .ATTR(epsilon, Float, 0.0001) + .OUTPUT(y, TensorType({DT_FLOAT16,DT_FLOAT})) + .OUTPUT(batch_mean, TensorType({DT_FLOAT})) + .OUTPUT(batch_variance, TensorType({DT_FLOAT})) + .OP_END_FACTORY_REG(GNTrainingUpdate) + +} //namespace ge + #endif /* GE_OP_REDUCE_OPS_H */ diff --git a/third_party/fwkacllib/inc/ops/rnn.h b/third_party/fwkacllib/inc/ops/rnn.h index c4d64b0a..b72d9a79 100644 --- a/third_party/fwkacllib/inc/ops/rnn.h +++ b/third_party/fwkacllib/inc/ops/rnn.h @@ -67,6 +67,13 @@ REG_OP(BasicLSTMCell) .ATTR(activation, String, "tanh") .OP_END_FACTORY_REG(BasicLSTMCell) +REG_OP(DynamicLSTM) + .INPUT(x, TensorType({DT_FLOAT32})) + .INPUT(w, TensorType({DT_FLOAT32})) + .INPUT(b, TensorType({DT_FLOAT32})) + .OUTPUT(output_h, TensorType({DT_FLOAT32})) + .OP_END_FACTORY_REG(DynamicLSTM) + /** *@brief: Basic LSTM Cell backward calculation.Calculate the gradient of input and hidden state. *@par Inputs: @@ -87,7 +94,7 @@ REG_OP(BasicLSTMCellInputGrad) .INPUT(dgate, TensorType({DT_FLOAT16})) .INPUT(w, TensorType({DT_FLOAT16})) .OPTIONAL_INPUT(dropout_mask, TensorType({DT_UINT8})) - .OUTPUT(dxt, TensorType({DT_FLOAT16})) + .OUTPUT(dxt, TensorType({DT_FLOAT16, DT_FLOAT32})) .OUTPUT(dht, TensorType({DT_FLOAT16, DT_FLOAT32})) .ATTR(keep_prob, Float, 1.0) .OP_END_FACTORY_REG(BasicLSTMCellInputGrad) diff --git a/third_party/fwkacllib/inc/ops/selection_ops.h b/third_party/fwkacllib/inc/ops/selection_ops.h index aafcece0..bbe203cd 100644 --- a/third_party/fwkacllib/inc/ops/selection_ops.h +++ b/third_party/fwkacllib/inc/ops/selection_ops.h @@ -89,7 +89,8 @@ REG_OP(RangeD) *@par Inputs: *Two inputs, including: -* @li x: A Tensor of type TensorType::BasicType(). +* @li x: A Tensor. +* Must be one of the following types: float16, float32, double, int64, int32, uint8, uint16, uint32, uint64, int8, int16, complex64, complex128, qint8, quint8, qint16, quint16, qint32. * @li multiples: A 1D Tensor of type int32 or int64. * The length must be the same as the number of dimensions in "input" @@ -496,7 +497,7 @@ REG_OP(UnsortedSegmentSumD) *@par Inputs: * Two inputs, including:\n *@li x: An ND Tensor (up to 8D). \n -*Must be one of the following types: int8, uint8, int16, uint16, int32, int64, bool, float32, double +*Must be one of the following types: int8, uint8, int16, uint16, int32, int64, bool, float16, float32, double, complex64, complex128, string. *@li axis: A 1D Tensor.\n *Must be one of the following types: int32, int64 @@ -1559,14 +1560,14 @@ REG_OP(ProposalD) * If reverse=false: (N, H, W, C)->(N, H/stride, W/stride, C*(stride*stride)) *@par Inputs: -*x: An (N, H, W, C) tensor. All types except double are supported. +*x: An (N, H, W, C) tensor. Type is float16, float32, int8, uint8, int16, uint16, int32, uint32, int64 or uint64.. *@par Attributes: *@li stride: An optional int32, specifying the plane or channel scaling factor. Defaults to "2". *@li reverse: An optional bool, specifying the conversion mode. If "true", depth to space conversion is performed. If "false", space to depth conversion is performed. Defaults to "false". *@par Outputs: -*y: An (N, H, W, C) tensor. All types except double are supported. +*y: An (N, H, W, C) tensor. Has same type as "x". *@attention Constraints: *@li If reverse=true: C/(stride*stride) yields an integer result. If reverse=false: W/stride and H/stride yield integer results. @@ -1593,7 +1594,7 @@ REG_OP(PassThrough) * @li x: A required Tensor. Must be one of the following types: float16, float32, int8, uint8, int16, uint16, int32, uint32,int64, uint64. * @li size: A required Tensor. Must be one of the following types: float16, float32, int8, uint8, int16, uint16, int32, uint32, int64, uint64. *@par Attributes: -*@li axis: A required int32, specifying the first dimension to crop. +*@li axis: A required int32, specifying the first dimension to crop. Defaults to "2". *@li offset: A required array, specifying the shift for all/each dimension to align the cropped bottom with the reference bottom. Must be one of the following types: float16, float32, int8, uint8, int16, uint16, int32, uint32, int64, uint64. *@par Outputs: *y: A required Tensor. Has the same type and shape as "size". diff --git a/third_party/fwkacllib/inc/ops/split_combination_ops.h b/third_party/fwkacllib/inc/ops/split_combination_ops.h index 700d34b7..7e4428d0 100644 --- a/third_party/fwkacllib/inc/ops/split_combination_ops.h +++ b/third_party/fwkacllib/inc/ops/split_combination_ops.h @@ -25,11 +25,11 @@ namespace ge { *@par Inputs: * Two inputs, including: *@li x: An ND Tensor. -*Must be one of the following types: float16, float32, int32, int8, int16, int64, uint8, uint16, uint32, uint64 +*Must be one of the types:float16, float32, double, int64, int32, uint8, uint16, uint32, uint64, int8, int16, complex64, complex128, qint8, quint8, qint16, quint16, qint32. *@li split_dim: Must be the following type:int32. Specifies the dimension along which to split. *@par Attributes: -*num_split: A required int8, int16, int32, or int64. Specifies the number of output tensors. No default value. +*num_split: A required int32. Specifies the number of output tensors. No default value. *@par Outputs: *y: Dynamic output.A list of output tensors. Has the same type and format as "x". @@ -186,6 +186,7 @@ REG_OP(ParallelConcat) *@par Attributes: *concat_dim: A required int8, int16, int32, or int64. Specifies the dimension along which to concatenate. No default value. +*N: An attribute int8, int16, int32, or int64. Specifies the number of elements in "x". Defaults to "1". *@par Outputs: *y: A Tensor. Has the same type and format as "x". @@ -267,7 +268,9 @@ REG_OP(ConcatD) *@par Inputs: * Two inputs, including: *@li x: Dynamic input.An NC1HWC0 or ND Tensor. -*Must be one of the following types: float16, float32, int32, int8, int16, int64, uint8, uint16, uint32, uint64 +*Must be one of the following types: float16, float32, double, int32, +* uint8, int16, int8, complex64, int64, qint8, quint8, qint32, uint16, +* complex128, uint32, uint64, qint16, quint16. *@li concat_dim: An int32, or int64. Specifies the dimension along which to concatenate. *@par Attributes: diff --git a/third_party/fwkacllib/inc/ops/transformation_ops.h b/third_party/fwkacllib/inc/ops/transformation_ops.h index 69951da9..7b8a94f8 100644 --- a/third_party/fwkacllib/inc/ops/transformation_ops.h +++ b/third_party/fwkacllib/inc/ops/transformation_ops.h @@ -94,6 +94,13 @@ REG_OP(Transpose) .OUTPUT(y, TensorType::BasicType()) .OP_END_FACTORY_REG(Transpose) +REG_OP(TransData) + .INPUT(src, TensorType::BasicType()) + .OUTPUT(dst, TensorType::BasicType()) + .REQUIRED_ATTR(src_format, String) + .REQUIRED_ATTR(dst_format, String) + .OP_END_FACTORY_REG(TransData) + /** *@brief Permutes the dimensions according to order.\n The returned tensor's dimension i will correspond to the input dimension order[i]. @@ -102,7 +109,7 @@ REG_OP(Transpose) *x: A Tensor. Must be one of the following types: float16, float32. *@par Attributes: -*order: A permutation of the dimensions of "x".support any axis transformation +*order: A permutation of the dimensions of "x".Type is int32.support any axis transformation.Defaults to "{0}" *@par Outputs: *y: A Tensor. Has the same type as "x". @@ -291,7 +298,7 @@ REG_OP(DepthToSpace) *@brief Permutes data into spatial data blocks and then prunes them. *@par Inputs: -*@li x: A 4D Tensor with format NC1HWC0. +*@li x: A 4D Tensor with format NHWC. *@li crops: A 1D list or tuple of int32 or int64. *Must be one of the following types: float16, float32 @@ -300,7 +307,7 @@ REG_OP(DepthToSpace) *block_size: A required int8, int16, int32, or int64. No default value. *@par Outputs: -*y: A 4D Tensor with format NC1HWC0, +*y: A 4D Tensor with format NHWC, * of type float16 or float32. @@ -365,7 +372,7 @@ REG_OP(BatchToSpaceD) *@par Inputs: * Two inputs, including: -*@li x: An NC1HWC0 Tensor. Must be one of the following types: +*@li x: An NHWC Tensor. Must be one of the following types: * float16, float32, double, int64, int32, uint8, uint16, uint32, uint64, int8, * int16, complex64, complex128, qint8, quint8, qint16, quint16, qint32. *@li paddings: A 2D tensor of type int, specifying the input. @@ -389,7 +396,7 @@ REG_OP(SpaceToBatch) *@brief Outputs a copy of the input tensor where values from the "height" and "width" dimensions are padded and rearranged to the "batch" dimension. *@par Inputs: -*x: An NC1HWC0 Tensor. Must be one of the following types: float16, float32, double, int64, int32, uint8, uint16, uint32, uint64, int8, int16, complex64, complex128, qint8, quint8, qint16, quint16, qint32. +*x: An NHWC Tensor. Must be one of the following types: float16, float32, double, int64, int32, uint8, uint16, uint32, uint64, int8, int16, complex64, complex128, qint8, quint8, qint16, quint16, qint32. *@par Attributes: @@ -598,6 +605,13 @@ REG_OP(Compress) .OUTPUT(compress_index, TensorType({DT_INT8})) .REQUIRED_ATTR(compress_parameters, ListInt) .OP_END_FACTORY_REG(Compress) + +REG_OP(CompressFcOp) + .INPUT(weight, TensorType({DT_INT8})) + .OUTPUT(weight_compress, TensorType({DT_INT8})) + .OUTPUT(compress_index, TensorType({DT_INT8})) + .REQUIRED_ATTR(compress_parameters, ListInt) + .OP_END_FACTORY_REG(CompressFcOp) } // namespace ge #endif // GE_OP_TRANSFORMATION_OPS_H diff --git a/third_party/fwkacllib/inc/register/op_registry.h b/third_party/fwkacllib/inc/register/op_registry.h index 1fcdf9de..1dc14b8b 100644 --- a/third_party/fwkacllib/inc/register/op_registry.h +++ b/third_party/fwkacllib/inc/register/op_registry.h @@ -35,6 +35,7 @@ enum RemoveInputType { OMG_MOVE_TYPE_SCALAR_VALUE, OMG_REMOVE_TYPE_WITH_COND = 1000, OMG_REMOVE_INPUT_WITH_ORIGINAL_TYPE, + OMG_INPUT_REORDER, }; struct RemoveInputConfigure { @@ -43,6 +44,7 @@ struct RemoveInputConfigure { RemoveInputType moveType; bool attrValue = false; std::string originalType; + std::vector input_order; }; class FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY OpRegistry { @@ -57,11 +59,11 @@ class FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY OpRegistry { void GetOpTypeByImplyType(std::vector &vec_op_type, const domi::ImplyType &imply_type); - domi::ParseParamFunc GetParseParamFunc(const std::string &op_type); + domi::ParseParamFunc GetParseParamFunc(const std::string &op_type, const std::string &ori_type); - domi::ParseParamByOpFunc GetParseParamByOperatorFunc(const std::string &op_type); + domi::ParseParamByOpFunc GetParseParamByOperatorFunc(const std::string &ori_type); - domi::FusionParseParamFunc GetFusionParseParamFunc(const std::string &op_type); + domi::FusionParseParamFunc GetFusionParseParamFunc(const std::string &op_type, const std::string &ori_type); domi::ParseSubgraphFunc GetParseSubgraphPostFunc(const std::string &op_type); @@ -72,14 +74,13 @@ class FMK_FUNC_HOST_VISIBILITY FMK_FUNC_DEV_VISIBILITY OpRegistry { bool GetOmTypeByOriOpType(const std::string &ori_optype, std::string &om_type); private: - std::unordered_map> op_ori_optype_map_; std::unordered_map op_run_mode_map_; - std::unordered_map opParseParamsFnMap_; + std::unordered_map op_parse_params_fn_map_; std::unordered_map parse_params_by_op_func_map_; - std::unordered_map fusionOpParseParamsFnMap_; + std::unordered_map fusion_op_parse_params_fn_map_; std::unordered_map op_types_to_parse_subgraph_post_func_; std::unordered_map> remove_input_configure_map_; - std::unordered_map originOpType2OmOpType_; + std::unordered_map origin_type_to_om_type_; }; } // namespace domi #endif // INC_REGISTER_OP_REGISTRY_H_ diff --git a/third_party/fwkacllib/inc/register/op_tiling.h b/third_party/fwkacllib/inc/register/op_tiling.h new file mode 100644 index 00000000..92067a20 --- /dev/null +++ b/third_party/fwkacllib/inc/register/op_tiling.h @@ -0,0 +1,130 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INC_OP_TILING_H_ +#define INC_OP_TILING_H_ + +#include "external/register/register_types.h" +#include "graph/debug/ge_attr_define.h" +#include "graph/node.h" + +#include +#include +#include +#include +#include +#include +#include "graph/node.h" + +#define REGISTER_OP_TILING_FUNC(optype, opfunc) \ + REGISTER_OP_TILING_FUNC_UNIQ_HELPER(optype, opfunc, __COUNTER__) + +#define REGISTER_OP_TILING_FUNC_UNIQ_HELPER(optype, opfunc, counter) \ + REGISTER_OP_TILING_FUNC_UNIQ(optype, opfunc, counter) + +#define REGISTER_OP_TILING_FUNC_UNIQ(optype, opfunc, counter) \ + static OpTilingInterf g_##optype##TilingInterf##counter(#optype, opfunc) + +namespace optiling { + +enum TensorArgType { + TA_NONE, + TA_SINGLE, + TA_LIST, +}; + + +using ByteBuffer = std::stringstream; + +struct TeOpTensor { + std::vector shape; + std::vector ori_shape; + std::string format; + std::string ori_format; + std::string dtype; + std::map attrs; +}; + + +struct TeOpTensorArg { + TensorArgType arg_type; + std::vector tensor; +}; + +struct OpRunInfo { + uint32_t block_dim; + std::vector workspaces; + ByteBuffer tiling_data; +}; + + +using TeOpAttrArgs = std::vector; +using TeConstTensorData = std::tuple; + +struct TeOpParas { + std::vector inputs; + std::vector outputs; + std::map const_inputs; + TeOpAttrArgs attrs; +}; + +using OpTilingFunc = std::function; + +using OpTilingFuncPtr = bool(*)(const std::string&, const TeOpParas&, const std::string&, OpRunInfo&); + +class FMK_FUNC_HOST_VISIBILITY OpTilingInterf +{ +public: + OpTilingInterf(std::string op_type, OpTilingFunc func); + ~OpTilingInterf() = default; + static std::map &RegisteredOpInterf(); +}; + + +template +ByteBuffer& ByteBufferPut(ByteBuffer &buf, const T &value) +{ + buf.write(reinterpret_cast(&value), sizeof(value)); + buf.flush(); + return buf; +} + +template +ByteBuffer& ByteBufferGet(ByteBuffer &buf, T &value) +{ + buf.read(reinterpret_cast(&value), sizeof(value)); + return buf; +} + +inline size_t ByteBufferGetAll(ByteBuffer &buf, char *dest, size_t dest_len) +{ + size_t nread = 0; + size_t rn = 0; + do { + rn = buf.readsome(dest + nread, dest_len - nread); + nread += rn; + } while (rn > 0 && dest_len > nread); + + return nread; +} + + +extern "C" ge::graphStatus OpParaCalculate(const ge::Node &node, OpRunInfo &run_info); + +} + +#endif // INC_OP_TILING_H_ diff --git a/third_party/fwkacllib/inc/runtime/base.h b/third_party/fwkacllib/inc/runtime/base.h index 49c9de6a..7539a549 100644 --- a/third_party/fwkacllib/inc/runtime/base.h +++ b/third_party/fwkacllib/inc/runtime/base.h @@ -68,6 +68,8 @@ typedef enum tagRtError { RT_ERROR_NO_STREAM_CB_REG = 0x96, // no callback register info for stream RT_ERROR_DATA_DUMP_LOAD_FAILED = 0x97, // data dump load info fail RT_ERROR_CALLBACK_THREAD_UNSUBSTRIBE = 0x98, // callback thread unsubstribe + RT_ERROR_DEBUG_REGISTER_FAILED = 0x99, // debug register fail + RT_ERROR_DEBUG_UNREGISTER_FAILED = 0x9A, // debug unregister fail RT_ERROR_RESERVED } rtError_t; @@ -184,14 +186,6 @@ RTS_API rtError_t rtGetLastError(); */ RTS_API rtError_t rtPeekAtLastError(); -/** - * @ingroup dvrt_base - * @brief set polling receive mode for task report - * @param [out] NA - * @return RT_ERROR_NONE for ok - */ -RTS_API rtError_t rtSetPollingMode(); - /** * @ingroup dvrt_base * @brief register callback for error code diff --git a/third_party/fwkacllib/inc/runtime/config.h b/third_party/fwkacllib/inc/runtime/config.h index 2e48cc57..3dad53c5 100644 --- a/third_party/fwkacllib/inc/runtime/config.h +++ b/third_party/fwkacllib/inc/runtime/config.h @@ -41,8 +41,7 @@ typedef enum tagRtChipType { CHIP_CLOUD, CHIP_MDC, CHIP_LHISI, - CHIP_OTHER_PHN, - CHIP_OTHER_OLD, + CHIP_DC, CHIP_END, } rtChipType_t; diff --git a/third_party/fwkacllib/inc/runtime/context.h b/third_party/fwkacllib/inc/runtime/context.h index 54621e86..b059268e 100644 --- a/third_party/fwkacllib/inc/runtime/context.h +++ b/third_party/fwkacllib/inc/runtime/context.h @@ -106,16 +106,6 @@ RTS_API rtError_t rtCtxGetCurrent(rtContext_t *ctx); */ RTS_API rtError_t rtCtxGetDevice(int32_t *device); -/** - * @ingroup rt_context - * @brief set ctx run mode: normal or dryrun - * @param [in] ctx: context - * @param [in] enable: set true means enable dryrun mode - * @param [in] flag: reserved - * @return RT_ERROR_NONE for ok - */ -RTS_API rtError_t rtCtxSetDryRun(rtContext_t ctx, rtDryRunFlag_t enable, uint32_t flag); - #ifdef __cplusplus } #endif diff --git a/third_party/fwkacllib/inc/runtime/dev.h b/third_party/fwkacllib/inc/runtime/dev.h index 928f2822..60928202 100644 --- a/third_party/fwkacllib/inc/runtime/dev.h +++ b/third_party/fwkacllib/inc/runtime/dev.h @@ -32,6 +32,7 @@ typedef struct tagRTDeviceInfo { uint32_t ts_cpu_core_num; uint32_t ai_cpu_core_num; uint32_t ai_core_num; + uint32_t ai_core_freq; uint32_t ai_cpu_core_id; uint32_t ai_core_id; uint32_t aicpu_occupy_bitmap; @@ -46,6 +47,13 @@ typedef enum tagRtRunMode { RT_RUN_MODE_RESERVED } rtRunMode; +typedef enum tagRtAicpuDeployType { + AICPU_DEPLOY_CROSS_OS = 0x0, + AICPU_DEPLOY_CROSS_PROCESS = 0x1, + AICPU_DEPLOY_CROSS_THREAD = 0x2, + AICPU_DEPLOY_RESERVED +} rtAicpuDeployType_t; + /** * @ingroup dvrt_dev * @brief get total device number. @@ -62,15 +70,40 @@ RTS_API rtError_t rtGetDeviceCount(int32_t *count); * @return RT_ERROR_DRV_ERR for error */ RTS_API rtError_t rtGetDeviceIDs(uint32_t *devices, uint32_t len); + /** * @ingroup dvrt_dev - * @brief get total device infomation. + * @brief get device infomation. * @param [in] device the device id - * @param [out] info the device info + * @param [in] moduleType module type + typedef enum { + MODULE_TYPE_SYSTEM = 0, system info + MODULE_TYPE_AICPU, aicpu info + MODULE_TYPE_CCPU, ccpu_info + MODULE_TYPE_DCPU, dcpu info + MODULE_TYPE_AICORE, AI CORE info + MODULE_TYPE_TSCPU, tscpu info + MODULE_TYPE_PCIE, PCIE info + } DEV_MODULE_TYPE; + * @param [in] infoType info type + typedef enum { + INFO_TYPE_ENV = 0, + INFO_TYPE_VERSION, + INFO_TYPE_MASTERID, + INFO_TYPE_CORE_NUM, + INFO_TYPE_OS_SCHED, + INFO_TYPE_IN_USED, + INFO_TYPE_ERROR_MAP, + INFO_TYPE_OCCUPY, + INFO_TYPE_ID, + INFO_TYPE_IP, + INFO_TYPE_ENDIAN, + } DEV_INFO_TYPE; + * @param [out] value the device info * @return RT_ERROR_NONE for ok * @return RT_ERROR_NO_DEVICE for can not find any device */ -RTS_API rtError_t rtGetDeviceInfo(int32_t device, rtDeviceInfo_t *info); +RTS_API rtError_t rtGetDeviceInfo(uint32_t deviceId, int32_t moduleType, int32_t infoType, int64_t *value); /** * @ingroup dvrt_dev @@ -130,6 +163,25 @@ RTS_API rtError_t rtEnableP2P(uint32_t devIdDes, uint32_t phyIdSrc); */ RTS_API rtError_t rtDisableP2P(uint32_t devIdDes, uint32_t phyIdSrc); +/** + * @ingroup dvrt_dev + * @brief get status + * @param [in] devIdDes the logical device id + * @param [in] phyIdSrc the physical device id + * @param [in|out] status status value + * @return RT_ERROR_NONE for ok + * @return RT_ERROR_NO_DEVICE for can not find any device + */ +RTS_API rtError_t rtGetP2PStatus(uint32_t devIdDes, uint32_t phyIdSrc, uint32_t *status); + +/** + * @ingroup dvrt_dev + * @brief get value of current thread + * @param [in|out] pid value of pid + * @return RT_ERROR_NONE for ok + */ +RTS_API rtError_t rtDeviceGetBareTgid(uint32_t *pid); + /** * @ingroup dvrt_dev * @brief get target device of current thread @@ -212,6 +264,15 @@ RTS_API rtError_t rtSetTSDevice(uint32_t tsId); */ RTS_API rtError_t rtGetRunMode(rtRunMode *mode); +/** + * @ingroup dvrt_dev + * @brief get aicpu deploy + * @param [out] aicpu deploy + * @return RT_ERROR_NONE for ok + * @return RT_ERROR_DRV_ERR for can not get aicpu deploy + */ +RTS_API rtError_t rtGetAicpuDeploy(rtAicpuDeployType_t *deplyType); + /** * @ingroup dvrt_dev * @brief set chipType @@ -225,6 +286,17 @@ RTS_API rtError_t rtSetSocVersion(const char *version); * @return RT_ERROR_NONE for ok */ rtError_t rtGetSocVersion(char *version, const uint32_t maxLen); + +/** + * @ingroup dvrt_dev + * @brief get status + * @param [in] devId the logical device id + * @param [in] otherDevId the other logical device id + * @param [in] infoType info type + * @param [in|out] value pair info + * @return RT_ERROR_NONE for ok + */ +RTS_API rtError_t rtGetPairDevicesInfo(uint32_t devId, uint32_t otherDevId, int32_t infoType, int64_t *value); #ifdef __cplusplus } #endif diff --git a/third_party/fwkacllib/inc/runtime/rt_model.h b/third_party/fwkacllib/inc/runtime/rt_model.h index 790492fc..5c85a3d7 100644 --- a/third_party/fwkacllib/inc/runtime/rt_model.h +++ b/third_party/fwkacllib/inc/runtime/rt_model.h @@ -65,6 +65,13 @@ typedef enum tagModelQueueFlag { #define EXECUTOR_TS ((uint32_t)0x01) #define EXECUTOR_AICPU ((uint32_t)0x02) +/* + * @ingroup rt_model + * @brief debug flag for kernel exception dump + */ +#define RT_DEBUG_FLAG_AICORE_OVERFLOW (0x1 << 0) +#define RT_DEBUG_FLAG_ATOMIC_ADD_OVERFLOW (0x1 << 1) + /** * @ingroup * @brief the type defination of aicpu model task command @@ -403,6 +410,26 @@ RTS_API rtError_t rtModelBindQueue(rtModel_t model, uint32_t queueId, rtModelQue */ RTS_API rtError_t rtModelGetId(rtModel_t model, uint32_t *modelId); +/* + * @ingroup rt_model + * @brief enable debug for dump overflow exception + * @param [in] addr: ddr address of kernel exception dumpped + * @param [in] model: model handle + * @param [in] flag: debug flag + * @return RT_ERROR_NONE for ok + * @return RT_ERROR_INVALID_VALUE for error input handle + */ +rtError_t rtDebugRegister(rtModel_t model, uint32_t flag, const void *addr, uint32_t *streamId, uint32_t *taskId); + +/* + * @ingroup rt_model + * @brief disable debug for dump overflow exception + * @param [in] model: model handle + * @return RT_ERROR_NONE for ok + * @return RT_ERROR_INVALID_VALUE for error input handle + */ +RTS_API rtError_t rtDebugUnRegister(rtModel_t model); + #ifdef __cplusplus } #endif diff --git a/third_party/fwkacllib/inc/toolchain/slog.h b/third_party/fwkacllib/inc/toolchain/slog.h index f77df225..261fe866 100644 --- a/third_party/fwkacllib/inc/toolchain/slog.h +++ b/third_party/fwkacllib/inc/toolchain/slog.h @@ -91,6 +91,10 @@ extern "C" { * max log length */ #define MSG_LENGTH 1024 +#define DEBUG_LOG_MASK (0x00010000) +#define SECURITY_LOG_MASK (0x00100000) +#define RUN_LOG_MASK (0x01000000) +#define OPERATION_LOG_MASK (0x10000000) typedef struct tagDCODE { const char *cName; @@ -169,83 +173,11 @@ enum { PROCMGR, // Process Manager, Base Platform BBOX, AIVECTOR, + TBE, + FV, INVLID_MOUDLE_ID }; -#ifdef MODULE_ID_NAME - -/** - * @ingroup slog - * - * set module id to map - */ -#define SET_MOUDLE_ID_MAP_NAME(x) \ - { #x, x } - -static DCODE g_moduleIdName[] = {SET_MOUDLE_ID_MAP_NAME(SLOG), - SET_MOUDLE_ID_MAP_NAME(IDEDD), - SET_MOUDLE_ID_MAP_NAME(IDEDH), - SET_MOUDLE_ID_MAP_NAME(HCCL), - SET_MOUDLE_ID_MAP_NAME(FMK), - SET_MOUDLE_ID_MAP_NAME(HIAIENGINE), - SET_MOUDLE_ID_MAP_NAME(DVPP), - SET_MOUDLE_ID_MAP_NAME(RUNTIME), - SET_MOUDLE_ID_MAP_NAME(CCE), -#if (OS_TYPE == LINUX) - SET_MOUDLE_ID_MAP_NAME(HDC), -#else - SET_MOUDLE_ID_MAP_NAME(HDCL), -#endif // OS_TYPE - SET_MOUDLE_ID_MAP_NAME(DRV), - SET_MOUDLE_ID_MAP_NAME(MDCFUSION), - SET_MOUDLE_ID_MAP_NAME(MDCLOCATION), - SET_MOUDLE_ID_MAP_NAME(MDCPERCEPTION), - SET_MOUDLE_ID_MAP_NAME(MDCFSM), - SET_MOUDLE_ID_MAP_NAME(MDCCOMMON), - SET_MOUDLE_ID_MAP_NAME(MDCMONITOR), - SET_MOUDLE_ID_MAP_NAME(MDCBSWP), - SET_MOUDLE_ID_MAP_NAME(MDCDEFAULT), - SET_MOUDLE_ID_MAP_NAME(MDCSC), - SET_MOUDLE_ID_MAP_NAME(MDCPNC), - SET_MOUDLE_ID_MAP_NAME(MLL), - SET_MOUDLE_ID_MAP_NAME(DEVMM), - SET_MOUDLE_ID_MAP_NAME(KERNEL), - SET_MOUDLE_ID_MAP_NAME(LIBMEDIA), - SET_MOUDLE_ID_MAP_NAME(CCECPU), - SET_MOUDLE_ID_MAP_NAME(ASCENDDK), - SET_MOUDLE_ID_MAP_NAME(ROS), - SET_MOUDLE_ID_MAP_NAME(HCCP), - SET_MOUDLE_ID_MAP_NAME(ROCE), - SET_MOUDLE_ID_MAP_NAME(TEFUSION), - SET_MOUDLE_ID_MAP_NAME(PROFILING), - SET_MOUDLE_ID_MAP_NAME(DP), - SET_MOUDLE_ID_MAP_NAME(APP), - SET_MOUDLE_ID_MAP_NAME(TS), - SET_MOUDLE_ID_MAP_NAME(TSDUMP), - SET_MOUDLE_ID_MAP_NAME(AICPU), - SET_MOUDLE_ID_MAP_NAME(LP), - SET_MOUDLE_ID_MAP_NAME(TDT), - SET_MOUDLE_ID_MAP_NAME(FE), - SET_MOUDLE_ID_MAP_NAME(MD), - SET_MOUDLE_ID_MAP_NAME(MB), - SET_MOUDLE_ID_MAP_NAME(ME), - SET_MOUDLE_ID_MAP_NAME(IMU), - SET_MOUDLE_ID_MAP_NAME(IMP), - SET_MOUDLE_ID_MAP_NAME(GE), - SET_MOUDLE_ID_MAP_NAME(MDCFUSA), - SET_MOUDLE_ID_MAP_NAME(CAMERA), - SET_MOUDLE_ID_MAP_NAME(ASCENDCL), - SET_MOUDLE_ID_MAP_NAME(TEEOS), - SET_MOUDLE_ID_MAP_NAME(ISP), - SET_MOUDLE_ID_MAP_NAME(SIS), - SET_MOUDLE_ID_MAP_NAME(HSM), - SET_MOUDLE_ID_MAP_NAME(DSS), - SET_MOUDLE_ID_MAP_NAME(PROCMGR), - SET_MOUDLE_ID_MAP_NAME(BBOX), - SET_MOUDLE_ID_MAP_NAME(AIVECTOR), - { NULL, -1 }}; -#endif // MODULE_ID_NAME - #if (OS_TYPE == LINUX) /** * @ingroup slog @@ -386,6 +318,11 @@ extern int CheckLogLevel(int moduleId, int logLevel); DlogWithKVInner(moduleId, level, pstKVArray, kvNum, "[%s:%d]" fmt, __FILE__, __LINE__, ##__VA_ARGS__); \ } while (0) +/** + * @ingroup slog + * @brief DlogFlush: flush log buffer to file + */ +void DlogFlush(void); /** * @ingroup slog diff --git a/third_party/prebuild/aarch64/liberror_manager.so b/third_party/prebuild/aarch64/liberror_manager.so new file mode 100755 index 0000000000000000000000000000000000000000..759d8e30accaaca6dd3cd9dcecbc97d03f1cb20b GIT binary patch literal 888880 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 literal 0 HcmV?d00001 diff --git a/third_party/prebuild/aarch64/libslog.so b/third_party/prebuild/aarch64/libslog.so new file mode 100755 index 0000000000000000000000000000000000000000..700fc118d85469af3225484aacdcda0facef3467 GIT binary patch literal 150360 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 Y6^{C+bf<6x_twbz|7Awv+Ft+v0O*4)_W%F@ literal 0 HcmV?d00001 diff --git a/third_party/prebuild/x86_64/liberror_manager.so b/third_party/prebuild/x86_64/liberror_manager.so new file mode 100755 index 0000000000000000000000000000000000000000..cd9ad8bcbde41fabcc1ff2895b3428afcd04ecc7 GIT binary patch literal 852544 zcmd442bdN`w=Fyb$&w^0;E)C+=PWtroMA{qo?#dUB!ehHau6g7h$I0Kkt8Z0AV~y7 zR6s-|iHL}b3JP~sueJGx`i67PJ?B67`S{33!286*n12rx+M6r@%?}9)4X#}tN#HIV zV*G_e`2*bis%3)9knLsZTe^g}bm?v#Wx88Ona=z!jsavoxk8|CNQf)ZS%)JRWlI!Jh)TmB7;hZZ57J)rFq}vT7myZuqZ& zw<)87-x=U74=feZz5(9p;LD1b!N5nul|bAfVD%6;7UA!K^+Mj+as4*%b-2!mu%owt zpM;#Qz&?im67b>(rvhJp#D9kCf#6vR?5gyi1f52K_c_FGL;Q~jw?{q=5l$o?CwmC6 zbGY6IygAZ;2mdhS769H9_@{6MkhU(a9aVzdir`xYzO)EC+KlVfa5IqZYg}gn?^IkL z1xDe?HuMkk=0Q4Pehgu6tlM;o4CwuB#*db?{{b9s&O#ura_KO$4?=ysd;e zd<@*Pz>kC9(NKifK>keVmlpm7_}Rq29Xx|wzM^Pcr-!Qvw--FS;QtgSz9g=PLB@W# z@rZu|an%rZv{e2s>9i4XrGUKzJwGH%$S9<#;PQs+54RKXk?@zm?+E`QU{=;>#fJBCuFs zMxP=4DzH+Jl>+hO;K~6z4Tho!w??{zkUtF9=@IUPxThemG_Z0eME-A((+u%#;qQij z3OtUQIk~uY)B$nPxK0fju9+N;a2L3%K`fq4;O)Vi8+pBp^y6{uXeH7)YKiM2;Li+w z4}z}>+|#&DjO(4?X@Tp%;J!zA6|jAfvk2km;5s7xHN-i(1%C`&O{9%b9u9AWJQDz$ zjPRRCQxV~>;gnf zt0Vpx{GE_J2jTT_n-DGxzY6%T!_`N;qg22uBW)JAKY;%Z{292}2#)}k$i?BhG5oW@ zo$F?Px1p9fj8eFAkmz@N>b10{;VUB+_qy%oiamO!;I){BLmg;EsbQ zJMi0*F2>%G|DAKl& zKAVBHM81dM&s6^XA?GLXeS+&8@H-)GS-4Vg=@9oVe50O_a~C|0{(+3ykX;|?I)iTm z!U=%Yhf59Y7Opdbe-Oeq;lB(yi4p!q@;X9J7r1$FSHSD&ZN&Wrzp2VL7T2wj=3}Lq z48JC>mqOk;_&1O?9DY0SUq$#1-20H#8}Z%2+ZJ(2fjx=%8_=sX!f(PoKwJx8m6T6) z_zA$f9C0_{e*ztfAiNS`NADs0B+?WFUqxWO;MO9KE%1*4e+Thh;r|Mk9Xy5L9DS+u zY2n{MTsP?U7sB1a?S`e*nnkWlS0d7C=3c#x&?O}vJ0Do1u#Nb&B zKL=!-0NH5QE2IlAv(kDQ;I^ypkypnLpKj3jx zU;3m*+MdGp!~fCY;LnP5zaagmz`g{JqYltvJg$eq&j~jaJUzg(329Q}x-Xog9`aMd ze+k#O5#J5rhSDt`!e<~SH?9xDe-`p*BU}#hzQy&g@OL2HC(vUg@R`cr$##?mEi`w+4cg1<1_Nkl~;{e8s!1$;61o)cdY#8+@>K|SQx z0M9z)F#&06fUmZA$05&Ra7&QJ(K6tfkR}zd#NZ8sO9?C=(#{5c5cm+KEd-g}aP25J z_{zaO1D-n0hdTwn{czvFbpZY`+%HZB@_HWe_mK8iT!+EG3%@1!X2SJ`dm3&OTodqp z1iu~9WQC6`L;3-0itrb3LlM3JFcP6m`&^{o2j9^F_<4b+0MARn z&)|9o^8XH4X~=Q2(w`Cj8knQe2=@f>;ci3jPq?mta3{$5 z6aEIcqiOfT+j@B07i%QrlMBXQjVuCIs3clk_wAh6PK8{r}z6L0Wd3&ipB#tC1K z6aKFRr^jKM7$;mRPWZnQ{0xbD{9h(a<~^)`#FKs#uz!R_Jw|5NYUE*r*? zXV@}e>*K*6#v_m(u7rSg@;{buUYB@ed>jW`FUCQ_w%~d@!Y}Ch8m>Qw8;a|J;++lu zIIe$%Um5N-gx?jvSRQ)qfuB>{=kkv~j77RQ;UjUvE;t1I>)=iy933aFIj|y<)d6_h zIC1}#;7u|89S?KQIM_wxu}0UgD?f|wVgdL+BW}MJ@1nN=%ZM?^V)%FC#ODHL6t2MD zIPn|fgpF?ku#$H1pZIMQ{}ud8lGQa%nt$SvA168_VI2K_h#L_nJTZ=p0>a;d-w1df z#ifDY0kS43ejfaGimNQ&MZJP>uQ=D6#UGzn2Yfx?Y9O*@9ELv;UIM&k94v0|0pha5 z{R(#$5d+||BJAiC{DKUIrliU zj&r@x$Cvcp;@(pzCjRZC)SX;}rKqBIKut5x|~;{IPIxX%w!DiN6B;{fPV% z*lC0diMO8;ED~Sb^e&teuBdox%6Iq%xM{fl6zQH)+E-kJsJnO*LeGT?e+j-n5S|79 zTlknFg~TYWOV%<@_;-Y_Dcu2tpNJFJ1mQM{bLlF={pq-JCJCt=CtlD)?-=CNmuv%@g8wI697S9z_@xn_ z0$5V`uf-#)Eb!sNx5K}KG*jWqBK_ZR)e$xt0NxS0cn)!nzJSXRM{XzZd=$jM2EfNF zyar)+%AhK)b0Ay^&QVYJMS%MhE%^>Vgm4txJn)_qV@HI;;-qVg@O8Fz6y^BRD!v##G4f26KsCGQRSDW$^)x*iO^8NznKPmc73;jY2GAX$F_{|Mn9;JSf- z9Nd$Ve^7GVWm&}U#r1pO?}h7$2p3bHnkBw&z{25P!}Vn)7>W3275`D3vQ$z00br*Q-yg1~_*~RX zg!@9)aD-7bFN!k}t}}{n1+b;KP7UYiJ>cJg_iy-(;D*3G1-uFJn*^ROaP4S5!h_%{;d&eV zQb;ohcoO&@;JOQ31B8us<2npV*%;=D4;29Bq12XPN&NJZY3Z6gViXwgt@ZS-3v`YRqVDEy@Q4DyV zhJO-q-4N~zf0^Q3{2})v`b+62$-f0WCDQZ<|6TEH#&sl|qe9@ztMCb2x54#VxLUww zL5Cu7@+l7d3a&2&DKRbp&pY6`1#B$B6W}`FdOBQl@EX+yJ{j(WWUWE?O++?>zZ|^x z#5)u5uL6G?xTC*uJqB@~!QTs)4fr?Uk4tBOUlEoBycOZnDZS$x94Gu5t_#LVKNaDr zia&{PNu*s4mj&*iGIW`?7ICfNA4l9Y zr3Do7GWbpb9|`B+tWRJM5LhM`3AzM4J!Cj~68`&OYNPA?hoA3{?El%lu;oWfDIMqpjIx7bkpFKM>q}A zzJqXc$tVN&$RZBfJDyWAGOOe>wOs zBmE$R9rZ=rW~I%C@B=tUzrmFT?_D@Y_u;RDdj{#Mz)uI)S-j69kE{q!19lV6(J#Qp zL2f^|*AaIf?mfj#LELpXM`eH?f^#$F5VKmCyA zYhcB}yBOj3;f^Su?4_uysnqSloMD*FfA@$ao8W6YzY6yz0Ph15*m%3xU6) z{9U>%aIL^s7WiQ0eID1pAiQ1jR|>y?>v{;s!hah4o8e~^PgUTzkj_y`@o#l;NzjhN z-v;+0@UOws0BOHOxHYgZ5PlQk5snv>4A{|eh z1?~aX4*5<7ehacXBm6zmOhz7#!V&)-{FiZk7Ch(SR|ii$xI5r41mCDU0?oj`LuuC| zTm!Olg69i_Pr@0E2i_Rh+aU9G_!khd5&kN;1c+-7e-fOd>acffBB zo=xzl!0!x~8$4|#`*+An18fJ%a}w!SBkpM@4_IwDM=yXkFT!)dGXp%c;ZFlk9`I&E z+-O|40{#rJK5&t69pH>EAiNsskHGzcG{3?*YJjvGfISKSdAL?^jv^rIH#kQRz3|Oe?|itE2;TsY z(Qfd}1@d`fi-}u20Sko12#g=8N@rl1o&OJT++1| zu*=9J6|geMZy5Yu@Oy**D*Qf(uO&H@{_nqcqTK#`%8;2l^pO(s9Ca<3Fu+GSw`S*P2^4ktmU4&9L56?Tn*5b@Nand9 z7WH%v^IiWL4Yg4CFJZ)|Mi380xg8b9!h@4vcQf%KghO&;p~B&9-63jFnDAr?iB~>N z{z}5HU_k5m?P0cctHY^@H`Czou=uZHqU88ryhAyzU3XNv1Lb$n;k4p}L(*Z;=lD0d zfea`#_{G=@3sny9?;0PF+4(LMUKNt=;d>3q#P7y#j{jYE;0I)OJBLA%!*gAuJ|^eh z9`3gU%yE8tqS1H!Rb9soGX6POfN}UxchCm(bnpuq6PR!E%goo-u&ZIw%JJVUOT3Kn zKSg~Cx&u}qlXDXrKk3C2*Lx9*@lH;+PLz{Nk(vIXKIPoOB+xM3_ao>u9e*}=I0Mus z_(i{rNr=PSrXs)1F|NNv{sr!#2ig7l2NQh9zseo32H|!Gbn98-o!#L~P+0Jb_7@F; z<1coTdb)KFN6n`bU++2(&_Ur9GcezUZa@Gs`Sog2{)g_c6$mvipl+pvy916O(=#(F zq|3MCRmwMej>IH9(8nG81HBadVl04(pTiHiLoT2S(q|U}4zItI{5iy*FJJukWo0Vr z^NTwa2eNz%ucG|wZg2@&Ao;V7QU3RO9#aIdn4e;@xnmvSP6uS!9@r{vpx9EVA? z<1hOo3djHf~|;#1uq9AtLLi3zgf z@B1q8yg@A5`83S8P*vt@&-Ix7S?Hki+{QqnxI~zeaH!-lGfxAgkxi-yoj- zKJhf-&xHxPq~A|| z%QxwZv|H_SwA+Xv6~>y_z;pQ)x=8-q!pouow>m5+@Wx_O+3~k>iv*zV%C{~Qi;sU| z1@ecfpEdhrLcqx>;PqcM(9t@4xI1hOvUX{2IQ2ZXj(BJgi+N8T;&Wv5Rjx0nVhBs_U<%Ad8Ka*7K-jEdy=ySar6(0t)lJ|=!) z7jYYB{`fiNti4P*X170nC;#x$#EVGIWz;t(e}bDtfvnxT2;(^X4>vIZ;c*V=*c6s) zQYDtl#w}mr(($M8#&vr!DRg)$jc={J8j66!S7<_SdOrA({2LEbAL~!AttNk&`P9ei zbAL>_oSc+ie|-YaOMLgYkNQWyJ$Gx6PEM?wxPkHpznJ&zAf7)B_4!qHcq^RvLAQVc z>MA@_cIrQ}8Tr>TI^;)eTsZk1DiO!J+i3D1J3|Fg%z$>UB%av4%mQi~{9=FT zed4iMneS7=UqQj0{&&g}uOj^W9>mwbMcmreLRb`b{0o03ZqLzpRCtGP?8SWDeA3Z8 zJujt75x4eaIyN*M|IB`rWA$o37VaFrRO6*EiV3-<@p&Vd)hM_ zg$~8ZKRlN9DK9zOB8gYlqW=`(7toMeT0w#L3M!tHGgafvC`DF)ogH4vEn_lGK9>ArJJ4=c|DO&a-nSfa+p?kF$`qminqe3PgTuFtUkZ6m-sTb7z;WW{9^qol=xQ-9<}Fd5tP5B z$fNc4B*sxL-=uEQ5rpglx;&ovX)WrO6h5^C<-Fit0s>imxYUFA(~{pn{2gZzzf+a` zy@g+soQslUc6&$tNDHky*|@!0KFSGoFS3E`xhpFB{N|N6OBvevhSv_HdrbX*QR@bE zwQgYJt5@^I|9rK{O!--A&~7%)>8ttFjVi?L{-)?ed$!j63u**3{|(}WG*2-*6xzu0 z&a21rb`yUj<^}G4MYFfHdnBhm_FUZgit;l(NB+9fr}RnUy|oUIPk8N=w8ID9 zII6+`;%{m`X!SNt5#s)OquZNv_hqHqBmvp@wrykLJGIU*65}mL6Qt*1jX!e{4$dq& zIR(7&d`Y!?cXVIuxyxLZc9^%AqX7PpF{0TCbcuB=b2Z?e^Tpy)}CZ6NPSkl zK>dHmxX{sq67jc7Kgj;OWq-3<#6s4G!+PFr%usGD^W8R@jIgP3o$+W*07mzsY~y!I99gK7}aHrXea>{C#96Vxla7lH!sBiVVh>}=0tkLJ|B zz1FX+Txl>tb9$!IOPAFo?0!+|KSp-2@#Nmsfv*elX^J$L$;IO z;WtwA`bRhe%gjq|M?Ti%lLixl8}H+^FAQvNP)yix2!%IWD| zpanISJ|Fx+{uX73Tm2b(miRK+IZXU7X*_l^75S~-9NmZflcQNKv%|hi#BZuSM|BFQ zB<3B?p2OWHBM8+mpyfS@uUtsn?ql*VsQ-4a-K)HZcv{V8tz6k~pgkw%3sp5=Kz5`t zXC_!Z%!I_f^EKARm9wWb|z=y?ef|Ix>^d#$zpvrzM( zLE_*36y;Y>%6#p)ONs%QvrjhNFOxr}BJpn8kdKg@Z6)F_Z@Dv+e_!qMLh&~kNc@EM zv&>ebX0Tkh+(&vq)?YW=Oxzz2Pdi6_HWr}%sNMmk+D!bS?pHVAe`%k3qxwf{$G*u) zeJ*+Xwh`}B&hX5Xe^>p^PVK*s)c(7*=Pz|9|IYcWZ?motrZ#%fT5HB!3D~(`!!bfY^Qc9O!BkVr2N=e+6R{b4PHVy z_m@zP<-30d$06msaYzyz*l_vAgi;?HcO@A~IR`agt1S6nzo2sMVZLiJGNL&=ODopE zJNz`|pLHKA0$ICMN&R3p^@FD8OWP=az3PwIGY<^n^lYf{kIC7Hg+zz9*1ELeS1=C^ z*5!`m5AwBjl1!Q7f4&YarJi}T4rci#y+}JG`HA+d6-dL`r31ucbiYjhyjysGL$|ZM zeZ?O+lzN^%O}k;+E1;E{CvHDX`TfMdryu1P+ejS4s(`8{Aih)UQK7(A$?Pnhr$8c&{^Lw$w|$Ff#n&&l5TjjV4HpRRT0uHygT8|Lfp>m9F1{0r^l z4-o%Zo$K)X*&E}@AAFnzyy?XMK=ZXw&DZR?s4$4-x*x`J)e!$bsy{nbe+md+)t3DA z3zFZS-&a$yT$Qw*mQ(znXr6mR^IVg&I4$KYb03oewGsc$eUx+j1KP8k@bNeZZ;Z2%ftydB2_&f745plThb3te^UIFY(UWKN=_gDSr{)_#5*z{)&}|PtyL5 zwGWl>fVg}sXdGhg-Z1S`FO8%=R{y)9gLeFWzrTAA@#@*hA1Zy;sNG(p`J>7CaXszO zrXKBP_?qR!H`OF=?RoKRw9lH_l)p*xlVT$5^qG*B{8k^jXQe*=`GnE$lHWg%@vGK9 zbA3qpXC%MGDauLp4ee=myO)UgtrEoZihmRaHcp=`s^_N9ujpv3FBWUy-Aq7z&VRvt z!xS0Tf_QD6N9r%UEDRE#oEWXk?bo`TYkwUj>P9*KIfTpIbidS}Mvy%uES7lX?JSo& zXY1&|7sT_2QO^bHujkEUd3U*km!O5gFV>CLkiSPF;->Svsi{v#Jug!ug~6T~03!|IoBslBrMSa1~enWOUn_B=*CLpgcle-bU*QiL>x|>6DNe`~-tIIk%*bjYG0cBz|A# z=wRvAT9E5XrxT$`8y7-T7 zC;$0nkKV_Xc)%V1;l9LkiGNKr^%>?qlnSzO=0HqL9Dl6l^_j$9Ka%{%-A7$P)^5zk z4RZX)wQghe?Mx!-xl89F%x>G~5}$N}c3UX(NljNiiEq(0-8k>BdWHe5P={Bt$0JZ7Kbx#hHfUH1V+kl8=~Y0BA~kMhkX zr#FzlpY|8ftOYbW3;8ps9k6z+1mdg3bdU)IOBm-BC@AvWfo$rnRUl>XG*>$dFryodu8^5K~zPW#{tYlu=xn_6X_a2fzC5-qfoujk%aCbY(?>~$3 zvE3F>SNuACt`{b5>vg&DAUOQ*j>PSLwJA^ejWv%md_i0CUr>K!a-wk1*U1S!$c?

=sbNbi}(=rT$j;qA*}~{PWcw+Vhec&$aPLMBqK6b96`5ZyL{E$tdTv&M}&twBv|R z)i}z%zvbxV_Qa2CU&i|1w8?mXt9bia_wllx)ANS*Q|x)%5<+~E=0VnezJvbF@h>b! zdxkUHkZP#^L3zFL`I!#HcWE9JD*jtpsLyK6TP@!TY2vT{_uitMty=%7A~|7)h}X?U z`&;`k3LQkCkM6tGtA1KHPNnl~59i&eK#t!(PxY+&%>wE-E#CvT$ltIO^R?$XcTeK8 zwT^1{@io*_C%;co+AXd0AE@=B%IX)wgx|pf==jf1Ais@gX5S$meDoT;X1CHhm$+#i z^|5+;`D5~T@$O4rowxi=`)<}>?83n>m+y(PkG9WKuChKHP(8tI3TRjc^84fJf?LU- z=^XVjJumm7KIsRrTumh>!$$I-(aS!@zf0#dI%z+sp7?)9JL&YC6izvz!Ux094!`v| z^Ia&sRBH0KEKmM2YBw5vLcDP(`G*M~+JbWY^Pg8SAaU~Vw5J?<9;cycaQO1F#O=QK ze}i)T^Fj?Ua55gL7?pXL#{ym`bE zc){1{c`7&WuZ@SJmr?(ze^7rkUjcR1IOoIZkLJ4%^FB9TJ?Qm!KZNnU6(tWztUzZd z=JFkdmpo0)3p9?_dU-f@*i?9PJx<8&YXzrq5o%6JQ?Nf|1A6ko^T)4Jj_JQFHTlzOUToz}9HV+NnDU>MoU>1oe^nLADJa}^ z(@y{6I;UatyDZ-h!|e0P zZt{1|MmwNd22}C~;xB6+VRBwaL+#|Z)%gMIkLE)ehwrS$e9;UB)Isg)S87*{KSNX2 zH-9~F>R`%A_95jo!MMcHYJ7Ob$xo*C)8u5nP5U3YP5rBpJ>*mdjoZERrO)d*8tApR zUxiWrJMN`W&;ZHF7)E{k^L2eNaCQ1*(>MgTFQ9@8RS!>5p9takw^9D7MwD;u;o=X- zUrGJDJ*VX_5bvz#)Y{cq7znw1J8J#M?63wO3UT<-jm)=*^8M&b;s-U~L2&{)5*mN~ z*`AO5>w8hoC5=D-Ov!vts~^O4GN5lYUn}LU+YEe6edwk033YTn!Soq~m#Llp)%6~b z+2@A#<2Gqt`K9uGt$+Ob|D$!?HQs*Z)9+FL!%JDO?0LMb^VJuRvbzmnm`={i;@;&*{eFqe){lvOO$$vxmFia5QYiF|Q zKBm-tv~g^ujnwmi_H9vZ0&2R8cv_8zt$*p>i})Gs=h}T4w1Iea$-#6rpq%ee|Eg!H zzm>~9AFlo+)Ba0W@!!?{&eLiiN(*1UmU1q7=U6J^MmRaW)&4&x{_YqUJN$kH-gj%? zs%SmUKkwwe(d77ds~wJ&oO1)1Z!YzNrhitgOZexn+y~8^oB+==7^Z$Em zkl#NiS-dLo(Rp}(Ye}Dtd*Z*p#Z_;ss@{f*e-ikeKC#-ru;=vJGRlwDb87YBNH>n&TgKUGrvGkY=Ldh7W-+PH4}ZsG}b->u%>X%&Bc%O!of zNFN)gT^>e#`e|Hb&&zr|;LdK_`_pb_&o!Ny?>zOVcHjS6L;gt`Pg?sqDwh2BG%m8| zI`bUjb0@G|roU^4(&LMc4I8uG$_yb7LTP8eJwKy!0w3y}fZ1o(Ta=$)^J0^KAv^IR z-n^!MapF<0Q6D?kS_?PS*`Z@y+QHhZVjAby)%PE)-Fp`S$M3K2rGWwtKd*H~tIy$O zi2LW1<{l!xQ0p_cj(Vjn^;}b$dRqNFjET9E-(TmJjlW@Q;yu@p-=6DPe-e-Wi@2T7 z%&zn8gY^Eal`E^xL7mb(&g|UiE9$vR_1Ww^CzA48dGB}3I3s?YC(o}N+@LCP|2$sP z#JumTv@c-tub_gt^0rpLX8mA?x0&x5&F_rA^$*1RYCcs*`3`-7_yyHZtLKxoZnN#yH{GR&Xqglys&vpI9)PIZio6yY#G${%5y{37MjR%ryKl;Aj_qXS$ zlFqFa>QDLh+&%L$4bpG zcScL*dsy-l>V3ToYOjpHL?+7dzdx`<=lcBq<@NKF-?-qT`Ch?9$>r;xr^**j{D$`X zEZ@)6UM*I8<=Rt6pH(KmKd%094Dp5Dd~-)N)~hbw`%_EOvRp|LKdR>tY{WP{|4u@@ zHnR;mJy-eOrrq#Z1@tE_9sfA3&zBW{m5G#7Xg}rHd^sr^D9686^~COD$Kk}2X#KyH zP-AKy@!ioc|d>sN!)L5FC3$uZ(pLEY~nAi z{raa<61Vo`kHzGlt#dUt@0b=w`TqN8%@UITjK)_TB!Bc6me)T=Jq8`Cl|`(9*Q^iu zo4Y?51f2_hLqe8mU(8>ZJ6D4F=F#}j+SQjPQqQznSKJ{vMGFzXp!HCOmIT;x{#}GyRi(Mmgua^`a+s62J2l<(SQz6``D!Q;FkvVL)9wQqS!= z7i9hQcNoB1TB#Cv_;Y$+{?^%M+sr6swrV@YY2jWe{-$>^=+U%m7w}n@)Kz{%I{W?X7Z|_1m zIDQjQ)lS4`>by{C$vN9jcGLX2p73|~(a!$+;GH!-$)=JQNc$b^!6k>W{2G&nwJw z`RhevACSMPH=kO!ig6w}q8>Y5jK5k3PICU{=dZx$4O zSL@sLwZ3h0tNvhc{0;S7qq+w4!wlm7Jnqy|+Gp+S)YG2FUI)m3@mb>5?p+y2JNWNA zADT>lfBh{d9!#f?e}3tn`kk89C?CV!fG+Dfosg0Eufnh9r5yi#lj5Hf-+PYt-O3w2 zi+Jasi6ePHZEq6)vn1u)_@trU&&sdo9oAn7RgZ4*ydf&mQlZNsqYdyr) zH+C-2bK$*rcDOxpKi`7dM@Xx7AWZsT_!HRkW$mkW6P^PDhWOg0+v-QEYhGZ_SHyP8 z>6n^!u<>vd8XR}Ou4o(*D)~FLp1kYWqx@Zyvi>BkM7*W=^XmH#vo+5}H3+C-cGi>Euo&VcX)rTeO{_|@1)v2%Xhizq5mC#qhC|b49#nd z|J3))cj-;$i{WcPf8~td&MmY)?0*O8)f_DEb2_hU@*n6t#tNOsFuYzS@}Ex4egVV9 zfEsE1kVEg4pqd5b&fmHFRbS^53JA}MisJCwJE(sd;eUn3um7yGlyh5lF!|@SAJJOp zy{$i*g>@Jw|0k{2Sv_2a2hHLBdEkWC$X`bF8PzqQAC{B<_#eEFS?Pszu1`CJ>N`SV z;%`)mcuT!!gkfPot1#ek`M#@lczYHurX}uwC#_t1%1I|XoBdx!L7bd5^Qnl*xjCQw zZT0+iQNGjB@H+lPRmfjS?bW`MEU*9FgICeOI{tR8$X`ct-bg_C3-$gBhQR^-J(B$X zc%~IPCMU;#5BK;e;=8mjX8ON@by2s^yUE+X{y^t3W^4V~?0iuD(a2KNv#9h*d5`kz zmSw%N`Y-|UP9Og~Qd>kgytDc{TgSMNFaCP?gWAt-6`5~V)Ne=grqZ5YsvWR){;TA) zTfeWVkM&=vXENUpy>-h=Ixn$m5&55?_>j->0+G}6)P3TZwgeP=iTFje10}`Z8VWf6 zZW^yzzQ0}|-o6m!SiLH6o_M8##O=Pkr~6pB3Cmki^50lV{yX=HoBW(3i2M7eq*#dnEw515#O?$`Pz6R>2Jgv{Yt!=^370!_DrJhQ4AEG3Gs52VwjT_fSqZ?ME11 zRQn_T`)gY>QvZEw56vdywlm+m($nhU?Z?<>{JY9`3H3+y4k*d<#LL?}Lin)G#B*x? zZv9s}jbm4Iq8u#C1azq(^%=K{b}&7&>Uq55?U$WO$a4ALznyrJ`OeHkIo6NdnMeMP zT9>foAy}1a!7b{OfYr&r;4MjVrAFjMzyz3BRE})=mzoPyE^ZNdTN*FLYP@9i z>TUH?&DBp~88e^~P|TIfUvEr(gz}GWq`o7aSZyn>>I?BnU zeG03G1=KEW)wvFPP7C3FIelK$Ifp9Bw?RANy)|Dp{f}$k-+v!)ODxN^Vk@5uYbRgZ zO5EQcIj-lcfu661%6F;yS%2TQLl5fHL*EygE&ekYKsx=etDmxVb>An%59XvjZJro* zp7u$0!^7X8{Aj%wX?8wyqCr~VbU|L>c9Ve$nlS9 z=hxyr0+;XK)v2eI>w}rpbE3Z2G(hr~s~x+h@td_*zi3~6r?`!0q5Sl!S2j+Y@+9r%?`I_# zNPMBUPW2ilR!;tG?faYj`O}D3z0UhGG9eY3*O>a8RQqi0%*YkwfAt^oTf336EOGyP zDXCOm|NCi?TA!b(cGd3th1!(ke`jy8>f0o(lUTi~8AUx)>U@aZ-@F=U?$kKb+W$Y$ z?%6Xg(+6G=G(_>?4ZBmH`r2=@`|JLH$JujUaoVA}gtfy2+~IzImuofkETQq6$@xd; z121TPmrioNIY2o{biNnOdq9_W6W^ST^~c6z3ouZ0`F`x3qiEHO{Qkat&nv{&7p5Fr z_v>DW_+70#nVrAbLH)O!BY$J*b5!HWA=ySPVWn}Pf0mYBcP}1FyGs+-JEL(? zsQA`!Y#%zbbE|{&pX0UZ9@-ccFh#`{{pAtnGdB`|BQqqKR*hWxeVueM+1l zo<-vvv-3aNM@a6q!(ZMee_riVTf4DoJ zQa#D0aiBe4ohp#u|IWbrQ`D#FH!PRU`%0_5ZK(F&+U+VjUp>g%Hyoky^Sc^9+kHQP z^#Qx5QZewBOr$=iw4Y`9_Sr{$=6^ta$|=!XGsvGr>(XJuH=?6=^&~2q{HEte7|%L< zSW)6u-%cc;oGw}qG5eI!IkW;ghh})AE|hb45ap~@zQ0$Yob<~ceO^+wCjSBbSrN9~ z1A2f4(CJg+b;_~&S^oL>?bEU@+l{OGu7y31`%nR#oFUUGC%@#cJWhT5{rdI;$?woc})pQ8fhbkX-)Y`jta4*8S) z#(Zr(Wa1U#{`ZHAsQuZc`Gwi@y3S*SYCqbZyR_=B%T0RJJ~_0n@lz7!YtIpy8I&to zNOV0+SmIDE6vWxTzIPtxfY$AcXx%=8(v;M?qW_(llcy=CV1DLn&tq;p2u{BLp7NOq z#BXU`&h#v#{u-?1g4Uzv2_HY6_-uV2wTAGtX=vvh+DEP}eAHvw+dCRh z`tN;Y_>^*1e9v;(eC>2&=37SZi?xvaYbPn*^dCD>QWsgyg|GvZS z5tLI*>+oUH|I`5DPinl;U-;ZwlruSocGx036gSlEtJdGedN`NSA(ylc;D68X_6W-P zL;LBL?_7<~qck5eeD}A!U$ym~xY_5|$DAwJSf2Wy);b}Ymw@Unp`5KccVYGagX-k} zTl;Z?#Q(~C@=wID_87Q%DJfdi`i|{ zP|B&T{U%$7sf~3lm+wuTo3(oQO-u`Rwoa`aP)j!VEd3bA|_x?gV`2SPWdouMIe2w+N#^D$8vt8=A zj_pz}$&b!0~O{RbnR@km7ARZK*EhL)#&teb znVzF@;KbF3ylp6_h%(tViE^rI{ATr`{dnT5zGr!Fh~EtxoE-oCyZ7`y!alu^&`A7M z#xmb>dXENAen8PNl#_oj<(vK8e}_8x{{M_@P`O5{T;n9?P#F0e=sUorg#Yv!aew`1 z<|g8mbsov;;k1jg+fTHQ)!Q$gXZ`7^{Shqt1a$T&`8%qAH@kgukoY{WpDnEV;D7I^ zjrOf(d*4wn^Ct84zmHn(E6TsAeHreDrueSmd@4NxMHg8bM80V-_9NV z(}(sssr5j+k6R8B&z6h!K{FoEb90$*C9gdktn*9ltB~K?tHe6TeoN(TD>)lgu2fnl zF+Cr2r2Ng^_j}809-2_|(2A1NAvyKDU54ed{`Bh9DA%^!Mh)TNl%A{(B;KwNFuMCG#~osda9ouj;=&?{hRRU8eDd)$_B*nXmtS z{(}gW$?FNaeD2WAtOrhs@o=d@r7*oy~4PtA3`| zJjL|<8~dB_wf}|xqMRS@QI0(qlk!lXm%Z=dB^84Q@s-t!?vHDHZ zb1ph8XMg{^!Aw2ZZ|b?W`?3+wspI#*U-4!e=Ij5TRIB&M@4ruau?q1wqiLT=m8(WY z%IUq72DW+A%)g00NW}4m-LE1mY5#Rv&$o6jc}vRo|BrIe%an6+59M3?xwZ`X{r#*% zgDAg%_g+J&*0211|25xJPC}jcXrpqyyPfOOW3_I8<|UvXb#8E-&JEf+YR6pzPXk6y_d;<)SG`*eT(+5ruS$}|E^7l z`}5z=22xH|J*QT0C*>kuOwTpC?|>T4q5d5|qy9~WFApW2rvq`@*Vws5k=oCW zBjVo=`dssxt=iW!dp5aDIkB2|*zGD6aAjR)6Pzf96s>%J<)Y*_?s*aZPo~50jiF z`VLGAjjLmYU(k6=e}8K2yOi($fAsD|#NX6;d#D%CRJ~6+RpTqmcPl1z&JOIQ|L#G3W-8yv&nf4=&RyGmT$_{kaf8mg+PM1XHI#Eu=k!d^g?e8; zDl^-8s2$Lls+51XHOp)AJAX+0Wp6zsnY9m3QjXc@uc746rS&UoKYO8Hwy?4cyk4cK zXEC*hHm>NZ`p|6&^(mr6+hqs;{CV_f%K7>u+RfIra@3=J5@?;l?2~9V`IpC#-^yD~ z^MxBZiCcYmQ{x|he10UM>{g6=8h_N2@sBsUe@6brUi)9QE%ovLpX-XwLuS%?o$1*; z8TltvqMp`%jypwsw(5D9>{hZ5^Yzb5WNblvn14=e8tpb)>meB=XS2q2x&CIpw$8t= zDD!=K7V&Jbi=(z$pY*>+@p;<#?ek|i^Yy>CJYt9Yzq~9V^YvafrH6#9)A-H*ABxCT zlrvS|C$Fmn!+&9UPnM#d_FVj&nEd{CHNSj?<%&5-{?{cZmDY#-b8EF!4-2RszAgTb zGcsTQJBP)tQ@;Pc?XFJbudML{ro90rRQsGw^AW4h*}IYdl=mKb*%-H8l3d%G2rQhr|T@0*=V=zL~UeJ^^A%5{2y%B$}# z+VfsFg1Gw69^~zzr{wzqs^iBK}Q!|6;z*!{ibk zm7j9%=$u%L@WGnz%)d%K^9mo|kNiD#AFbWEbwzq=Tx8?tPJa@Q(RXI7J$Ypi@w>}t zH~YVUyuFC~-%F{c^~R%a!41?|`jpN>eWrQeW30cLa{PU(La30=p8o$>JuKBfCE^K=R^vew5{&xTts9nvX zb~TLbA=BTXKF1f+&NeRkxEFE%obwN=4@16ZzBc|T(2e=}|67!)0qxLJ?V&w)Ust1i z|GVP3G(NAQ@wwgKTuaDbK<8ELIelOAzUn_vADhSB&^YrW)i=8@5eKMe^9$s+e&Gk* z-xIoDCV%qR%-4U9^2zO#Q*{mHgvp+-={*O(yL2I6MVMX&O{_}^8?+>89V*FRdWyS<4Y7jAa`Oz#EM z(>YR;|FqWs^K7FWdme9XmHtl>x95FH{`l?FeLnTvrRO@0%9R84-97K;im=}16&}-t z{CU-$m_DC&ChmW~ZYerYC#Sc!E>~n0@#^Z=N=g2LW|R}F{c$VTrNOk1KhOF?=LUD{ z+~9MP({>c)`{&ALt6%$2{hB?G4mowjl-h3yw)=T~O+Ky@5vb`YNC3GJF z{dSLXCL|zkN8F24`zoRC&*t>=Kvq#>B#?w=1qpLj3EEmg}jgU zoQ{W~-1&jj>k|k1={AJCpmp!odQYmZ^dF#cvj4x*C3MdDPt{L!>jB-;x#eQAgXxpG zH}%}5bA~0w|7>OInckZxw$ysm-8$6Y))jj$BL7jX|C{{v<#fN6u>RY)sIl5Z|NGwS zr%=w}Ym~o3`gC|eIsQ3>MnkB7E3GS9zMWE1j(^@}Xcgiuy>d0*O}wf0c`>~W=$kZ@ z|Eku>8w*dgiTs1qkC+|y;=ysxm;b+b=P;0Scwe0ZET;ZzgVu-N)%vitV=MN=-#(ny zd5OP2q&dx0vM2 z`k49V_s(tJOF_JUbKXa5_ts;b&FOiiCiS;`e?|lA@cT2#Z+hP8OMJ1uCjc`BbYLlQ z|NG<7i-TmeM$bKm&kAJM)CUO zzo_wf1=aI+{~`Z7dXEyr{(x$1Ab*8qlv7Ula;+!(`dr#f{>|DSxBjBkI?Dgs>lgmi z`(NLOv7TG|P*~>x4`}>@<|3f-s<-}kFBfV&uvg;&v;X?t)aTSz)?2I3OSMl_RQp86 zze)Scjr2dT1C{T6FvM4H500h$&%N_4^EI#c|9?68RoY>@w~n-DHJ|t5-Z*or^sk(a z_P6ojr^P8}@&e+v&Xc4QasT_Vp?QdB=|=t$D%Z-@)W3k<`!+l8(DM@F9vl#cc>#U( z3ib5Y`HSYGoSQmFfo1uC3fCb%UG0O}ISCfB+;g$p`+isLA>?nnoB5i}i|c#oyETro zdQ~0^FYdWGr2Q9ke*txWj(RTBJkjcN<{RYyxCHIcMEp%^(mrFg|7i8_rzOOP>ODup zr+!C#iuTv6Tx%~7_y0%!ORazU?^#Si$K>>n9>DSzFn#(_P7>|=8@^NLJ(lVGLv`_w z7)kzany*=V9zBfmOKBb0@SW3%U%$b8(d-B0`g5mGAB|(p{>>K>&vxfgJlTrlR;Kuce%gPf(8K+fZ_ve@Wcxf60vG_ur?UyMlHo z3(dF^EBb(FtbcC-3fF@kdZ?{S5_LH@7);eD*7LVvD#SsBgC zFdYbJN=nLETABL1FFf%)>i?rRuKPBW@_+N@FJ*Loa-ZH`z%VbMoRO6Oqw0BI)x$#C zm#mta`C7eAx{dt)JU8>##Qp#KXpxxsVU3GgD&O7c2wXj{RFQTF6aMX4;{N}zf7XKK zU9WkTjgzlUCI5{dD97x-C=e+ij z4=QrTW5$PDagHKBIaM&Qadz;`_*6k`LzCU zUH|K3c0N=h{&Ib^k#gp(q@0?PbN3o?|NCISHKg5Ed;3atbCG|s_O(w+&S!a8UjN+7 z=?;|R?_-Wvzxk^A&2Ex&t|jFs(|U{D_bOq;hxefV?ZjUT^)o*EuR2YALh_Rz!_k1A zu1GolcWGAZoJJNsch;`{_BQ!_ySTSs`%@}%(UcR){6OJI1y0Q$;{VZ zr)~T+?Qk?X?PiFSgb#6$7tk zX7Y#Brv9%e^2L{^zyE*PAM1R}VV!TW^?`xEQ%;P&^Je3X#2RP%|KIu(CMM4Q{`!At zYTD<7&Q)5yy@%Ob;3=4Uz_J#uZoJWWG=6e4W*k-8ZObj-r%f|G!qc2IW-M ze8I*uJwGMBSNjpD_5n4{6uf@hVo2C9@pUx9n zzp&zW%K1#+JF@3@D8>WM{*(0{baBbgnv?wg_wmZw*t-a95`dCd-KuCcsDiV$xoJu81g`BSxTV){Ruk@&IdgoTlbo43W{(IbS^&_4-hR?6H zx2NBwe1ARjfab-0H81WcJ&$Og^f~Q&+WJNc?Ta)Sm4nEJnw`up!e47f-6jkK>}@{gdQcIAD!6U%Gu-aQ!H;V0G3gvt*26Oq4p zaq?UH-26$}Ayq2QPwja@a~oo5B_i-L%%U9syu^Shl;gj5pEE1@{r~sb-kA7gy_ag^ zu8CbKr;)e+wd6d@<-gbS#^02)So@C+r0fA~069CH(KsK?4hao)eC^MK(v;($!%4lK z`IgZ<1<3=7M#mSQoJdv>bXGe;R@X^znzTwgK~D> zAwQ0n1T;wHnxXNV)rTda@z>{)uTf9`|BPZaPnqn^Q*vSd!j*TU>Xo&Vy`!klaGiTW z^A*s}muXM`f2K0!r5ykNmvaH)4G%YKkPS9pW~|!Z}z7?mGwQD zDU#p*8{%m+pR%+Kb?z&%-e0zM<|#cd!}Pq^xMebKfV0m5^W|l6r7o!Yru_~i52&v8$4Ba%gUv_U%%%Jc-oD|7nissUd4b9KzB&0% z>boclB|l4h-mh+JdB3baWY#!#<7(p8zeMk*oP(NQV^|wdNiewb`s2NQI{&cK+Xrm* z9^W(SqW70sZX@?q~eJ%_ne%t##)y?9}H(5W{+eVV}02^3NpXuIrVt*(~{vqlY zGKqif7vw*+h2^#Wu7sYWsybI-?PN~%zy5bQhI~NzsWeWAko>3i5%>S!ImLA5+w>^& zoh|;os<$~+Z|%MeQTz71+JQpiuTz-z*?yUFFuV^aCHCvBO%@b*?)bOcr{1Uc*6Ts8 zqmp?kr=$M2aRcFytA(lOB<)|@IH76{@<(f5!|dNecJu!q=Y-btb5x+7RzKg+IRXD% zZl45{KQ%eqE6evMoy#hzajDtI6(dwS$MvRaURg-{;3nttcHS5NeX-iwAMyX^<-5e> zUtfy#ZGizw+ymd`J3{-0R)112r~Uo!04IV1j(@ZE zZP5${)Liwd+!h!Lls;MDh4j(9%zrudmSfAzcznk%i&JVw!dS(4}A)O;?rgKCsC2hca%=d5gccy=@&D6i4 z#xvFqJjg=+_FAv7`?whmj?>>i_qDYM^_;KuPg_s^d|>?h5x;#-IjO#3{j_>j0PAM) z)rY?{e!i^s*7O;Zn|kKyMEn27+ZnRI4&~n-N%<#bhiCMD<;AMx$FMMx!vAH9g-d zNd9#rDc{zMYA&gn(@{=QGE^ThrCkN7qf@qQZ5Si84)4Dm|mcppXSkJxQ#0|_aVRk|G7`RM*h~HQ~!mM|N2ViyGif$*gDBt?Th*IqpaE|z2xn)ub;_$ zZ|i>q&@2bkLjT*6Q15%%eEjvtoa>8Gd*y!*{Hs#bf2P(+%+62fxoD_;H$3J6^(w=1 zy{iAsw05BWS;{&1KJ8Fh{2}4ty&?xhg%67D8WS5H9unRss!wc4cu#mC;q_WJ3hxmS z6Va>BpxB6*mW^uk9}pGMvTL{g5sZsJCcOL5u1-ML{(Xjnv3=tfvE?I%c8`dT?K2>% zMu}QOfVFHL7yLJ-;sI0R24IRE799bldJc#fSgU!17U3a-Vq>~TMk7ho3z1EotRDTF z4gnryRJ>ex^KRj>F%c2rQ3HBJgm>#YD57|=@D|~bC{X0!$XZSRbIz_qJlJRCJtHbfQg-n(lIl-dItGB^9{hQjp-BBt53*~K~d2N#rAZm%HzIA z4aTh+TD(MfIJkzAtzL^k-D}lq8Q!#ZpQbH~g-14R(4s_m)8Zw=!-sSY?~i&A9^187 zt;iOo!-u2lpw@Kn2bKDT_w3rIKjg;EF;d!<3a{QL7FOwrniSPN0@iC9`M9{^7KdA3 z+)LPfNVkVp^`g4AsMRh0+z=Dg8mk>nl|HD_#13sikH-l_B-#G`gK z3hy>JGCF*4^h5RHkpnY}ug8EFqW&Y!vS92J)jg(`GpKt8P^te-ZF?G=$s_xqi1Bi@ zC~nV1$wx%Qsli^vziOu!;qIE5!HarmA*aQoaXlg)mh{nx=zkgO-y*D9KN=Ak`8ZJx zX{mq9JhI-uO!%m*=>Ct3iR|-GSoy$Y-3LUW(p!~yq_)J%@X#1gCtijUu0r~DQvHiJ zLGi~WaAM+15y$B9MgPn64;xE7^(|scMR)BJQ?EgbL5+)-s1*}4Af{2*sII*tV#)^> z_0R;@s)ZcUnnv`G=pI{qP;9*xrNRM~>K`#^5Jc2!(KSG7p%Pdt+$S=+f4vsv!W(ug zUaQu_Y6m`N0b_DaHIV;`0&#@ImMHd6LcfS%;oS#BMt6+~1erqr$}#r;d08JmN&l~N zuE{R8vAY}pZ7V+%^8eg$HUHNQ_ploNua)gzoqWm2uF-fj9{)&rkI%mz0<_(=;@0;6 zv80D~{hvzu->)10sssOHT~+Ewo3ww`fHFhSlnsb+4ImzuK`?my3F1ZnWr%+hV+KYZPKigu58H*sie_5hVHd2si#K6*V|ABBoDw z4A3IoSh9buk{Itk>@G`%*B%_z-HpC&#N&p|K|(L)VHLsH>yb1pX?U-pL&KvZVg?Ox zgZtQF;X{hWFUU&odMHnl8zDzTM#l~_^&XiU{A;Wh2M#(wH;`!&Tejzbm>0Un^r+XZ zR+E)(&{O>F@dU6Ho*Vp6& zPcF2Y?e&dnt95X#-iM@Hn(efE4KNhs(mAV|W z^kcjq-vU-_LthNey!LeZWAeo^DElKlU`@MAg>O>N>1$fgHCk(x<$4cmNZw9w9L*RS zG9Eh6s&2^LWThu!+)5wEe+>l1t}@tcxMr-ZE}p62mT?BG4i^mw1SeMx>WR+#+S7PJ zy*gB9^47{BEgSdjVoeU464m4>xvs{>REBD$har(qRe!{v(7&lp(#O=ZkXZYcs*G37 zZq0aNR&6r5APa@@2TLW$OFgp+57xSMFq<;&5~s0&c!j%9%xs`E@AibgO5(LdOKPBG z?lCx;3CR#*abgq&#_#!;jLf24F(U2M%68B&CG$18`07qF=|=wzTyxc(JNrRwLGxf> z4@L%axbINwS3V{clJZjcK2ld^n5MloF3CmXjc1R_SdN ze;Z6MJ)A`;KbA=&Js75LuD#i>w9i+%)z*djVEQ7DXHgh^Fq6i}qhTsb?f!bbs}G66 zMDo*FG)A7wB$1yCQ#dCA3gixTNnTXDeeQg>y-|4vB0}YSt6ZMP+TBzKagzN6-kyaf zD$l!fbB$I9b68!gcA(9vbh_<+yVhXYt9M4@-eL^7DSNt(*6te zzS8(sYgpxWfJ^gwLiFr{hTyau?1@&g=3auDw+DT5eXY{zcNdcsr|hWmsi>~_R2v%K zPP^HtT`JF&%XWohIuE!GYHi53ivwP?0)VW8u~6nanBCTg^S5oc1y94Ej|?t={DVK1 z<~{Y<-yfzu|M;c7Z~BjKmeT$R^LKkzc+*Dp*}7Rxfi)iP=ma*UR&CNcIa4Vg>pYFi zi4s|%_N3qPZyw0GI2KElFefWtESH~NftjrmJcZ)l>9~jDo#WUuGL$D_+cDof+>RFR zCe|YJ@}L#DJ$|D*aTIQMR@8Uyx1?^j74^Zol>uwCb`f(H=>~VKG${aTzp#`s+v$pe zrvYt;HAqzYhhc53tincX6h3iE=7Mh>lDF!uHQiG}brF6ET9K@tV3QINF#K-W*rM{0 zXKWOt@WEx$y;_>p2cSrb&~b~7aHVfla4Wsl=EmAeRSBa9s%y?$^K^V35X)??xlrAn8m6v=YoeVG|a!T^Pha}Fi&SU3th`R%(!K@fo(vjE8I|e2t}f$6zvzgnCrmvt%zTaDuGT z4jk7mckC42O1b9W&)sOvxN*<3n|!Cao*t_0f`;7>Zyplu(@Az%M$-f4H` z^nD7xHSog0Tj)cHx?-!wOh>$KEVq=Ou#i*-;<4k}cdy2XBj=vZDxO@`k2rw3fUel31sDe_3z3#Lx3||8h4G?^EDE)w^9{6E4X#CfX{O zSH*Bvf$#?(*6v2N=`MR_E0$M|>aFKn3q73D{F>WYc~_I2c_-7EsUNfo{byrd=DfRC z)TPc#w0iNzU563SG$pse6L%Eh+Z3ATMQ!najh2XHkB8s>31a0fuFC2gNv zX*YZ2r6-nG+ACUXh4HqY%NJ*!>*Au4D;7;)!44b}J}OtKDu_4GjHY3s?E_dWv`HrRv^ zy#famHF_|^s(*(MFvPehZ)1IA!=SsQBII!J;R}q@BKt_}Z`GfxKv^VbjChu3?dtT~ zICi^R#sgSGb|YpH(1oWk0^O<`g+RZa^ThMGjhe!bGsYs$7%{Igl?M>EyoFip$UTS$ zd_-R`u1`^}_T4JZ_@%%i#K)=?o!qQ$-HB_?YgK%N3LNkX4gW-9r@J+g`Z8kHo>H3C zGabK86o;3i0@kK}w|z+|M|x$*%(Bn0&1BnadeewQ>`In z!JH&L!vo4zA%K-2Q3Q(+Y0FOwCrXADoU!KNr>&EN>quCcWe?y<=HIRl?Ru+Ja9fKT z5FhbXbMk-nJ5W!6A6={)HK%LUDKLiit#W3p68zjdaLTL_7W9!=Ey%5kYx;^?ArkU zql|DCoRKb^Mf}_oz6dGaMb^n4BQkH!qoIAHL@b58j;mhKMG8xKwjx#wqZ%E8IK%h- z#v3&GVdpJ)bPU|Cqpz;~PF!PLez}M^1&L@5768#N49hpLi@?d82iMTNV;;N%<}wMz z0?+AKX1k-FH25a1#>3&VgAP4Tn;2&mF(*HJhz)wo$U*VP=9~4cx;Q0`ph6%K$e!*s zCFi>En(_3S@=4y&s)%ojlU`8kaoV@7z_IgRfWonKu-cP*4w6Y+tilDZW&CX15Nw;s z(yWTnP@x@%*0jKe%UQ)p0*BNO@MQxNa_rq7TKT#~2wFlV&;91%`-clsIV$Nw>Cgk8 z6aVNO=aCj7MxO^IOlaLcYgxA8JY!9K*23wpB-?1r+x=Gh^Zm_Evkq19N#!!Ncu^&S zu+E@3p-PoA@Ys_WQA)NkTR9G!j1h0q1X?(?3N<_C&3K(=bX1~e zQX8U&l$r*PQCJG8o`0oMv;7>xl2##jM?CuSyu4tOS?IgUAl1L}(ITU+rK;AHad4-3 z4m+x932O>6Kv)w3K~T#TBQ1|P$%JD)VAhe4sIu8=yna(H64;4LokqP@f3AVRRAngc zZr0!zODjoP99E817EeG&qWtzlW(UxkX>`W#>}`{#cvWK}7gq2z#Leg_*_JLvwUkq* zW1cmq$_U-r<3z;xbUl>rF1I2^cCsODM7HC|BQ@$a7*2*pWt*)bK~##`MDGErwlMu9!CIfZ=oX9v`yiGerznHEfr zf-YET>q_=puMFJ@G=WDeD~Gj;6FX4O*8y#Ym#RLu&B$)61u=em-!Ol~N7fmr#t`N( zFh#wkq;SEY_}-K?7>SfUIUhDn`Cs)hL}Fcw8} zf4$p&&iwnlq)CmC?)usG#ApVu@1&!*#ut9e;KT|AC-hYRXEjYS598=wEXL>@3q!gY zS1-zbR5BBldDu%qUqi@*ufT+?Mk?yBcy@23dE8th4>TX;x4wZu&LEi2a+#Q~)Ug}lquiva{f991wf--RKBmM$w z1m=EcQMMBbg0XmT-8D&Uj9XgTl>$<#Bv7}uSGZyHgt1{o&D*Psxu7hXb}ueNq@)W{ zqT{$DL(+Q#UNo@6S;<8FsN}Aa>!uB+C^GJ|ntMcw4Be5mbU{S2_JWTBJ8r zv-(y>Hv;7&k}mq=d1tQYw=z@#`ISd0x2TM^Z+%vPxN}j}`xJ++?6+ zolW)c#@iwPZX>A{V_$>p_q2Qm=U@Rz=`JU6V=tCSGSPyp$*53NDADsOu>-gXn6hwV zVoSq#;0e3QsWujVDi4pMH#TiJ%`Eucpxd^y3y*%ucioPxF38Xc3p4T8y1WXFY;U@x zT3EE28T%S9I4*&Mn%s6FmdGS%Lm3W(o<4?%l|&vNqLUQn0Yc+U0@THr1duYk$)q%a zGAbJ$p%{Z+b{RYZFS85nT+oGfrH6BL#T$LpLueEXKH_Sto;xZBTxGo>D4yd9s@Fymjqkarsw*89py)b--a zrwHpx-Pj{{FVbAIetw5~$4Y^K24PZtt{Q>3ViBXJM<7+mbNXa4VLJw<ToY2PP%P9hd@#P%CXA#=1_%$VVl=~~6ktI@nGp&#A5Z&A5l+ML+cW;{#Cc#pb zQzi@|ABPlHBpCY%mRP4_NnDpN&7OCT#OWq0jV{LK zgPrpd?k=cX71abF5JfIjizjd!k?8!sl8SMegF0F{x*!Apvvs*TVm#(bKvimbXVz^X zGsz-kQ5h|Xd{w{dtE(g}Q9rqyxfhLqAQ{0{j+@(9jOdWw%3hSb%4e`I)!4rEvGi}n zPl0d6?eG-7RW9`6yJqT2$qBTR-kZv7`M46}#gw&!q);}Mog@S23Pe*qB5|Hk(49}* z?PJ_&jCa05JZCW~!_h3~>v)Fmu;ec4x1Y69#$JD7oq6p66qg;$@98#pkp^9uP7N7` zwS?|fFxagzc_h|n{FCPvltXE+x>Z?2_HaqHB&m@qF2`aKuD^Z+Rzd12oL!u>Wz}p; zelkLwHIH6<9$d{fJlW6GUqjc#W38;aAs0#UpKVUT(&5O26gm&_Q5j2LW@_Vojdh_3 zPlT=~em;O{S&k^``M4em#nHy9hAJ({wo)^QyE|^hsNJKomJS#TWl8{LumFvZOO*~e z`*UT(WV(`!s)ie@@j^`ia4{VLEb0)U#@pD zaVJIMfPE18R$xJkuC8r2R+BF6`L#zpxWj(wUFk*Vr z{hUi$b2y1F44(7CiHJ=tNPtSkPi?;b=$k|O6j%J#N&hl~y}=t07CS#k_`^EV%R!Vn zQFt!)QHT)G-UXs6ahWT&5&j@H9!gw3Qd_7A^AwV8`B11(A`0jb1$G^I(}N@z)5G9? z+)id_h~yQ-R7r`!U^ix@Ls3N5PN*VeaJHrpS^$ypkTK^YKhmeiV#zWiqN3lbG+tR@ zV06)hRGt>Mm30GUA8BfhLcJssFslV{HD^Q(wvhcgR-Su()NP}dx~`$97O;dorj81I zB9=Im#V(pHWrJA1^iipdY=yX~3&T;sLY}kfU5ufaBK+iPC5mz1aXb|)NGNF2tNAb_ zNPY3NtqWhIlrb-0ET9D&WM5Q+u3WRbb>o~JaTO7hLldrTNZ+zi-;iQo*7}d&P8}%P09LkO1MX8O z>4wzwuzR=4w(_X&Y@?By7+S7Hf#5w=L$_R-Uu&E{U+>mYl*K7aVBNO?YB7zhC#ZxK zWc>wI3L@N2V7HCNJ1I+5<2+cKR@fl+$vDg@DaSa??U$rR%O)9&|D;mPNQ!kE3w0D{ z8HhfS(WCbRADtGmCl_Fw!{p@lnxH@^mZdOa_K3`kgTp?F#7E(fiuuxrFrF%A728cN z2@Le42sR;RTr7c+6Up%nF9Qk}#y)b{?p0VjNN^4USz=q}Jc($Y^4ZcUt(^=WoZUl& zIb&Ufui#kv`%#a}2Q0QwV;pq>bdaGp9=BVvw6!Ijs7r1xGVZ6ig?MIQ+(=x*pfc?p zanCRkP7j$4r7D5GM5Vrh5q2?^Xm}yYQ%3SE79SPusjK2AV>1?F7_x2?>{vW2@$CU= zJF?8tS$tdL;HG6MmpvfSZH;y7R(W4;ko)8E z?EOq}F*2ZPKg+kcF^_Q(+B2Cu8uUlRD!y4{`gBH2Y9J(ayGKty>CavJz4Q=Rt-&CF z{W|ljl+`ac4R?_kU0;r!1A)ZovYp$EY{b#qjs*UVu^$)tgfInc&;w9*x|2p};Jlx( z9NOhXNYv<`>PVoB4-q-Ybz#j0T$kIZ3q#SL{Fu4e*-SbJVHKZp_%Zd$+Rxan3oP4Y zN*?>Xyau0UnPY#2)jyU}8jGjY!P6!;k7&nyYpK18H#9$&+F?>+4ajB1#<%6K$CNiqT4Jp{e8piCk8P^~! zn0YdJrN`a959SjdJU}W-8H%)+#kqjF4!cp8iqWxd=yXCK8q=vw>}%KRHr72uf#OfZ zwD}%#jP-pgJfZMh(5%3nxKSym7!t6WBKIlu8spO9K`~-hDaFKAP7FvYrMk`-ef;hi z>5xl2u)<@9pb}Gaol?b|!R}zndp3eVG3-TShQ~1sbGa(_D&a*{UOEb#$62#-T2^m;Qn}h3q?D zabt6+61fwU?P$nt6uAPh2cJ-5B4WZckR^flR*sn1Eb)C;5191?kmFKmv~=+O4$YNB z{Kux}a0PU^)X`Hhb*K~I^YY^YHWfr!e-%=$gwzk&d(Te$Ah(i40uRyM>Fn+U`X5sU z5k#^o2e)D~hzzn@jAby%>VeIWk7wrfi8)xKf+v2a#MCvOi4o1zkft)^bx}c9*jUAW zJZGKUk(uo@%SxUq`ooka`Fx{^0<|h8ygX0`3H_QRT@afrByNtfc(9K^^#YC2u`{)eTJw~sswyyPJbCKr$Bv$=R8Ch)zS(lQ);wg?ITE#Z zz9at+Ez~YvL^DsrIbhj^F4|QAJ)yRFB8v-Lr6f~qrP^0MQ!q+E{sCuRKpm^LwyN3` zHHj22cRuC(>;p5l`jjm)0a7)(_31JQ4IZ79V4q?|7 ztBVOdoLiL6w%cwjw+pm}klPAO?qX8AgoZ*He_TqJ;8$tubQ!j2APKBUsQZvK`f1De^)e#B0r{Yak}LyULG zP$9-|{Ny;DwIhE?>Rw#82 zx`I4$zS@MiBI{+m++4{%CjEEqp)3b=U7ay;&hUjBqjg$?;ud#x%ve-518#Z}hQhzwy+3c;O ztQ=UFGIRSk3nD3XwG3!K$qmB}V~~qP-t2y^VQa-jVZ^owNO6K8#;lAhmcw_Cjw^E#Qt^dfEH8Y&h@=W22j zi7twnU6sS8wYjm1P?ht|b~WWxF3q)Z0dJqLbgQikb$i{5FSxO;gwkAu%r#tmCci2U zt+v~+-?zd!$H)5p2U?p=UE^R}nUfY|r<*}mMy+~x2X=f3AF7t4B_ZlZHi^A=(6Bqh zcgD2!RCYh}t(z?=eUX-AXsqk1G#{ryon_=dV2Xr=zS4fw5|2WIJ5YC_!)yoLGfxH@ zx7Wn@r>#}{RlSE+dYw}U$Es;{FG-~e@#KfqC4nxjvOzU$ncl49i=lOs)Z>x zl+ZJg(Nx!%#AGK1#>h9J<`U0_n;b`0IDHq7`f6Kk)EN59bHN!;vb90?nsM}+btufxP%xVZG@1WV{csEv@nXbFxLjj*CqCeOHmo#9!7KQ`c(TMU*ptvs@`2( z8l5#LYZ~))ng4t8tm>ls^ov(QaV3q@pv7A>1+g`1Lcub|9ho@|1I&ELf0;P&4F0fQ zG%qLoevHM=dpK<)U70vd;U{qIL`Fx6PXN_u6I(#*6aVaZfe41d<1~cKb^i9wdsu+FBH+Wmm(m zByNM4BT6N+obq~BzDv%S>0C-v+EHYb!er!nSAPNmH4@&))Qhff?Xtx6_; z&yHyKF1ANT=)RSY)Y`}NBh{5GCe$M78M_AFA88Ylu5!edU`l;T7wy|^-ESjPv9V#&#Hdwd5T5VR5 zgK;%&cg(^tuFl61r!1d~Ef|-a4Wsro(2v&iLx*WbjxDvxgke|L){LEq)c0@!hH!@S zc3}gROL~n@)+YqEFAA}%9I28z^Fn`JSAejJ@r-<193?)h#8+JUB)Y2lY+c7facWDe z5mmm(rTsefrO}dVPhNO6Xi)$(p_yb_JEX^yLnQesxH7&B@54jlfVcxwiD{PhgMOgm z8*K$AH%KDKfXCT`-LoipS|3!|VJ9FohbMJxQ@C!tZ+xZ*k{$S>WfK(=xYBR-7h$p} zm1^tNF1A;-(eJ^r3QMA<>@X{$R=DZ#5fG zi(Sv+Ah}sdt?oo4;^KOQo=-y`_e66m$9v=$&vrCy>om$%OD<~_FNg|8_BRzwn5(pW zVBJEz#2(jM;c=g*)~zhH{oDe^v+;!rmJ?HQ072$H%Jv@S-4SjW*(SMr@$p3>bvv4) zyMdYR_PU{Hsj>Gn!UY2#hpCL-FmlLR`Zd{r86U?c)r=!Ww-w!LnfrrtQuIy;{n7lx zGJYx<%L%zgoK&MI%WbOZ&6A~NQbSyhBlMSfP^noMy;`Y7s@w7IZ+K?>}j@Gwrnx7pL;`7 z5mMwj_8lZcGChF^#ao^b-eSU+;=E;&v^c(@(%d++nz7d=Nm}+knj|IG`!!WE z%r>MXPw=2^BOxiPyp@r}#-gW>CyceJZ=Ez7mB1%Sns?5_Q9T9p#B#o3i^p#kbk%m| z?0j-#cnZr>Mh7CftL>6Ydx?u^F}>^SXgE9`Ye%b;pv6D4*=a)mwb5xVp4yl#pHO*J zMG9Z9SJ&WtZ`Tx5499`Mm9tk@Pue`t@n=axE^UeO7uU7Y!ERkCQ@V9i;$%_u+g!aV zjTqdtUG8R=>&GW+Rs?5t6Vtg*!D3X&N}}0guuJAjokqP@f3AV}epfT1(vN+qPjM^i z$@n71%#xL33O+USGVj*m6c9R6)hm36-zih@F=~P3$%gY}FdNP^&5JUXtaaf;?uvi~ z7QJC-p0zX5!0uevq3|aoUEA1K^3+0T8(X zOpLh*NS^cxu|z5E(?fJB2!jO}^c>Q_U;CnMqwm6S@dX_tYsQEMZ=q z=T<@Bru1aSO=&7ua`u&KDoGxPej=6`8ZrU%})>xf~7MPG!x`n7RNn zdn}8Y6+BeKtg-<-YX>PC2Wv{!e~2nhU8??w*i^hS49*7;s}`$Gv-0CljuN-S$E7q) z8-l>z$x1svxe>|UDU+kas!KBrxmG2*n0a){=9kil)u_ z!%x$A#p}XO!#I^nI?GnG4p%qrb18Dz)U2_{m`#!)u%pO=axjS2sh+&F8s3Dpi>rFt zXmCPqgxQSJ?$SZmjd&>z<%Fn9apdJfbfpPdU&ZAQK9aCg9ju%|{s>eY?q1Si5J;0P z*9jG4BLz4}X`PQZZ@+6mc339t*8`>ysMJgoAK>B95)YVCL{X+xw?XK|exZ$)_Jsg(}zLn(hzRQniI zNt^~P^htabw81dyAWd-84O7+IXu-d@n33Fo(m?bEs$cYqu1ne{**T7ndq-Iz4}7%X zt|UyNwq8euUigcbAGLK3aE=->@>r}M4USy7 z$LgXozJLr0#!$T&Y#y5%Ua{Uv%U5rKuaIBg__uQUmf9=>y#8^>$*{_}Cfmfta(j)= zbE9zQrlK&)#~Tj0J@IZbDZ1ljY+B{R+Dh>9vs3+rZ_FDn6(J z5US#n>dFQVhu)z1K!L|vztrn53M&c(w`@D+FTzdocb7Np&$6l%&juBXjF{fk{R4`W z3yh1}9hwbD3qwzF7P(p5J&oM-ZJm3_<$j4IRyOE0W$Wc`j=6y-Dze#ud{_PT5>Dvq zlnj1hAfL!hEKWo!2_!koRNXpBY%uCLM4FBkQVK0f>^_`DLvkM(y{sh&S$Q}vBMoY9 zvi;gY*;yQV5a+0NP`GZ;+^i{)=yC;C`qc|s988&)kx#KX;XI;{JPk?3-ly6|FNt-0 zFzknt@Mz2_CzO4j56n^-cGg2C!Z*X|W!s%al9i~gaBb69MXXa56kYH2+UF$*dI?tw z9hE7Yz^3)b4>lQz491Wpfs9K0%xkytuDJya-KDQ|F!ZalmKhFPG^s>p5S#lqT-8Ue zW5`<%iMSy*y*_kXsIsKO!%dYYtazCU(aeZYto)kKNRW5vNf{*Jw!;;9Qf`;5ga;q) zAC_8ONHB1|ZAKpP=7uDxZtidK*&mo)wrIcXwtB0&3SVy1xVky_*BiacbB+GG$Rp}@ zgzbPlfa<}<`NgxXey=O#(8Wx!UhP%tz4LwT;vCcA2amkaohSMqJQyk=U=Fc6}>gP9K|NBFl3kixw8MMAP>lCYKiwPOU zHts_~2G}*mQyEDILO>2_wrf>L=k1oU3EQtU$)@p~H5Ny8U$NiadNTX9kW~B&y=NOp zlifDCQpVDU^OEpOvdB*;%}L#zN>e4@XiB#0ja;dl3mLysNp+r8siMe!bWT&ZUV^d= zl3m2a1mql{@;^orFx3W8E*87PRQXdP$aqT zY-0A93_ct%1`ZgLyj+XYuYoZvoZoEK`i*vL@rl(&;zT>BlEXu~#?33A+!f?x7-f@X z{$jm_D$P)6hgbSUIYM;rL-a-hv%->qt@tDPPIu>iZ6Wv3hO|p4bz|DzGB;}dLETO z5RQ$JO%4Pspyl$Z#w^$ZBuVYj>3U2)KHBV<#EldgQ_E7M!q)2*sg9^^4+HcDu!OGk z^w(;=FAo0!F>RrIO2JbgX2$Wl1vSLw#+K7_d(qOLNTILFbL7eNrR{q&eUm(Cmenb0A5EF5Sxl8r5z+v zAxO(IN3SCcT2#_yD$yK5cW87HQ$dW?X-rI{^xC>K^St91A@MTj9EhSb;kw%)0+3zVB5_YKct~H8%*oiZ618( zR%)fOlg|imN~SUMYjPV^1d8?xQ8ePFTiQkmF7H4l~~-$M_N53 zPJ?;5rl6fhI<*bxXjABg)+q9Pqc!k(u^t2sd%)A&4b1xSzDUSD=T?Hz=5Mpjm@N@& z3)+Y5b&k#J)t*qEX?KZCuHd&3#ZCvRAb@*VpQh46G`>k95z=cX#J%&g*iUv+{y1Mk zz{8NY~^uV z2njA^5-Ewfj0UZGis2H9Fr@BAwdo9&ixU13`h-w{6?syd4dkV~j?4pGzv06p>z%0F zIaxBesG||v10)+rNp?MWxi*}|Cp-6gG#o!Y+c`1?P_}cdh(Hwgw9$;?K+>~AZ*l@D z1j1>Z2VEd0Jp)86n^;x_@(wG*vSFP@$q~uT z!**uCnz-Ng*$&yv(UMTL49?^$U%bbrhX~QBola z&GvKkF1(F55vCRjo`TjdaNjJ)3W<~rRlNDX3V<`+M=DK5RK@7Xk)-Poi zXPb%<9I|%pjt!{V^g~s^Ev%f!z!=?PwwZ_%9K;^B2vJc(yx>l?c zDyErehQa$T_{d=@pP!* zGAPfy)lcOj$-MqgCB?b*1XD?UWW4EOw{r%Yn8FjQ`xtg!%m9{9eN5%xl8z+CEUbMo z`^1Mdmal9?)Ure+2cnu#mx!9GHH6vlPSU5i?Q!9@PlDS%W^)cix$O=Tn9Wpokg&*J zPMl9WNLYRrxr0PTMlXvK=A$vc_Zc%hWT*zigKRmN;pHP^EG#6IuNAL=R?ZJ81|6zD z5#(YMq(dfOK1}`fZu>dp9$M4c!sLh6b2_P(OCY&?a_9n!p^!dT7kqz35u3#!j$vJfzmw6x z1hg3w9_1xTP*6eU8CXl!K)o#mg3yI27}=BJR6ESjo^)fIZyf2i6x#Q$B8ByS&$|oq zwi~E)F}I_YE?mwq41Cb@IQ#l(nS{TD28T6T?`~d6u)$Ss)k#zt*|rJ@RjY$N|uaEFR~!`|4)QX0zwi6sIM=^~Ly zty%4=S`d)Rv^DD@5)f7-k)@ERE?vrB4J%G`GN_W!s5Tp)Twl{fw+dS9Qrkq22?k#k zHj#4{!g8@JBr1%i#(fks#gRxQn>2PmRui!dDl1}BkPQe8vm-KqPY2k55_~bdkbBL- zvngte**&Hrj!YbSA16bOMaY01YR1BM23`sofDAiV-JCdg8h)7H@=qyu4>;j_DY@@MF770Uhjv!Gd(A)!|d=n9uSMAr8 zE6L&s#F$B2>T|^Q9baaMGP3??EB*TG7i$utCGrPEB9l8)CrC&2x&Ea}x85L|3FG#oP9V9Tbs>hrPFS-AjYBj@~Y*NNE>Z57R~mBMy*oscH3QD zpbAT3t&k^ql8fGoKaLGxmXP$M9d zx9o!|5ZaE0_sh5XYmpVYzFoc3ebKoRnj_n%UuY%V#QwfalWQVZ2Qa;u)63`CM zrp>QV-~bGq7MF#C)%25ay%&G-Dy3D9Wz;pv(LE}u9=&$W<0_x5O{H#fPsHW&LQ-_k zZ?>T6)W^BlRdD$DatlW44)DdfMys=lGLCE24vzXtr`ztgYwcz>?-7w+{$JVVjr3Pj z^QoL{N)@jh&o_7LIO_E`kEMhCSOVpSY`?KIUlIn118l9XM6EG?NNJ%}-$I3pOOP=e ztqSg97wU3;9P~q!voL&{T^i zmgl7MuSNB-HbQ$OD`+wIcVTiO%lVz0xxXpA$VH0t+bcQlfV`dSYxVodwBiyL(0Z=jT^n@2 z7UzOx?*awDIz+vC0rb(-4kwQ))RK zbDYj-D3g435r6nR@j*Qv(#S<_Wm5KPX+z)3<{-0q1HaAPUV*B)sXYL^iQgYFp(I## z;SbPsW@R7dL`Rgd*!OF0YU4^GpGDhId*{@9Qr~7hWh0Hqz+0}37qLm?i|H3Piq&JS zcwtBoQ+zkrMuC1TtIYnKye3|j=%7UunP42BB8Jnzw~UuDv7+>#vf|3oA5@dtK^?%4 z@{Fq3Pkjn`f3y|UG<=Jmf>uYJu8{e9^=@C8e@wR z5rK%CU6MHam`?7Hl$;R_99w6^YePv=uKPj!e%RiF;x!J92=^{CF{-d=u|p6fqHJoW z=+Yme3Y$o$Ru$$MpQKbAc39I7MPxNFDhXKQmf=xY5$EA}0z@_QDz%a@2EWGh3DAMt z;bHNN>318g3;HOWST1N?F_1It;)Mszr^55cf} z>`ZN=);uMHSHa!;$x}~1cJx%GavH13v{^2nhjFdxE~sEBK%{}%#f#F+8N{gRP3B#M z!Nt)yzT~17;!U1WOdmYS#09&eY9gK5n1#vDJX>}TSw~~@ODWmPY~^?zp7KhCB#p?E zN)s(1);gzYR$o|1?xFOi=&2Oje)cj;^6D_70;O=SgST?6UxMaUJjG+;p@c)hxe*zj zU|xV>v$770ytq2PeiP2@`1ry3Z6tLO7{UpgpgCnHslGE3+u(P56(HRKsySq2Rz}A~ zmpfWp<6=il$?AkDSzS$|q&ZB><`-XNOx0k0+Uyml3ox6rKB4&#Nbqo zJL!4VPB#0>D=k1~bM>RAc~G3SWD1C!su7dCn5uB4%4ZZ`E)cNBah35S;4RrodLDLB z2G2CPlFA8Vdto(ghL?`27ZHgB_P6Fzey2>;3W+F3IR<`4PWXW-h@y{NVQ>VCsUr~iWUuX&r`JPiQ<*@3c z8X%NmC>aZm97GZej_4-{ilu`IcVYZO*E~D|legSZwL>oMt(fHj))U-Edi7v-K&XChd$!KxqNaz8BuO9m+HeHA@^Orpn6TI;A}Pq$j$NR0E8k)A88Tyh?n zMYO!~v^CjG|3Xrc6dCd6(>52m0?~OgPP*Bc0%?v!?1&tW(?^_XbWi|26s_u3SWa3w z?R0-s)C=J{MGO33r8(4t^u5tRnI<`Wsz%JaOAgqcp;IX=2B)4%%Bb(Zeo`IkxgVo6 zf#O{0B}b&HN1Cv}j$R^9Bzb)CIe^kB6_d+JB->LKGhd9v3j)Iv6B1jTM3ORWVwSO| zyhMD1BI5&vTSf4cpD10qj&PN#(q@J^nSO*1PkCs|4H{92As(0xW_VIYm|QX0;_fsc zzYdS02zy#3Fa>8Xe6kQydA_f77ml82>T|m4W?#{~QF9i$2t<)88>W`J12=&bPltGj zj-yIqXLsf>EV9MB4Jq zup)ycdMk3CK|@B$oftH*cQ=hY$AQ=f6ANnx_qW$x!b?Ydsnu512ii{36;XmJsvxfF zXbtDyvjF-eo6vx8e~<*50Gf!kh(fW0|xH=Q+ zo-V2ry11vDlzu!crB5aOxup3MtdM(kx->ssU$`VoZJ^jcT{N3jmG64M({ggf&da2< z3d%RfW~HBkvkrJe8`k!?!`o_ zfHRi7%u>k5nt6~=7 z=Rs?rWls=_Jhqa_XT9F+AYY;crW8-;iJX)!!z*MtK6sMI{{%D$$2yXuT09M*ZP%Ic zOXZ!bim**CG;Je(Qifb24I>LSIp$c?`L;WW+aXTr^Z7eZgmHRzdi!34G^65!HIw(!i}2mv$y ztzK(htTshsBR)-~IeAtb&g2F99dZW#Q$t?>MbU1gxOgvngu&J_OxIZ{oRdo@eX(x8 z7mhEGGe=Ug>x4XMr%t9Pg_y%5PVL*IS=yl_)-uB*g&UaU<*}7&$`Yj7NkIihHsbS$ z1hFvra>g7WWyo2Yr-_HBHLj~KZ!~j*xju41DV(_hOEjU$H$9G(p8u7yoP-4 z7usELn2cQLhZ2vw_*XR)l~FRPBNij0>ViHBbuu#StJCS5!$$X5Y=| zSv)1;DY`R6x1#96J~^fHC!A{6o<+X=W}|kgJcqsHMCM5jf}N)o}-JF!RDESaA$=CoiSZv*a}*wIfxP!E~pW$x88us6z-EHXo@B*a$ML^ znv!{wV+m=KpA&Hh${jO%LsVM;M}X|4GaICDP%nCCmrW1`qeL;Yh>(b;Q>m}EoeHXf zqB}!S7ma6@dJjdkXHSBHw%5RH@kl=fiPW#?VpvVyZYTY6G2Iq4fcAbS^%zy^FvTN-YD+1;DW&d&3VKt_z(xg`kvKmZ z2hPPFO{ROAdvp<$t&uu~W!_oi)v7bxFKsA7lnU0;fz4yJzQLUN@lP=tT$Ei>I|+24 zE|k~cs`8^bM<87U8hTNL70xzm``9~*NUgRa7oWE=_AIb4CyWtPkuVZF#sqd_!3q^IG9bvBY9ZL&_(} z80IaMC!I!gdHh2PKRGv~-1G3co`;@I1QfWgGj`h0V-V&fEO)_|0oN|&DJBY1QT^$> zC$yJCsX~h(1q&;ZUswBs>GB06yA9|p&$8BO<3u64UWaB2#T3iPuA>U{cpG*>{jhN4 zhjF8^xHO=HoQB@`sG%}tM(*5Xc}h%pxdJ!PNk~e`dN`b>W^ya*fLt4mfW04weAHB| zEyPr=8I@>;o~z)l$JP?{m)OFwu^@Bl+Uk&>Wc0DdQ?&fqj6Ate!bUs@G!g3qSRBq= zwmyg#c1%?BOE#2{d0eUQ_0~5zu6Kq6zedK-gT0%C@ZNxJDzr6*T@*p~ueb*#-?rC> z0b@R%zKx2>h2T0RVW-=`?WKV_uJ*!G>03&3KAqBb>5wV{+^RNR(|NN^3!jZ;t;WsT z-nqoVZz7r5u~n2kIzA3#__CCqima1hi#`m@QB_p-lJM~?RXxVC#`7k($LhfrbgSK2 zTMJ)RX(7w9euMR`!#3T=^_>SaPr4#R(^iCl>f+#PW3ACe!tq9%cXXv+y`ZjS;W1_Y zUIe2Jp1$ydhzPgk^a|qP5k8g};hmv7GTx}Pxz^q=^;i9J)#HsNGzIcZu1X6Ke6;*q z?A6)lL>)9&IER&3sN}!|E1xw*3o7SZwdQE&6O~7>r4aHKLZEAKyd*-FUp}nq{yIAq zO9Fa5T>Vfy4CP`~7Palw8VEVeU?EwDnPJb5fHWb2KGB*0)M!lhSX|p~!3^XtFSK5U zTfQN!Q}VKs^4-x21R3AGy4~+aBK0Z!?r6P(jPGu4_q&l|eG0!jR$aj_qWC)B*zR}3 zfKcdnLqZA~DrnAfC4dU53Zf4D^Oe+k4x%buVqKlrU}Y1MM^PrOlDNhsD=nr~m&kk0 zIVPDlh(n9tIYtpKsp^mwvogx_h6imx$9YCgsTV_|W}IOM4pO{2W!fNRPjHge<`^Ta zG+oeQXV#VOd*A>WX=&gp8q)x&5*#};K&tO4A0S;cWM>9Q_dVqUq|2P_%mC@Wr+t8w z&p!C{P8@FPj+CQ_@D6h!RZC`vMo9HN^co_qa?EW2CtOBkix&oS2~-CHR|-+hI@d|@ zi7v3epu$J`GYV657Ii=>G_vm*A+m_9poa6B#!vVA|{QE$b*Z z36FjEXtHYLGphb%{8>^3NrxG(DR7o$?N1sp5sFRJjtmq^L%;DU%)D`d+&A*Wf?qaxfur(^sdiBo2*$;`N>a%8dcj6rcVAe|WO z#L3%3*nk+dAp69``mU3;G>~b6mxf)z6QnMhb0S$##pXnmOde_VNyJbRNsOR2=+u6M zs5(!Y6dzAiP&gIK8D9`g@iD1I63hLOIu9ASKTf6R{+QHPOx<}_i@`VXjhv=aI-bh5COU$-coG+H(6t*< zc+!MNH;du$`0g~r@xcotRdOtj1G4AuQRVqNSiX*zD2OQ0Nod^(j_$qakP0s3MYj_~ z#o{MiZ3@rLCnk$01QamLMDp^4$Qwulp2qd3`Ag({EtWTe(gf&IPVhf&b^4t*ldt=W9Bgq#z7q>DgT4Q{c5l0=Z~47j02; zNYNnlNvnjJ0!l^8(rveOyzX?&=9H2op_IJL=m^Z`b_cCgfqB}Ir$nVfcWGJ!snAnT zx}7Ij4m79<)PeSRMQ@2T7FjU0v2%-}#+*2a$}VPnGtRBW^mD6VilfKV0D@)G2UW7; zeCXN2M8RJ&+}LNGLOlBThV+4wNQ;PAE0vzeCRoMeMO?&T8-SP6 zN~KqC!uh+>ZmFG)_#ysS+vo~;+J!$?m<@3d4yB-@@meZsVJCfFW> z7A1A`GI(@)Qtb(jUalJhC!^1_YfN-xvp6u1Z9vjM>hhu0lp&_<)ReKZJK&mpUN1R; zNEoM5p9)cNlQ1k>`HqU*(wed`i>td3APRBKN5WRMg8NPg2C+f0V}h!S_-APQ1}1KH z2XoGorC!~V0wQ6lJo#k>^LvKfEp3-%XKj~8xQ7J8Gsarfl9#y z`YY*BxqwCeJBB7G96X5(=r+C|7c*N?PDt~~glsx^l?+}dR?5m|TNdK!?ct+SgQOCr zu$Ub+7PFEBagJ*ps1wEe#uT!5ge}Y7iF(*%cxMgUYsmN)&y*mMZ6*_YL7T_s0m%3e zjAn-0WO~lyIPqXG#u+Npr3l_wDyKJcETV>iI;#U2Vlo|;#1kswn8aP<$Aov5O15o% z_3*vHkekeLkS?RS!(6KFZMVj@v;7DwDdCpX(;dNSc|IOoQW`pGrE!6zJ6^(iRZN0O z+}6{wJWl})cH(z(9Fm4xEVrFbv9acIb*`43oTiwi z^^neqolcQI1YDkxMys>gufT1yUPVy*TD60WiOQQ62h0H@#T2va2Ii2}$$mw)v1wh4 z$y%%xbGl6$8{*lko9EA?%vbK=iR6K$CS&|6cY86`Y7)V^o2tl?z86WfI`QwX6L~y|q?eQYqGC{F2FC;YLnLKxvd>>u6{pJuJLoALt4?NbcpA z=C&i&6_-#Arc>_nv7QEh#vk_9C5s8=m~tg&#iMi$Kyu>es@*kJQz~a>QsvfOdF&$e zd=q1FOmPs_S0)WUn%e)D9XBWqlpgc*<*lqj_&_(p$R}z^PZNjVJd2+CO*|jkh zkD6Zu2I;dLQZi_XrhioYRYwTM-W_^Og411w-0lr%O7Fr3G%)BX@M@W}HKo!zN~H_0rJZUkzwLCmvkD&GA=q> z5JWkz`j|8YDjm;GQlRtQ>?8#`q0Ej_pe!#@^WUTwg35@Ja}WZj7unyOT#%20qy@MR zHk}l+CleMsmA64vZ!0u!gHHt^5%x$3A+o@7JoUr!3U1Sq`$2S*80WdlVr#9wRq1bb zn)SsKXL~v;L>X~T`Y&kWRg#F6zUY#%kOriFK%22}|HA#WKr@VqNCKCgTS30xEjTz_ zFS*z)ao{>>1iz32*Q7$?y=RvMk2*bZd;qn-)TD@tx_qT38&J0HW6I?#o-ZNQMQO@D zfqcPE$!bvBL2ApnI+`0r`+|+MbhDkrBFO`iyKpKOk)MH+I!b^qpE1c1pX9HwDlPE{ z+LN?HfgH-l&Bb~rmP2U;gvEjxXeFl(r;A z@q7bmoMEE$`9-J8`b=QkXU%o?u8u_Lr|jw-8a;`x66MLZ{jUlV__Oc!YQ_f&rcAhDj`-D*06DFN-nk?6XYPSsq7l*(%e&4nu~Gm z9X|Cycxna#@fFnaQ#|fIjZ02tv^sIq(^Doyr7xRoo*RolpE7YgV@>(FIJu%G11o77 zY!O~=iw3K!sCq5qkUAm|1PNZ`+ZdPZmlh+btr6h-$ns>2chA-@K~ulcsdnq|By=_6 zjMS#f)lJ9Hmnuh_&2~-I@sQ*I)0D(OmaABkVbCd8u**E9a&r&NIZPN>{RpVE9#!$J zS@;PR3kgH-5`z^}D*$p5yDJnKJcz6ZShi*yqLRIDmmY8JeVx9VtJ*kIQra$ORnY~K$#vhgUAL6+4 zR=?V4!Iw|DkvMB73X;jzcA-#XjOK#Mlk!0ePn=M{irwj|N&wha*0(C-cG2cuOWf{8Zu}p{W z`lKI~F&>=$P?k$8#X}4$76tRynD%r+{b5@S9{A~oS?vU>A{$~JyTx?8R#&kikS;i_ ze<5vaxA>^%0{V%2Ef>;P*;W@wiwo8^8SOEc`izn%tyiJfM5jG3mYzI8!^0TEpsVJ1 z-VxL3s@B-I=>UA2v>j6g4o4>^(-=@pY+!msZo(2!P)wkj(;{O6g_lVV0UcvSy06$Y z*P=moxXsCFko7!Gl!<wK@cfyIR4ggYuQenBv)-z~&+k&FE&>Ze23)*i z^h~gXio?DM+cNhqUaEoKLX;hl`O`=O_dUZxV^8i<{%L6%228v+JF`^-R|IA+Tt5J3ZaFukC4A_a%A>52r)*`O7CyB!7i?i^4f zz;h5A0tCakNCx=`)t*)xsDaiIWEsJ3(U`$O!EWK`w9s3L4~xgT>C2fXRfW3c7{LV_ zyr|1>4H~<2HZbgQ7`8AHD}iFhmIjTm6Ok#R+imED8dZMaRi#^Btv0K|+|MHdSH(w*ExAd{&&Lp7A9i=-3t*AJS!F2# ztvDHc6moS=B{A9=s#I7F)L1x#QsZjoX6svsu5RI|b^6$LcM(}nW-EuRJN^DhV7(mB zxk?QN?>>lfmDrop$f&e{WD z4|6-?W{#*{IF>H@>V$c6c4BIAlhYCDCQlr;0=QkoM|Bng@H5#8&^gK^ja^2C{c!s< z1q8?9HLj9@#Lp4jD7_#pn?9;mD_ZwClFuzEMV-fZ%q&p2QgkGOt+v!r8y#GwEuEAf zwpXJ@{o8k%B(F+z?htV^?X*oQk*s{xliOzUGwW&cwK2xLm$MvcX{$Qj^tiWb7Cm++ zI%1N`-Z*iKp&{zTo%X07a)dk@k!feYktlM$FN3K+>B#pgQYXQTa@@*!Hhs>z&V@Mp zkUg3&G)v;VB~Z(TW+i#SP934p#HBdu(&kL_)!i|cn@+Xe!vCpArX6XWKzH(5HH7<* zQQ_+3GKF3Eg76RfCeC8ZuJghOLQZnmNw1X7N;jLK{pX%1S+DWGlvs>{{-g+@!O z;HQEnVGYB2+%AcmV44QbI^s^jcti4BL4vzhV`Fnec@I09l=HLF7;1^h{jh^eR}ov? z8cgh%1?rgFhccysH#!xTiklY4Q98h#oHuUCGdFSeoSsI?=r~rM%#z)5Sk+QcjjAoP z^ZFW4RGaD`(ru1gGp5hqNmQWnHY(%FgJo`Ajm790HRCDi9ik@_V!$TAb#!9SqBy2aQ!MFGhUM#F(`;&n730X4K17+973~%l z@u~2(09Bm?a*4|UMNkwrL&GF)eWa|t9-Uz=k~32=ACJ|vC^?iMWk_ux zxGxOY;}AlWJyLBC84+@EP#z8i(FMOBY#s4fP>~RZmc36?|ie3Dg@dOV_W1M7zcTWSKIAoz1kXuu=#Ni zRyt_LtTxy~#*;zgP#; zY#6l7wLU(5n|nEZB$pI57EaGS-mys6lagRCAtm4dT4*A)q%whtP^v%K@Nu8iVW&E* zP>8s-I?pDEI1mfcG;2alG=|28tPLOhA{sHL;KM%9uHot9S|bIT4HFFvPEAFNsK$g> z2FxYFm!K76rP$0yeM2Hjlmg*^va$qT8(NDHmcxVvK!<>!3>#!3UQ#iYSt(Ckn^&0) zOA~H2vnbu?A#TVJk5BJC!UHmP|DK)?LgOAls$=W zxhRB@P(1W-DrMZg+Xut$vGB}_FP(CigqXkme4N;IQy_(4YJJkPT657HUE*yK4|u_P zHaUJX6tLLOZu@I1N0QTFGehlZBh*J!dqVZ?qdKjM7}Q52vX+M4IbrsC>`bj0ZMxQ6 z>!Z4i=DV^S;*1@+SQWns#$**lm!#SKF-OOZ1Dc!@tZe7JXcIzgvd{F}DxD@0xFpC3n{n81fjS6TL5k@mz*AUlrp1Ke^=8WJMUPBO zZze5fE6&0}WJM~@^szn`Mlu~&c~wl>1=%XUL4 zisU?m347>05tN**9sH;YW_I~Lg1!M5thfZV{nI0bnuys3V!h0y^4LNZ-&hoIh^5vj z3Nkde;4{j5LjT5$488{bw-65s&SLsCu+YH@`m{l2Nm^0G){R<)T%HV+1ny`*# zTa)W>)_UPj=nJ8ZeX6q7=ylpAVbJ6TS9e`UW`*{pNq@D7s3KUbA$9{fB}9ABKP*X_ zDvj3pHf~~P$oJVfKQvmM&3*+0*Q*=Ii&gE&t0@o9>4(izl}@+aZ`ayQJYK17RV$s% zZoP7`(d}OjUb5l@Ls=HPyk& z#2AcxPLwRkV3tA?Jn!()s>FM59W?~tn^Cn-g;UT!JRB$ts#)G%&yT}j?87+ZF6YyR&x@)c^4A1P78c3*m#>wU)R~%{k7UFULk)Tls`Az zzb@49n*3E#O&EObmCZ(LbL*8`3kP3$@PL4GGnM*UwO_^2x(e3X>sPydK&>@-O`TQX zVJ+mb@8ap1Cm(;jvL8J^d1~>o$18KM+VB2#!2N6PRrC7aXV2o<1F^qK`)3|Md-mvK zia(<9hz-V+gs(BY2D_Ff=~LEe9vzkeA54({;%j4h1yPk zci3Na>-lNjwXLt>Z{U9mwS)dn`=S@8P!ex1?fo#GZzXKEkLb*XDPS}{FWK+sqdj}X zb&tvKi&Mf>7XR<;@67HmNqh47JkKQG8V1SkA^z+3zBk@VKKh=Y*m3r~VcHw!S=+w+ z@38NsQ_HOm^Y1hYLiixr;w33OC{9nP?f5gz! z#A7+*^EAMZKdi~l?MuKX57 zlYH`}ih}4IL_u~I8ec+|HnVEU%%$Ea`|KyDo`u$~%#@o)!yv@wly5FBavuJ*w#q%#$ zzrSSW+YGk(ZjBeu;9G9G=btz8j^?+$cwYbh0{Z<$Gw*AD$ImmL^XVT9LS8)cV+N}| z`YQarYvv8+?>BwVvu`znas4gx_v_W~FPeFa`Fl7Oxw?={bh_Tl`CG%51`-~A6wk3fv?qv!u!4~=^13O&#G zEIt1})sy=FyZ7(Dea4T_^KBu@{CAx2579%Zepw>?EA7Mh-?N0z(RZCA{FAAjb;AG8 z3w%Rcg#Q5HmkIxCYX1u3ME_O7Kb-o#PWV?4zV{dP_(&6s-=OD@nWZDt-u(stJmdFM z@H^B_o#>pQd1U-9J^$N&cxE2ZJTiWdo_~_Y=RVAzAJOwyX#KC#^Ni0>zwb!T?;-pnXgqJzcrw1r>{l5w#`jV?ze3}=m*`=9 zA3eWC>-h-bFQIlG5^~?Ac zJ^z(7{#OZqj>h3Q?K{TLrQlmB_|+8rMhd>0=9l}uMs&WB=)6w&0*&V_!at0@_fZOd zkMIQ={|AKs2KI%V`|{szqMz}F6nrTKUr)g=r{LF8@S7?4ofP~beOI5pcZQz-c8r_- z-hCEW7N)V!at1qy-WBk(SMKdFQRrH5dJrW-!S`E#{Fe()MjRG8vGq%_#N8spHI)< zCH(6Nzfbs2626Q0OoQm(P53tuzK8H1Cww2_zfSl9;eSr}5rfP3&eHfFH@JK+<4e@e z0zH40@YfQ4j__5&JB0sF!nX+je!?#kexLAbgnuQ?<8{K{NcauHUqb8uCgFDozeV_; z5`LTTUBvh95MCnu9^p3$zfbs26aIkk!?eyH68<#dj|l%Vw#i9=^* zZV~=c!fzA)c7)#{{Ot+9OSt9D^7B2yWzX3E?i2oU3*vtt5dO}DKO}q);g1M^SHfrR z`gweJ!gmq=9)#~E{5=WZL->0UzL)U5gzqE#eF&c={Cx>uApHFZKSKEX6MmfV4EaBG(|2V?0 z6aG5FZxH_Rgx@6m69~UW_$Ly6oA6H}{0`w2!tWA(j_`YguM&Qr@EYL{2wx-oA>nnx z9}#|@@R?ur>;D4by9jR(zMJr82;W0^lkmNSZxFtZ@D|~-gtrM_AiP8P5yD?j_;JF! zgf9`^Bm6AkeZtQXev$BX!k;6&L--cqTZCUC{4(L6O!yVT{|n()3I7zruMz&KgkLB8 z(+Iyo_+`Rx68;&4-y-}o3BOJFXAypf@XsdvF5#a;_&vfum+Twj@V$h8IpO;Vzee~h;a^So0^$Fi@FRqO z4dKTL|60PA2>&|5&l3I(gr6h)I^pYtecMwzeV`}ApADr-$VEv!oQF3yM%u~;r9st0mAPS{)2=+ zApD02e@OT(!XFX-!-UWLnqU7vLijGif0Xdug#Q@fdkFst!uJw>oA7;v{}kb~g#R?* z3xxj+;YSGn--I70{AUSYBK+qFKTG)kA^aTScL-l6{O1Yp5dH?jw+MeD;g<>jCBm-| z{>y}4CHyYo*9iX=!mktltAyVm{7r=4B>dM2zeV_O5PqBR-z5AF;r9r?OZaaQevk0q zA^bk!zf1T7!hetOhlIbG@JEFIKH)RJ?$`f)!gmq=2ZZk?{0|A=L--#NzL)T~5WbJ_ zKPG&Z@INJdf$#@}A0hnD2tQ8vpA)`B_+JoymhitK{2bwbP53(D|CjI%;SUMlBK&U% zzfAbw5`Km7za#uA;eSu~HNyXa@au&CBjGm)|0lw468?zrTZI2J;kOC@7sBrl{%?fe zCH&tBzeo6g5PqLf{5=W3LHK(Sev|O`A^aBM?@Rb? z!rzbZJA}VK;dcrDK*H}4{y~J_Cww2_4+#HY!XFa;A%s67{1t@H{H9<3uOxgI;jbcm zH{o-H?;(6Y;d==`K=?kw=Lw%B{2<{AgdZaO2;qkbKTi0E6TU?FM-YCN@Q)(RFX9@o#!WRgy5PpR4D&fZoUnP8r@EYM~2|rKxIl?ax zzE1c$;T^)CA$*JQX9>Sdc$4regl`ajmGBnf*9h+rex2~w6Mlp6F5x!`?-72B@J+&R z6Mm8KJA^++_+7%c2){@8CBp9${>g+tApE}&{*drbA^Z{HpGx@5Z~68A>4fhh{4(LY z3I7bj_YnS>gzqK%vkBiv_~#HlOZevzzCidD!jBOC`Gg-Q{J#>uMEDmFewOerB>WuV zUqtvi;a^O6hwv{Ue2efeBm6SqUrzWH!oPy>tAu|g;nxWND#EW5evR-Ognu>RHwph5 z!fz4&wS?a${Obt6L-^MdewXlXBK#iV-%R*@!oQX92ZVnc;SUM_cETSKeuMCt-}dYO zI|$!J_;(V%oAB=%(vZxa46|9Qgi5dH?j?-KqCgx@3l7YV;l_!|j-K=>~a{*drrCj1fM zcL|^Q9l!p6mGE7JzlreOg#Q}hdkFtc!uJw>kMMnj|2E;Xg#QlV3xxkJ;YSGnJ;IL@ z{$|3L2)|GGS;GH-@Nr$U{}JIG!rwyp7U6$F_+`TXl<+HrKOp=n;eST>HNyXr z@au&CHQ_f1{~N+@68^V@-y;0)2)|AE-xGd^@P8ouF5&-3_&vh^iSYY`KO+1A;r~qd zL&E=s@JEFIE8#Q0>(~Fk5x$G?eNiB;{Mx9`11(gNBHvzpC$YSgf9^O zLc)&_{vyJU6aLnOFA=_r@Uw)!nDBFizl890!rzAQ4&g5)e2eh6CHyksZ%6nQ!rz|o ztAy_+{2Jl!K=^gSUq<*1!e37KO~T)a@LPnx3*om3-$VEv!rztfyM(_R;r9rCcf#)z z{+@(CApE@ue@OUy6aI+sy@b#Fo?rj(L-;Pj-`Sn zgi9ot{cnNr4XJluOj>$;U&V?37;dpL->Bew+Npn{4(K( z2){!3VZyHx{^5jQBm5%>zfSl^5`Kg5k0Sgg;U7)-Ey6#B@Y{qRA^Z;EM+v`6_~V4% zBm5ZQ_X#f({($f&2!BZUlY~Da{5at=Z}#hdk?>uFpCEiU;im}SL-=XJ_Y(dz;rj?* zB7BzcR};QK_!+{F5dIp%j}!i}gf9{PafF{G{B?w%BmCnDUnl$%3GWbIA$*JQD&dz2 zuMvKQ@HN7(5`LcWYlL4Q{5s+5gx?_i8NzQ8{w(3Q2yYU8oA3?7?-1T1{4U{b!tW8@ zCHy|&J;EOl-Y5Ja;hTg%BK#uZGr#ZG|8s=zBK#8Jy9xgk!uJsVX@u`3{L=~FNBCzD zK1=v#623tAXAypc@XsdvIN_f|_!8ltOZZvBKcDb(gnt3y>x6$H;T^)ii101KznJjL zg#S0fuMmEf@T-J>3E|fW|5C!Q6aHm{-yr)incN6|Cgzq8zTM6Gw z__qON9Rh;b#f|O~TI+evj~V z!hf6a4≦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ⅇmUZxf@@B({dMX!YR2EFThE;124o0xg9UU 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

KC>mZWF_sowM$DIw+1+Lx;X5D4(tY;UGMedhQ_` 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 H^z;88J0#7R literal 0 HcmV?d00001 diff --git a/third_party/prebuild/x86_64/libslog.so b/third_party/prebuild/x86_64/libslog.so index b476618dc715be24fd75bd5c89a168b0dd9c60a1..01b75e405aa1eb9d1a221806aa5c7355a1c40f7e 100755 GIT binary patch literal 103704 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 RTmDCPO3Hsygbc0a|1VuhErtL9 literal 89440 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