From d61a3bc83f29febb3c808e69ffb5fe819a60bf31 Mon Sep 17 00:00:00 2001 From: HailoRT-Automation <98901220+HailoRT-Automation@users.noreply.github.com> Date: Wed, 28 Sep 2022 22:49:02 +0300 Subject: [PATCH] v4.10.0 v4.10.0 --- .hailort.png | Bin 146846 -> 1454787 bytes CMakeLists.txt | 8 +- README.md | 2 + common/config_definitions.json | 3 +- common/config_schema.json | 2 +- common/include/context_switch_defs.h | 75 +- common/include/control_protocol.h | 86 +- common/include/d2h_events.h | 4 +- common/include/firmware_status.h | 27 +- common/include/md5.h | 3 +- common/include/user_config_common.h | 13 + common/include/utils.h | 14 + hailort/CMakeLists.txt | 43 +- hailort/LICENSE-3RD-PARTY.md | 7 +- .../cmake/common_compiler_options.cmake | 2 +- .../cmake/execute_cmake.cmake | 0 hailort/common/async_thread.hpp | 8 +- hailort/common/circular_buffer.hpp | 7 +- hailort/common/filesystem.hpp | 35 + hailort/common/os/posix/filesystem.cpp | 128 ++ hailort/common/os/posix/socket.cpp | 4 +- hailort/common/os/windows/ethernet_utils.cpp | 2 +- hailort/common/os/windows/filesystem.cpp | 16 + hailort/common/os/windows/socket.cpp | 4 +- hailort/common/string_utils.cpp | 14 + hailort/common/string_utils.hpp | 2 + hailort/common/utils.hpp | 62 + hailort/drivers/common/hailo_ioctl_common.h | 106 +- hailort/drivers/win/include/Public.h | 9 +- hailort/hailort_service/CMakeLists.txt | 47 + hailort/hailort_service/hailort.service | 13 + .../hailort_service/hailort_rpc_service.cpp | 744 ++++++++ .../hailort_service/hailort_rpc_service.hpp | 125 ++ hailort/hailort_service/hailort_service.cpp | 60 + .../service_resource_manager.hpp | 113 ++ hailort/hailortcli/CMakeLists.txt | 15 +- hailort/hailortcli/benchmark_command.cpp | 47 +- hailort/hailortcli/benchmark_command.hpp | 6 +- hailort/hailortcli/command.cpp | 31 +- hailort/hailortcli/command.hpp | 2 +- hailort/hailortcli/common.hpp | 28 +- .../download_action_list_command.cpp | 155 +- .../download_action_list_command.hpp | 54 +- hailort/hailortcli/fw_config_serializer.cpp | 13 +- hailort/hailortcli/fw_control_command.cpp | 19 +- hailort/hailortcli/graph_printer.cpp | 67 +- hailort/hailortcli/graph_printer.hpp | 8 +- hailort/hailortcli/hailortcli.cpp | 209 +- hailort/hailortcli/hailortcli.hpp | 43 +- hailort/hailortcli/infer_stats_printer.cpp | 330 ++-- hailort/hailortcli/infer_stats_printer.hpp | 10 +- hailort/hailortcli/inference_progress.cpp | 54 +- hailort/hailortcli/inference_progress.hpp | 20 +- hailort/hailortcli/inference_result.hpp | 88 +- hailort/hailortcli/mon_command.cpp | 165 ++ hailort/hailortcli/mon_command.hpp | 41 + hailort/hailortcli/run_command.cpp | 702 ++++--- hailort/hailortcli/run_command.hpp | 7 +- hailort/hailortcli/scan_command.cpp | 78 +- hailort/hailortcli/scan_command.hpp | 5 +- hailort/libhailort/CMakeLists.txt | 21 +- .../bindings/gstreamer/CMakeLists.txt | 2 +- .../bindings/gstreamer/gst-hailo/common.hpp | 10 +- .../gstreamer/gst-hailo/gsthailonet.cpp | 4 +- .../gstreamer/gst-hailo/gsthailosend.cpp | 27 +- .../gst-hailo/network_group_handle.cpp | 268 +-- .../gst-hailo/network_group_handle.hpp | 52 +- .../examples/hef_infer_pipeline_vstream.py | 4 +- .../platform/hailo_platform/__init__.py | 7 +- .../pyhailort/control_object.py | 703 +------ .../pyhailort/hailo_control_protocol.py | 323 +--- .../hailo_platform/pyhailort/hw_object.py | 8 +- .../pyhailort/power_measurement.py | 17 +- .../hailo_platform/pyhailort/pyhailort.py | 1160 +++++++++++- .../tools/hailocli/config_definitions.json | 3 +- .../tools/hailocli/hailocli_commands.py | 67 +- .../hailo_platform/tools/hailocli/main.py | 21 +- .../notebooks/HRT_0_Inference_Tutorial.ipynb | 2 +- .../bindings/python/platform/setup.py | 2 +- .../bindings/python/src/CMakeLists.txt | 1 + .../bindings/python/src/device_api.cpp | 35 +- .../bindings/python/src/device_api.hpp | 12 +- .../bindings/python/src/hef_api.cpp | 2 +- .../python/src/internal/CMakeLists.txt | 12 +- .../src/internal/pyhailort_internal.cpp | 12 +- .../bindings/python/src/pyhailort.cpp | 297 +-- .../bindings/python/src/quantization_api.cpp | 251 +++ .../bindings/python/src/quantization_api.hpp | 56 + .../bindings/python/src/vdevice_api.hpp | 44 +- .../bindings/python/src/vstream_api.cpp | 6 +- .../cmake/toolchains/linux.armv7l.cmake | 2 +- .../cmake/toolchains/linux.armv7lhf.cmake | 2 +- .../cmake/toolchains/qnx.aarch64.cmake | 8 +- .../cmake/toolchains/qnx.x86_64.cmake | 8 +- hailort/libhailort/examples/CMakeLists.txt | 2 +- hailort/libhailort/examples/README.md | 6 +- .../examples/c/infer_pipeline_example.c | 2 +- .../examples/c/multi_device_example.c | 22 +- .../examples/c/power_measurement_example.c | 14 +- .../examples/c/raw_streams_example.c | 16 +- .../c/switch_network_groups_example.c | 2 +- ...ingle_io_network_groups_manually_example.c | 2 +- .../libhailort/examples/cpp/CMakeLists.txt | 4 + .../examples/cpp/infer_pipeline_example.cpp | 4 +- .../examples/cpp/multi_device_example.cpp | 16 +- .../cpp/multi_network_vstream_example.cpp | 4 +- .../examples/cpp/multi_process_example.cpp | 177 ++ .../examples/cpp/multi_process_example.sh | 71 + .../examples/cpp/raw_streams_example.cpp | 15 +- .../cpp/switch_network_groups_example.cpp | 2 +- ...switch_network_groups_manually_example.cpp | 2 +- hailort/libhailort/hef.proto | 44 +- hailort/libhailort/include/hailo/buffer.hpp | 12 +- hailort/libhailort/include/hailo/device.hpp | 59 +- hailort/libhailort/include/hailo/expected.hpp | 4 +- hailort/libhailort/include/hailo/hailort.h | 168 +- .../include/hailo/hailort_common.hpp | 19 +- hailort/libhailort/include/hailo/hef.hpp | 14 + .../include/hailo/inference_pipeline.hpp | 15 +- .../include/hailo/network_group.hpp | 27 +- hailort/libhailort/include/hailo/stream.hpp | 12 +- hailort/libhailort/include/hailo/vdevice.hpp | 23 +- hailort/libhailort/include/hailo/vstream.hpp | 241 ++- hailort/libhailort/scheduler_mon.proto | 32 + hailort/libhailort/src/CMakeLists.txt | 19 +- hailort/libhailort/src/buffer.cpp | 33 +- hailort/libhailort/src/channel_allocator.cpp | 102 + hailort/libhailort/src/channel_allocator.hpp | 52 + hailort/libhailort/src/config_buffer.cpp | 43 +- hailort/libhailort/src/config_buffer.hpp | 13 +- .../active_network_group_holder.hpp | 2 +- .../src/context_switch/config_manager.hpp | 3 +- .../src/context_switch/hcp_config_manager.cpp | 89 +- .../hcp_config_network_group.cpp | 13 +- .../src/context_switch/hef_metadata.cpp | 191 +- .../src/context_switch/hef_metadata.hpp | 96 +- .../multi_context/resource_manager.hpp | 181 +- .../vdma_config_activated_network_group.hpp | 4 +- .../multi_context/vdma_config_manager.hpp | 19 +- .../vdma_config_network_group.hpp | 51 +- .../src/context_switch/network_group.cpp | 105 +- .../context_switch/network_group_internal.hpp | 114 +- .../context_switch/network_group_wrapper.cpp | 24 + .../context_switch/network_group_wrapper.hpp | 7 + .../context_switch/pipeline_multiplexer.cpp | 306 ++- .../context_switch/pipeline_multiplexer.hpp | 79 +- .../src/context_switch/resource_manager.cpp | 407 ++-- .../hcp_config_activated_network_group.hpp | 5 + .../single_context/hcp_config_manager.hpp | 3 +- .../hcp_config_network_group.hpp | 4 +- .../vdma_config_activated_network_group.cpp | 20 +- .../context_switch/vdma_config_manager.cpp | 308 ++- .../vdma_config_network_group.cpp | 139 +- hailort/libhailort/src/control.cpp | 95 +- hailort/libhailort/src/control.hpp | 28 +- hailort/libhailort/src/control_protocol.cpp | 79 +- hailort/libhailort/src/control_protocol.hpp | 8 +- hailort/libhailort/src/core_device.cpp | 20 +- hailort/libhailort/src/core_device.hpp | 12 +- hailort/libhailort/src/d2h_events_parser.cpp | 39 +- hailort/libhailort/src/ddr_channels_pair.cpp | 131 ++ hailort/libhailort/src/ddr_channels_pair.hpp | 65 + hailort/libhailort/src/device.cpp | 89 +- hailort/libhailort/src/device_internal.cpp | 19 +- hailort/libhailort/src/device_internal.hpp | 13 +- hailort/libhailort/src/eth_device.cpp | 53 +- hailort/libhailort/src/eth_device.hpp | 1 + hailort/libhailort/src/eth_stream.cpp | 11 +- hailort/libhailort/src/hailort.cpp | 108 +- hailort/libhailort/src/hailort_common.cpp | 26 + hailort/libhailort/src/hailort_defaults.hpp | 27 +- hailort/libhailort/src/hailort_logger.cpp | 26 +- hailort/libhailort/src/hailort_rpc_client.cpp | 801 ++++++++ hailort/libhailort/src/hailort_rpc_client.hpp | 95 + hailort/libhailort/src/hef.cpp | 1682 ++++++++++------- hailort/libhailort/src/hef_internal.hpp | 228 ++- hailort/libhailort/src/hw_consts.hpp | 8 - hailort/libhailort/src/inference_pipeline.cpp | 68 +- ...te_buffer.cpp => inter_context_buffer.cpp} | 163 +- .../libhailort/src/inter_context_buffer.hpp | 52 + .../libhailort/src/intermediate_buffer.hpp | 67 - hailort/libhailort/src/layer_info.hpp | 80 +- .../libhailort/src/network_group_client.cpp | 275 +++ .../src/network_group_scheduler.cpp | 830 ++++++-- .../src/network_group_scheduler.hpp | 202 +- hailort/libhailort/src/os/hailort_driver.hpp | 72 +- .../src/os/posix/hailort_driver.cpp | 343 +++- .../libhailort/src/os/posix/mmap_buffer.cpp | 26 + hailort/libhailort/src/os/posix/qnx/event.cpp | 10 +- .../src/os/windows/hailort_driver.cpp | 134 +- hailort/libhailort/src/pcie_device.cpp | 48 +- hailort/libhailort/src/pcie_device.hpp | 11 +- hailort/libhailort/src/pcie_stream.hpp | 4 - hailort/libhailort/src/pipeline.cpp | 125 +- hailort/libhailort/src/pipeline.hpp | 62 + hailort/libhailort/src/rpc_client_utils.hpp | 82 + hailort/libhailort/src/scheduler_mon.hpp | 59 + .../src/shared_resource_manager.hpp | 123 ++ hailort/libhailort/src/stream.cpp | 32 +- hailort/libhailort/src/stream_internal.cpp | 14 + hailort/libhailort/src/stream_internal.hpp | 76 +- hailort/libhailort/src/thread_safe_map.hpp | 10 + hailort/libhailort/src/thread_safe_queue.hpp | 70 +- hailort/libhailort/src/transform.cpp | 77 +- hailort/libhailort/src/vdevice.cpp | 401 +++- hailort/libhailort/src/vdevice_internal.hpp | 90 +- hailort/libhailort/src/vdevice_stream.cpp | 193 +- hailort/libhailort/src/vdevice_stream.hpp | 94 +- .../libhailort/src/vdevice_stream_wrapper.cpp | 371 ++++ .../libhailort/src/vdevice_stream_wrapper.hpp | 130 ++ hailort/libhailort/src/vdma/channel_id.hpp | 56 + .../libhailort/src/vdma/continuous_buffer.hpp | 7 - hailort/libhailort/src/vdma/mapped_buffer.cpp | 43 +- hailort/libhailort/src/vdma/mapped_buffer.hpp | 11 +- hailort/libhailort/src/vdma/sg_buffer.cpp | 10 + hailort/libhailort/src/vdma/sg_buffer.hpp | 9 +- hailort/libhailort/src/vdma/vdma_buffer.cpp | 32 + hailort/libhailort/src/vdma/vdma_buffer.hpp | 7 +- .../src/vdma/vdma_mapped_buffer_impl.cpp | 108 ++ .../src/vdma/vdma_mapped_buffer_impl.hpp | 116 ++ hailort/libhailort/src/vdma_channel.cpp | 447 +++-- hailort/libhailort/src/vdma_channel.hpp | 46 +- hailort/libhailort/src/vdma_channel_regs.hpp | 12 +- .../libhailort/src/vdma_descriptor_list.hpp | 2 - hailort/libhailort/src/vdma_device.cpp | 40 +- hailort/libhailort/src/vdma_device.hpp | 4 +- hailort/libhailort/src/vdma_stream.cpp | 119 +- hailort/libhailort/src/vdma_stream.hpp | 21 + hailort/libhailort/src/vstream.cpp | 837 ++++++-- hailort/libhailort/src/vstream_internal.hpp | 267 ++- hailort/pre_build/CMakeLists.txt | 10 +- hailort/pre_build/external/CMakeLists.txt | 8 +- hailort/pre_build/tools/CMakeLists.txt | 20 +- hailort/rpc/CMakeLists.txt | 33 + hailort/rpc/hailort_rpc.proto | 410 ++++ hailort/rpc/rpc_definitions.hpp | 23 + hailort/scripts/download_hefs.cmd | 2 +- hailort/scripts/download_hefs.sh | 4 +- 238 files changed, 16031 insertions(+), 5628 deletions(-) rename hailort/{libhailort => }/cmake/common_compiler_options.cmake (91%) rename hailort/{libhailort => }/cmake/execute_cmake.cmake (100%) create mode 100644 hailort/hailort_service/CMakeLists.txt create mode 100644 hailort/hailort_service/hailort.service create mode 100644 hailort/hailort_service/hailort_rpc_service.cpp create mode 100644 hailort/hailort_service/hailort_rpc_service.hpp create mode 100644 hailort/hailort_service/hailort_service.cpp create mode 100644 hailort/hailort_service/service_resource_manager.hpp create mode 100644 hailort/hailortcli/mon_command.cpp create mode 100644 hailort/hailortcli/mon_command.hpp create mode 100644 hailort/libhailort/bindings/python/src/quantization_api.cpp create mode 100644 hailort/libhailort/bindings/python/src/quantization_api.hpp create mode 100644 hailort/libhailort/examples/cpp/multi_process_example.cpp create mode 100755 hailort/libhailort/examples/cpp/multi_process_example.sh create mode 100644 hailort/libhailort/scheduler_mon.proto create mode 100644 hailort/libhailort/src/channel_allocator.cpp create mode 100644 hailort/libhailort/src/channel_allocator.hpp create mode 100644 hailort/libhailort/src/ddr_channels_pair.cpp create mode 100644 hailort/libhailort/src/ddr_channels_pair.hpp create mode 100644 hailort/libhailort/src/hailort_rpc_client.cpp create mode 100644 hailort/libhailort/src/hailort_rpc_client.hpp rename hailort/libhailort/src/{intermediate_buffer.cpp => inter_context_buffer.cpp} (53%) create mode 100644 hailort/libhailort/src/inter_context_buffer.hpp delete mode 100644 hailort/libhailort/src/intermediate_buffer.hpp create mode 100644 hailort/libhailort/src/network_group_client.cpp create mode 100644 hailort/libhailort/src/rpc_client_utils.hpp create mode 100644 hailort/libhailort/src/scheduler_mon.hpp create mode 100644 hailort/libhailort/src/shared_resource_manager.hpp create mode 100644 hailort/libhailort/src/vdevice_stream_wrapper.cpp create mode 100644 hailort/libhailort/src/vdevice_stream_wrapper.hpp create mode 100644 hailort/libhailort/src/vdma/channel_id.hpp create mode 100644 hailort/libhailort/src/vdma/vdma_buffer.cpp create mode 100644 hailort/libhailort/src/vdma/vdma_mapped_buffer_impl.cpp create mode 100644 hailort/libhailort/src/vdma/vdma_mapped_buffer_impl.hpp create mode 100644 hailort/rpc/CMakeLists.txt create mode 100644 hailort/rpc/hailort_rpc.proto create mode 100644 hailort/rpc/rpc_definitions.hpp diff --git a/.hailort.png b/.hailort.png index fdc84976f3453aa8686afc64e88078f775d43716..c9adb26f806db2cbc4bed441e8260ddca53eb6c6 100644 GIT binary patch literal 1454787 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 lA^=heEk$BzjSiB{J$JZ9sC}f9b)cO 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%0D+g8mW8 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�!_H(wYPMU)~6(uq=~iFBzE=}meUklu-O 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

Fa8|LgopX5~)_E;+MYuAam^1xU#6x#7{`u z__dMuI-gjaj`W&?He-F>RxE$)BI7f1xZm24c|SfkiiG_N*-?ehrNp34|H~aiH|C?G zcf@Z1M*;gyyJ3}F8RutA%KD30dbY`P6*HF~P?|1{RHEn338*@8-JziYEqY$>gO-D; zq?E|yM+O0*e0518uLgH(A~rdcbuX1GvBkM%A(~_-Jg6qk7{cCeXiV`Kh~MOWLB;bV zA+|`?d}etwuw%=)WomhGe#BWIqyW#oW(QNw$+P#6laot_pdeSkUDT;i5S6aw+Lp8I zN^h5@qp7PU4~SXxIf;StyHnJg(6V>Y7OXa2-?SCl+t=5FcH9w?SE7CEe$T{6u0Q9g zaFb7Y%x)s>S$t2Kb`Q38h&Tk*E)NYzZB4$s@wC>RKcqF;AVR=kbp)wIm#7d~+}>u7 zX#bIcP4JfOpU=p!^wQItu*7+@sBpP4Mt!mA%h!6WgZq^7$l3zUlf-kERl>%Z=b_Ti z++7@F-CktPs-bIt^F0vQ?Kq}n8*!gQut&h~O68*xs zl9{aomv2r&^v>m)t6>VNpLl$@US|5wh$=VAng+NN$(!lLSJj&nr;8XH#WzH{k? zFCX_c+np56Pgsc>TrSdKi-=Fx9~Me@iNH7hBy#c>CM*}<`b<&Ec!})$Y?)4@bO)uEZHTtKkk?f8m+tUPd3o=LFTN>YjsUBXV9%s2N+}+o?w{)11$&vELbMYPUrLo=P0 z7H#&e#B6CrrrU2y9Cc)~%UBcqjSpiHhtty;h0~{3qeb`>W1K~KEaR!cqULjZ3R?-^_dh5F0liBl|-{&$ytXAty^4>tN)wP;w4K-YyPNZWMYN94pLp@f_ zji!Vd`5deDWp%UlT2;>X1pm|#_PY?Uc?I>CoMpe|)D~9cqQ7Z}u~Q@D(RJ`^6%92~ zjQo8x>c#cTEryz3OZJw2z$^sbv-~@`j=!N_|BvRL|MTJhc5D7u63G9qKKcJ`jqY}U z#-R!w65PZbUqXWJcz|yEsT24uTlySa@Gj@YM6~lg>Y35c<@4xYg)t+r9|Kxju~C+O zkYp|Sd@g-bTki$jl8eDv&8T~HP}_xGQ~KfxNF5?p|MIg?McbVZFS=Imr?0k?_-<(`e!+bC!!Yady~^C|@fs=061ESlfwx>U zGhx-OsQ_d4Ox@5H+~zL`P;p3!H_~wqgJAL5gJGE~>R8c08GfT6$rgP}LVv1mrfLb} z+c{X1Y9294{9ogjQvfUseFEi2rh|u>35n1%jTT#i05lhTl0b}yBYWx}gSr4N8~{ba zKmH-<8_oPdKmsV{l?uSWZ^@M*9Mcx0RKJN_*jhX^J}bk(Q8;+PMb#paeA{TW>iY$f zhR_%0EE!;RUShCXCqm-!a{$uj1A3Ky;*;K*U|-f%)IDUdBQ4no+OS0X=LJbXi%d^O zfe5}CjubrX!?+T9H-*6-cqv>L26p7ohwupi2a;nKnNBZ6?E}T$;G>8F-i=+*jn+wH za;cv=u$E1!bzqywZn=>nFAI}!{=1$-y z7U_t&kx?lwljU06;NzTN{d*&-;vP@A$ml&6E_vZPSsj*ZPfZ4mTYhla7z?zVQiQ1V z?+fS0lKGyH{vi<}?(PZTu(xks>;?`I_U1|uGsDc*vZD{=h&t*r9wQ7P9LkDX+xqdl zN892j)%Rx>0#N5tZYgv2ei%T;XTk~lx5w-KS9_EZ}gU>>iK6Qc)@LKGT&6 z4jeIw@^+=%g<0v=c7<;H6lH#9CHL;$`xPfBa6kAu8P$0oS~UP~aKLnPVAftWA!f($MoUBQ%jCkr zHIBrmkMe!#3{>ntCa{0gzRyqDADU?Q>YHj_5P(&FT!2bI!;EthmcV2AJSj+P-*ng&V83=T#YH^%{TkfV4}R>RU~N5AMio(aX)ZAF&3#xsWoZ1G>;s{ ztx&&na`3PDI{bf`Un2vm1TwJ9<@|B_pt8=L<*a#u6m%dRa(^%gfI z5R8rjM)YHRbwN@^hcX6X;}ldk+-lj#LhbFXcl3!xo?GMa)W+jai!Q)Ye{I=PSW=&G;ra${-#h#Clz-d z_gX|f>6tj1MeG-To~W1kir2g7?Wz}tz7f1 zYDV-aofuLceQ(E~0bSeb4cGBR^iaWm8=7y(YJ08fq5|N9OH=(@UJc`+1;uJv833W6 z>(F^9HjGk-sq>4F4|_$KZ+>xlOAcE~Sb8RPgK3~lS-^rgH#X6F-r3$J!}?p;=1y_p z_D{63tLp=Ssj%#i&vJAwJydjXHHu`v{USxh+I*GKsOACt_QbPclDiI$-$CoojHWC2}%lj2_|oY zs7SA7qB5MhHE&fA-FrTAw6!hO+`VZ^76k4>GUnZ3oIIdrVKZ~UH2f+kOOXGs+J2Ku%6(WO~cwJez$pEy(FX zvY)olO<0a`y=N;7R z5(BcIKir5k0h#B_zoY9eLN=hl3Z7dD)H33!%}fZJNA`LVJw`&y=cGUk>V_D9SN<!F?t@-$3* z#SXNUc2v(vjgsp}>kQhwZ{&O`|L&lBz!h8%n8pSL0$A)YqP?YToipHoe`}Jt47b=& zovHY|fw#ugGBJD1_@*sbkB{|U0Sld+eNjbded*nw?GJ`{Lu=wTr6w~&kfxVAOg!OQ zz#0MQ?$5%Xxfmx^dm$Eo{UV%cJ&4K5N=!uw>xlSVxQhH_7cv9PgPaEFtJQcQeE)b4}XM@M|j1R$4%|9EvTtFz@fuFv$IvK5&8{FF+6V8XP zdOU+v?#Wnn*gts7(3Ml5&mIX94mu-VpsSh}8lBCnk%6y{4mB2}p=!QV4^$2Nha8$3 zBh{F+X(yQWP6kRul^psWAU#7|I>j8GIXKwNzOHNzuEs(EJK=Hob@?Xjd6tK}$E?3OfUl$7Vz8RVy<2e{D5@l)ck&Zxl9E^&8&w~gul#9>% zFW8G#U66U($v?Y4-9~k(n1pR0qjFlgKAs7XJQdgtoK#j^hNpqRhACZ85!a8*t*)K2 z>wws^W-3odcXG+lQAp>_ZN-*IVCm4X5vp{_LG)>HG&NIRqqtcMG!bAFhzTZ2t+I5Y z^PokXe<@nNcvPwiAxA%*p(9 zPCGKQIb&4yC3PRg>`N-?S?>R`W{Z!CU6L`7m0u8Np!ViseOX&qqqVUPUFuUa+`@hf zke+RNYdgnyFfj`$H%Q4Kt^J)m$;u)mrEq#@Yo$x8PsLbQ*zCz*OUN}wIYSTXjW~RP ztPqqp3qD{j8I}5=-QUpn*TlqGxSuQ6GVw|3?@(w>Ueqn~AIqA>j23<0$L}nOm6udz z-QBu(jLAc0ZY`B1`>%d8_%ZCfpUY?QDLuwTjk0D(^3_L@E1nOpnpwr9nm5kmja@5a za`Za?68D?>*<)Fo{4vELct(2;dP^uK`rUK&+7^_{WV_Am-d8@f+UsQMb@f$7p47Lo z>RjKH%#=TA^pGjY7Z!ae<(bPJBm9tI@q88d*QWWE94$$-Lvn-D7zSVCw_%?^$jZWf zXGGM1)5t90bPo`tJy&b^)`UgwKVfbJB^Hh8Df4+{Wj&G<<>DF~nSxscp}!^%e9db( z$VgdRUYP$eAY!TreH8XY>AOSJ)UTBf2Nq!2Tvq!H|ARrIeonNec(`1pkby$fyPwpz z0$OIvRx!ud&O?CuLKlt}DAq+HbxT8$spU6cyy zxb6GYWJ3}wvqzU2i>P`ryXfs-h?YS+T~1iXVaM^%{`5s{|Jxa3yD;JdtqZfvCtUZe zK31Ct>r+N>8Qqc_He_<0-eh~WryCQu;;LR~Yry4%u zyey^g++#s&ApNJ_xNBuyf5YgaP$#8^i;5ow)rOv_j(kF1fy%v189w3HD|Qa6x+3aV zUds`7nvdsE-d-mibB!W|Jv)(Js+1MblwqRkGvfm~o#YIoS6fPwlgW4JHAWxJ92!0m zVYsbHUq)K_Bxn1{v-SAz8d3LtMN#QULx-i>WT^uR_WX>d`ws6Jcmf1u=I;;+WDnL4U1&-_VHAa%uCK>6x@%X9(n=Q zTir!~I@cU3L=j>3D)_SL5#w6?583;NuAhw45MN{$af>TWlAtHGLU*Cd%voSDl`7Xs zU~&d_&%E8SMC&M(tozb)bQHdO|Lx+A{|2Wf#$fQjN?rc%ntl4)cB_ET-!>}LoAjJp zqIJU}lf{Vp3xUin!Vg^TZF23@p5J9eSqo9Jnty(~ixz4c8q|%LVO#85WL7Qgk z{?yq?nXe-c5ZKshjw`a~2YDJE@5iNi_hTJ?`0uzs{PgXfDENx$+3*WuAXQ-VdI_N$ z$#-Vx7uJ$#X*}caU-x5 z6-Jszx}I9xzA}UET}P{Tc$*?sn@2gi`?M2Vr5ekG05IZ7*H9%uzw+mA6wrU(IGY`N z!!9q39TX-p`7kamdtmkTYd=imsJ*1<-{HoG9pEpgg3j6}A33XHEStxS^$S=7`g$SN}bm>d+M%UB~2{(J`Uy&p?A0|v_+~duU z8ZXRBmMtpJU&K1PY+jA5I^eH-MZNmW<$jbUWiq*u%}ZzMIjNLzQi;8#sq8D4v`I+5 zk#3WpunrDXDFO`rAOfQ0?})RChpg>0{*BxrKV34@>M``TPLomX?a3p*v~;xJDBaAS zoKT|~piixRpMU9L{(JJ5)}qx&L9hPxuq9s$WqEd`b!)x5tCvWYHQVfUmX+9BVf@bX z3%`t(=H`u59f%xtwa;m53RGFM@7LS-yJfsR)oLH zvz?cGJ6<_&pNBgsyGeSN!<_T?%qG9?U9H;3Y3z{U*Iq*MLrlJ(x)HLs$5vKirjxP@ zz_8h%251CC9lt-4pa!mzWS8x>M;e4*nu*!73s0+yBlvt-xhXA8ejD#1VSn$I-#p(hZgM_RRZdA?zEjBGoYenq zIanbn|IS_#$n&)&vC3mHxH{Lk{LJt*j29E`tFR{1u@|eeMOOVV7^wOg)m~0ULI0(k!Dt?;t9WqN(I4ukeH81JeTv? zQXFZ7;jq|{2`&thoHQ5 zP;?hNNRxjoosjF#>9_2+X6~|h++5c{?POLeocx=aM&jiI?)b+%<2p*ZwB*5u*}nt7 zx)UeFaQ;*U9qcLcI^eGK6!eexZzn|VZpWoHN6mk3*vlM0it4l{OTCluV(#IKbuMbh{1o)ku?`z#7?ah)ZdBHVK_IM|j>)B-f9=YmEUI15X!bLXB{DNRU9nW+aGaX<2v*bI9d=YhCX&VCL`LR7+PY2*`G6nMuBgtFytGuE$ z;kMJm!%}7ijZW{Gt-#R^DrCW^bq=*xX?p28LAoLh0J<9c4)slMno^QC%*1 zN1vOonV+83yl9I8H_Pt3`q$k{=abJ`^jI!VeyAWMQkyT(yAjQhU}j^M&GRn%7}LxQ zTjv~w$0nIKLcI?@-CUnZ_NDF9eNnyAEuX$`PS+%Con4YBD$C6os}y~c?Qp9=FAM!ucQhFw^7nb` z>uezMmp=Cw-?~NK9t<5~4w$gC_ZQE5;^Xj&^<@5Oq&RzOs6}~kB>SttKp9ICR%IcG zBU6l3d_B{Zk3l_Ie@L9Ep4Knsy8muD*PhbWe4fA<0Dt4C34ZT)%1r2Q&eu0^G0O3! z^upjElQk_@AV}0$(-4Q_7m^+(p333sQWBVd!PQ@iv2fRxP8S&y_?~d zy^C|&*SMjG#kV{a@C1#kIdn$Uq&22H56LHEPIWJRAR|5SLNpE3E%ZQc=k+{Pf;&7m zH%%y@F5XT`^STk_Fkk64l=IdlJ7wwlR<1Ue5;kf|J%Qv?Y&%M`9npaH7x#4GFG!2- zN}XpMY=C?W>8?1>h4w!>fl<^Q{riEOf5!vb-tMOFYGpO@b&f`m#ISkZ7pqMzM*HCMsx@7m2&hdn9UF;ZNeXsD}DtdL{} ze3^&YRu5c9(oq`+u}k`2VS{|HCz$7@YTeEQKG(h7_przr&Y!}D7D)`+`cag6zg;qa zAS$v;kFFT(Ym`(QoB7AR=x=DU|2GaiU#dQV>HHfvK~^J^i`VOy7yZXmopoF;T6hKr zUj^J++ukS)_KPenvkuD=rJ0sv+I^?LE^nAf-Xa|=q&0DNHtIR2p*`O51%JT}e-h*p z;BihqDMHvlL;ZA|f?CU$XbmYs*q+B#3~EnWyWg7Dp3rgYU|)Y5&5?CRzeqB zHAOy-tnu|?zY}4!=`6*#c%HZZcq+sD(F`^*_v_>vZDlo~;M|8}qt_E3jXk_m*~f4oQkHIKc`4N=i& zVb4@GI(_jW}wrBkH2#y{W3V8F!(V#X!sCc(ry+f%;h-ZnWhbHui@^Z+~xU zkJ%DjZW(vVM&t@SSNzL8eFheEqY58 zwq5Gh>yntaR=JJ4n*67H0$>oVsH1&e1ABV-EwJibkvLBjMJ=70HdaAU!2N-@A)((e zD8ETXlf678>J{*e5wx8;2;=Mt_y+GSnv1-Qe)})(2Ga?r$xIjD8ox>k4jpe5w$E(1FTYvR(fXva+9}>BPP7c4u$Vu<_6$B$9sT=VaRUrTd zAH=m@WpUMzu@Jg*+ePcVGsFi*2R_Bw=`8OIZG$8_a4v` zizALvk^X<(V$V;qclhqxDh4oO}69QYV zNFp@m6l^AeP6H}=VGu1SQ(*AGA%{&VoI7s72dkX6=vDb%Cgz zya5cA`ta&-|8FV~v(J0XuZQ_W1;HA?rxr`rlfT6$Li-c}Yqh`&BH=lrSaQ1+R#pv^ zLV*QzC7f<8s_2z|t};P?=%Q-^?i!7}hzq*@y6sNpJHOC-?oinzRLtn9?7?%OC;Kp@ zgen3C2MT!afn_83<4W#BG@tEN@rCc0ftjkHK@a-YVW(B7?;(f}A6I+f0CBSQ=ct@ln9v~c;!p@7@<6wq+(JFl>M?^UI zNIlSlponulN4Bq3LHY(bPeCZ~k!jG@Mcf(k2p-f2_^l{|ivel;Uo#N{P9n}DTQDZ* zrgdQ429BO$kZ_ID!zSz~-V=1t*AW3^iaXfGu8)>fSr5$>&=i#R4c6ETGawP>`w=QA#2rARxU-SDN&Q5LyUPP*4z%t{}bF zfb<#!G*sy|K%_$gL`o7uiud*NJ?GwY?)kO#TX)@c??0@?2N{^7>t=X#1mMFb|f^m z773iG6ExuC*y6DPU;t~V15tO`iJJ5^Y>QN_UK6zqQidpp9E{Vc-O_MPps`hTQUggx z;t>%NRvHvtp1E&Q!nP(llM9ED*I*%R3Ldv_36kuH2N0y_~x>cqBSfsBK| zi+~X))@IXo8I3a`&t~ht(?>CY8*W~Bz zb|4|Y+kOJWZ(E}|0y~O<##eDI(L5OlGqK*aY}Y6n%3$ZGrVU54!b zVTuAKNrK|ewvxe1B2)qiL|*}R6`62q6FM7&Q;4 zb_n43{?3g+HK(N$<~147z_l=c4v6pnJ7@x82DMBG*#+-I$eAI(BMu|}?jjfi2#Xi_ zzmLS0iX|Z_O4ycYI?vy|A;`%7XC?lJ;~6flXQd3uFDu$6$%b{tWX4%3{b zHIcFPhp0myY;6w%OaRe;#S)E?E>v)eEtLdl9l$jT`!8?DhG20(no>x}BHPA zH^tGKAZ(Ru`UfgIe9`}n+pBnNr~3w;?aAyd?=z8U;VfO};53Vp4Y4Az5*PR4646qZ z68`wFG3DI%?33>Rg%5u$*xnO@iuJa0UJX6M&j=fyFa;{t-oF}VpxS*k<|^^SLu1x* z+SLs1PtS@AQpF;J-X#Z!ybjCFebu|(tNr!sN7dt@XV49dt%;_-{~gzSYf>AlN8&X2 zO4Gia4xis;*&_+weJ-ax+!$_5>c#@j7o5I;(**{Eo%>*eyP{Q+5vrZF_p#RYVCs~C&rX^w! z+Lx**Zq(CZt2L!a=)I1v9PgpD@tR^a0iD=3!S~8kuXJYF3MlOlD&X zEkzCCakxJTw8 ztv;~fuNjszewcUpEe-W%+}}I<0SzHi?)@)8Blp9wPEer~(s+gQ1{gd=wt2cjU6 zYN|@rw-F>&CzxZltub{@re?LZ(bq?`j9+wirN0(lSPc{yZN7q^?^W27*@I6?RP+79Wr7iUzThc#EZ-oK4D@GgUd1B_*9W04`I1sydyDy7DB?r5F%}N zCw*hlsA&4@S8sPbDNm`v2|5`JNi@{rjC@Lf6Q$(I?1HPV(CH2EAf=-g>T%_?u97`HhWss&!etOjNGm z$DyxkaMQOhnHm=KTDO-JGZ90i6wA$+4)nnU-*mm}*HKl@{Z51ROBI>%x*H|Qk77j< zl{1kowyT~=js7nhz6LQqlx3xw#@A|~cW$!o;j@F&`hWWrj6QF+T2yLw@#1tlsn2NzN_GO{?Z}x$`z*GrE1B5FvWM z={5tX*Ew9yS&pqz3f-NUFHY-@0i+dA%qk1d)@omh+Y-Uyazi>2|69qY)~Nqzpo$G$38KWgkSbfs&$ojyK6% zSV>|sHPX*;dBLKI2(W8u>)Vd`K`ZaOZDVJ>eF1YLzdr8hy9%KOws)Q1lg$GRkDl1y zaP|yJyh*O2)+mwz4nrSAq;l`j{N9`CPiuYcZ*GMbRKFOW)YUKcs&LIKzJ1BaG}8LQ z2~YU`;2`b{F196NWFJw_yxie%6c7zy04ah4mDPnyQ!|d!LiH7Zz`P~nXJm?e_ueB$_e`BAh~I-qYxUd zsdhfHs_6PVkofWuO9OK``7Bw1Iz;eX?yL$9>kQ*b(`a(wm8p3z6EIei`^MCX{XurS z+ijcsg3t9~Uy&~qEK>ecboYOBTj3LEour2_@bbh;dExtKx!5P#XEu(zNMx@>7rCX; zEG>IaIzmW=pgqL2?$kEy^dnu%d9AA=%O~Ox_T5?VbZ;0Tws$F+vbt)*ZFG1=+T=)N z(VxH9JUAo>rQMnRbb&RFE-^n?E*p}rL)l5QaLsAM}PG@SIf20{0TpHxDB&L zs#)zGG7FS$s<3yD@dC_^NV+{z6R>XoLwx7psNXH_ybX~8vDCU@qeVy4# zS!j;Nk?!|A-V3)lax_MmBxUmFyneBWR|Q2(lAz{OYqs3_Q~H$q>za7vftmSk+5GgQ z+tlQUWk5$Y(lM#ob*5f+alpCSKQQ2$yOX>$$60Xwtky}rT?_8vcmtKYWL7OdESkP$ zGpRB<*_>PMUjdRYN;}q6Y@4C^w(kVa^~*eWuagLP4yR6KwQ2a8_j)z`AWfn}#;4(^$v}&YH<)NWjMOwO%xNj_^JXA`rp;8>rI zF|Yi27CT{`B3pJtXn}tt)vhcE=03jYqM+d6$A7u}&X!+@R!&wpN-4hJ`-qN}q%x=J z`4LS+lW1A>(<2hkBGrYMBKl`kL7(x48ct7h-(O{;4Ji5$Vt!%3{6R{MFlvy&+Dn*H zZd0MRKg;Hit$^ayo-v0zKd)8G+p#EeHMztYq!eFq-yW^A3!2O=CLzD>)#R*TXPEuW#4MJX9ZE8;xSlj`S#bDRo?(FKco7n8X3uloumvQySb9lj z2{O6OyJs4I-s*mkO|h|RQM`HT$EMfK%+;4dYC(use>Z?&fgXuRNlIyRXrv15~QB`vR1$ ztfcTwPTNR-?DOD#tPvdh3_AXhYtvNj{&ZnE^^zOSQ!)NXYF%oo_}5px zVvtW>;(XY*z``U$L|1IkjnBB={O)7#<@iMW!#6RQQ?6d=#OANQ2R6lDNh^Mvvy{}Y z4^2MNpmMcIwMHLLp8p0UlShPR!p_i4E4B+Tpl9iJe`X0((v zpMKFsj6z9SvS|NaYy6eO&(0sqyoI`Z3l@V$85fsmon*$H*z4(?L;=IdZuJKSezr@_ z#p;eyZ7zPRVDBr|uZ~1_$8m*rzf*hr7;-KsvoH4%No&?*!wwKApZJT|8xRX7Kpav; zX*K5oygHK*a&Y%BJU>BX6r*Jpqp5iw6$+nAnz0WUi+@nrCXVojRGhn7y&{FO|K;`@7D?)nDG77bFU};Eg=0 z(d{f$cFM*mQVZDR`QSt{#98!KT2zq#Wz{jqPwM&>b7>a2xfT|YWl+v$E5RmLSG6Ys z5`(-aPRM8{yBpW-PFr;mp{kvxBBQ9pRV+6$!1@?W zCMxvAuduC0f(^cA>ke{HRrxhpOKJG+t|^u3YQH~B_E10KOzZo~dYhvwen0o7<@XyO z+rVWRpNqwd@>2m#{J0J8RA@oh`K{XoxOslc|1$U5k+AX}@)p%6yNcaAR^i(P8K7v; zC5NH4r~9O5HH^YjE8@ z2`qs7%2mN%zCl(6XiJC`k=?2v=o|iJL48*E158ZV2x8&(Dq24i@(1;FVb0DneOO&GIOdLjrmgVU#q40^tL;H!w&A z^kCC=Xa3|ozVV{7h_=HW#(kscl3Pbx95sA=Jijp|dWk{me#3ttc^Ud=Khf$qfnrMI zw2ePZnaCXlK%Hss{KHhK??HD95?En`Br_J$#=-80Ar(_iItw!m*qUo-+A82HatGq% zNd2UD^QhnHkkvD_F4xPxneK}?nP?vQeA`O=<%FuC5YzI*=!V~)*bIeSnJ#@c+OB_b zjB~104Np}p-Sk185!n=>W%&vbCu!p16f=lN-W7VE+AgB#=JgWGKv{TH&26Gwm7wV9 z?KBR}^dj*VK8(uL>j;Xj^kQ3T;4(F6#9K-!COgjGhnABxhz3#r;|1bh$lYo$t zj<&iCcs%>J(+=>{fu5MKX_W~20z*D=YIJzi$)0hsR`&A@q&A~1Oyp52ZYNGSL)%sQ z)T7eu-tOqftMU@c#SH$WDNIzcDR+(h0-7mP@epb)FwnWm?nFDmuti-BvG8TE+i&ZA z$Hi_JlKp|8o@uszVJzCeZ3bhCfayl{ER^Q$46QoH3vfCrK7aC=7FXJ}X$N9}J+P60 z%FZ4j!mkskt7DjutoTJk>FR2Q8@B{hBIZUZ)R<5C{#66jr%$#o z%ZQ2$mfN;V<~v#r8OQQ7(VC1A;;2M=*xvJB=4fzYdTGRTGon%n=CSN!mBKzbMy&Z1 za6NC(smiPxjgbtVOI1f9rN2Vmjy$U)J5iJH>5S!{-as+U(A#L1;s)xIB;F> zJ4Iy{b1J8)kmsy^u-{pS17}HhzTAR>NYT9FGGuv@kw#>cfcoj$i%q$ek#~k-4h>7T zg^p)RWrSX8coEgLZyRDBoQZ>k+X*NsEu;Kb#ct6d;{ohT_mZ>JlfPED7DUDhd_cGF zK1^7C*~s?p=1qs^(cG>HJ;L4*aS%ih5Zh(9BDoVK$!Z*2&v=HxMYXm~F!wDUu5m}H zMi&>GB+GRhje5>uB#(!PoCcBVqUY~uhgNA`L)O>_3inok5I82kzfE1GI3<^ZL0%ss4ZH1Sm9qWVm92{5&(`mgm2 zMq4@5g=o{<`e_DRkz1XgF@f{BKgS16l^xE+XI^@#U29^(>6!DZMJhL$ts zl-pBNe(R4(7g=BbA0D26IZT_!4pwMo46wh*ZpRjo#6E2qWami=8SdH+9>0Xi3wCK= zbjFOBNJ_CQ=}XFeR{WCjOFJO1AU>lc^T%g4U5;9>viQ8fX~)sW4!Q5{EGN(QJ_!|b zjbsZHsHe5MYwltx6SRT@I&C$`oZ{<2h`b^>H>$L!RP9I7^f%W`@f=JKgMNQR#3Xo( zf3vv6oxon3SDGr;U9w)%^3}eyC8_3iQx7vI)qEIiCG`&^`rjbs*AiBSX~=4@d?gSh z(AdmVU1f7}C!x3?J~N0E<1CsN6dCCjfG>3wZNufnN2V%rb2%he)D_rq-by`l%On{Z ztvq`0;n`Swsm|?t;m(6ZC^5d~LyqX0-=owh ziL4dxzc`PIC``wU^Jdgh3L|iU zY`ecRD1fJuawnUmU-!RTO*qGT@Z>+uC`a0^WqLM?$x&YB|vxS3@3I!3Y+A znAn5xa~f1x#yDdZMnd6o{xD6>;{d9~MZ@^Oi=RveNSj%1RJwAfyB4#6lEr$ zE+Pr5563lDt{rBElq(Wl(M1D~Rhd}+FkJ`)iZ+pZ+v%F9W8+X_`$#d}42(xo33vhz zGccuNn8-P6ZX%oI{WJTOjSTeY4uchYjM0TaZZDM+`HD9~%IcR{c>geU!SEa1WRE}q zy*>cX{AH#rg-}^DNnm}L@Ok~UwaKPmV~4L;)j~YxDM66#dMd(qBslf}JUH3Z?rJo! zuAG?#Ov(Eh;hWdpr5y*t_kC{Yc23^|_G`ap+Esx5&`VhLM*zG^Y@OA$Z509LK@m8y;l5q0Z~*{PXul$jJYN5)csy|;W&Dl6}EMz6;>vE zo_HLABt%J-`nbc$W@e)kH-#p>JRt$^s_Id`>+(2psbrx923Q{sBU|BPQ7~VXAeVf_ z$lpnqSxwtoV4J7XU?>wjds`3~4S2e2&U?FK=;VX*gs@XQwhSP|D;%FSLtbPYlDAtY z0pbhrx0Q(YcMAq3`-Hw&uj#X5!j>W>?ir~?$m-$>8@KN$@$skGqko0H1 zMXz=d`l0%~`)kIvC58z0YS`XjQ;Ld9BR)Kht_3hihyWzaPGXRaCrg5ssEByf7X1*1 z(t{tqZ6G#XTAI9gO=N>8@cR&k?_mJAJ(0i;+;OB#a^}Hg8*?lXw9Ns1kz1xd6%W1D z*oq`fir}=!m2K%>iTE(^Vc{PpD?{>R>kzO5@cKj@V1JUEkK^a;WnTqwM-UHTAkWT~vo=9g2p6|tOKC+Y^(GnDUn6Nbf29+C5$O23S zA%M=}2FB^5T=`pra*@DJp8?M!mTc(&|DqT~&kG`Msr_HAl%x8KSORK-oO5#`%DAF! zCGeLwaqTbuAC!%^j4iZ*k01b5x1gxR(kF~q=IQCnn2KyKGO19D==27=3fQ?bqqB*-@;#-1)ZGDBxU|Po#G$b!nQuZ zHgh24^wFgmopWz&JR}tt-K=)dhn);@Lb(>Bkp2tcXD~`K>H&^LIPh;JdFx-Tv7-Vs z*QknQ+dXN3Rl+!&KxF+DIfp&~fWO@U2Wm7LGN!1&qx|IkHTpx~735O@6Uc!uGYIgn zMl*@$fsxEg69u+nQSRUc0#*<4mm!CJff_^9f#^U&n+O#A9?%OP4EY~>qyNu)gZw5} zc?6NIb&UXQpGac|$QW)gI(W)z{%tE_@sMnjnzWV%Bm(271E&m)r>kH#7=xI}rX{iv zz^bYw<52)jp^i)_q=NraWL|=2{xVdWfqY_ANXQ|gnnVO9^Z$`Y$1(KMOnL0YZb@Kg5Va{@lMyPPQY|!pQ#+$*?U) zaA078u}}up90eExGk=*R?f)-77Yp9gzzGf8^*gMh%+dF62h6hfNKSsl1Q2U;I-PcF zxxQRGBOG;F<3ZXXBC>ctW|MjkV81+RGL*bWLX zU4O~CuQdjaPGs1%z%>S|5rA%Rf{oPR zJt`c&PVoN?JC0oM)rr}tA%Xg5hJRFP*3}g#LGrYj*hJSC3fPX$-9M`IgJTHC5|tjM)ge&Yu@xcmO?5OXA+exm)vS^&kE;=`Se4MNN__r&7DyEPjS-4 zgR6~ynE3S#fIL5=@j#sSBbDXYxk)OFKq+LU1?g4l5CAP(Y<}(!la3$&ZPxR_<`Rhr zS|145#hsS{w5aJ2@xojd5Z~GaM$mIWaYIgcZeq821@<(g4;G08ccden3rg^XW2jz* ztB9WxD2w40TvZylqOz9fWxcHCh5r32`><5X8}%-a-8CFzO!ihwh)keC#x5oqk}@-I zq_cK^&swopNW$FevBqnolN@i=&K>E7)K|E`qAO!t&`mEr_+>Sp)mUnAj6C?&aBK93 zXhm75T}eaC+;`B4TE}ula+T(FT5(;610KtXQ400QG1Q)#a=*P_dg|x-58~nyE6I1_ zzr9|UE@M$O;QhXz8O>!os9}IR(2{FfRdoqPkO~l-(QHY(}=A5B0^HX zqhFw zoWN=)MlNcdLf?0@RC>26bMf_J7SfgkAkC7DCj1utGvySWI@PQIoVJrazff-xFz-YQ z>AJnX20$f%kU#1!>;*XNy40b#-P)2W6R@7!WM^H}`nE{elg7zkeZrPCQEqNSeU@%= zNVZgO+-+*+OXtA>Op0s>sj9m5cZH|2bZKqqTF1`y+1mA;xP9DEQ_ZMZQn`yFx!E0~ zqCcJ{)qIfg6R(Gw36wk|uydw59BrVV|Juw>Qc}*yBJra-g_mDt2}+jnQpySNS1^|q zEOe~yyY_4B*;LFud=`vV$m2^%Kh+lN;M^}d;O@%Yd~5&4m7zEik?{2SPXzV4)chZ& zT4$v{O#XMV4(u-A6^Cl7Gf=iTu<1kFp51OXL13>RMGTT^-Kze@PLJ0#Ml8bs?b~Cp zUInvj4gZ|So{x3n_0}36P z(OlS+Op(K=tjZ{3P7rZ+9fK5q3|_>(&M_+=n7kzPK>5l{V6b5Cdok`Ss*_@&v&e;F zm76v73Pxh=vY8+E))Nu8^?@2gzn*tW9Z=L8Qe)iOny0@?p*u`#!_9q`ZEjw9qjR29AV-9P-5!nA2j&5hnl;|i?`{xFF`z9ic93;hB=@~4~n%Bw`NT!Eqj zj5UhF&#b);XY=R}7wvg;t&cTAT>qR1TFXxF1C)j7J=H%CZV}sBfLc(H=&1xCjZSe+ zrx_*Aq{OLEQ7UifiNo_-%mhwAJL(yVdbS`vh1w%q8X`Jc&O2^mSHxmU0JHe(@NoOp zu#dR=s;r?W$Yq6xs#w$>W*aDpL@@)5Rf2nwB0C;bNFp|}9*O~XG=)Sx^@mCMfWRmu z2@)rJP;qe0wES7P(>xscRhA?EvqK4HdNWC}t( z3o?z7PlRKhL{)3xo+Q+;Ok#ZzKkF&hWF^1t%SS;LG=uPZl{fQKsV<@ex+YgPUM6zh zmT=99O!FBYHgVYHC@%OkHd^lC{e7t*CjF#<&eg(WLvQWKwasn^qgQDgc5Cg7`I@eI zxD#+Yzri6C?P7I?Fi}34VFGsn$>rcpobdZv&|)}jvhu5A<>rk*$6ZG;l4po^ig;9c zg6Y%T(%|5){Z3`crB;8KUJH{}|Q^T<*s|n=juS76h41 z;uqpnJGazh{t=m)5uYyIE`>W;$(!jX6wc5E_xCBiOHokU(T9FhKmEWwLiW?l0j}fZ z)1ynfTr-wrh*neC^;cC_k4p74s;)8kQ_ht9id+ZRu>Z~|LA*e8gn<(ZrU%vPcEJjD zDtZrcoL`IGVH^u`Dmo@=q_g5}0+v06Hj`PYa%k?Kmv*%Ff+w_7V&_7IiHk{Mx z?&`uhY{9Eb-^VJXTu-^~d`o_R{fCJbEXW}}E$L3R!{m{Qb=3IxHTiyJ~fDy+W;a5^^ zgi$^o>3?|xQrqSap6w#?6dj72sdb90HnpSl@3CkTq8UCx;T4i|pYp+dZRhaz0j<_q z@3or?AI^7d)Hu=EF;TcH+tbNR_*fh|UuH&&jrEzjo52|WwKmNh%^_63^g?V8`^VOe z9iLp78y^;%ek7^SknGnT<>mFk!5;UUD}dBy+>_M%Yv&+1ZNy1P`~E%@`rx5yfTcF{ z0#ki%kU~wHmS6JZhF3CRd|^TT^9UGy24e?{K?5aNH=(wc)wF6!bJf_0&e*IBySa4v z^`X=}M~&(RRpZWrg|~%I--+oTdEIV4m|eJ>*#k;CcstTOx~$n9y}^hL@<$>f5Wxx& zK+;Srfc_WdRCEh)TaX39vU*m;Kub3h3c^S?^M;pqwQ@M()lE!;O(_@k=anLsoTQDs zwmRpPj3uQmzJRXwg)0+nYokXn9eTEFM6&GzqNhEJ85TZ zOxdH#SnH@scc=CPEv7FgQ3P1_Ch5whlxbM5YRM1`s(Rrh1Gp27kE%zyeSFTN>U}~O2{w{8v z{f>;$IGUpu>0atOvNV<#%3dGF(!FspSHq<}Y~6btNsMqO#zHq+zz+N5XC7_CQ#_#p zpv!@qr_=eZxQw#SJP_;UDtw)Ovbvz$Wj@99sb-CTD^V|n@vjtJ4UlCbt+@)UpcJdp zqU7zEdoVdX_Is6jLrux>!*`E9iJB|d%go;w7uzZZioKNmv9OT$?jiDc?Tayu)K$m{ zhxhBF?p2pPbb?Qz*8nFo*o2)%N2Rg&Q@m**7@lJ}mfU}sQh>^mB_(&(3?A0>eDq)e zrT>Z$Y0Tsw?oUc8zHVX?km_ofo6+Q;q-lJHSzY3X-5PeUd>-0(GDHzTze;c+p$7|- ziCZ3aO3!I1~GRHv}%)I`>}j z>Xu>T2cWhW``WM)!`$w7x@{}kKAYl18>_0d6AiH@)fy4{=i9f~X1=st_Ud~QuFn#5 zGv9Fz_I~8Pc-z64Lvh|IT^;?Kg`CyH&gPBH5lYGTXjpfWnS-J;C#!tY&lNE-+Ux#o z#;RLk+LNRx+&KJU?KWh~?4xqT!&Nf}BP~#^ibvy{1LBaC`|&$%v+ImZ4As?j;M&&g zjp>dNgLfa?Pkt~`Rp$eL=*VVUuX}ZA=05)>av@?bjRM`Vda>k9?E@?}wf+7NHx*bd z{9+!s;ocTpExv2LVn5+{`(;<&n|GhrEwn6do^px5<5XvH_vv9w%FOmx%$NAS)ssLh z7{E2dWuiXW(M0NhAqL7zQ~Ch;XXlBC1Pclp+tk7m@~aFSTrDfl{qo|gjSUWwmOuV8 ztLH8UMffR_^CdF=rn_Au^&05cR>-%l6Lq$O_@erGT~_+!(Ef$`CY zFMhBueoHgnu(4F@ZBoCxA8Mg&CV5}XU9}wwLGd?5+J>!fmu>`7*+WSE^ygn){DS8a zW;hE4GY3?7#d*vf@h4c^-oINUi@rWry)ot~OSu>v7~2dA*pkGj6~3+0M=PH(_gTZ0 zN>sApY~%tsWa$Vk;b-5^_8V@#aKkqD8%?xg4Y#GF7Yz@guP+FhT8>oubqlL2IG48v`a^xbMyb!~H49pk=V2BD<1}WZ`1#rTOY7___}^iR zt25{=ulEPFBWY|=e0qt1=HmK7PX2Rb`S~jk8+l0 AiwvbwZuYE6wFsoj#ahxcvh zr`bs`l-69T>1w;js!4e@rl$<191@GmyG$&I>*XEG{G}JIxa_qoHfDp~UlPs?a!ERP zJ(=B{e9Y7ytQ^uAlLaKFw??sm%(uS8Ob{}oY~)*ib&u4FZ8LX_I8=|l&OMJudr4UP zTJ-ytWHF768}f*Sy|6QC!4z$2$iTW~(Q$8~_dq<`%CIKGOrZA^ z;Ia~z?Ag6VTr3$YQp%0cOL7#+9*OPWI*BpdVENM~ z!zv1KmTDFm*mYECGzsQ!-X8K}260?*a&69ecUoS>dm1wXk9|L#_@nt?PDLGL4u0=i za%@DB^=$!W^zN|Io%h$8a1)%_x1@~weXGi*pItODNHrdOFR+ZO_S4@}#o6r924Go+@Wi2Atov+!> zZ3vh-{mbO65-DgJ6rZy}joaLNjYKb2r`Jy3m8$M^l40oA6qF7+-Sjpga@ukvL+Zob z@&o0B+Fs3=rnZl#JrWr*FTD1l_Z#ct)sRuWP=Sv&-@^_FL+Nw7tUmU;jRW<=aJ5EQ z)Qe{?Yz5lNvN2cOX6I}q1!lEQ3-}u{pF*?8KaB0}D#%#199Vq`>lgvYo>1n&UWp&BFr+L(ZoPF+E@-?@8Ki8vv(&$aa6Jy7$Vd5dP8(>XiOt{d+_(3P{Ps;Kf6p1!>!LuTqG5Iz|gT}_!I*;E* zlJb(xO${l+>>+pYPu=AB(idvns{rBYdKbCUl(gwLZ0!(maXp;bmTIX&y@>50^-8x#ONXo0fq2Gh2P5li_f> z%rwPi)6C4+{zUHP%M(KDnoaXNc7wEZrOMwan|2gxmNHV4?+^;33~H8g?< zu41of)1^h>l;@^?zM`{ zj_99E>Nol({Ibg?Rl%q$J?l;zHJ1TJ^{zEy>R&XR-yK|hBj31rgVZ&us^R9Ah1N;T zDt_;v%VC1ah>6S<_?WLN_oE=)-62MrPtJG!(V+$NUH&&>fJ3(&WB%ha>zei-CJ-x$ zSa4$xFA+f#F{X^rf^5TQt-jE%P|$|47@pXQ>e?jv#hA!KX_m`3wPgE55(Lew-FHnI zt!+F~OPTqsmR6D-BpqulF`JM#C=rs)tEiMX+0WN@i%-R>rGRsQ#A zVTcKIup?t&`^Gftdb1a=FC8*D4R3Dh;)3?+n(9W&HHRx-+zNHWtrrx00IBQG2^L&6 z;rx(f=jEH0+xTz95vC2~isxSSGOP7w+cYjC+M4lg)3T|AFFLi?3!e5iL0s~pS-H01 z(3<~Q1)-a&(Iqb)UJ7ts$&eY!MtIb$hra86F<;5B1f zb(^Fg_Syb(e_Em^a)wV=O+9|Iy!q{P`s;?A4%N8FJ|kKmd$!7pQ@YxP?*)lYai^gT zP0z?>i}9)x9C<}{w2aEzA?vet^W{l)q$+)5W?p^Lr-2?TJ5WjLr;(vfLGIdf z9J0)%FJfHIhIBH#-sNMQU+c&WoxM?lXb-~3E|K?sGR*95Hb*(^v#Zf^7<50ZqxoRe zG)$kGFx4>Fnvoh)(B?>`{TxO++}t-Z(z#KLyJnoL9lu%f5W5TkT(?>WK=t>8dIq%> zvt4gDo!LhdD!da*76fAuXsav;gCh+HIXC<3p-9C4VG8-tMOvW^9cEU;3Mmz|j48NQ zr9Z@hz?(t%Udul_T_ev)RKM)`l-Rp7n)@m19Z!$=Mx;DZsU#GRV>xw2gl>Vue9)Ee zD+$%rV4YHQVd%`Qj3W5{FvSr%K|cvzLsPAqlLzp(sg`ui6qlm4&4q4E=gWX!S|#}( z^i6Mb+lNZ}Xiq#E{`7i_Q(^Zh!+DH~p!Neeh(C|R2T3qSEq%twQLdsicH#CmYcGC3 zO$38iw$RIOZOd0dbdDmLDwrz5sAj0IttS9~%l{v&yB}v$5B@ra1<4#~#<@+UaKB`~ z3_%s>jRCZY*>z!!1?P&Q=ri)CPiw?oe;B9nu1+6KmvN?g1>wmz^G%q0kDER9 z-?Qt9qNM(MVK=Wxh-^*{MtsmA9ayi9_LxuQs-WI3RTC{#%#5K_xN2FRn-ep#SRbGw zm2H``iDxm!!M{|@SpbjvTQ(q6`^;b?s_6+fEe(_=zIqEjv4DZCo&PY#=OA)0VdXyj ztj6ty8hs+P>C0_(XU@*1x@7!RbC^gCxovt@NdfvyOqc(;$99R>7rzJbPIN<4LsRq_ zUFERduh%LW8S^YHiVu1XhCdjhOeNV%$-#prospVGkZE-5 zcDcQe^0AOtxelH+9n%zu4|E7uvCNA*C(C{@&x8V9Rug2q8_z`5Q+z$cm-MyidAZWX z1|@NMItE`+G}Rc=i^W83mEb|0CqvO=PBOJ9S5;| z_h2=O&`9g3&dy||VlG%`E@{68+Q4X<*&X&$JWT8sjE~(4pDpQAGZgMVxq}vZR7WhK zmUNeiomKSHvABjf^Ll(;#)o8{Lmv@PZo3xz{(xY$+gGZDcAp6L`C+zvn_@dX|8dzb zStkCmbtw1V6MxT3XLI7k0S`Md4_i*jMY)*e@D zaSXC9335>DC_6T-Yvzr{%pWGtB)#&O%>rOMc5RLE7wWzOGr2U8955gz-U9Zj2w#2U z&*5&`+;r_dLy_3+-#k7*?iT;C4a8DFYn8=Y@qoe7?V71lJDqHM5e!@iLqFBzIBf%v zibu;7wAIPy7sv&moN%q2!;>2yx~s;G;{%c}#mgS}Y>mI5u(S##UwSkAjdfMAn*Tk8 zZzxlw&f4bv&RT=wUSpj}0=%p_3@KCd`R>z-P=OtxMik3iB|>7Xz||E%{aCX|tF0*SU(( za%*JQafjpY<+So((oLUjT+X_$2c6%#B-44L-tx?i@T{G}Z2Y6x14Htj*MZP(?82SJ z1LV%+@^ad_CTLTLnSDv((WA;k66Se z8*Y6e8`GfE>F~x|Hz(&l-!AC2uiI9P6^$P=@CXzwDLMOqE7k2C-%?RxOP^50ge&B0 z&?F4X{>+O}vZ*ZONeB1`oO+{k0nA1DWpa&dT94&97J)2ZC5mCZCFa%_dU)W5Q$AZp zmCp=2bBcv--!@x3bArOK@lYA#`!=aU99KHk+?4BGv4La!obgRtbJ&i^FGoktn)-?x z>X^Cg`i`0I=RA3$5R-GlNk2E|qh@vXxIk|LCBonA0+0Y&I!fi$)Z=K;3bdm_VncaF z(m4f6WATsm?uGe)d@DJx*U1kBhrXWsUeKVvi0EUSO+wcVik4*4*1;W`rfc&8gRB(8 zV82-?RkNDm4t4WVHuZ6knW|_DyVuGepMN(wqqxpi7&qtXM^JS)j+><3ne#k;PeN><^g$P;ofAyF!KyhcG+tl+@r8lwuni&ysYn9&&T;r^^r3JBlV__6iqA*C+PS z^|E7AEt?-Z@&eOc**dx;zF&EzZT>fQ4M%cr1JmwN_;wSH%rxibDB`Ka&<$l3DOhzW zD(aVaX6H?D!JaW=oyGo@+k0jd!A`R~sSnd9JDp8kubstoSB(_Lo=sG9zutbG`N8rs zn>SHtG{<{44L~Tm5N~0*d3DcwG5{a4Mq0|Ub4ay^?}Wm^*;U{ua8;nko)wGz+ViB{ zG0PNKD4OLsRnfSS3jyW%Jt?@ow9Xy@77a0KQ=>EaCLeA#DeK

s|dl)^}Da)z6?$ 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�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

*j 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

UQQ#WN7osUocw8+!+8$7;8qEH2s$6 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 z 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ð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

  • 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

    @Z7dei7^R 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%0ACE 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 z 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

    {|65GqzI>dc38yCp2E2FG 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 \ + (vdma_channel_index) = ((src) & CONTEXT_SWITCH_DEFS__PACKED_VDMA_CHANNEL_ID__VDMA_CHANNEL_INDEX_MASK); \ + } while (0) + #pragma pack(push, 1) typedef struct { uint16_t core_bytes_per_buffer; @@ -86,6 +100,7 @@ typedef enum __attribute__((packed)) { CONTEXT_SWITCH_DEFS__ACTION_TYPE_FETCH_CCW_BURSTS, CONTEXT_SWITCH_DEFS__ACTION_TYPE_VALIDATE_VDMA_CHANNEL, CONTEXT_SWITCH_DEFS__ACTION_TYPE_BURST_CREDITS_TASK_START, + CONTEXT_SWITCH_DEFS__ACTION_TYPE_DDR_BUFFERING_RESET, /* Must be last */ CONTEXT_SWITCH_DEFS__ACTION_TYPE_COUNT @@ -141,12 +156,12 @@ typedef struct { typedef struct { uint16_t descriptors_count; - uint8_t cfg_channel_number; + uint8_t packed_vdma_channel_id; } CONTEXT_SWITCH_DEFS__fetch_cfg_channel_descriptors_action_data_t; typedef struct { uint16_t ccw_bursts; - uint8_t cfg_channel_number; + uint8_t config_stream_index; } CONTEXT_SWITCH_DEFS__fetch_ccw_bursts_action_data_t; typedef struct { @@ -172,7 +187,7 @@ typedef struct { } CONTEXT_SWITCH_DEFS__disable_lcu_action_data_t; typedef struct { - uint8_t vdma_channel_index; + uint8_t packed_vdma_channel_id; uint8_t edge_layer_direction; bool is_inter_context; uint8_t host_buffer_type; // CONTROL_PROTOCOL__HOST_BUFFER_TYPE_t @@ -180,7 +195,7 @@ typedef struct { } CONTEXT_SWITCH_DEFS__deactivate_vdma_channel_action_data_t; typedef struct { - uint8_t vdma_channel_index; + uint8_t packed_vdma_channel_id; uint8_t edge_layer_direction; bool is_inter_context; bool is_single_context_network_group; @@ -189,7 +204,7 @@ typedef struct { } CONTEXT_SWITCH_DEFS__validate_vdma_channel_action_data_t; typedef struct { - uint8_t vdma_channel_index; + uint8_t packed_vdma_channel_id; uint8_t stream_index; uint8_t network_index; uint32_t frame_periph_size; @@ -199,14 +214,14 @@ typedef struct { } CONTEXT_SWITCH_DEFS__fetch_data_action_data_t; typedef struct { - uint8_t vdma_channel_index; + uint8_t packed_vdma_channel_id; uint8_t stream_index; bool is_dummy_stream; } CONTEXT_SWITCH_DEFS__change_vdma_to_stream_mapping_data_t; typedef struct { - uint8_t h2d_vdma_channel_index; - uint8_t d2h_vdma_channel_index; + uint8_t h2d_packed_vdma_channel_id; + uint8_t d2h_packed_vdma_channel_id; uint8_t network_index; uint32_t descriptors_per_frame; uint16_t programmed_descriptors_count; @@ -218,7 +233,7 @@ typedef struct { } CONTEXT_SWITCH_DEFS__lcu_interrupt_data_t; typedef struct { - uint8_t vdma_channel_index; + uint8_t packed_vdma_channel_id; } CONTEXT_SWITCH_DEFS__vdma_dataflow_interrupt_data_t; typedef struct { @@ -235,7 +250,7 @@ typedef struct { } CONTEXT_SWITCH_DEFS__wait_nms_idle_data_t; typedef struct { - uint8_t vdma_channel_index; + uint8_t packed_vdma_channel_id; uint8_t stream_index; bool is_inter_context; } CONTEXT_SWITCH_DEFS__wait_dma_idle_data_t; @@ -250,16 +265,17 @@ typedef struct { /* edge layers structs */ typedef struct { + uint8_t packed_vdma_channel_id; uint8_t stream_index; - uint8_t vdma_channel_index; CONTEXT_SWITCH_DEFS__stream_reg_info_t stream_reg_info; + CONTROL_PROTOCOL__host_buffer_info_t host_buffer_info; uint32_t initial_credit_size; bool is_single_context_app; } CONTEXT_SWITCH_DEFS__activate_boundary_input_data_t; typedef struct { + uint8_t packed_vdma_channel_id; uint8_t stream_index; - uint8_t vdma_channel_index; uint8_t network_index; CONTEXT_SWITCH_DEFS__stream_reg_info_t stream_reg_info; CONTROL_PROTOCOL__host_buffer_info_t host_buffer_info; @@ -267,48 +283,55 @@ typedef struct { } CONTEXT_SWITCH_DEFS__activate_inter_context_input_data_t; typedef struct { + uint8_t packed_vdma_channel_id; uint8_t stream_index; - uint8_t vdma_channel_index; CONTEXT_SWITCH_DEFS__stream_reg_info_t stream_reg_info; - uint64_t host_descriptors_base_address; - uint8_t desc_list_depth; + CONTROL_PROTOCOL__host_buffer_info_t host_buffer_info; uint32_t initial_credit_size; + uint8_t connected_d2h_packed_vdma_channel_id; } CONTEXT_SWITCH_DEFS__activate_ddr_buffer_input_data_t; typedef struct { + uint8_t packed_vdma_channel_id; uint8_t stream_index; - uint8_t vdma_channel_index; CONTEXT_SWITCH_DEFS__stream_reg_info_t stream_reg_info; - uint32_t frame_credits_in_bytes; - uint16_t desc_page_size; + CONTROL_PROTOCOL__host_buffer_info_t host_buffer_info; } CONTEXT_SWITCH_DEFS__activate_boundary_output_data_t; typedef struct { + uint8_t packed_vdma_channel_id; uint8_t stream_index; - uint8_t vdma_channel_index; uint8_t network_index; CONTEXT_SWITCH_DEFS__stream_reg_info_t stream_reg_info; CONTROL_PROTOCOL__host_buffer_info_t host_buffer_info; } CONTEXT_SWITCH_DEFS__activate_inter_context_output_data_t; typedef struct { + uint8_t packed_vdma_channel_id; uint8_t stream_index; - uint8_t vdma_channel_index; CONTEXT_SWITCH_DEFS__stream_reg_info_t stream_reg_info; - uint32_t frame_credits_in_bytes; - uint64_t host_descriptors_base_address; - uint16_t desc_page_size; - uint8_t desc_list_depth; + CONTROL_PROTOCOL__host_buffer_info_t host_buffer_info; uint32_t buffered_rows_count; } CONTEXT_SWITCH_DEFS__activate_ddr_buffer_output_data_t; +typedef union { + CONTEXT_SWITCH_DEFS__activate_boundary_input_data_t activate_boundary_input_data; + CONTEXT_SWITCH_DEFS__activate_inter_context_input_data_t activate_inter_context_input_data; + CONTEXT_SWITCH_DEFS__activate_ddr_buffer_input_data_t activate_ddr_buffer_input_data; + CONTEXT_SWITCH_DEFS__activate_boundary_output_data_t activate_boundary_output_data; + CONTEXT_SWITCH_DEFS__activate_inter_context_output_data_t activate_inter_context_output_data; + CONTEXT_SWITCH_DEFS__activate_ddr_buffer_output_data_t activate_ddr_buffer_output_data; +} CONTEXT_SWITCH_COMMON__activate_edge_layer_action_t; + typedef struct { - uint8_t channel_index; + uint8_t packed_vdma_channel_id; + uint8_t config_stream_index; CONTROL_PROTOCOL__host_buffer_info_t host_buffer_info; } CONTEXT_SWITCH_DEFS__activate_cfg_channel_t; typedef struct { - uint8_t channel_index; + uint8_t packed_vdma_channel_id; + uint8_t config_stream_index; } CONTEXT_SWITCH_DEFS__deactivate_cfg_channel_t; #pragma pack(pop) diff --git a/common/include/control_protocol.h b/common/include/control_protocol.h index a5d9e36..a08768c 100644 --- a/common/include/control_protocol.h +++ b/common/include/control_protocol.h @@ -35,13 +35,14 @@ extern "C" { #define CONTROL_PROTOCOL__MAX_SERIAL_NUMBER_LENGTH (16) #define CONTROL_PROTOCOL__MAX_PART_NUMBER_LENGTH (16) #define CONTROL_PROTOCOL__MAX_PRODUCT_NAME_LENGTH (42) -#define CONTROL_PROTOCOL__MAX_CONTEXT_SWITCH_APPLICATIONS (8) +#define CONTROL_PROTOCOL__MAX_CONTEXT_SWITCH_APPLICATIONS (32) #define CONTROL_PROTOCOL__MAX_NUMBER_OF_CLUSTERS (8) #define CONTROL_PROTOCOL__MAX_CONTROL_LENGTH (1500) -#define CONTROL_PROTOCOL__MAX_TOTAL_CONTEXTS (32) +#define CONTROL_PROTOCOL__MAX_TOTAL_CONTEXTS (128) #define CONTROL_PROTOCOL__SOC_ID_LENGTH (32) #define CONTROL_PROTOCOL__MAX_CFG_CHANNELS (4) #define CONTROL_PROTOCOL__MAX_NETWORKS_PER_NETWORK_GROUP (8) +#define CONTROL_PROTOCOL__MAX_VDMA_ENGINES_COUNT (3) /* Tightly coupled with the sizeof PROCESS_MONITOR__detection_results_t and HAILO_SOC_PM_VALUES_BYTES_LENGTH */ #define PM_RESULTS_LENGTH (24) @@ -52,6 +53,8 @@ extern "C" { /* Tightly coupled to HAILO_MAX_TEMPERATURE_THROTTLING_LEVELS_NUMBER */ #define MAX_TEMPERATURE_THROTTLING_LEVELS_NUMBER (4) +#define MAX_OVERCURRENT_THROTTLING_LEVELS_NUMBER (8) + #define CONTROL_PROTOCOL__MAX_NUMBER_OF_POWER_MEASUREMETS (4) #define CONTROL_PROTOCOL__DEFAULT_INIT_SAMPLING_PERIOD_US (CONTROL_PROTOCOL__PERIOD_1100US) #define CONTROL_PROTOCOL__DEFAULT_INIT_AVERAGING_FACTOR (CONTROL_PROTOCOL__AVERAGE_FACTOR_1) @@ -143,7 +146,7 @@ extern "C" { CONTROL_PROTOCOL__OPCODE_X(HAILO_CONTROL_OPCODE_CORE_IDENTIFY, true, CPU_ID_CORE_CPU)\ CONTROL_PROTOCOL__OPCODE_X(HAILO_CONTROL_OPCODE_D2H_EVENT_MANAGER_SET_HOST_INFO, false, CPU_ID_APP_CPU)\ CONTROL_PROTOCOL__OPCODE_X(HAILO_CONTROL_OPCODE_D2H_EVENT_MANAGER_SEND_EVENT_HOST_INFO, false, CPU_ID_APP_CPU)\ - CONTROL_PROTOCOL__OPCODE_X(HAILO_CONTROL_OPCODE_SWITCH_APPLICATION, false, CPU_ID_CORE_CPU)\ + CONTROL_PROTOCOL__OPCODE_X(HAILO_CONTROL_OPCODE_SWITCH_APPLICATION /* obsolete */, false, CPU_ID_CORE_CPU)\ CONTROL_PROTOCOL__OPCODE_X(HAILO_CONTROL_OPCODE_GET_CHIP_TEMPERATURE, false, CPU_ID_APP_CPU)\ CONTROL_PROTOCOL__OPCODE_X(HAILO_CONTROL_OPCODE_READ_BOARD_CONFIG, true, CPU_ID_APP_CPU)\ CONTROL_PROTOCOL__OPCODE_X(HAILO_CONTROL_OPCODE_WRITE_BOARD_CONFIG, true, CPU_ID_APP_CPU)\ @@ -169,6 +172,7 @@ extern "C" { CONTROL_PROTOCOL__OPCODE_X(HAILO_CONTROL_OPCODE_CORE_PREVIOUS_SYSTEM_STATE, false, CPU_ID_CORE_CPU)\ CONTROL_PROTOCOL__OPCODE_X(HAILO_CONTROL_OPCODE_CORE_WD_ENABLE, false, CPU_ID_CORE_CPU)\ CONTROL_PROTOCOL__OPCODE_X(HAILO_CONTROL_OPCODE_CORE_WD_CONFIG, false, CPU_ID_CORE_CPU)\ + CONTROL_PROTOCOL__OPCODE_X(HAILO_CONTROL_OPCODE_CONTEXT_SWITCH_CLEAR_CONFIGURED_APPS, false, CPU_ID_CORE_CPU)\ typedef enum { #define CONTROL_PROTOCOL__OPCODE_X(name, is_critical, cpu_id) name, @@ -353,7 +357,8 @@ typedef struct { typedef enum { CONTROL_PROTOCOL__HAILO8_A0 = 0, - CONTROL_PROTOCOL__HAILO8_B0, + CONTROL_PROTOCOL__HAILO8, + CONTROL_PROTOCOL__HAILO8L, CONTROL_PROTOCOL__MERCURY_CA, CONTROL_PROTOCOL__MERCURY_VPU, /* Must be last!! */ @@ -877,10 +882,10 @@ typedef struct { typedef struct { uint8_t dynamic_contexts_count; - uint32_t host_boundary_channels_bitmap; - uint8_t cfg_channel_numbers[CONTROL_PROTOCOL__MAX_CFG_CHANNELS]; + uint32_t host_boundary_channels_bitmap[CONTROL_PROTOCOL__MAX_VDMA_ENGINES_COUNT]; uint8_t power_mode; // CONTROL_PROTOCOL__power_mode_t CONTROL_PROTOCOL__INFER_FEATURE_LIST_t infer_features; + CONTROL_PROTOCOL__VALIDATION_FEATURE_LIST_t validation_features; uint8_t networks_count; uint16_t batch_size[CONTROL_PROTOCOL__MAX_NETWORKS_PER_NETWORK_GROUP]; } CONTROL_PROTOCOL__application_header_t; @@ -888,8 +893,6 @@ typedef struct { typedef struct { uint32_t context_switch_version_length; uint32_t context_switch_version; - uint32_t validation_features_length; - CONTROL_PROTOCOL__VALIDATION_FEATURE_LIST_t validation_features; uint32_t application_count_length; uint8_t application_count; uint32_t application_header_length; @@ -906,7 +909,6 @@ typedef enum { typedef struct { CONTROL_PROTOCOL__CONTEXT_SWITCH_VERSION_t context_switch_version; - CONTROL_PROTOCOL__VALIDATION_FEATURE_LIST_t validation_features; uint8_t application_count; CONTROL_PROTOCOL__application_header_t application_header[CONTROL_PROTOCOL__MAX_CONTEXT_SWITCH_APPLICATIONS]; } CONTROL_PROTOCOL__context_switch_main_header_t; @@ -969,6 +971,7 @@ typedef enum { CONTROL_PROTOCOL__CONTEXT_SWITCH_ACTION_ADD_REPEATED, CONTROL_PROTOCOL__CONTEXT_SWITCH_ACTION_FETCH_CCW_BURSTS, CONTROL_PROTOCOL__CONTEXT_SWITCH_ACTION_BURST_CREDITS_TASK_START, + CONTROL_PROTOCOL__CONTEXT_SWITCH_ACTION_EDGE_LAYER_ACTIVATION_ACTIONS_POSITION, /* must be last*/ CONTROL_PROTOCOL__CONTEXT_SWITCH_ACTION_COUNT, @@ -1054,27 +1057,23 @@ typedef struct { uint32_t bytes_in_pattern; } CONTROL_PROTOCOL__host_buffer_info_t; +/* TODO: merge CONTROL_PROTOCOL__edge_layer_common_info_t into the header (HRT-7113) */ typedef struct { uint8_t communication_type; uint8_t edge_connection_type; } CONTROL_PROTOCOL__edge_layer_header_t; typedef struct { - uint8_t stream_index; + uint8_t engine_index; uint8_t vdma_channel_index; + uint8_t stream_index; uint8_t network_index; CONTROL_PROTOCOL__nn_stream_config_t nn_stream_config; } CONTROL_PROTOCOL__edge_layer_common_info_t; -typedef struct { - uint64_t host_descriptors_base_address; - uint8_t desc_list_depth; -} CONTROL_PROTOCOL__host_desc_address_info_t; - typedef struct { CONTROL_PROTOCOL__edge_layer_common_info_t common_info; - uint32_t frame_credits_in_bytes; - uint16_t desc_page_size; + CONTROL_PROTOCOL__host_buffer_info_t host_buffer_info; } CONTROL_PROTOCOL__network_boundary_output_t; typedef struct { @@ -1084,9 +1083,7 @@ typedef struct { typedef struct { CONTROL_PROTOCOL__edge_layer_common_info_t common_info; - uint32_t frame_credits_in_bytes; - CONTROL_PROTOCOL__host_desc_address_info_t host_desc_address_info; - uint16_t desc_page_size; + CONTROL_PROTOCOL__host_buffer_info_t host_buffer_info; uint32_t buffered_rows_count; } CONTROL_PROTOCOL__ddr_buffer_output_t; @@ -1097,7 +1094,7 @@ typedef struct { typedef struct { CONTROL_PROTOCOL__edge_layer_common_info_t common_info; - uint16_t desc_page_size; + CONTROL_PROTOCOL__host_buffer_info_t host_buffer_info; uint32_t initial_credit_size; } CONTROL_PROTOCOL__network_boundary_input_t; @@ -1109,8 +1106,10 @@ typedef struct { typedef struct { CONTROL_PROTOCOL__edge_layer_common_info_t common_info; - CONTROL_PROTOCOL__host_desc_address_info_t host_desc_address_info; + CONTROL_PROTOCOL__host_buffer_info_t host_buffer_info; uint32_t initial_credit_size; + uint8_t connected_d2h_engine_index; + uint8_t connected_d2h_channel_index; } CONTROL_PROTOCOL__ddr_buffer_input_t; typedef struct { @@ -1121,6 +1120,12 @@ typedef struct { uint8_t should_use_stream_remap; } CONTROL_PROTOCOL__stream_remap_data_t; +typedef struct { + CONTROL_PROTOCOL__host_buffer_info_t config_buffer_info; + uint8_t engine_index; + uint8_t vdma_channel_index; +} CONTROL_PROTOCOL__config_channel_info_t; + #if defined(_MSC_VER) // TODO: warning C4200 #pragma warning(push) @@ -1133,8 +1138,8 @@ typedef struct { uint8_t is_last_control_per_context; uint32_t cfg_channels_count_length; uint8_t cfg_channels_count; - uint32_t config_buffer_infos_length; - CONTROL_PROTOCOL__host_buffer_info_t config_buffer_infos[CONTROL_PROTOCOL__MAX_CFG_CHANNELS]; + uint32_t config_channel_infos_length; + CONTROL_PROTOCOL__config_channel_info_t config_channel_infos[CONTROL_PROTOCOL__MAX_CFG_CHANNELS]; uint32_t context_stream_remap_data_length; CONTROL_PROTOCOL__stream_remap_data_t context_stream_remap_data; uint32_t number_of_edge_layers_length; @@ -1209,14 +1214,14 @@ typedef struct { /* Must be first */ CONTROL_PROTOCOL__ACTION_HEADER_t header; uint16_t descriptors_count; - uint8_t cfg_channel_handle; + uint8_t config_stream_index; } CONTROL_PROTOCOL__READ_VDMA_ACTION_t; typedef struct { /* Must be first */ CONTROL_PROTOCOL__ACTION_HEADER_t header; uint16_t ccw_bursts; - uint8_t cfg_channel_handle; + uint8_t config_stream_index; } CONTROL_PROTOCOL__FETCH_CCW_BURSTS_ACTION_t; typedef struct { @@ -1283,7 +1288,9 @@ typedef struct { typedef struct { /* Must be first */ CONTROL_PROTOCOL__ACTION_HEADER_t header; + uint8_t h2d_engine_index; uint8_t h2d_vdma_channel_index; + uint8_t d2h_engine_index; uint8_t d2h_vdma_channel_index; uint32_t descriptors_per_frame; uint16_t programmed_descriptors_count; @@ -1299,6 +1306,11 @@ typedef struct { CONTROL_PROTOCOL__ACTION_HEADER_t header; } CONTROL_PROTOCOL__BURST_CREDITS_TASK_START_ACTION_T; +typedef struct { + /* Must be first */ + CONTROL_PROTOCOL__ACTION_HEADER_t header; +} CONTROL_PROTOCOL__EDGE_LAYER_ACTIVATION_ACTIONS_POSITION_MARKER_T; + typedef struct { CONTROL_PROTOCOL__TRIGGER_t trigger; uint16_t triggers_action_count; @@ -1360,6 +1372,8 @@ typedef struct { uint8_t application_index; uint32_t dynamic_batch_size_length; uint16_t dynamic_batch_size; + uint32_t keep_nn_config_during_reset_length; + uint8_t keep_nn_config_during_reset; } CONTROL_PROTOCOL__change_context_switch_status_request_t; typedef struct { @@ -1371,13 +1385,6 @@ typedef struct { uint8_t interrupt_sub_index; } CONTROL_PROTOCOL__set_dataflow_interrupt_request_t; -typedef struct { - uint32_t application_index_length; - uint8_t application_index; - uint32_t dynamic_batch_size_length; - uint16_t dynamic_batch_size; -} CONTROL_PROTOCOL__switch_application_request_t; - typedef struct { uint32_t connection_type_length; uint8_t connection_type; @@ -1429,6 +1436,8 @@ typedef struct { CONTROL_PROTOCOL_fuse_info_t fuse_info; uint32_t pd_info_length; uint8_t pd_info[PM_RESULTS_LENGTH]; + uint32_t partial_clusters_layout_bitmap_length; + uint32_t partial_clusters_layout_bitmap; } CONTROL_PROTOCOL__get_extended_device_information_response_t; /* Tightly coupled to hailo_throttling_level_t */ @@ -1446,8 +1455,8 @@ typedef struct { uint8_t current_overcurrent_zone; uint32_t red_overcurrent_threshold_length; float32_t red_overcurrent_threshold; - uint32_t orange_overcurrent_threshold_length; - float32_t orange_overcurrent_threshold; + uint32_t overcurrent_throttling_active_length; + bool overcurrent_throttling_active; uint32_t temperature_throttling_active_length; bool temperature_throttling_active; uint32_t current_temperature_zone_length; @@ -1464,6 +1473,10 @@ typedef struct { int32_t red_temperature_threshold; uint32_t red_hysteresis_temperature_threshold_length; int32_t red_hysteresis_temperature_threshold; + uint32_t requested_overcurrent_clock_freq_length; + uint32_t requested_overcurrent_clock_freq; + uint32_t requested_temperature_clock_freq_length; + uint32_t requested_temperature_clock_freq; } CONTROL_PROTOCOL__get_health_information_response_t; typedef enum { @@ -1659,7 +1672,6 @@ typedef union { CONTROL_PROTOCOL__d2h_event_manager_send_host_info_event_request_t d2h_event_manager_send_host_info_event_request; CONTROL_PROTOCOL__read_board_config_request_t read_board_config_request; CONTROL_PROTOCOL__write_board_config_request_t write_board_config_request; - CONTROL_PROTOCOL__switch_application_request_t switch_application_request; CONTROL_PROTOCOL__config_context_switch_breakpoint_request_t config_context_switch_breakpoint_request; CONTROL_PROTOCOL__get_context_switch_breakpoint_status_request_t get_context_switch_breakpoint_status_request; CONTROL_PROTOCOL__enable_debugging_request_t enable_debugging_request; @@ -1713,7 +1725,7 @@ typedef struct { bool is_first_control_per_context; bool is_last_control_per_context; uint8_t cfg_channels_count; - CONTROL_PROTOCOL__host_buffer_info_t config_buffer_infos[CONTROL_PROTOCOL__MAX_CFG_CHANNELS]; + CONTROL_PROTOCOL__config_channel_info_t config_channel_infos[CONTROL_PROTOCOL__MAX_CFG_CHANNELS]; CONTROL_PROTOCOL__stream_remap_data_t context_stream_remap_data; uint8_t number_of_edge_layers; uint8_t number_of_trigger_groups; diff --git a/common/include/d2h_events.h b/common/include/d2h_events.h index b293a97..e2c78d9 100644 --- a/common/include/d2h_events.h +++ b/common/include/d2h_events.h @@ -102,10 +102,10 @@ typedef struct { typedef struct { uint32_t overcurrent_zone; float32_t exceeded_alert_threshold; - float32_t sampled_current_during_alert; + bool is_last_overcurrent_violation_reached; } D2H_EVENT_health_monitor_overcurrent_alert_event_message_t; -#define D2H_EVENT_HEALTH_MONITOR_OVERCURRENT_ALERT_EVENT_PARAMETER_COUNT (2) +#define D2H_EVENT_HEALTH_MONITOR_OVERCURRENT_ALERT_EVENT_PARAMETER_COUNT (4) /* D2H_EVENT_health_monitor_lcu_ecc_error_event_message_t should be the same as hailo_health_monitor_lcu_ecc_error_notification_message_t */ typedef struct { diff --git a/common/include/firmware_status.h b/common/include/firmware_status.h index 88350aa..edea0ac 100644 --- a/common/include/firmware_status.h +++ b/common/include/firmware_status.h @@ -108,6 +108,9 @@ Updating rules: FIRMWARE_STATUS__X(HAILO_STATUS_UPDATE_CLOCK_CORE_CPU_FAILED)\ FIRMWARE_STATUS__X(HAILO_STATUS_CONTROL_ETH_INIT_FAILED)\ FIRMWARE_STATUS__X(HAILO_STATUS_SEMAPHORE_INIT_FAILED)\ + FIRMWARE_STATUS__X(HAILO_STATUS_DRAM_DMA_SERVICE_INIT_FAILED)\ + FIRMWARE_STATUS__X(HAILO_STATUS_VDMA_SERVICE_INIT_FAILED)\ + FIRMWARE_STATUS__X(HAILO_STATUS_ERROR_HANDLING_STACK_OVERFLOW)\ \ FIRMWARE_MODULE__X(FIRMWARE_MODULE__DATAFLOW)\ FIRMWARE_STATUS__X(HAILO_DATAFLOW_STATUS_INVALID_PARAMETER)\ @@ -399,6 +402,8 @@ Updating rules: FIRMWARE_STATUS__X(CONTROL_PROTOCOL_STATUS_INVALID_CFG_CHANNELS_COUNT_LENGTH)\ FIRMWARE_STATUS__X(CONTROL_PROTOCOL_STATUS_INVALID_DYNAMIC_BATCH_SIZE_LENGTH)\ FIRMWARE_STATUS__X(CONTROL_PROTOCOL_STATUS_INVALID_INFER_FEATURES_LENGTH) /* DEPRECATED */\ + FIRMWARE_STATUS__X(CONTROL_PROTOCOL_STATUS_INVALID_CONFIG_CHANNEL_INFOS)\ + FIRMWARE_STATUS__X(CONTROL_PROTOCOL_STATUS_INVALID_IS_BATCH_SIZE_FLOW_LENGTH)\ \ FIRMWARE_MODULE__X(FIRMWARE_MODULE__POWER_MEASUREMENT)\ FIRMWARE_STATUS__X(HAILO_POWER_MEASUREMENT_STATUS_POWER_INIT_ERROR)\ @@ -735,6 +740,11 @@ Updating rules: FIRMWARE_STATUS__X(CONTEXT_SWITCH_STATUS_INVALID_CFG_CHANNELS_COUNT)\ FIRMWARE_STATUS__X(CONTEXT_SWITCH_STATUS_INVALID_HOST_BUFFER_TYPE)\ FIRMWARE_STATUS__X(CONTEXT_SWITCH_STATUS_BURST_CREDITS_TASK_IS_NOT_IDLE)\ + FIRMWARE_STATUS__X(CONTEXT_SWITCH_STATUS_KERNEL_COUNT_OVERFLOW)\ + FIRMWARE_STATUS__X(CONTEXT_SWITCH_STATUS_INVALID_CONFIG_STREAM_INDEX)\ + FIRMWARE_STATUS__X(CONTEXT_SWITCH_STATUS_ADD_ACTION_TO_BATCH_SWITCH_BUFFER_REACHED_FORBIDDEN_MEMORY_SPACE)\ + FIRMWARE_STATUS__X(CONTEXT_SWITCH_TASK_STATUS_WAIT_FOR_INTERRUPT_INTERRUPTED_BY_BATCH_CHANGE_REQUEST)\ + FIRMWARE_STATUS__X(CONTEXT_SWITCH_STATUS_CANT_CLEAR_CONFIGURED_APPS_WHILE_ACTIVATED)\ \ FIRMWARE_MODULE__X(FIRMWARE_MODULE__D2H_EVENT_MANAGER)\ FIRMWARE_STATUS__X(HAILO_D2H_EVENT_MANAGER_STATUS_MESSAGE_HIGH_PRIORITY_QUEUE_CREATE_FAILED)\ @@ -774,7 +784,7 @@ Updating rules: FIRMWARE_STATUS__X(HEALTH_MONITOR_QUEUEING_OVERCURRENT_MESSAGE_FAILED)\ FIRMWARE_STATUS__X(HEALTH_MONITOR_QUEUEING_CORE_RESET_MESSAGE_FAILED)\ FIRMWARE_STATUS__X(HEALTH_MONITOR_QUEUEING_SOFT_RESET_MESSAGE_FAILED)\ - FIRMWARE_STATUS__X(HEALTH_MONITOR_INVALID_OVERCURRENT_OVERCURRENT_ZONE)\ + FIRMWARE_STATUS__X(HEALTH_MONITOR_INVALID_OVERCURRENT_OVERCURRENT_ZONE) /* DEPRECATED -*/\ FIRMWARE_STATUS__X(HEALTH_MONITOR_INVALID_MESSAGE_TYPE)\ FIRMWARE_STATUS__X(HEALTH_MONITOR_INVALID_TEMPERATURE_ALARM_TYPE) /* DEPRECATED - See TEMPERATURE_PROTECTION */\ FIRMWARE_STATUS__X(HEALTH_MONITOR_SETUP_SAFETY_INTERRUPTS_FAILED)\ @@ -843,7 +853,7 @@ Updating rules: FIRMWARE_STATUS__X(GPIO_BAD_PINMUX_GROUP)\ \ FIRMWARE_MODULE__X(FIRMWARE_MODULE__OVERCURRENT_PROTECTION)\ - FIRMWARE_STATUS__X(OVERCURRENT_PROTECTION_INVALID_ALERT_THRESHOLD_VALUE)\ + FIRMWARE_STATUS__X(OVERCURRENT_PROTECTION_INVALID_ALERT_THRESHOLD_VALUE) /* DEPRECATED */\ FIRMWARE_STATUS__X(OVERCURRENT_PROTECTION_INVALID_SAMPLING_PERIOD_VALUE)\ FIRMWARE_STATUS__X(OVERCURRENT_PROTECTION_INVALID_AVERAGING_FACTOR_VALUE)\ FIRMWARE_STATUS__X(OVERCURRENT_PROTECTION_UNSUPPORTED_SENSOR_TYPE)\ @@ -851,7 +861,7 @@ Updating rules: FIRMWARE_STATUS__X(OVERCURRENT_PROTECTION_ALREADY_ACTIVE)\ FIRMWARE_STATUS__X(OVERCURRENT_PROTECTION_IS_NOT_ACTIVE)\ FIRMWARE_STATUS__X(OVERCURRENT_PROTECTION_INVALID_BOARD_CONFIG_VALUES)\ - FIRMWARE_STATUS__X(OVERCURRENT_PROTECTION_NULL_POINTER_PASSED)\ + FIRMWARE_STATUS__X(OVERCURRENT_PROTECTION_NULL_POINTER_PASSED) /* DEPRECATED */\ \ FIRMWARE_MODULE__X(FIRMWARE_MODULE__POWER)\ FIRMWARE_STATUS__X(POWER_INVALID_CONVERSION_TYPE)\ @@ -908,6 +918,7 @@ Updating rules: FIRMWARE_STATUS__X(DDR_BUFFER_STATUS_TRYING_TO_CHANGE_STATE_TO_INFER_WHILE_ALREADY_DURING_INFER)\ FIRMWARE_STATUS__X(DDR_BUFFER_STATUS_NO_DDR_PAIRS_TO_RUN_INFER_ON)\ FIRMWARE_STATUS__X(DDR_BUFFER_STATUS_INFER_REACHED_TIMEOUT)\ + FIRMWARE_STATUS__X(DDR_BUFFER_STATUS_OVERFLOW_IN_DESCRIPTORS_PER_BATCH)\ \ FIRMWARE_MODULE__X(FIRMWARE_MODULE__PL320)\ FIRMWARE_STATUS__X(PL320_MAILBOX_BUSY)\ @@ -984,6 +995,8 @@ Updating rules: FIRMWARE_STATUS__X(VDMA_SERVICE_STATUS_INITIAL_DESC_BIGGER_THAN_TOTAL)\ FIRMWARE_STATUS__X(VDMA_SERVICE_STATUS_INVALID_INITIAL_CREDIT_SIZE)\ FIRMWARE_STATUS__X(VDMA_SERVICE_STATUS_TOO_LARGE_BYTES_IN_PATTERN)\ + FIRMWARE_STATUS__X(VDMA_SERVICE_STATUS_INVALID_ENGINE_INDEX)\ + FIRMWARE_STATUS__X(VDMA_SERVICE_STATUS_INVALID_CONSTANTS)\ \ FIRMWARE_MODULE__X(FIRMWARE_MODULE__MEMORY_LOGGER)\ FIRMWARE_STATUS__X(MEMORY_LOGGER_STATUS_DEBUG_INSUFFICIENT_MEMORY)\ @@ -1007,6 +1020,13 @@ Updating rules: FIRMWARE_STATUS__X(DRAM_DMA_SERVICE_TOTAL_DESCS_COUNT_MUST_BE_POWER_OF_2)\ FIRMWARE_STATUS__X(DRAM_DMA_SERVICE_INVALID_DESCS_COUNT)\ FIRMWARE_STATUS__X(DRAM_DMA_SERVICE_DESC_PER_INTERRUPT_NOT_IN_MASK)\ + FIRMWARE_STATUS__X(DRAM_DMA_SERVICE_STATUS_INVALID_ENGINE_INDEX)\ + FIRMWARE_STATUS__X(DRAM_DMA_SERVICE_STATUS_INVALID_QM_INDEX)\ + FIRMWARE_STATUS__X(DRAM_DMA_SERVICE_STATUS_INVALID_CHANNEL_TYPE)\ + FIRMWARE_STATUS__X(DRAM_DMA_SERVICE_STATUS_INVALID_PERIPH_BYTES_PER_BUFFER)\ + FIRMWARE_STATUS__X(DRAM_DMA_SERVICE_STATUS_INVALID_BYTES_IN_PATTERN)\ + FIRMWARE_STATUS__X(DRAM_DMA_SERVICE_STATUS_INVALID_STREAM_INDEX)\ + FIRMWARE_STATUS__X(DRAM_DMA_SERVICE_STATUS_INVALID_CHANNEL_INDEX)\ \ FIRMWARE_MODULE__X(FIRMWARE_MODULE__NN_CORE_SERVICE)\ FIRMWARE_STATUS__X(NN_CORE_SERVICE_STATUS_INVALID_ARG_PASSED)\ @@ -1020,6 +1040,7 @@ Updating rules: FIRMWARE_STATUS__X(DATA_STREAM_WRAPPER_STATUS_INVALID_STREAM_INDEX)\ FIRMWARE_STATUS__X(DATA_STREAM_MANAGER_STATUS_INVALID_CREDIT_TYPE)\ FIRMWARE_STATUS__X(DATA_STREAM_MANAGER_WRAPPER_STATUS_INVALID_HOST_BUFFER_TYPE)\ + FIRMWARE_STATUS__X(DATA_STREAM_MANAGER_STATUS_BATCH_CREDITS_OVERFLOW)\ \ FIRMWARE_MODULE__X(FIRMWARE_MODULE__BURST_CREDITS_TASK)\ FIRMWARE_STATUS__X(BURST_CREDITS_TASK_STATUS_TRYING_TO_ADD_ACTION_WHILE_NOT_IN_IDLE_STATE)\ diff --git a/common/include/md5.h b/common/include/md5.h index d012b5d..71a315d 100644 --- a/common/include/md5.h +++ b/common/include/md5.h @@ -30,7 +30,8 @@ /** Start of modifications for the open source file. **/ #include "stdint.h" -typedef uint8_t MD5_SUM_t[16]; +#define MD5_DIGEST_LENGTH 16 +typedef uint8_t MD5_SUM_t[MD5_DIGEST_LENGTH]; /* Any 32-bit or wider unsigned integer data type will do */ typedef size_t MD5_u32plus; /** End of modifications. **/ diff --git a/common/include/user_config_common.h b/common/include/user_config_common.h index 009bc4c..8ecd24c 100644 --- a/common/include/user_config_common.h +++ b/common/include/user_config_common.h @@ -69,6 +69,19 @@ typedef enum { SOC__NN_CLOCK_100MHz = 100 * 1000 * 1000 } SOC__NN_CLOCK_HZ_t; +typedef enum { + SOC__CPU_CLOCK_200MHz = SOC__NN_CLOCK_400MHz >> 1, + SOC__CPU_CLOCK_187MHz = SOC__NN_CLOCK_375MHz >> 1, + SOC__CPU_CLOCK_175MHz = SOC__NN_CLOCK_350MHz >> 1, + SOC__CPU_CLOCK_162MHz = SOC__NN_CLOCK_325MHz >> 1, + SOC__CPU_CLOCK_150MHz = SOC__NN_CLOCK_300MHz >> 1, + SOC__CPU_CLOCK_137MHz = SOC__NN_CLOCK_275MHz >> 1, + SOC__CPU_CLOCK_125MHz = SOC__NN_CLOCK_250MHz >> 1, + SOC__CPU_CLOCK_112MHz = SOC__NN_CLOCK_225MHz >> 1, + SOC__CPU_CLOCK_100MHz = SOC__NN_CLOCK_200MHz >> 1, + SOC__CPU_CLOCK_50MHz = SOC__NN_CLOCK_100MHz >> 1 +} SOC__CPU_CLOCK_HZ_t; + typedef enum { WD_SERVICE_MODE_HW_SW = 0, WD_SERVICE_MODE_HW_ONLY, diff --git a/common/include/utils.h b/common/include/utils.h index 8bdfec3..7e48489 100644 --- a/common/include/utils.h +++ b/common/include/utils.h @@ -99,6 +99,18 @@ PP_HASCOMMA(PP_COMMA __VA_ARGS__ ()),\ PP_NARG_(__VA_ARGS__, PP_RSEQ_N())) +#define PP_ISEMPTY(...) \ +_PP_ISEMPTY( \ + PP_HASCOMMA(__VA_ARGS__), \ + PP_HASCOMMA(PP_COMMA __VA_ARGS__), \ + PP_HASCOMMA(__VA_ARGS__ (/*empty*/)), \ + PP_HASCOMMA(PP_COMMA __VA_ARGS__ (/*empty*/)) \ + ) + +#define PP_PASTE5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4 +#define _PP_ISEMPTY(_0, _1, _2, _3) PP_HASCOMMA(PP_PASTE5(_PP_IS_EMPTY_CASE_, _0, _1, _2, _3)) +#define _PP_IS_EMPTY_CASE_0001 , + #define UNUSED0(...) #define UNUSED1(x) (void)(x) #define UNUSED2(x,y) (void)(x),(void)(y) @@ -111,4 +123,6 @@ #define ALL_UNUSED_IMPL(nargs) ALL_UNUSED_IMPL_(nargs) #define ALL_UNUSED(...) ALL_UNUSED_IMPL( PP_NARG(__VA_ARGS__))(__VA_ARGS__ ) +#define MICROSECONDS_IN_MILLISECOND (1000) + #endif /* __UTILS_H__ */ diff --git a/hailort/CMakeLists.txt b/hailort/CMakeLists.txt index 3b8206d..fa34309 100644 --- a/hailort/CMakeLists.txt +++ b/hailort/CMakeLists.txt @@ -2,22 +2,29 @@ cmake_minimum_required(VERSION 3.0.0) # Set firmware version add_definitions( -DFIRMWARE_VERSION_MAJOR=4 ) -add_definitions( -DFIRMWARE_VERSION_MINOR=8 ) -add_definitions( -DFIRMWARE_VERSION_REVISION=1 ) +add_definitions( -DFIRMWARE_VERSION_MINOR=10 ) +add_definitions( -DFIRMWARE_VERSION_REVISION=0 ) +if(HAILO_BUILD_SERVICE) + add_definitions( -DHAILO_SUPPORT_MULTI_PROCESS ) +endif() message(STATUS "Building pre_build") -include(${CMAKE_CURRENT_LIST_DIR}/libhailort/cmake/execute_cmake.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/cmake/execute_cmake.cmake) set(HAILO_EXTERNAL_DIR ${CMAKE_CURRENT_LIST_DIR}/external) +set(HAILO_PRE_BUILD_BUILD_TOOLS ${CMAKE_CURRENT_LIST_DIR}/pre_build/build/tools) + +set(PRE_BUILD_BUILD_TYPE "Release") execute_cmake( SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/pre_build BUILD_DIR ${CMAKE_CURRENT_LIST_DIR}/pre_build/build CONFIGURE_ARGS - -DCMAKE_BUILD_TYPE=Release + -DCMAKE_BUILD_TYPE=${PRE_BUILD_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_LIST_DIR}/pre_build/install -DHAILO_EXTERNAL_DIR=${HAILO_EXTERNAL_DIR} -DHAILO_OFFLINE_COMPILATION=${HAILO_OFFLINE_COMPILATION} + -DHAILO_BUILD_SERVICE=${HAILO_BUILD_SERVICE} BUILD_ARGS - --config ${CMAKE_BUILD_TYPE} --target install ${CMAKE_EXTRA_BUILD_ARGS} + --config ${PRE_BUILD_BUILD_TYPE} --target install ${CMAKE_EXTRA_BUILD_ARGS} PARALLEL_BUILD ) @@ -53,13 +60,24 @@ endif() add_subdirectory(external/protobuf/cmake EXCLUDE_FROM_ALL) if(NOT MSVC) set_target_properties(libprotobuf PROPERTIES POSITION_INDEPENDENT_CODE ON) + set_target_properties(libprotobuf-lite PROPERTIES POSITION_INDEPENDENT_CODE ON) +endif() + +if(HAILO_BUILD_SERVICE) + if(CMAKE_HOST_UNIX) + set(HAILO_GRPC_CPP_PLUGIN_EXECUTABLE "${HAILO_PRE_BUILD_BUILD_TOOLS}/build_grpc/grpc_cpp_plugin") + else() + set(HAILO_GRPC_CPP_PLUGIN_EXECUTABLE "${HAILO_PRE_BUILD_BUILD_TOOLS}/build_grpc/${PRE_BUILD_BUILD_TYPE}/grpc_cpp_plugin.exe") + endif() endif() +set(HAILO_PROTOBUF_PROTOC $) set(HAILORT_INC_DIR ${PROJECT_SOURCE_DIR}/hailort/libhailort/include) set(HAILORT_SRC_DIR ${PROJECT_SOURCE_DIR}/hailort/libhailort/src) set(HAILORT_COMMON_DIR ${PROJECT_SOURCE_DIR}/hailort/) set(COMMON_INC_DIR ${PROJECT_SOURCE_DIR}/common/include) set(DRIVER_INC_DIR ${PROJECT_SOURCE_DIR}/hailort/drivers/common) +set(RPC_DIR ${PROJECT_SOURCE_DIR}/hailort/rpc) if(HAILO_BUILD_PYBIND) if(NOT PYTHON_EXECUTABLE AND PYBIND11_PYTHON_VERSION) @@ -87,6 +105,17 @@ if(CMAKE_SYSTEM_NAME STREQUAL QNX) target_compile_definitions(pevents PRIVATE -DWFMO) endif() +if(HAILO_BUILD_SERVICE) + set(BUILD_TESTING OFF) # disabe abseil tests + set(gRPC_ZLIB_PROVIDER "module" CACHE STRING "Provider of zlib library") + # The following is an awful hack needed in order to force grpc to use our libprotobuf+liborotoc targets + # ('formal' options are to let grpc recompile it which causes a name conflict, + # or let it use find_package and take the risk it will use a different installed lib) + set(gRPC_PROTOBUF_PROVIDER "hack" CACHE STRING "Provider of protobuf library") + add_subdirectory(external/grpc EXCLUDE_FROM_ALL) + add_subdirectory(rpc) +endif() + # microprofile if(HAILO_MICROPROFILE) add_library(microprofile STATIC EXCLUDE_FROM_ALL external/microprofile/microprofile.cpp) @@ -114,6 +143,10 @@ add_subdirectory(common) add_subdirectory(libhailort) add_subdirectory(hailortcli) +if(HAILO_BUILD_SERVICE) + add_subdirectory(hailort_service) +endif() + if(HAILO_WIN_DRIVER) add_subdirectory(drivers/win) add_subdirectory(packaging) diff --git a/hailort/LICENSE-3RD-PARTY.md b/hailort/LICENSE-3RD-PARTY.md index 9c86db7..46aead9 100644 --- a/hailort/LICENSE-3RD-PARTY.md +++ b/hailort/LICENSE-3RD-PARTY.md @@ -1,15 +1,16 @@ | Package | Copyright (c) | License | Version | Notes | References | |:---------------------------------|:----------------------------------|:-------------------|:---------------|:-------------------------------------------|:------------------------------------------------------------------------------| -| CLI11 | University of Cincinnati | 3-Clause BSD | 1.7 | Cloned entire package | https://github.com/CLIUtils/CLI11 | +| CLI11 | University of Cincinnati | 3-Clause BSD | 2.2.0 | Fork | https://github.com/hailo-ai/CLI11 | | Catch2 | Catch2 Authors | BSL-1.0 | 2.13.7 | Cloned entire package | https://github.com/catchorg/Catch2 | -| protobuf | Google Inc. | BSD | 3.11.4 | Cloned entire package | https://github.com/protocolbuffers/protobuf | +| protobuf | Google Inc. | BSD | 3.19.4 | Cloned entire package | https://github.com/protocolbuffers/protobuf | | pybind11 | Wenzel Jakob | BSD | 2.6.2 | Cloned entire package | https://github.com/pybind/pybind11 | | spdlog | Gabi Melman | MIT | 1.6.1 | Cloned entire package | https://github.com/gabime/spdlog | | folly | Facebook, Inc. and its affiliates | Apache License 2.0 | v2020.08.17.00 | Copied only the file `folly/TokenBucket.h` | https://github.com/facebook/folly | | nlohmann_json_cmake_fetchcontent | ArthurSonzogni | MIT License | v3.9.1 | Cloned entire package | https://github.com/ArthurSonzogni/nlohmann_json_cmake_fetchcontent | | readerwriterqueue | Cameron Desrochers | Simplified BSD | 1.0.3 | Cloned entire package | https://github.com/cameron314/readerwriterqueue | -| DotWriter | John Vilk | MIT License | master | Cloned entire package (forked) | https://github.com/jvilk/DotWriter | +| DotWriter | John Vilk | MIT License | master | Fork | https://github.com/hailo-ai/DotWriter | | benchmark | Google Inc. | Apache License 2.0 | 1.6.0 | Cloned entire package | https://github.com/google/benchmark.git | | md5 | Alexander Peslyak | cut-down BSD | - | Copied code from website | http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 | | pevents | Mahmoud Al-Qudsi | MIT License | master | Cloned entire package | https://github.com/neosmart/pevents.git | | microprofile | Jonas Meyer | Unlicense License | 3.1 | Cloned entire package | https://github.com/jonasmr/microprofile | +| grpc | Google Inc. | Apache License 2.0 | 1.46.0 | Cloned entire package | https://github.com/grpc/grpc | diff --git a/hailort/libhailort/cmake/common_compiler_options.cmake b/hailort/cmake/common_compiler_options.cmake similarity index 91% rename from hailort/libhailort/cmake/common_compiler_options.cmake rename to hailort/cmake/common_compiler_options.cmake index da48baf..071eed9 100644 --- a/hailort/libhailort/cmake/common_compiler_options.cmake +++ b/hailort/cmake/common_compiler_options.cmake @@ -4,7 +4,7 @@ FUNCTION(disable_exceptions target) if(WIN32) # TODO: Find the right flag for windows (-fno-exceptions equivalent) elseif(UNIX) - target_compile_options(libhailort PRIVATE -fno-exceptions) + target_compile_options(${target} PRIVATE -fno-exceptions) else() message(FATAL_ERROR "Unexpeced host, stopping build") endif() diff --git a/hailort/libhailort/cmake/execute_cmake.cmake b/hailort/cmake/execute_cmake.cmake similarity index 100% rename from hailort/libhailort/cmake/execute_cmake.cmake rename to hailort/cmake/execute_cmake.cmake diff --git a/hailort/common/async_thread.hpp b/hailort/common/async_thread.hpp index fa53bfa..6771852 100644 --- a/hailort/common/async_thread.hpp +++ b/hailort/common/async_thread.hpp @@ -12,7 +12,6 @@ #include #include #include -#include namespace hailort { @@ -27,7 +26,7 @@ public: explicit AsyncThread(std::function func) : m_result(), m_thread([this, func]() { - m_result.store(func()); + m_result = func(); }) {} @@ -46,14 +45,15 @@ public: if (m_thread.joinable()) { m_thread.join(); } - return m_result.load(); + return std::move(m_result); } private: - std::atomic m_result; + T m_result; std::thread m_thread; }; + template using AsyncThreadPtr = std::unique_ptr>; diff --git a/hailort/common/circular_buffer.hpp b/hailort/common/circular_buffer.hpp index 8df5461..8da5bf0 100644 --- a/hailort/common/circular_buffer.hpp +++ b/hailort/common/circular_buffer.hpp @@ -87,7 +87,7 @@ public: CB_DEQUEUE(m_circ, 1); } - T& front() + T &front() { return m_array[CB_TAIL(m_circ)]; } @@ -102,6 +102,11 @@ public: return 0 == CB_AVAIL(m_circ, CB_HEAD(m_circ), CB_TAIL(m_circ)); } + size_t size() + { + return CB_PROG(m_circ, CB_HEAD(m_circ), CB_TAIL(m_circ)); + } + private: circbuf_t m_circ; std::vector m_array; diff --git a/hailort/common/filesystem.hpp b/hailort/common/filesystem.hpp index 8f6a583..3afe4e8 100644 --- a/hailort/common/filesystem.hpp +++ b/hailort/common/filesystem.hpp @@ -16,6 +16,7 @@ #include "hailo/expected.hpp" #include #include +#include #if defined(__GNUC__) #include @@ -29,7 +30,10 @@ public: Filesystem() = delete; static Expected> get_files_in_dir_flat(const std::string &dir_path); + static Expected> get_latest_files_in_dir_flat(const std::string &dir_path, std::chrono::milliseconds time_interval); + static Expected get_file_modified_time(const std::string &file_path); static Expected is_directory(const std::string &path); + static hailo_status create_directory(const std::string &dir_path); static bool has_suffix(const std::string &file_name, const std::string &suffix) { return (file_name.size() >= suffix.size()) && equal(suffix.rbegin(), suffix.rend(), file_name.rbegin()); @@ -92,6 +96,37 @@ private: #endif }; +// TODO: HRT-7304 - Add support for windows +#if defined(__GNUC__) +class TempFile { +public: + static Expected create(const std::string &file_name, const std::string &file_directory = ""); + ~TempFile(); + + std::string name() const; + +private: + TempFile(const char *path); + + std::string m_path; +}; + +class LockedFile { +public: + // The mode param is the string containing the file access mode, compatible with `fopen` function. + static Expected create(const std::string &file_path, const std::string &mode); + ~LockedFile(); + + int get_fd() const; + +private: + LockedFile(FILE *fp, int fd); + + FILE *m_fp; + int m_fd; +}; +#endif + } /* namespace hailort */ #endif /* _OS_FILESYSTEM_HPP_ */ diff --git a/hailort/common/os/posix/filesystem.cpp b/hailort/common/os/posix/filesystem.cpp index fd96ce2..a22d237 100644 --- a/hailort/common/os/posix/filesystem.cpp +++ b/hailort/common/os/posix/filesystem.cpp @@ -13,11 +13,13 @@ #include #include +#include namespace hailort { const char *Filesystem::SEPARATOR = "/"; +const std::string UNIQUE_TMP_FILE_SUFFIX = "XXXXXX\0"; Expected Filesystem::DirWalker::create(const std::string &dir_path) { @@ -85,6 +87,61 @@ Expected> Filesystem::get_files_in_dir_flat(const std:: static_assert(false, "Unsupported Platform!"); #endif +Expected Filesystem::get_file_modified_time(const std::string &file_path) +{ + struct stat attr; + auto res = stat(file_path.c_str(), &attr); + CHECK_AS_EXPECTED((0 == res), HAILO_INTERNAL_FAILURE, "stat() failed on file {}, with errno {}", file_path, errno); + auto last_modification_time = attr.st_mtime; + return last_modification_time; +} + +#if defined(__linux__) + +Expected> Filesystem::get_latest_files_in_dir_flat(const std::string &dir_path, + std::chrono::milliseconds time_interval) +{ + std::time_t curr_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + const std::string dir_path_with_sep = has_suffix(dir_path, SEPARATOR) ? dir_path : dir_path + SEPARATOR; + + auto dir = DirWalker::create(dir_path_with_sep); + CHECK_EXPECTED(dir); + + std::vector files; + struct dirent *entry = nullptr; + while ((entry = dir->next_file()) != nullptr) { + if (entry->d_type != DT_REG) { + continue; + } + + const std::string file_path = dir_path_with_sep + std::string(entry->d_name); + auto file_modified_time = get_file_modified_time(file_path); + CHECK_EXPECTED(file_modified_time); + + auto time_diff_sec = std::difftime(curr_time, file_modified_time.value()); + auto time_diff_millisec = time_diff_sec * 1000; + if (time_diff_millisec <= static_cast(time_interval.count())) { + files.emplace_back(file_path); + } + } + + return files; +} + +#elif defined(__QNX__) +Expected> Filesystem::get_latest_files_in_dir_flat(const std::string &dir_path, + std::chrono::milliseconds time_interval) +{ + // TODO: HRT-7643 + (void)dir_path; + (void)time_interval; + return make_unexpected(HAILO_NOT_IMPLEMENTED); +} +// Unsupported Platform +#else +static_assert(false, "Unsupported Platform!"); +#endif // __linux__ + Expected Filesystem::is_directory(const std::string &path) { struct stat path_stat{}; @@ -94,4 +151,75 @@ Expected Filesystem::is_directory(const std::string &path) return S_ISDIR(path_stat.st_mode); } +hailo_status Filesystem::create_directory(const std::string &dir_path) +{ + auto ret_val = mkdir(dir_path.c_str(), S_IRWXU | S_IRWXG | S_IRWXO ); + CHECK((ret_val == 0) || (errno == EEXIST), HAILO_FILE_OPERATION_FAILURE, "Failed to create directory {}", dir_path); + return HAILO_SUCCESS; +} + +Expected TempFile::create(const std::string &file_name, const std::string &file_directory) +{ + if (!file_directory.empty()) { + auto status = Filesystem::create_directory(file_directory); + CHECK_SUCCESS_AS_EXPECTED(status); + } + + std::string file_path = file_directory + file_name + UNIQUE_TMP_FILE_SUFFIX; + char *fname = static_cast(std::malloc(sizeof(char) * (file_path.length() + 1))); + std::strncpy(fname, file_path.c_str(), file_path.length() + 1); + + int fd = mkstemp(fname); + CHECK_AS_EXPECTED((-1 != fd), HAILO_FILE_OPERATION_FAILURE, "Failed to create tmp file {}, with errno {}", file_path, errno); + close(fd); + + return TempFile(fname); +} + +TempFile::TempFile(const char *path) : m_path(path) +{} + +TempFile::~TempFile() +{ + // TODO: Guarantee file deletion upon unexpected program termination. + std::remove(m_path.c_str()); +} + +std::string TempFile::name() const +{ + return m_path; +} + +Expected LockedFile::create(const std::string &file_path, const std::string &mode) +{ + auto fp = fopen(file_path.c_str(), mode.c_str()); + CHECK_AS_EXPECTED((nullptr != fp), HAILO_OPEN_FILE_FAILURE, "Failed opening file: {}, with errno: {}", file_path, errno); + + int fd = fileno(fp); + int done = flock(fd, LOCK_EX | LOCK_NB); + if (-1 == done) { + LOGGER__ERROR("Failed to flock file: {}, with errno: {}", file_path, errno); + fclose(fp); + return make_unexpected(HAILO_FILE_OPERATION_FAILURE); + } + + return LockedFile(fp, fd); +} + +LockedFile::LockedFile(FILE *fp, int fd) : m_fp(fp), m_fd(fd) +{} + +LockedFile::~LockedFile() +{ + if (-1 == flock(m_fd, LOCK_UN)) { + LOGGER__ERROR("Failed to unlock file with errno {}", errno); + fclose(m_fp); + } +} + +int LockedFile::get_fd() const +{ + return m_fd; +} + } /* namespace hailort */ diff --git a/hailort/common/os/posix/socket.cpp b/hailort/common/os/posix/socket.cpp index 2f4ac8f..74d5fc3 100644 --- a/hailort/common/os/posix/socket.cpp +++ b/hailort/common/os/posix/socket.cpp @@ -129,7 +129,9 @@ hailo_status Socket::pton(int af, const char *src, void *dst) CHECK_ARG_NOT_NULL(dst); inet_rc = inet_pton(af, reinterpret_cast(src), dst); - CHECK(1 == inet_rc, HAILO_ETH_FAILURE, "Could not convert string ip address to sockaddr struct"); + if (1 != inet_rc) { + return HAILO_ETH_FAILURE; + } return HAILO_SUCCESS; } diff --git a/hailort/common/os/windows/ethernet_utils.cpp b/hailort/common/os/windows/ethernet_utils.cpp index 70ddc0a..8fec8d4 100644 --- a/hailort/common/os/windows/ethernet_utils.cpp +++ b/hailort/common/os/windows/ethernet_utils.cpp @@ -170,7 +170,7 @@ hailo_status EthernetUtils::get_interface_from_board_ip(const char *board_ip, ch struct in_addr board_ip_struct{}; auto status = Socket::pton(AF_INET, board_ip, &board_ip_struct); - CHECK_SUCCESS(status); + CHECK_SUCCESS(status, "Invalid board ip address {}", board_ip); for (const auto& network_interface : network_interfaces.value()) { auto arp_table = ArpTable::create(network_interface.index()); diff --git a/hailort/common/os/windows/filesystem.cpp b/hailort/common/os/windows/filesystem.cpp index caf19e1..f274385 100644 --- a/hailort/common/os/windows/filesystem.cpp +++ b/hailort/common/os/windows/filesystem.cpp @@ -120,6 +120,22 @@ Expected> Filesystem::get_files_in_dir_flat(const std:: return files; } + +Expected> Filesystem::get_latest_files_in_dir_flat(const std::string &dir_path, std::chrono::milliseconds time_interval) +{ + // TODO: HRT-7304 + (void)dir_path; + (void)time_interval; + return make_unexpected(HAILO_NOT_IMPLEMENTED); +} + +Expected Filesystem::get_file_modified_time(const std::string &file_path) +{ + // TODO: HRT-7304 + (void)file_path; + return make_unexpected(HAILO_NOT_IMPLEMENTED); +} + Expected Filesystem::is_directory(const std::string &path) { if (path.length() > MAX_PATH) { diff --git a/hailort/common/os/windows/socket.cpp b/hailort/common/os/windows/socket.cpp index 42cc79d..1c7789f 100644 --- a/hailort/common/os/windows/socket.cpp +++ b/hailort/common/os/windows/socket.cpp @@ -138,7 +138,9 @@ hailo_status Socket::pton(int af, const char *src, void *dst) CHECK_EXPECTED_AS_STATUS(module_wrapper); inet_result = inet_pton(af, src, dst); - CHECK(1 == inet_result, HAILO_ETH_FAILURE, "Failed inet_pton. WSALE={}", WSAGetLastError()); + if (1 != inet_result) { + return HAILO_ETH_FAILURE; + } return HAILO_SUCCESS; } diff --git a/hailort/common/string_utils.cpp b/hailort/common/string_utils.cpp index 78440de..ee4ff47 100644 --- a/hailort/common/string_utils.cpp +++ b/hailort/common/string_utils.cpp @@ -10,6 +10,7 @@ #include "common/string_utils.hpp" #include "common/utils.hpp" +#include #include #include @@ -71,4 +72,17 @@ Expected StringUtils::to_uint8(const std::string &str, int base) return static_cast(number.value()); } +std::string StringUtils::to_hex_string(const uint8_t *array, size_t size, bool uppercase, const std::string &delimiter) +{ + std::stringstream stream; + for (size_t i = 0; i < size; i++) { + const auto hex_byte = uppercase ? fmt::format("{:02X}", array[i]) : fmt::format("{:02x}", array[i]); + stream << hex_byte; + if (i != (size - 1)) { + stream << delimiter; + } + } + return stream.str(); +} + } /* namespace hailort */ diff --git a/hailort/common/string_utils.hpp b/hailort/common/string_utils.hpp index 120c7a8..b1f0366 100644 --- a/hailort/common/string_utils.hpp +++ b/hailort/common/string_utils.hpp @@ -21,6 +21,8 @@ public: static Expected to_int32(const std::string &str, int base); static Expected to_uint8(const std::string &str, int base); static Expected to_uint32(const std::string &str, int base); + + static std::string to_hex_string(const uint8_t *array, size_t size, bool uppercase, const std::string &delimiter=""); }; } /* namespace hailort */ diff --git a/hailort/common/utils.hpp b/hailort/common/utils.hpp index a93c6aa..f74cb21 100644 --- a/hailort/common/utils.hpp +++ b/hailort/common/utils.hpp @@ -19,6 +19,7 @@ #include #include + namespace hailort { @@ -221,6 +222,46 @@ _ISEMPTY( \ } while(0) #define CHECK_SUCCESS_AS_EXPECTED(status, ...) _CHECK_SUCCESS_AS_EXPECTED(status, ISEMPTY(__VA_ARGS__), "" __VA_ARGS__) +#ifdef HAILO_SUPPORT_MULTI_PROCESS +#define _CHECK_SUCCESS_AS_RPC_STATUS(status, reply, is_default, fmt, ...) \ + do { \ + const auto &__check_success_status = (status); \ + if (__check_success_status != HAILO_SUCCESS) { \ + reply->set_status(static_cast(__check_success_status)); \ + LOGGER__ERROR( \ + _CONSTRUCT_MSG(is_default, "CHECK_SUCCESS_AS_RPC_STATUS failed with status={}", fmt, __check_success_status, ##__VA_ARGS__) \ + ); \ + return grpc::Status::OK; \ + } \ + } while(0) +#define CHECK_SUCCESS_AS_RPC_STATUS(status, reply, ...) _CHECK_SUCCESS_AS_RPC_STATUS(status, reply, ISEMPTY(__VA_ARGS__), "" __VA_ARGS__) + +#define CHECK_EXPECTED_AS_RPC_STATUS(expected, reply, ...) CHECK_SUCCESS_AS_RPC_STATUS(expected.status(), reply, __VA_ARGS__) + +#define _CHECK_AS_RPC_STATUS(cond, reply, ret_val, ...) \ + do { \ + if (!(cond)) { \ + reply->set_status(ret_val); \ + LOGGER__ERROR( \ + _CONSTRUCT_MSG(is_default, "CHECK_AS_RPC_STATUS failed with status={}", fmt, ret_val, ##__VA_ARGS__) \ + ); \ + return grpc::Status::OK; \ + } \ + } while(0) +#define CHECK_AS_RPC_STATUS(cond, reply, ret_val, ...) _CHECK_AS_RPC_STATUS((cond), (reply), (ret_val), ISEMPTY(__VA_ARGS__), "" __VA_ARGS__) + +#define _CHECK_GRPC_STATUS(status, ret_val) \ + do { \ + if (!status.ok()) { \ + LOGGER__ERROR("CHECK_GRPC_STATUS failed with error massage: {}.", status.error_message()); \ + return ret_val; \ + } \ + } while(0) + +#define CHECK_GRPC_STATUS(status) _CHECK_GRPC_STATUS(status, HAILO_RPC_FAILED) +#define CHECK_GRPC_STATUS_AS_EXPECTED(status) _CHECK_GRPC_STATUS(status, make_unexpected(HAILO_RPC_FAILED)) +#endif + #define _CHECK_EXPECTED(obj, is_default, fmt, ...) \ do { \ const auto &__check_expected_obj = (obj); \ @@ -244,6 +285,15 @@ _ISEMPTY( \ } while(0) #define CHECK_EXPECTED_AS_STATUS(obj, ...) _CHECK_EXPECTED_AS_STATUS(obj, ISEMPTY(__VA_ARGS__), "" __VA_ARGS__) +#ifndef _MSC_VER +#define IGNORE_DEPRECATION_WARNINGS_BEGIN _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#define IGNORE_DEPRECATION_WARNINGS_END _Pragma("GCC diagnostic pop") +#else +#define IGNORE_DEPRECATION_WARNINGS_BEGIN +#define IGNORE_DEPRECATION_WARNINGS_END +#endif + constexpr bool is_powerof2(size_t v) { // bit trick return (v & (v - 1)) == 0; @@ -271,6 +321,18 @@ static uint32_t get_max_value_of_unordered_map(const std::unordered_map &m return max_count; } +template +static uint32_t get_min_value_of_unordered_map(const std::unordered_map &map) +{ + uint32_t min_count = UINT32_MAX; + for (auto &name_counter_pair : map) { + if (name_counter_pair.second < min_count) { + min_count = name_counter_pair.second; + } + } + return min_count; +} + } /* namespace hailort */ #endif /* HAILO_UTILS_H_ */ \ No newline at end of file diff --git a/hailort/drivers/common/hailo_ioctl_common.h b/hailort/drivers/common/hailo_ioctl_common.h index 64603a1..3b25845 100644 --- a/hailort/drivers/common/hailo_ioctl_common.h +++ b/hailort/drivers/common/hailo_ioctl_common.h @@ -8,9 +8,10 @@ // This value is not easily changeable. // For example: the channel interrupts ioctls assume we have up to 32 channels -#define MAX_VDMA_CHANNELS (32) -#define SIZE_OF_VDMA_DESCRIPTOR (16) -#define VDMA_DEST_CHANNELS_START (16) +#define MAX_VDMA_CHANNELS_PER_ENGINE (32) +#define MAX_VDMA_ENGINES (3) +#define SIZE_OF_VDMA_DESCRIPTOR (16) +#define VDMA_DEST_CHANNELS_START (16) #define CHANNEL_IRQ_TIMESTAMPS_SIZE (128 * 2) // Should be same as MAX_IRQ_TIMESTAMPS_SIZE (hailort_driver.hpp) #define CHANNEL_IRQ_TIMESTAMPS_SIZE_MASK (CHANNEL_IRQ_TIMESTAMPS_SIZE - 1) @@ -56,12 +57,13 @@ typedef uint8_t bool; #define HAILO_GENERAL_IOCTL_MAGIC 'g' #define HAILO_VDMA_IOCTL_MAGIC 'v' -#define HAILO_WINDOWS_IOCTL_MAGIC 'w' +#define HAILO_NON_LINUX_IOCTL_MAGIC 'w' #elif defined(__QNX__) // #ifdef _MSC_VER #include #include #include +#include // defines for devctl #define _IOW_ __DIOF #define _IOR_ __DIOT @@ -69,6 +71,7 @@ typedef uint8_t bool; #define _IO_ __DION #define HAILO_GENERAL_IOCTL_MAGIC _DCMD_ALL #define HAILO_VDMA_IOCTL_MAGIC _DCMD_MISC +#define HAILO_NON_LINUX_IOCTL_MAGIC _DCMD_PROC #else // #ifdef _MSC_VER #error "unsupported platform!" @@ -103,13 +106,24 @@ enum hailo_allocation_mode { /* structure used in ioctl HAILO_VDMA_BUFFER_MAP */ struct hailo_vdma_buffer_map_params { +#if defined(__linux__) || defined(_MSC_VER) void* user_address; // in +#elif defined(__QNX__) + shm_handle_t shared_memory_handle; // in +#else +#error "unsupported platform!" +#endif // __linux__ size_t size; // in enum hailo_dma_data_direction data_direction; // in uintptr_t allocated_buffer_handle; // in size_t mapped_handle; // out }; +/* structure used in ioctl HAILO_VDMA_BUFFER_UNMAP */ +struct hailo_vdma_buffer_unmap_params { + size_t mapped_handle; +}; + /* structure used in ioctl HAILO_DESC_LIST_CREATE */ struct hailo_desc_list_create_params { size_t desc_count; // in @@ -118,8 +132,8 @@ struct hailo_desc_list_create_params { uint64_t dma_address; // out }; -/* structure used in ioctl HAILO_WINDOWS_DESC_LIST_MMAP */ -struct hailo_windows_desc_list_mmap_params { +/* structure used in ioctl HAILO_NON_LINUX_DESC_LIST_MMAP */ +struct hailo_non_linux_desc_list_mmap_params { uintptr_t desc_handle; // in size_t size; // in void* user_address; // out @@ -135,7 +149,8 @@ struct hailo_desc_list_bind_vdma_buffer_params { /* structure used in ioctl HAILO_VDMA_CHANNEL_ENABLE */ struct hailo_vdma_channel_enable_params { - uint32_t channel_index; // in + uint8_t engine_index; // in + uint8_t channel_index; // in enum hailo_dma_data_direction direction; // in // If desc_list_handle is set to valid handle (different than INVALID_DRIVER_HANDLE_VALUE), // the driver will start the channel with the given descriptors list. @@ -146,28 +161,39 @@ struct hailo_vdma_channel_enable_params { /* structure used in ioctl HAILO_VDMA_CHANNEL_DISABLE */ struct hailo_vdma_channel_disable_params { - uint32_t channel_index; // in + uint8_t engine_index; // in + uint8_t channel_index; // in uint64_t channel_handle; // in }; /* structure used in ioctl HAILO_VDMA_CHANNEL_WAIT_INT */ struct hailo_vdma_channel_wait_params { - uint32_t channel_index; // in - uint64_t channel_handle; // in - uint64_t timeout_ms; // in - struct hailo_channel_interrupt_timestamp *timestamps; // out - uint32_t timestamps_count; // inout + uint8_t engine_index; // in + uint8_t channel_index; // in + uint64_t channel_handle; // in + uint64_t timeout_ms; // in + uint32_t timestamps_count; // inout +// In linux send address to local buffer because there isnt room on stack for array +#if defined(__linux__) + struct hailo_channel_interrupt_timestamp *timestamps; // out +#elif defined(__QNX__) || defined(_MSC_VER) + struct hailo_channel_interrupt_timestamp timestamps[CHANNEL_IRQ_TIMESTAMPS_SIZE]; // out +#else +#error "unsupported platform!" +#endif // __linux__ }; /* structure used in ioctl HAILO_VDMA_CHANNEL_ABORT */ struct hailo_vdma_channel_abort_params { - uint32_t channel_index; // in + uint8_t engine_index; // in + uint8_t channel_index; // in uint64_t channel_handle; // in }; /* structure used in ioctl HAILO_VDMA_CHANNEL_CLEAR_ABORT */ struct hailo_vdma_channel_clear_abort_params { - uint32_t channel_index; // in + uint8_t engine_index; // in + uint8_t channel_index; // in uint64_t channel_handle; // in }; @@ -215,12 +241,24 @@ struct hailo_bar_transfer_params { uint8_t buffer[MAX_BAR_TRANSFER_LENGTH]; // in/out }; -/* structure used in ioctl HAILO_VDMA_CHANNEL_REGISTERS */ -struct hailo_channel_registers_params { - enum hailo_transfer_direction transfer_direction; // in - off_t offset; // in - size_t size; // in - uint32_t data; // in/out +/* structure used in ioctl HAILO_VDMA_CHANNEL_READ_REGISTER */ +struct hailo_vdma_channel_read_register_params { + uint8_t engine_index; // in + uint8_t channel_index; // in + enum hailo_dma_data_direction direction; // in + size_t offset; // in + size_t reg_size; // in, can be either 1, 2 or 4 + uint32_t data; // out +}; + +/* structure used in ioctl HAILO_VDMA_CHANNEL_WRITE_REGISTER */ +struct hailo_vdma_channel_write_register_params { + uint8_t engine_index; // in + uint8_t channel_index; // in + enum hailo_dma_data_direction direction; // in + size_t offset; // in + size_t reg_size; // in, can be either 1, 2 or 4 + uint32_t data; // in }; /* structure used in ioctl HAILO_VDMA_BUFFER_SYNC */ @@ -267,6 +305,10 @@ struct hailo_device_properties { enum hailo_board_type board_type; enum hailo_allocation_mode allocation_mode; enum hailo_dma_type dma_type; + size_t dma_engines_count; +#ifdef __QNX__ + pid_t resource_manager_pid; +#endif // __QNX__ }; struct hailo_driver_info { @@ -331,7 +373,8 @@ enum hailo_vdma_ioctl_code { HAILO_VDMA_CHANNEL_WAIT_INT_CODE, HAILO_VDMA_CHANNEL_ABORT_CODE, HAILO_VDMA_CHANNEL_CLEAR_ABORT_CODE, - HAILO_VDMA_CHANNEL_REGISTERS_CODE, + HAILO_VDMA_CHANNEL_READ_REGISTER_CODE, + HAILO_VDMA_CHANNEL_WRITE_REGISTER_CODE, HAILO_VDMA_BUFFER_MAP_CODE, HAILO_VDMA_BUFFER_UNMAP_CODE, HAILO_VDMA_BUFFER_SYNC_CODE, @@ -348,19 +391,20 @@ enum hailo_vdma_ioctl_code { HAILO_VDMA_IOCTL_MAX_NR, }; -#define HAILO_VDMA_CHANNEL_ENABLE _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_CHANNEL_ENABLE_CODE, struct hailo_vdma_channel_enable_params) +#define HAILO_VDMA_CHANNEL_ENABLE _IOWR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_CHANNEL_ENABLE_CODE, struct hailo_vdma_channel_enable_params) #define HAILO_VDMA_CHANNEL_DISABLE _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_CHANNEL_DISABLE_CODE, struct hailo_vdma_channel_disable_params) #define HAILO_VDMA_CHANNEL_WAIT_INT _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_CHANNEL_WAIT_INT_CODE, struct hailo_vdma_channel_wait_params) #define HAILO_VDMA_CHANNEL_ABORT _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_CHANNEL_ABORT_CODE, struct hailo_vdma_channel_abort_params) #define HAILO_VDMA_CHANNEL_CLEAR_ABORT _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_CHANNEL_CLEAR_ABORT_CODE, struct hailo_vdma_channel_clear_abort_params) -#define HAILO_VDMA_CHANNEL_REGISTERS _IOWR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_CHANNEL_REGISTERS_CODE, struct hailo_channel_registers_params) +#define HAILO_VDMA_CHANNEL_READ_REGISTER _IOWR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_CHANNEL_READ_REGISTER_CODE, struct hailo_vdma_channel_read_register_params) +#define HAILO_VDMA_CHANNEL_WRITE_REGISTER _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_CHANNEL_WRITE_REGISTER_CODE, struct hailo_vdma_channel_write_register_params) -#define HAILO_VDMA_BUFFER_MAP _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_BUFFER_MAP_CODE, struct hailo_vdma_buffer_map_params) -#define HAILO_VDMA_BUFFER_UNMAP _IO_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_BUFFER_UNMAP_CODE) +#define HAILO_VDMA_BUFFER_MAP _IOWR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_BUFFER_MAP_CODE, struct hailo_vdma_buffer_map_params) +#define HAILO_VDMA_BUFFER_UNMAP _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_BUFFER_UNMAP_CODE, struct hailo_vdma_buffer_unmap_params) #define HAILO_VDMA_BUFFER_SYNC _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_BUFFER_SYNC_CODE, struct hailo_vdma_buffer_sync_params) #define HAILO_DESC_LIST_CREATE _IOWR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_DESC_LIST_CREATE_CODE, struct hailo_desc_list_create_params) -#define HAILO_DESC_LIST_RELEASE _IO_(HAILO_VDMA_IOCTL_MAGIC, HAILO_DESC_LIST_RELEASE_CODE) +#define HAILO_DESC_LIST_RELEASE _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_DESC_LIST_RELEASE_CODE, uintptr_t) #define HAILO_DESC_LIST_BIND_VDMA_BUFFER _IOR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_DESC_LIST_BIND_VDMA_BUFFER_CODE, struct hailo_desc_list_bind_vdma_buffer_params) #define HAILO_VDMA_LOW_MEMORY_BUFFER_ALLOC _IOWR_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_LOW_MEMORY_BUFFER_ALLOC_CODE, struct hailo_allocate_low_memory_buffer_params) @@ -372,14 +416,14 @@ enum hailo_vdma_ioctl_code { #define HAILO_VDMA_CONTINUOUS_BUFFER_FREE _IO_(HAILO_VDMA_IOCTL_MAGIC, HAILO_VDMA_CONTINUOUS_BUFFER_FREE_CODE) -enum hailo_windows_ioctl_code { - HAILO_WINDOWS_DESC_LIST_MMAP_CODE, +enum hailo_non_linux_ioctl_code { + HAILO_NON_LINUX_DESC_LIST_MMAP_CODE, // Must be last - HAILO_WINDOWS_IOCTL_MAX_NR, + HAILO_NON_LINUX_IOCTL_MAX_NR, }; -#define HAILO_WINDOWS_DESC_LIST_MMAP _IOWR_(HAILO_WINDOWS_IOCTL_MAGIC, HAILO_WINDOWS_DESC_LIST_MMAP_CODE, struct hailo_windows_desc_list_mmap_params) +#define HAILO_NON_LINUX_DESC_LIST_MMAP _IOWR_(HAILO_NON_LINUX_IOCTL_MAGIC, HAILO_NON_LINUX_DESC_LIST_MMAP_CODE, struct hailo_non_linux_desc_list_mmap_params) #endif /* _HAILO_IOCTL_COMMON_H_ */ diff --git a/hailort/drivers/win/include/Public.h b/hailort/drivers/win/include/Public.h index 96b442a..8423713 100644 --- a/hailort/drivers/win/include/Public.h +++ b/hailort/drivers/win/include/Public.h @@ -75,7 +75,7 @@ struct tCompatibleHailoIoctlParam #define HAILO_GENERAL_IOCTL_MAGIC 0 #define HAILO_VDMA_IOCTL_MAGIC 1 -#define HAILO_WINDOWS_IOCTL_MAGIC 2 +#define HAILO_NON_LINUX_IOCTL_MAGIC 2 @@ -111,13 +111,16 @@ struct tCompatibleHailoIoctlData hailo_vdma_buffer_sync_params VdmaBufferSync; hailo_fw_control FirmwareControl; hailo_vdma_buffer_map_params VdmaBufferMap; + hailo_vdma_buffer_unmap_params VdmaBufferUnmap; hailo_desc_list_create_params DescListCreate; + uintptr_t DescListReleaseParam; hailo_desc_list_bind_vdma_buffer_params DescListBind; hailo_d2h_notification D2HNotification; hailo_device_properties DeviceProperties; hailo_driver_info DriverInfo; - hailo_channel_registers_params ChannelRegisters; - hailo_windows_desc_list_mmap_params DescListMmap; + hailo_vdma_channel_read_register_params ChannelRegisterRead; + hailo_vdma_channel_write_register_params ChannelRegisterWrite; + hailo_non_linux_desc_list_mmap_params DescListMmap; hailo_read_log_params ReadLog; hailo_mark_as_in_use_params MarkAsInUse; } Buffer; diff --git a/hailort/hailort_service/CMakeLists.txt b/hailort/hailort_service/CMakeLists.txt new file mode 100644 index 0000000..2a6fdff --- /dev/null +++ b/hailort/hailort_service/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.0.0) + +add_executable(hailort_service + hailort_rpc_service.cpp + hailort_service.cpp + service_resource_manager.hpp +) +target_compile_options(hailort_service PRIVATE ${HAILORT_COMPILE_OPTIONS}) +set_property(TARGET hailort_service PROPERTY CXX_STANDARD 14) +target_link_libraries(hailort_service + libhailort + spdlog::spdlog + grpc++_unsecure + hailort_rpc_grpc_proto) +target_include_directories(hailort_service + PRIVATE + ${HAILORT_INC_DIR} + ${COMMON_INC_DIR} + ${RPC_DIR} +) +disable_exceptions(hailort_service) + +# Install systemd unit file +set(SYSTEMD_UNIT_DIR "/lib/systemd/system/") +if(NOT CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(SYSTEMD_UNIT_DIR ${CMAKE_INSTALL_PREFIX}/${SYSTEMD_UNIT_DIR}) +endif() + +set(HAILORT_SERVICE_UNIT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/hailort.service) +install( + FILES "${HAILORT_SERVICE_UNIT_FILE}" + DESTINATION "${SYSTEMD_UNIT_DIR}" + CONFIGURATIONS Release + COMPONENT hailort_service +) + +install( + TARGETS hailort_service + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + CONFIGURATIONS Release +) + +# Create empty directory for hailort log file +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(HAILORT_LOG_DIR "/var/log/hailo") + install(DIRECTORY DESTINATION ${HAILORT_LOG_DIR}) +endif() \ No newline at end of file diff --git a/hailort/hailort_service/hailort.service b/hailort/hailort_service/hailort.service new file mode 100644 index 0000000..f702002 --- /dev/null +++ b/hailort/hailort_service/hailort.service @@ -0,0 +1,13 @@ +[Unit] +Description=HailoRT service +Documentation=https://github.com/hailo-ai/hailort + +[Service] +Type=forking +ExecStart=/usr/local/bin/hailort_service +Restart=on-failure +RemainAfterExit=yes +Environment="HAILORT_LOGGER_PATH=/var/log/hailo" + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/hailort/hailort_service/hailort_rpc_service.cpp b/hailort/hailort_service/hailort_rpc_service.cpp new file mode 100644 index 0000000..c24c626 --- /dev/null +++ b/hailort/hailort_service/hailort_rpc_service.cpp @@ -0,0 +1,744 @@ +/** + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + **/ +/** + * @file hailort_rpc_service.cpp + * @brief Implementation of the hailort rpc service + **/ + +#include "hailort_rpc_service.hpp" +#include "rpc/rpc_definitions.hpp" +#include "service_resource_manager.hpp" +#include "common/utils.hpp" +#include "hailo/network_group.hpp" +#include "hailo/vdevice.hpp" +#include "hailo/vstream.hpp" +#include "hailo/hailort_common.hpp" +#include + +namespace hailort +{ + +grpc::Status HailoRtRpcService::client_keep_alive(grpc::ServerContext *ctx, const keepalive_Request *request, + empty*) +{ + auto client_id = request->process_id(); + while (!ctx->IsCancelled()) { + sleep(hailort::HAILO_KEEPALIVE_INTERVAL_SEC); + } + LOGGER__INFO("Client disconnected, pid: {}", client_id); + syslog(LOG_NOTICE, "Client disconnected, pid: %i", client_id); + ServiceResourceManager::get_instance().release_by_pid(client_id); + ServiceResourceManager::get_instance().release_by_pid(client_id); + ServiceResourceManager::get_instance().release_by_pid(client_id); + ServiceResourceManager::get_instance().release_by_pid(client_id); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::get_service_version(grpc::ServerContext*, const get_service_version_Request*, + get_service_version_Reply *reply) +{ + hailo_version_t service_version = {}; + auto status = hailo_get_library_version(&service_version); + CHECK_SUCCESS_AS_RPC_STATUS(status, reply); + auto hailo_version_proto = reply->mutable_hailo_version(); + hailo_version_proto->set_major_version(service_version.major); + hailo_version_proto->set_minor_version(service_version.minor); + hailo_version_proto->set_revision_version(service_version.revision); + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::VDevice_create(grpc::ServerContext *, const VDevice_create_Request *request, + VDevice_create_Reply *reply) +{ + // Deserialization + const auto params_proto = request->hailo_vdevice_params(); + std::vector device_ids; + device_ids.reserve(params_proto.device_ids().size()); + for (auto device_id_str : params_proto.device_ids()) { + auto device_id_struct = HailoRTCommon::to_device_id(device_id_str); + CHECK_SUCCESS_AS_RPC_STATUS(device_id_struct.status(), reply); + device_ids.push_back(device_id_struct.release()); + } + + hailo_vdevice_params_t params = { + .device_count = params_proto.device_count(), + .device_infos = nullptr, + .device_ids = device_ids.data(), + .scheduling_algorithm = static_cast(params_proto.scheduling_algorithm()), + .group_id = params_proto.group_id().c_str(), + .multi_process_service = false + }; + + auto vdevice = VDevice::create(params); + CHECK_EXPECTED_AS_RPC_STATUS(vdevice, reply); + + auto &manager = ServiceResourceManager::get_instance(); + auto handle = manager.register_resource(request->pid(), std::move(vdevice.release())); + reply->set_handle(handle); + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::VDevice_release(grpc::ServerContext*, const Release_Request *request, + Release_Reply *reply) +{ + auto &manager = ServiceResourceManager::get_instance(); + auto status = manager.release_resource(request->handle()); + reply->set_status(static_cast(status)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::VDevice_configure(grpc::ServerContext*, const VDevice_configure_Request *request, + VDevice_configure_Reply *reply) +{ + auto hef_as_string = request->hef(); + auto hef_memview = MemoryView::create_const(hef_as_string.c_str(), hef_as_string.length()); + auto hef = Hef::create(hef_memview); + CHECK_SUCCESS_AS_RPC_STATUS(hef.status(), reply); + + NetworkGroupsParamsMap configure_params_map; + for (auto &name_configure_params_pair : request->configure_params_map()) { + ConfigureNetworkParams network_configure_params; + auto proto_configure_params = name_configure_params_pair.params(); + network_configure_params.batch_size = static_cast(proto_configure_params.batch_size()); + network_configure_params.power_mode = static_cast(proto_configure_params.power_mode()); + network_configure_params.latency = static_cast(proto_configure_params.latency()); + + // Init streams params + for (auto &proto_name_streams_params_pair : proto_configure_params.stream_params_map()) { + auto proto_streams_params = proto_name_streams_params_pair.params(); + auto stream_direction = static_cast(proto_streams_params.direction()); + hailo_stream_parameters_t stream_params; + if (stream_direction == HAILO_H2D_STREAM) { + stream_params = { + .stream_interface = static_cast(proto_streams_params.stream_interface()), + .direction = stream_direction, + {.pcie_input_params = { + .reserved = 0 + }} + }; + } else { + stream_params = { + .stream_interface = static_cast(proto_streams_params.stream_interface()), + .direction = stream_direction, + {.pcie_output_params = { + .reserved = 0 + }} + }; + } + network_configure_params.stream_params_by_name.insert({proto_name_streams_params_pair.name(), stream_params}); + } + + // Init networks params + for (auto &proto_name_network_params_pair : proto_configure_params.network_params_map()) { + auto proto_network_params = proto_name_network_params_pair.params(); + hailo_network_parameters_t net_params { + .batch_size = static_cast(proto_network_params.batch_size()) + }; + + network_configure_params.network_params_by_name.insert({proto_name_network_params_pair.name(), net_params}); + } + + configure_params_map.insert({name_configure_params_pair.name(), network_configure_params}); + } + + auto lambda = [](std::shared_ptr vdevice, Hef &hef, NetworkGroupsParamsMap &configure_params_map) { + return vdevice->configure(hef, configure_params_map); + }; + auto &vdevice_manager = ServiceResourceManager::get_instance(); + auto networks = vdevice_manager.execute>(request->handle(), lambda, hef.release(), configure_params_map); + CHECK_SUCCESS_AS_RPC_STATUS(networks.status(), reply); + + auto &networks_manager = ServiceResourceManager::get_instance(); + for (auto network : networks.value()) { + auto handle = networks_manager.register_resource(request->pid(), network); + reply->add_networks_handles(handle); + } + + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::VDevice_get_physical_devices_ids(grpc::ServerContext*, + const VDevice_get_physical_devices_ids_Request* request, VDevice_get_physical_devices_ids_Reply* reply) +{ + auto lambda = [](std::shared_ptr vdevice) { + return vdevice->get_physical_devices_ids(); + }; + auto &vdevice_manager = ServiceResourceManager::get_instance(); + auto expected_devices_ids = vdevice_manager.execute>>(request->handle(), lambda); + CHECK_EXPECTED_AS_RPC_STATUS(expected_devices_ids, reply); + auto devices_ids = expected_devices_ids.value(); + auto devices_ids_proto = reply->mutable_devices_ids(); + for (auto &device_id : devices_ids) { + devices_ids_proto->Add(std::move(device_id)); + } + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::ConfiguredNetworkGroup_release(grpc::ServerContext*, const Release_Request *request, + Release_Reply *reply) +{ + auto &manager = ServiceResourceManager::get_instance(); + auto status = manager.release_resource(request->handle()); + reply->set_status(static_cast(status)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::ConfiguredNetworkGroup_make_input_vstream_params(grpc::ServerContext*, + const ConfiguredNetworkGroup_make_input_vstream_params_Request *request, + ConfiguredNetworkGroup_make_input_vstream_params_Reply *reply) +{ + auto lambda = [](std::shared_ptr cng, bool quantized, + hailo_format_type_t format_type, uint32_t timeout_ms, uint32_t queue_size, std::string network_name) { + return cng->make_input_vstream_params(quantized, format_type, timeout_ms, queue_size, network_name); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto expected_params = manager.execute>>(request->handle(), lambda, request->quantized(), static_cast(request->format_type()), + request->timeout_ms(), request->queue_size(), request->network_name()); + CHECK_EXPECTED_AS_RPC_STATUS(expected_params, reply); + auto params_map = reply->mutable_vstream_params_map(); + for (auto& name_to_params : expected_params.value()) { + NamedVStreamParams named_params; + named_params.set_name(name_to_params.first); + auto params = name_to_params.second; + auto proto_params = named_params.mutable_params(); + auto proto_user_buffer_format = proto_params->mutable_user_buffer_format(); + proto_user_buffer_format->set_type(params.user_buffer_format.type); + proto_user_buffer_format->set_order(params.user_buffer_format.order); + proto_user_buffer_format->set_flags(params.user_buffer_format.flags); + proto_params->set_timeout_ms(params.timeout_ms); + proto_params->set_queue_size(params.queue_size); + proto_params->set_vstream_stats_flags(params.vstream_stats_flags); + proto_params->set_pipeline_elements_stats_flags(params.pipeline_elements_stats_flags); + params_map->Add(std::move(named_params)); + } + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::ConfiguredNetworkGroup_make_output_vstream_params(grpc::ServerContext*, + const ConfiguredNetworkGroup_make_output_vstream_params_Request *request, + ConfiguredNetworkGroup_make_output_vstream_params_Reply *reply) +{ + auto lambda = [](std::shared_ptr cng, bool quantized, + hailo_format_type_t format_type, uint32_t timeout_ms, uint32_t queue_size, std::string network_name) { + return cng->make_output_vstream_params(quantized, format_type, timeout_ms, queue_size, network_name); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto expected_params = manager.execute>>(request->handle(), + lambda, request->quantized(), static_cast(request->format_type()), + request->timeout_ms(), request->queue_size(), request->network_name()); + CHECK_EXPECTED_AS_RPC_STATUS(expected_params, reply); + auto params_map = reply->mutable_vstream_params_map(); + for (auto& name_to_params : expected_params.value()) { + NamedVStreamParams named_params; + named_params.set_name(name_to_params.first); + auto params = name_to_params.second; + auto proto_params = named_params.mutable_params(); + auto proto_user_buffer_format = proto_params->mutable_user_buffer_format(); + proto_user_buffer_format->set_type(params.user_buffer_format.type); + proto_user_buffer_format->set_order(params.user_buffer_format.order); + proto_user_buffer_format->set_flags(params.user_buffer_format.flags); + proto_params->set_timeout_ms(params.timeout_ms); + proto_params->set_queue_size(params.queue_size); + proto_params->set_vstream_stats_flags(params.vstream_stats_flags); + proto_params->set_pipeline_elements_stats_flags(params.pipeline_elements_stats_flags); + params_map->Add(std::move(named_params)); + } + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::ConfiguredNetworkGroup_get_default_stream_interface(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_default_stream_interface_Request *request, + ConfiguredNetworkGroup_get_default_stream_interface_Reply *reply) +{ + auto lambda = [](std::shared_ptr cng) { + return cng->get_default_streams_interface(); + }; + auto &net_group_manager = ServiceResourceManager::get_instance(); + auto expected_stream_interface = net_group_manager.execute>(request->handle(), lambda); + CHECK_EXPECTED_AS_RPC_STATUS(expected_stream_interface, reply); + reply->set_stream_interface(static_cast(expected_stream_interface.value())); + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::ConfiguredNetworkGroup_get_output_vstream_groups(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_output_vstream_groups_Request *request, + ConfiguredNetworkGroup_get_output_vstream_groups_Reply *reply) +{ + auto lambda = [](std::shared_ptr cng) { + return cng->get_output_vstream_groups(); + }; + auto &net_group_manager = ServiceResourceManager::get_instance(); + auto expected_output_vstream_groups = net_group_manager.execute>>>(request->handle(), lambda); + CHECK_EXPECTED_AS_RPC_STATUS(expected_output_vstream_groups, reply); + auto output_vstream_groups = expected_output_vstream_groups.value(); + auto groups_proto = reply->mutable_output_vstream_groups(); + for (auto& group : output_vstream_groups) { + VStreamGroup group_proto; + for (auto& name : group) { + auto vstream_group_proto = group_proto.mutable_vstream_group(); + vstream_group_proto->Add(std::move(name)); + } + groups_proto->Add(std::move(group_proto)); + } + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +void serialize_vstream_infos(ConfiguredNetworkGroup_get_vstream_infos_Reply *reply, + const std::vector &infos) +{ + auto vstream_infos_proto = reply->mutable_vstream_infos(); + for (auto& info : infos) { + VStreamInfo info_proto; + info_proto.set_name(std::string(info.name)); + info_proto.set_network_name(std::string(info.network_name)); + info_proto.set_direction(static_cast(info.direction)); + auto format_proto = info_proto.mutable_format(); + format_proto->set_flags(info.format.flags); + format_proto->set_order(info.format.order); + format_proto->set_type(info.format.type); + if (info.format.order == HAILO_FORMAT_ORDER_HAILO_NMS) { + auto nms_shape_proto = info_proto.mutable_nms_shape(); + nms_shape_proto->set_number_of_classes(info.nms_shape.number_of_classes); + nms_shape_proto->set_max_bbox_per_class(info.nms_shape.max_bboxes_per_class); + } else { + auto shape_proto = info_proto.mutable_shape(); + shape_proto->set_height(info.shape.height); + shape_proto->set_width(info.shape.width); + shape_proto->set_features(info.shape.features); + } + auto quant_info_proto = info_proto.mutable_quant_info(); + quant_info_proto->set_qp_zp(info.quant_info.qp_zp); + quant_info_proto->set_qp_scale(info.quant_info.qp_scale); + quant_info_proto->set_limvals_min(info.quant_info.limvals_min); + quant_info_proto->set_limvals_max(info.quant_info.limvals_max); + vstream_infos_proto->Add(std::move(info_proto)); + } +} + +grpc::Status HailoRtRpcService::ConfiguredNetworkGroup_get_input_vstream_infos(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_vstream_infos_Request *request, + ConfiguredNetworkGroup_get_vstream_infos_Reply *reply) +{ + auto lambda = [](std::shared_ptr cng, std::string network_name) { + return cng->get_input_vstream_infos(network_name); + }; + auto &net_group_manager = ServiceResourceManager::get_instance(); + auto expected_vstream_infos = net_group_manager.execute>>(request->handle(), lambda, request->network_name()); + CHECK_EXPECTED_AS_RPC_STATUS(expected_vstream_infos, reply); + serialize_vstream_infos(reply, expected_vstream_infos.value()); + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::ConfiguredNetworkGroup_get_output_vstream_infos(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_vstream_infos_Request *request, + ConfiguredNetworkGroup_get_vstream_infos_Reply *reply) +{ + auto lambda = [](std::shared_ptr cng, std::string network_name) { + return cng->get_output_vstream_infos(network_name); + }; + auto &net_group_manager = ServiceResourceManager::get_instance(); + auto expected_vstream_infos = net_group_manager.execute>>(request->handle(), lambda, request->network_name()); + CHECK_EXPECTED_AS_RPC_STATUS(expected_vstream_infos, reply); + serialize_vstream_infos(reply, expected_vstream_infos.value()); + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::ConfiguredNetworkGroup_get_all_vstream_infos(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_vstream_infos_Request *request, + ConfiguredNetworkGroup_get_vstream_infos_Reply *reply) +{ + auto lambda = [](std::shared_ptr cng, std::string network_name) { + return cng->get_all_vstream_infos(network_name); + }; + auto &net_group_manager = ServiceResourceManager::get_instance(); + auto expected_vstream_infos = net_group_manager.execute>>(request->handle(), lambda, request->network_name()); + CHECK_EXPECTED_AS_RPC_STATUS(expected_vstream_infos, reply); + serialize_vstream_infos(reply, expected_vstream_infos.value()); + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::ConfiguredNetworkGroup_set_scheduler_timeout(grpc::ServerContext*, + const ConfiguredNetworkGroup_set_scheduler_timeout_Request *request, + ConfiguredNetworkGroup_set_scheduler_timeout_Reply *reply) +{ + auto lambda = [](std::shared_ptr cng, std::chrono::milliseconds timeout_ms) { + return cng->set_scheduler_timeout(timeout_ms); + }; + auto &net_group_manager = ServiceResourceManager::get_instance(); + auto status = net_group_manager.execute(request->handle(), lambda, static_cast(request->timeout_ms())); + reply->set_status(status); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::ConfiguredNetworkGroup_set_scheduler_threshold(grpc::ServerContext*, + const ConfiguredNetworkGroup_set_scheduler_threshold_Request *request, + ConfiguredNetworkGroup_set_scheduler_threshold_Reply *reply) +{ + auto lambda = [](std::shared_ptr cng, uint32_t threshold, std::string network_name) { + return cng->set_scheduler_threshold(threshold, network_name); + }; + auto &net_group_manager = ServiceResourceManager::get_instance(); + auto status = net_group_manager.execute(request->handle(), lambda, request->threshold(), + request->network_name()); + reply->set_status(status); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::InputVStreams_create(grpc::ServerContext *, const VStream_create_Request *request, + VStreams_create_Reply *reply) +{ + std::map inputs_params; + for (auto& param_proto : request->vstreams_params()) { + auto vstream_params_proto = param_proto.params(); + auto user_buffer_format_proto = vstream_params_proto.user_buffer_format(); + hailo_format_t format; + format.flags = hailo_format_flags_t(user_buffer_format_proto.flags()); + format.order = hailo_format_order_t(user_buffer_format_proto.order()); + format.type = hailo_format_type_t(user_buffer_format_proto.type()); + hailo_vstream_params_t params = { + .user_buffer_format = format, + .timeout_ms = vstream_params_proto.timeout_ms(), + .queue_size = vstream_params_proto.queue_size(), + .vstream_stats_flags = hailo_vstream_stats_flags_t(vstream_params_proto.vstream_stats_flags()), + .pipeline_elements_stats_flags = hailo_pipeline_elem_stats_flags_t(vstream_params_proto.pipeline_elements_stats_flags()) + }; + inputs_params.emplace(param_proto.name(), std::move(params)); + } + + auto lambda = [](std::shared_ptr cng, const std::map &inputs_params) { + return cng->create_input_vstreams(inputs_params); + }; + auto &net_group_manager = ServiceResourceManager::get_instance(); + auto vstreams_expected = net_group_manager.execute>>(request->net_group(), lambda, inputs_params); + CHECK_EXPECTED_AS_RPC_STATUS(vstreams_expected, reply); + auto vstreams = vstreams_expected.release(); + auto &manager = ServiceResourceManager::get_instance(); + auto client_pid = request->pid(); + + for (size_t i = 0; i < vstreams.size(); i++) { + auto handle = manager.register_resource(client_pid, make_shared_nothrow(std::move(vstreams[i]))); + reply->add_handles(handle); + } + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::InputVStream_release(grpc::ServerContext *, const Release_Request *request, + Release_Reply *reply) +{ + auto &manager = ServiceResourceManager::get_instance(); + auto status = manager.release_resource(request->handle()); + reply->set_status(static_cast(status)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::OutputVStreams_create(grpc::ServerContext *, const VStream_create_Request *request, + VStreams_create_Reply *reply) +{ + std::map output_params; + for (auto& param_proto : request->vstreams_params()) { + auto vstream_params_proto = param_proto.params(); + auto user_buffer_format_proto = vstream_params_proto.user_buffer_format(); + hailo_format_t format; + format.flags = hailo_format_flags_t(user_buffer_format_proto.flags()); + format.order = hailo_format_order_t(user_buffer_format_proto.order()); + format.type = hailo_format_type_t(user_buffer_format_proto.type()); + hailo_vstream_params_t params = { + .user_buffer_format = format, + .timeout_ms = vstream_params_proto.timeout_ms(), + .queue_size = vstream_params_proto.queue_size(), + .vstream_stats_flags = hailo_vstream_stats_flags_t(vstream_params_proto.vstream_stats_flags()), + .pipeline_elements_stats_flags = hailo_pipeline_elem_stats_flags_t(vstream_params_proto.pipeline_elements_stats_flags()) + }; + output_params.emplace(param_proto.name(), std::move(params)); + } + + auto lambda = [](std::shared_ptr cng, const std::map &output_params) { + return cng->create_output_vstreams(output_params); + }; + auto &net_group_manager = ServiceResourceManager::get_instance(); + auto vstreams_expected = net_group_manager.execute>>(request->net_group(), lambda, output_params); + CHECK_EXPECTED_AS_RPC_STATUS(vstreams_expected, reply); + auto vstreams = vstreams_expected.release(); + auto &manager = ServiceResourceManager::get_instance(); + auto client_pid = request->pid(); + + for (size_t i = 0; i < vstreams.size(); i++) { + auto handle = manager.register_resource(client_pid, make_shared_nothrow(std::move(vstreams[i]))); + reply->add_handles(handle); + } + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::OutputVStream_release(grpc::ServerContext *, const Release_Request *request, + Release_Reply *reply) +{ + auto &manager = ServiceResourceManager::get_instance(); + auto status = manager.release_resource(request->handle()); + reply->set_status(static_cast(status)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::ConfiguredNetworkGroup_get_name(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_name_Request *request, + ConfiguredNetworkGroup_get_name_Reply *reply) +{ + auto lambda = [](std::shared_ptr cng) { + return cng->name(); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto network_group_name = manager.execute(request->handle(), lambda); + reply->set_network_group_name(network_group_name); + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::InputVStream_write(grpc::ServerContext*, const InputVStream_write_Request *request, + InputVStream_write_Reply *reply) +{ + auto buffer_expected = Buffer::create_shared(request->data().length()); + CHECK_EXPECTED_AS_RPC_STATUS(buffer_expected, reply); + std::vector data(request->data().begin(), request->data().end()); + auto lambda = [](std::shared_ptr input_vstream, const MemoryView &buffer) { + return input_vstream->write(std::move(buffer)); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto status = manager.execute(request->handle(), lambda, MemoryView::create_const(data.data(), data.size())); + CHECK_SUCCESS_AS_RPC_STATUS(status, reply, "VStream write failed"); + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::ConfiguredNetworkGroup_get_network_infos(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_network_infos_Request *request, + ConfiguredNetworkGroup_get_network_infos_Reply *reply) +{ + auto lambda = [](std::shared_ptr cng) { + return cng->get_network_infos(); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto expected_network_infos = manager.execute>>(request->handle(), lambda); + CHECK_EXPECTED_AS_RPC_STATUS(expected_network_infos, reply); + auto infos_proto = reply->mutable_network_infos(); + for (auto& info : expected_network_infos.value()) { + infos_proto->Add(std::string(info.name)); + } + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::OutputVStream_read(grpc::ServerContext*, const OutputVStream_read_Request *request, + OutputVStream_read_Reply *reply) +{ + std::vector data(request->size()); + auto lambda = [](std::shared_ptr output_vstream, MemoryView &buffer) { + return output_vstream->read(std::move(buffer)); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto status = manager.execute(request->handle(), lambda, MemoryView(data.data(), data.size())); + CHECK_SUCCESS_AS_RPC_STATUS(status, reply, "VStream read failed"); + reply->set_data(data.data(), data.size()); + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::ConfiguredNetworkGroup_get_all_stream_infos(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_all_stream_infos_Request *request, + ConfiguredNetworkGroup_get_all_stream_infos_Reply *reply) +{ + auto lambda = [](std::shared_ptr cng) { + return cng->get_all_stream_infos(); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto expected_stream_infos = manager.execute>>(request->handle(), lambda); + CHECK_EXPECTED_AS_RPC_STATUS(expected_stream_infos, reply); + auto proto_stream_infos = reply->mutable_stream_infos(); + for (auto& stream_info : expected_stream_infos.value()) { + StreamInfo proto_stream_info; + if (stream_info.format.order == HAILO_FORMAT_ORDER_HAILO_NMS) { + auto proto_nms_info = proto_stream_info.mutable_nms_info(); + proto_nms_info->set_number_of_classes(stream_info.nms_info.number_of_classes); + proto_nms_info->set_max_bboxes_per_class(stream_info.nms_info.max_bboxes_per_class); + proto_nms_info->set_bbox_size(stream_info.nms_info.bbox_size); + proto_nms_info->set_chunks_per_frame(stream_info.nms_info.chunks_per_frame); + proto_nms_info->set_is_defused(stream_info.nms_info.is_defused); + auto proto_nms_info_defuse_info = proto_nms_info->mutable_defuse_info(); + proto_nms_info_defuse_info->set_class_group_index(stream_info.nms_info.defuse_info.class_group_index); + proto_nms_info_defuse_info->set_original_name(std::string(stream_info.nms_info.defuse_info.original_name)); + } else { + auto proto_stream_shape = proto_stream_info.mutable_stream_shape(); + auto proto_stream_shape_shape = proto_stream_shape->mutable_shape(); + proto_stream_shape_shape->set_height(stream_info.shape.height); + proto_stream_shape_shape->set_width(stream_info.shape.width); + proto_stream_shape_shape->set_features(stream_info.shape.features); + auto proto_stream_shape_hw_shape = proto_stream_shape->mutable_hw_shape(); + proto_stream_shape_hw_shape->set_height(stream_info.hw_shape.height); + proto_stream_shape_hw_shape->set_width(stream_info.hw_shape.width); + proto_stream_shape_hw_shape->set_features(stream_info.hw_shape.features); + } + proto_stream_info.set_hw_data_bytes(stream_info.hw_data_bytes); + proto_stream_info.set_hw_frame_size(stream_info.hw_frame_size); + auto proto_stream_info_format = proto_stream_info.mutable_format(); + proto_stream_info_format->set_type(stream_info.format.type); + proto_stream_info_format->set_order(stream_info.format.order); + proto_stream_info_format->set_flags(stream_info.format.flags); + proto_stream_info.set_direction(static_cast(stream_info.direction)); + proto_stream_info.set_index(stream_info.index); + proto_stream_info.set_name(stream_info.name); + auto proto_quant_info = proto_stream_info.mutable_quant_info(); + proto_quant_info->set_qp_zp(stream_info.quant_info.qp_zp); + proto_quant_info->set_qp_scale(stream_info.quant_info.qp_scale); + proto_quant_info->set_limvals_min(stream_info.quant_info.limvals_min); + proto_quant_info->set_limvals_max(stream_info.quant_info.limvals_max); + proto_stream_info.set_is_mux(stream_info.is_mux); + proto_stream_infos->Add(std::move(proto_stream_info)); + } + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::ConfiguredNetworkGroup_get_latency_measurement(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_latency_measurement_Request *request, + ConfiguredNetworkGroup_get_latency_measurement_Reply *reply) +{ + auto lambda = [](std::shared_ptr cng, const std::string &network_name) { + return cng->get_latency_measurement(network_name); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto expected_latency_result = manager.execute>(request->handle(), lambda, request->network_name()); + CHECK_EXPECTED_AS_RPC_STATUS(expected_latency_result, reply); + reply->set_avg_hw_latency(static_cast(expected_latency_result.value().avg_hw_latency.count())); + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::InputVStream_get_frame_size(grpc::ServerContext*, const VStream_get_frame_size_Request *request, + VStream_get_frame_size_Reply *reply) +{ + auto lambda = [](std::shared_ptr input_vstream) { + return input_vstream->get_frame_size(); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto frame_size = manager.execute(request->handle(), lambda); + reply->set_frame_size(static_cast(frame_size)); + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::OutputVStream_get_frame_size(grpc::ServerContext*, const VStream_get_frame_size_Request *request, + VStream_get_frame_size_Reply *reply) +{ + auto lambda = [](std::shared_ptr output_vstream) { + return output_vstream->get_frame_size(); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto frame_size = manager.execute(request->handle(), lambda); + reply->set_frame_size(static_cast(frame_size)); + reply->set_status(static_cast(HAILO_SUCCESS)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::InputVStream_flush(grpc::ServerContext*, const InputVStream_flush_Request *request, + InputVStream_flush_Reply *reply) +{ + auto lambda = [](std::shared_ptr input_vstream) { + return input_vstream->flush(); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto flush_status = manager.execute(request->handle(), lambda); + reply->set_status(static_cast(flush_status)); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::InputVStream_name(grpc::ServerContext*, const VStream_name_Request *request, + VStream_name_Reply *reply) +{ + auto lambda = [](std::shared_ptr input_vstream) { + return input_vstream->name(); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto name = manager.execute(request->handle(), lambda); + reply->set_name(name); + reply->set_status(HAILO_SUCCESS); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::OutputVStream_name(grpc::ServerContext*, const VStream_name_Request *request, + VStream_name_Reply *reply) +{ + auto lambda = [](std::shared_ptr output_vstream) { + return output_vstream->name(); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto name = manager.execute(request->handle(), lambda); + reply->set_name(name); + reply->set_status(HAILO_SUCCESS); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::InputVStream_abort(grpc::ServerContext*, const VStream_abort_Request *request, + VStream_abort_Reply *reply) +{ + auto lambda = [](std::shared_ptr input_vstream) { + return input_vstream->abort(); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto status = manager.execute(request->handle(), lambda); + reply->set_status(status); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::OutputVStream_abort(grpc::ServerContext*, const VStream_abort_Request *request, + VStream_abort_Reply *reply) +{ + auto lambda = [](std::shared_ptr output_vstream) { + return output_vstream->abort(); + }; + auto &manager = ServiceResourceManager::get_instance(); + auto status = manager.execute(request->handle(), lambda); + reply->set_status(status); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::InputVStream_resume(grpc::ServerContext*, const VStream_resume_Request *, + VStream_resume_Reply *) +{ + // TODO - HRT-7892 + // auto lambda = [](std::shared_ptr input_vstream) { + // return input_vstream->resume(); + // }; + // auto &manager = ServiceResourceManager::get_instance(); + // auto status = manager.execute(request->handle(), lambda); + // reply->set_status(status); + return grpc::Status::OK; +} + +grpc::Status HailoRtRpcService::OutputVStream_resume(grpc::ServerContext*, const VStream_resume_Request *, + VStream_resume_Reply *) +{ + // TODO - HRT-7892 + // auto lambda = [](std::shared_ptr output_vstream) { + // return output_vstream->resume(); + // }; + // auto &manager = ServiceResourceManager::get_instance(); + // auto status = manager.execute(request->handle(), lambda); + // reply->set_status(status); + return grpc::Status::OK; +} + +} + diff --git a/hailort/hailort_service/hailort_rpc_service.hpp b/hailort/hailort_service/hailort_rpc_service.hpp new file mode 100644 index 0000000..ed52176 --- /dev/null +++ b/hailort/hailort_service/hailort_rpc_service.hpp @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + **/ +/** + * @file hailort_rpc_service.hpp + * @brief TODO + **/ + +#ifndef HAILO_HAILORT_RPC_SERVICE_HPP_ +#define HAILO_HAILORT_RPC_SERVICE_HPP_ + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4244 4267 4127) +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif +#include +#include "hailort_rpc.grpc.pb.h" +#if defined(_MSC_VER) +#pragma warning( pop ) +#else +#pragma GCC diagnostic pop +#endif + +namespace hailort +{ + +class HailoRtRpcService final : public HailoRtRpc::Service { + +public: + virtual grpc::Status client_keep_alive(grpc::ServerContext *ctx, const keepalive_Request *request, + empty*) override; + + virtual grpc::Status get_service_version(grpc::ServerContext *, const get_service_version_Request *request, + get_service_version_Reply *reply) override; + + virtual grpc::Status VDevice_create(grpc::ServerContext *, const VDevice_create_Request *request, + VDevice_create_Reply *reply) override; + virtual grpc::Status VDevice_release(grpc::ServerContext *, const Release_Request *request, + Release_Reply* reply) override; + virtual grpc::Status VDevice_configure(grpc::ServerContext*, const VDevice_configure_Request* request, + VDevice_configure_Reply* reply) override; + virtual grpc::Status VDevice_get_physical_devices_ids(grpc::ServerContext*, const VDevice_get_physical_devices_ids_Request* request, + VDevice_get_physical_devices_ids_Reply* reply) override; + + virtual grpc::Status InputVStreams_create(grpc::ServerContext *, const VStream_create_Request *request, + VStreams_create_Reply *reply) override; + virtual grpc::Status InputVStream_release(grpc::ServerContext * , const Release_Request *request, + Release_Reply *reply) override; + virtual grpc::Status OutputVStreams_create(grpc::ServerContext *, const VStream_create_Request *request, + VStreams_create_Reply *reply) override; + virtual grpc::Status OutputVStream_release(grpc::ServerContext *, const Release_Request *request, + Release_Reply *reply) override; + virtual grpc::Status InputVStream_write(grpc::ServerContext*, const InputVStream_write_Request *request, + InputVStream_write_Reply *reply) override; + virtual grpc::Status OutputVStream_read(grpc::ServerContext*, const OutputVStream_read_Request *request, + OutputVStream_read_Reply *reply) override; + virtual grpc::Status InputVStream_get_frame_size(grpc::ServerContext*, const VStream_get_frame_size_Request *request, + VStream_get_frame_size_Reply *reply) override; + virtual grpc::Status OutputVStream_get_frame_size(grpc::ServerContext*, const VStream_get_frame_size_Request *request, + VStream_get_frame_size_Reply *reply) override; + virtual grpc::Status InputVStream_flush(grpc::ServerContext*, const InputVStream_flush_Request *request, + InputVStream_flush_Reply *reply) override; + virtual grpc::Status InputVStream_name(grpc::ServerContext*, const VStream_name_Request *request, + VStream_name_Reply *reply) override; + virtual grpc::Status OutputVStream_name(grpc::ServerContext*, const VStream_name_Request *request, + VStream_name_Reply *reply) override; + virtual grpc::Status InputVStream_abort(grpc::ServerContext*, const VStream_abort_Request *request, + VStream_abort_Reply *reply) override; + virtual grpc::Status OutputVStream_abort(grpc::ServerContext*, const VStream_abort_Request *request, + VStream_abort_Reply *reply) override; + virtual grpc::Status InputVStream_resume(grpc::ServerContext*, const VStream_resume_Request *request, + VStream_resume_Reply *reply) override; + virtual grpc::Status OutputVStream_resume(grpc::ServerContext*, const VStream_resume_Request *request, + VStream_resume_Reply *reply) override; + + virtual grpc::Status ConfiguredNetworkGroup_release(grpc::ServerContext*, const Release_Request* request, + Release_Reply* reply) override; + virtual grpc::Status ConfiguredNetworkGroup_make_input_vstream_params(grpc::ServerContext*, + const ConfiguredNetworkGroup_make_input_vstream_params_Request *request, + ConfiguredNetworkGroup_make_input_vstream_params_Reply *reply) override; + virtual grpc::Status ConfiguredNetworkGroup_make_output_vstream_params(grpc::ServerContext*, + const ConfiguredNetworkGroup_make_output_vstream_params_Request *request, + ConfiguredNetworkGroup_make_output_vstream_params_Reply *reply) override; + virtual grpc::Status ConfiguredNetworkGroup_get_name(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_name_Request *request, + ConfiguredNetworkGroup_get_name_Reply *reply) override; + virtual grpc::Status ConfiguredNetworkGroup_get_network_infos(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_network_infos_Request *request, + ConfiguredNetworkGroup_get_network_infos_Reply *reply) override; + virtual grpc::Status ConfiguredNetworkGroup_get_all_stream_infos(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_all_stream_infos_Request *request, + ConfiguredNetworkGroup_get_all_stream_infos_Reply *reply) override; + virtual grpc::Status ConfiguredNetworkGroup_get_default_stream_interface(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_default_stream_interface_Request *request, + ConfiguredNetworkGroup_get_default_stream_interface_Reply *reply) override; + virtual grpc::Status ConfiguredNetworkGroup_get_output_vstream_groups(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_output_vstream_groups_Request *request, + ConfiguredNetworkGroup_get_output_vstream_groups_Reply *reply) override; + virtual grpc::Status ConfiguredNetworkGroup_get_input_vstream_infos(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_vstream_infos_Request *request, + ConfiguredNetworkGroup_get_vstream_infos_Reply *reply) override; + virtual grpc::Status ConfiguredNetworkGroup_get_all_vstream_infos(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_vstream_infos_Request *request, + ConfiguredNetworkGroup_get_vstream_infos_Reply *reply) override; + virtual grpc::Status ConfiguredNetworkGroup_set_scheduler_timeout(grpc::ServerContext*, + const ConfiguredNetworkGroup_set_scheduler_timeout_Request *request, + ConfiguredNetworkGroup_set_scheduler_timeout_Reply *reply) override; + virtual grpc::Status ConfiguredNetworkGroup_set_scheduler_threshold(grpc::ServerContext*, + const ConfiguredNetworkGroup_set_scheduler_threshold_Request *request, + ConfiguredNetworkGroup_set_scheduler_threshold_Reply *reply) override; + virtual grpc::Status ConfiguredNetworkGroup_get_output_vstream_infos(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_vstream_infos_Request *request, + ConfiguredNetworkGroup_get_vstream_infos_Reply *reply) override; + virtual grpc::Status ConfiguredNetworkGroup_get_latency_measurement(grpc::ServerContext*, + const ConfiguredNetworkGroup_get_latency_measurement_Request *request, + ConfiguredNetworkGroup_get_latency_measurement_Reply *reply) override; +}; + +} + +#endif // HAILO_HAILORT_RPC_SERVICE_HPP_ \ No newline at end of file diff --git a/hailort/hailort_service/hailort_service.cpp b/hailort/hailort_service/hailort_service.cpp new file mode 100644 index 0000000..3f2071d --- /dev/null +++ b/hailort/hailort_service/hailort_service.cpp @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + * + * @file hailort_service.cpp + * @brief main for hailort service + * TODO: move to user guide (HRT-7559) + * * To run as without daemonize the executable: + * 1) Compile with `./build.sh` + * 2) Run `./bin/linux.x86_64.debug/hailort_service standalone` + * + * * To run as daemon service please follow the steps: + * 1) Install the HailoRT: + * cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release -DHAILO_BUILD_SERVICE=1 && sudo cmake --build build --target install + * + * 2) Reload systemd manager configuration: + * sudo systemctl daemon-reload + * + * 3) Enable and start the service + * sudo systemctl enable --now hailort.service + * + * 4) Stop service + * sudo systemctl stop hailort.service +*/ + +#include "hailort_rpc_service.hpp" +#include "rpc/rpc_definitions.hpp" +#include "common/utils.hpp" +#include "hailo/hailort_common.hpp" + +#include +#include + +void RunService() { + std::string server_address(hailort::HAILO_DEFAULT_UDS_ADDR); + hailort::HailoRtRpcService service; + + grpc::ServerBuilder builder; + builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); + builder.SetMaxReceiveMessageSize(-1); + builder.RegisterService(&service); + std::unique_ptr server(builder.BuildAndStart()); + chmod(hailort::HAILO_DEFAULT_SERVICE_ADDR.c_str(), S_IROTH | S_IWOTH | S_IRUSR | S_IWUSR); + server->Wait(); +} + +int main(int argc, char *argv[]) +{ + bool is_standalone = ((1 < argc) && (strcmp("standalone", argv[1]) == 0)); + if (!is_standalone) { + int ret = daemon(0,0); + if (ret < 0) { + syslog(LOG_ERR, "Failed to create daemon with errno %i", errno); + exit(EXIT_FAILURE); + } + } + + RunService(); + return 0; +} \ No newline at end of file diff --git a/hailort/hailort_service/service_resource_manager.hpp b/hailort/hailort_service/service_resource_manager.hpp new file mode 100644 index 0000000..d051ac0 --- /dev/null +++ b/hailort/hailort_service/service_resource_manager.hpp @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + **/ +/** + * @file service_resource_manager.hpp + * @brief manages handles for resource objects. + * + **/ + +#ifndef HAILO_SERVICE_RESOURCE_MANAGER_HPP_ +#define HAILO_SERVICE_RESOURCE_MANAGER_HPP_ + +#include "hailo/expected.hpp" +#include "common/utils.hpp" + +#include +#include + +namespace hailort +{ + +template +struct Resource { + Resource(uint32_t pid, std::shared_ptr resource) + : pid(pid), resource(std::move(resource)) + {} + std::shared_timed_mutex resource_mutex; + uint32_t pid; + std::shared_ptr resource; + +}; + +template +class ServiceResourceManager +{ +public: + static ServiceResourceManager& get_instance() + { + static ServiceResourceManager instance; + return instance; + } + + template + K execute(uint32_t key, Func &lambda, Args... args) + { + std::unique_lock lock(m_mutex); + auto resource_expected = resource_lookup(key); + assert(resource_expected); + + auto resource = resource_expected.release(); + std::shared_lock resource_lock(resource->resource_mutex); + lock.unlock(); + K ret = lambda(resource->resource, args...); + + return ret; + } + + uint32_t register_resource(uint32_t pid, std::shared_ptr const &resource) + { + std::unique_lock lock(m_mutex); + + // Create a new resource and register + auto index = m_current_handle_index; + m_resources.emplace(m_current_handle_index++, std::make_shared>(pid, std::move(resource))); + return index; + } + + hailo_status release_resource(uint32_t key) + { + std::unique_lock lock(m_mutex); + auto found = m_resources.find(key); + CHECK(found != m_resources.end(), HAILO_NOT_FOUND, "Failed to release resource with key {}, resource does not exist", key); + std::unique_lock resource_lock(found->second->resource_mutex); + m_resources.erase(key); + return HAILO_SUCCESS; + } + + void release_by_pid(uint32_t pid) + { + std::unique_lock lock(m_mutex); + for (auto iter = m_resources.begin(); iter != m_resources.end(); ) { + if (iter->second->pid == pid) { + std::unique_lock resource_lock(iter->second->resource_mutex); + iter = m_resources.erase(iter); + } else { + ++iter; + } + } + } + +private: + ServiceResourceManager() + : m_current_handle_index(0) + {} + + Expected>> resource_lookup(uint32_t key) + { + auto found = m_resources.find(key); + CHECK_AS_EXPECTED(found != m_resources.end(), HAILO_NOT_FOUND, "Failed to find resource with key {}", key); + + auto resource = found->second; + return resource; + } + + std::mutex m_mutex; + uint32_t m_current_handle_index; + std::unordered_map>> m_resources; +}; + +} + +#endif /* HAILO_SERVICE_RESOURCE_MANAGER_HPP_ */ diff --git a/hailort/hailortcli/CMakeLists.txt b/hailort/hailortcli/CMakeLists.txt index ba3dc6a..1cb4956 100644 --- a/hailort/hailortcli/CMakeLists.txt +++ b/hailort/hailortcli/CMakeLists.txt @@ -23,6 +23,7 @@ set(HAILORTCLI_CPP_FILES temp_measurement.cpp parse_hef_command.cpp graph_printer.cpp + mon_command.cpp ) if(UNIX) @@ -52,8 +53,14 @@ add_executable(hailortcli target_compile_options(hailortcli PRIVATE ${HAILORT_COMPILE_OPTIONS}) set_property(TARGET hailortcli PROPERTY CXX_STANDARD 14) set_property(TARGET hailortcli PROPERTY INSTALL_RPATH "$ORIGIN" "../lib/") # Link with a relative libhailort -target_link_libraries(hailortcli libhailort CLI11::CLI11 nlohmann_json spdlog::spdlog readerwriterqueue) -target_link_libraries(hailortcli DotWriter) +target_link_libraries(hailortcli + libhailort + CLI11::CLI11 + nlohmann_json + spdlog::spdlog + readerwriterqueue + DotWriter + scheduler_mon_proto) # TODO: Remove microprofile after removing pipeline.cpp from hailortcli sources target_link_libraries(hailortcli microprofile) @@ -72,4 +79,6 @@ target_include_directories(hailortcli install(TARGETS hailortcli RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - CONFIGURATIONS Release) \ No newline at end of file + CONFIGURATIONS Release +) +cli11_install_completion_file(hailortcli) \ No newline at end of file diff --git a/hailort/hailortcli/benchmark_command.cpp b/hailort/hailortcli/benchmark_command.cpp index f7260f7..7c3d1c1 100644 --- a/hailort/hailortcli/benchmark_command.cpp +++ b/hailort/hailortcli/benchmark_command.cpp @@ -18,8 +18,7 @@ BenchmarkCommand::BenchmarkCommand(CLI::App &parent_app) : Command(parent_app.add_subcommand("benchmark", "Measure basic performance on compiled network")), m_params({}) { - add_device_options(m_app, m_params.device_params); - add_vdevice_options(m_app, m_params.device_params); + add_vdevice_options(m_app, m_params.vdevice_params); m_params.measure_overall_latency = false; m_params.power_measurement.measure_current = false; m_params.show_progress = true; @@ -56,7 +55,7 @@ BenchmarkCommand::BenchmarkCommand(CLI::App &parent_app) : // TODO HRT-5363 support multiple devices m_app->parse_complete_callback([this]() { - PARSE_CHECK((this->m_params.device_params.vdevice_params.device_count == 1) || this->m_csv_file_path.empty() || this->m_not_measure_power, + PARSE_CHECK((m_params.vdevice_params.device_count == 1) || m_csv_file_path.empty() || m_not_measure_power, "Writing power measurements in csv format is not supported for multiple devices"); }); } @@ -73,22 +72,37 @@ hailo_status BenchmarkCommand::execute() auto streaming_mode_info = fps_streaming_mode(); CHECK_EXPECTED_AS_STATUS(streaming_mode_info, "FPS in streaming mode failed"); - // TODO - HRT-6931 - measure latnecy only in the case of single device. + // TODO - HRT-6931 - measure latency only in the case of single device. std::cout << "Measuring HW Latency" << std::endl; auto latency_info = latency(); CHECK_EXPECTED_AS_STATUS(latency_info, "Latency measuring failed"); + assert(hw_only_mode_info->network_group_results().size() == streaming_mode_info->network_group_results().size()); + assert(latency_info->network_group_results().size() == streaming_mode_info->network_group_results().size()); + std::cout << std::endl; std::cout << "=======" << std::endl; std::cout << "Summary" << std::endl; std::cout << "=======" << std::endl; - std::cout << "FPS (hw_only) = " << hw_only_mode_info->fps().value() <overall_latency()) { - std::cout << " (overall) = " << InferResultsFormatUtils::latency_result_to_ms(overall_latency.value()) << " ms" << std::endl; + + for (auto &hw_only_res : hw_only_mode_info->network_group_results()) { + auto network_group_name = hw_only_res.network_group_name(); + auto streaming_res = std::find_if(streaming_mode_info->network_group_results().begin(), streaming_mode_info->network_group_results().end(), + [network_group_name] (NetworkGroupInferResult &infer_results) { return (infer_results.network_group_name() == network_group_name); }); + CHECK(streaming_mode_info->network_group_results().end() != streaming_res, HAILO_INTERNAL_FAILURE, "Failed to fun streaming results for network group {}", network_group_name); + + auto latency_res = std::find_if(latency_info->network_group_results().begin(), latency_info->network_group_results().end(), + [network_group_name] (NetworkGroupInferResult &infer_results) { return (infer_results.network_group_name() == network_group_name); }); + CHECK(latency_info->network_group_results().end() != latency_res, HAILO_INTERNAL_FAILURE, "Failed to fun latency results for network group {}", network_group_name); + + std::cout << "FPS (hw_only) = " << hw_only_res.fps().value() <overall_latency()) { + std::cout << " (overall) = " << InferResultsFormatUtils::latency_result_to_ms(overall_latency.value()) << " ms" << std::endl; + } } if (!m_not_measure_power) { for (const auto &pair : streaming_mode_info->m_power_measurements) { @@ -98,7 +112,6 @@ hailo_status BenchmarkCommand::execute() std::cout << " Power in streaming mode (average) = " << data.average_value << " " << power_units << std::endl; std::cout << " (max) = " << data.max_value << " " << power_units << std::endl; } - } if (!m_csv_file_path.empty()){ @@ -106,13 +119,13 @@ hailo_status BenchmarkCommand::execute() auto printer = InferStatsPrinter::create(m_params, false); CHECK_EXPECTED_AS_STATUS(printer, "Failed to initialize infer stats printer"); printer->print_benchmark_csv_header(); - printer->print_benchmark_csv(m_params.hef_path, hw_only_mode_info.release(), - streaming_mode_info.release(), latency_info.release()); + printer->print_benchmark_csv(hw_only_mode_info.value(), + streaming_mode_info.value(), latency_info.value()); } return HAILO_SUCCESS; } -Expected BenchmarkCommand::hw_only_mode() +Expected BenchmarkCommand::hw_only_mode() { m_params.transform.transform = (m_params.inputs_name_and_file_path.size() > 0); m_params.power_measurement.measure_power = false; @@ -121,7 +134,7 @@ Expected BenchmarkCommand::hw_only_mode() return run_command_hef(m_params); } -Expected BenchmarkCommand::fps_streaming_mode() +Expected BenchmarkCommand::fps_streaming_mode() { m_params.power_measurement.measure_power = !m_not_measure_power; m_params.mode = InferMode::STREAMING; @@ -131,7 +144,7 @@ Expected BenchmarkCommand::fps_streaming_mode() return run_command_hef(m_params); } -Expected BenchmarkCommand::latency() +Expected BenchmarkCommand::latency() { m_params.power_measurement.measure_power = false; m_params.measure_latency = true; diff --git a/hailort/hailortcli/benchmark_command.hpp b/hailort/hailortcli/benchmark_command.hpp index 38178e0..2b8c399 100644 --- a/hailort/hailortcli/benchmark_command.hpp +++ b/hailort/hailortcli/benchmark_command.hpp @@ -21,9 +21,9 @@ public: hailo_status execute() override; private: - Expected hw_only_mode(); - Expected fps_streaming_mode(); - Expected latency(); + Expected hw_only_mode(); + Expected fps_streaming_mode(); + Expected latency(); inference_runner_params m_params; bool m_not_measure_power; diff --git a/hailort/hailortcli/command.cpp b/hailort/hailortcli/command.cpp index 49b1e04..f0f7754 100644 --- a/hailort/hailortcli/command.cpp +++ b/hailort/hailortcli/command.cpp @@ -41,34 +41,21 @@ DeviceCommand::DeviceCommand(CLI::App *app) : hailo_status DeviceCommand::execute() { - if ((DeviceType::PCIE == m_device_params.device_type ) && - ("*" == m_device_params.pcie_params.pcie_bdf)) { - return execute_on_all_pcie_devices(); + auto devices = create_devices(m_device_params); + if (!devices) { + return devices.status(); } - auto device = create_device(m_device_params); - if (!device) { - return device.status(); - } - - return execute_on_device(*device.value()); + return execute_on_devices(devices.value()); } -hailo_status DeviceCommand::execute_on_all_pcie_devices() +hailo_status DeviceCommand::execute_on_devices(std::vector> &devices) { auto status = HAILO_SUCCESS; // Best effort - auto all_devices_infos = Device::scan_pcie(); - if (!all_devices_infos) { - return all_devices_infos.status(); - } - for (auto &dev_info : all_devices_infos.value()) { - auto device = Device::create_pcie(dev_info); - if (!device) { - return device.status(); - } - - auto execute_status = execute_on_device(*device.value()); + for (auto &device : devices) { + std::cout << "Executing on device: " << device->get_dev_id() << std::endl; + auto execute_status = execute_on_device(*device); if (HAILO_SUCCESS != execute_status) { - std::cerr << "Failed to execute on device: " << device.value()->get_dev_id() << ". status= " << execute_status << std::endl; + std::cerr << "Failed to execute on device: " << device->get_dev_id() << ". status= " << execute_status << std::endl; status = execute_status; } } diff --git a/hailort/hailortcli/command.hpp b/hailort/hailortcli/command.hpp index 9ad9f9c..729b5dc 100644 --- a/hailort/hailortcli/command.hpp +++ b/hailort/hailortcli/command.hpp @@ -65,7 +65,7 @@ public: protected: virtual hailo_status execute_on_device(Device &device) = 0; - hailo_status execute_on_all_pcie_devices(); + hailo_status execute_on_devices(std::vector> &devices); private: hailo_device_params m_device_params; diff --git a/hailort/hailortcli/common.hpp b/hailort/hailortcli/common.hpp index 54179f7..8c8ce43 100644 --- a/hailort/hailortcli/common.hpp +++ b/hailort/hailortcli/common.hpp @@ -50,4 +50,30 @@ struct FileSuffixValidator : public CLI::Validator { } }; -#endif /* _HAILO_HAILORTCLI_COMMON_HPP_ */ \ No newline at end of file +// Based on NLOHMANN_JSON_SERIALIZE_ENUM (json/include/nlohmann/json.hpp) +// Accepts a static array instead of building one in the function +#define NLOHMANN_JSON_SERIALIZE_ENUM2(ENUM_TYPE, _pair_arr)\ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + auto it = std::find_if(std::begin(_pair_arr), std::end(_pair_arr), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(_pair_arr)) ? it : std::begin(_pair_arr))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + auto it = std::find_if(std::begin(_pair_arr), std::end(_pair_arr), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(_pair_arr)) ? it : std::begin(_pair_arr))->first; \ + } + +#endif /* _HAILO_HAILORTCLI_COMMON_HPP_ */ diff --git a/hailort/hailortcli/download_action_list_command.cpp b/hailort/hailortcli/download_action_list_command.cpp index d88ca7c..0eb16c4 100644 --- a/hailort/hailortcli/download_action_list_command.cpp +++ b/hailort/hailortcli/download_action_list_command.cpp @@ -10,12 +10,16 @@ #include "download_action_list_command.hpp" #include "common.hpp" #include "common/file_utils.hpp" -#include "md5.h" +#include "common/string_utils.hpp" #include #include #define MHz (1000 * 1000) +// div factor is valid only for Hailo8-B0 platform. +// TODO - HRT-7364 - add CPU subsystem frequency into the device extended info control +// and use it for get the timer's frequency +#define NN_CORE_TO_TIMER_FREQ_FACTOR (2) constexpr int DownloadActionListCommand::INVALID_NUMERIC_VALUE; @@ -38,7 +42,7 @@ hailo_status DownloadActionListCommand::execute(Device &device, const std::strin auto extended_info = device.get_extended_device_information(); CHECK_EXPECTED_AS_STATUS(extended_info); - const auto clock_cycle = extended_info->neural_network_core_clock_rate / MHz; + const auto clock_cycle = (extended_info->neural_network_core_clock_rate / NN_CORE_TO_TIMER_FREQ_FACTOR) / MHz; ordered_json action_list_json = { {"version", ACTION_LIST_FORMAT_VERSION()}, @@ -90,6 +94,7 @@ Expected DownloadActionListCommand::parse_hef_metadata(const std:: return hef_info_json; } + bool DownloadActionListCommand::is_valid_hef(const std::string &hef_file_path) { // Open hef, to check that it's valid @@ -108,14 +113,8 @@ Expected DownloadActionListCommand::calc_md5_hexdigest(const std::s MD5_Update(&md5_ctx, hef_bin->data(), hef_bin->size()); MD5_Final(md5_sum, &md5_ctx); - std::stringstream hexdigest; - for (uint32_t i = 0; i < ARRAY_ENTRIES(md5_sum); i++) { - // cast to int needed for proper formatting - static const int NUM_HEX_DIGITS_IN_UNIT8 = 2; - hexdigest << std::hex << std::setfill('0') << std::setw(NUM_HEX_DIGITS_IN_UNIT8) << static_cast(md5_sum[i]); - } - - return hexdigest.str(); + const bool LOWERCASE = false; + return StringUtils::to_hex_string(md5_sum, ARRAY_ENTRIES(md5_sum), LOWERCASE); } hailo_status DownloadActionListCommand::write_json(const ordered_json &json_obj, const std::string &output_file_path, @@ -131,6 +130,11 @@ hailo_status DownloadActionListCommand::write_json(const ordered_json &json_obj, return HAILO_SUCCESS; } +// We want to make sure that the switch-case bellow handles all of the action types in order to prevent parsing errors +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic error "-Wswitch-enum" +#endif Expected DownloadActionListCommand::parse_action_data(uint32_t base_address, uint8_t *action, uint32_t current_buffer_offset, uint32_t *action_length, CONTEXT_SWITCH_DEFS__ACTION_TYPE_t action_type, uint32_t timestamp, uint8_t sub_action_index, bool sub_action_index_set, bool *is_repeated, uint8_t *num_repeated, @@ -275,14 +279,24 @@ Expected DownloadActionListCommand::parse_action_data(uint32_t bas data_json = *reinterpret_cast(action); action_length_local = sizeof(CONTEXT_SWITCH_DEFS__deactivate_cfg_channel_t); break; + case CONTEXT_SWITCH_DEFS__ACTION_TYPE_DDR_BUFFERING_RESET: + data_json = json({}); + action_length_local = 0; + break; + case CONTEXT_SWITCH_DEFS__ACTION_TYPE_COUNT: + // Fallthrough + // Handling CONTEXT_SWITCH_DEFS__ACTION_TYPE_COUNT is needed because we compile this file with -Wswitch-enum default: - std::cerr << "PARSING ERROR ! unknown action main type" << std::endl; + std::cerr << "PARSING ERROR ! unknown action main type " << action_type << std::endl; return make_unexpected(HAILO_INTERNAL_FAILURE); } action_json["data"] = data_json; *action_length = static_cast(action_length_local); return action_json; } +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif Expected DownloadActionListCommand::parse_single_repeated_action(uint32_t base_address, uint8_t *action, uint32_t current_buffer_offset, uint32_t *action_length, @@ -410,12 +424,68 @@ Expected DownloadActionListCommand::parse_network_groups(Device &d return network_group_list_json; } -void to_json(json& j, const CONTEXT_SWITCH_DEFS__fetch_cfg_channel_descriptors_action_data_t& data) { - j = json{{"descriptors_count", data.descriptors_count}, {"channel_index", data.cfg_channel_number}}; +template +static json unpack_vdma_channel_id(const ActionData &data) +{ + uint8_t engine_index = 0; + uint8_t vdma_channel_index = 0; + CONTEXT_SWITCH_DEFS__PACKED_VDMA_CHANNEL_ID__READ(data.packed_vdma_channel_id, engine_index, vdma_channel_index); + return json{{"vdma_channel_index", vdma_channel_index}, {"engine_index", engine_index}}; +} + +void to_json(json &j, const CONTEXT_SWITCH_DEFS__deactivate_vdma_channel_action_data_t &data) +{ + j = unpack_vdma_channel_id(data); +} + +void to_json(json &j, const CONTEXT_SWITCH_DEFS__validate_vdma_channel_action_data_t &data) +{ + j = unpack_vdma_channel_id(data); +} + +void to_json(json &j, const CONTEXT_SWITCH_DEFS__activate_boundary_input_data_t &data) +{ + j = unpack_vdma_channel_id(data); + j["stream_index"] = data.stream_index; +} + +void to_json(json &j, const CONTEXT_SWITCH_DEFS__activate_inter_context_input_data_t &data) +{ + j = unpack_vdma_channel_id(data); + j["stream_index"] = data.stream_index; +} + +void to_json(json &j, const CONTEXT_SWITCH_DEFS__activate_ddr_buffer_input_data_t &data) +{ + j = unpack_vdma_channel_id(data); + j["stream_index"] = data.stream_index; +} + +void to_json(json &j, const CONTEXT_SWITCH_DEFS__activate_boundary_output_data_t &data) +{ + j = unpack_vdma_channel_id(data); + j["stream_index"] = data.stream_index; } -void to_json(json& j, const CONTEXT_SWITCH_DEFS__fetch_ccw_bursts_action_data_t& data) { - j = json{{"channel_index", data.cfg_channel_number}}; +void to_json(json &j, const CONTEXT_SWITCH_DEFS__activate_inter_context_output_data_t &data) +{ + j = unpack_vdma_channel_id(data); + j["stream_index"] = data.stream_index; +} + +void to_json(json &j, const CONTEXT_SWITCH_DEFS__activate_ddr_buffer_output_data_t &data) +{ + j = unpack_vdma_channel_id(data); + j["stream_index"] = data.stream_index; +} + +// Needs to be backwards compatible, so we use "channel_index" instead of "vdma_channel_index". +void to_json(json& j, const CONTEXT_SWITCH_DEFS__fetch_cfg_channel_descriptors_action_data_t& data) { + uint8_t engine_index = 0; + uint8_t vdma_channel_index = 0; + CONTEXT_SWITCH_DEFS__PACKED_VDMA_CHANNEL_ID__READ(data.packed_vdma_channel_id, engine_index, vdma_channel_index); + j = json{{"descriptors_count", data.descriptors_count}, {"channel_index", vdma_channel_index}, + {"engine_index", engine_index}}; } void to_json(json& j, const CONTEXT_SWITCH_DEFS__enable_lcu_action_non_default_data_t& data) { @@ -437,8 +507,26 @@ void to_json(json& j, const CONTEXT_SWITCH_DEFS__disable_lcu_action_data_t& data } void to_json(json& j, const CONTEXT_SWITCH_DEFS__change_vdma_to_stream_mapping_data_t& data) { - j = json{{"vdma_channel_index", data.vdma_channel_index}, {"stream_index", data.stream_index}, - {"type", data.is_dummy_stream ? "dummy" : "active"}}; + j = unpack_vdma_channel_id(data); + j["stream_index"] = data.stream_index; + j["type"] = data.is_dummy_stream ? "dummy" : "active"; +} + +void to_json(json &j, const CONTEXT_SWITCH_DEFS__fetch_data_action_data_t &data) +{ + j = unpack_vdma_channel_id(data); + j["stream_index"] = data.stream_index; +} + +void to_json(json &j, const CONTEXT_SWITCH_DEFS__wait_dma_idle_data_t &data) +{ + j = unpack_vdma_channel_id(data); + j["stream_index"] = data.stream_index; +} + +void to_json(json &j, const CONTEXT_SWITCH_DEFS__vdma_dataflow_interrupt_data_t &data) +{ + j = unpack_vdma_channel_id(data); } void to_json(json& j, const CONTEXT_SWITCH_DEFS__lcu_interrupt_data_t& data) { @@ -446,3 +534,36 @@ void to_json(json& j, const CONTEXT_SWITCH_DEFS__lcu_interrupt_data_t& data) { const auto lcu_index = CONTEXT_SWITCH_DEFS__PACKED_LCU_ID_LCU_INDEX_READ(data.packed_lcu_id); j = json{{"cluster_index", cluster_index}, {"lcu_index", lcu_index}}; } + +void to_json(json &j, const CONTEXT_SWITCH_DEFS__activate_cfg_channel_t &data) +{ + uint8_t engine_index = 0; + uint8_t vdma_channel_index = 0; + CONTEXT_SWITCH_DEFS__PACKED_VDMA_CHANNEL_ID__READ(data.packed_vdma_channel_id, engine_index, vdma_channel_index); + j = json{{"config_stream_index", data.config_stream_index}, {"channel_index", vdma_channel_index}, + {"engine_index", engine_index}}; +} +void to_json(json &j, const CONTEXT_SWITCH_DEFS__deactivate_cfg_channel_t &data) +{ + uint8_t engine_index = 0; + uint8_t vdma_channel_index = 0; + CONTEXT_SWITCH_DEFS__PACKED_VDMA_CHANNEL_ID__READ(data.packed_vdma_channel_id, engine_index, vdma_channel_index); + j = json{{"config_stream_index", data.config_stream_index}, {"channel_index", vdma_channel_index}, + {"engine_index", engine_index}}; +} + +void to_json(json &j, const CONTEXT_SWITCH_DEFS__add_ddr_pair_info_action_data_t &data) +{ + uint8_t h2d_engine_index = 0; + uint8_t h2d_vdma_channel_index = 0; + uint8_t d2h_engine_index = 0; + uint8_t d2h_vdma_channel_index = 0; + + CONTEXT_SWITCH_DEFS__PACKED_VDMA_CHANNEL_ID__READ(data.h2d_packed_vdma_channel_id, h2d_engine_index, + h2d_vdma_channel_index); + CONTEXT_SWITCH_DEFS__PACKED_VDMA_CHANNEL_ID__READ(data.d2h_packed_vdma_channel_id, d2h_engine_index, + d2h_vdma_channel_index); + + j = json{{"h2d_engine_index", h2d_engine_index}, {"h2d_vdma_channel_index", h2d_vdma_channel_index}, + {"d2h_engine_index", d2h_engine_index}, {"d2h_vdma_channel_index", d2h_vdma_channel_index}}; +} diff --git a/hailort/hailortcli/download_action_list_command.hpp b/hailort/hailortcli/download_action_list_command.hpp index e669982..65638a1 100644 --- a/hailort/hailortcli/download_action_list_command.hpp +++ b/hailort/hailortcli/download_action_list_command.hpp @@ -11,9 +11,11 @@ #define _HAILO_DOWNLOAD_ACTION_LIST_COMMAND_HPP_ #include "hailortcli.hpp" +#include "common.hpp" #include "command.hpp" #include "context_switch_defs.h" +#include "common/utils.hpp" #include @@ -62,7 +64,7 @@ private: // JSON serialization -NLOHMANN_JSON_SERIALIZE_ENUM(CONTEXT_SWITCH_DEFS__ACTION_TYPE_t, { +static std::pair mapping[] = { {CONTEXT_SWITCH_DEFS__ACTION_TYPE_FETCH_CFG_CHANNEL_DESCRIPTORS, "fetch_cfg_channel_descriptors"}, {CONTEXT_SWITCH_DEFS__ACTION_TYPE_TRIGGER_SEQUENCER, "trigger_sequencer"}, {CONTEXT_SWITCH_DEFS__ACTION_TYPE_FETCH_DATA_FROM_VDMA_CHANNEL, "fetch_data_from_vdma_channel"}, @@ -93,38 +95,42 @@ NLOHMANN_JSON_SERIALIZE_ENUM(CONTEXT_SWITCH_DEFS__ACTION_TYPE_t, { {CONTEXT_SWITCH_DEFS__ACTION_TYPE_WAIT_FOR_DMA_IDLE_ACTION, "wait_for_dma_idle_action"}, {CONTEXT_SWITCH_DEFS__ACTION_TYPE_WAIT_FOR_NMS_IDLE, "wait_for_nms_idle"}, {CONTEXT_SWITCH_DEFS__ACTION_TYPE_FETCH_CCW_BURSTS, "fetch_ccw_bursts"}, - {CONTEXT_SWITCH_DEFS__ACTION_TYPE_COUNT, nullptr}, -}); + {CONTEXT_SWITCH_DEFS__ACTION_TYPE_DDR_BUFFERING_RESET, "ddr_buffering_reset"} +}; +static_assert(ARRAY_ENTRIES(mapping) == CONTEXT_SWITCH_DEFS__ACTION_TYPE_COUNT, + "Missing a mapping from a CONTEXT_SWITCH_DEFS__ACTION_TYPE_t to it's string value"); + +NLOHMANN_JSON_SERIALIZE_ENUM2(CONTEXT_SWITCH_DEFS__ACTION_TYPE_t, mapping); // Default implementions NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__repeated_action_header_t, count, last_executed, sub_action_type); NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__trigger_sequencer_action_data_t, cluster_index); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__deactivate_vdma_channel_action_data_t, vdma_channel_index); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__validate_vdma_channel_action_data_t, vdma_channel_index); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__fetch_data_action_data_t, vdma_channel_index, stream_index); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__add_ddr_pair_info_action_data_t, h2d_vdma_channel_index, d2h_vdma_channel_index); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__vdma_dataflow_interrupt_data_t, vdma_channel_index); NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__sequencer_interrupt_data_t, sequencer_index); NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__wait_nms_idle_data_t, aggregator_index); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__wait_dma_idle_data_t, vdma_channel_index, stream_index); NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__module_config_done_interrupt_data_t, module_index); NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__application_change_interrupt_data_t, application_index); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__activate_boundary_input_data_t, vdma_channel_index, stream_index); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__activate_inter_context_input_data_t, vdma_channel_index, stream_index); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__activate_ddr_buffer_input_data_t, vdma_channel_index, stream_index); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__activate_boundary_output_data_t, vdma_channel_index, stream_index); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__activate_inter_context_output_data_t, vdma_channel_index, stream_index); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__activate_ddr_buffer_output_data_t, vdma_channel_index, stream_index); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__activate_cfg_channel_t, channel_index); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__deactivate_cfg_channel_t, channel_index); +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CONTEXT_SWITCH_DEFS__fetch_ccw_bursts_action_data_t, config_stream_index); // Non-default implementations -void to_json(json& j, const CONTEXT_SWITCH_DEFS__enable_lcu_action_default_data_t& data); -void to_json(json& j, const CONTEXT_SWITCH_DEFS__enable_lcu_action_non_default_data_t& data); -void to_json(json& j, const CONTEXT_SWITCH_DEFS__disable_lcu_action_data_t& data); -void to_json(json& j, const CONTEXT_SWITCH_DEFS__fetch_cfg_channel_descriptors_action_data_t& data); -void to_json(json& j, const CONTEXT_SWITCH_DEFS__fetch_ccw_bursts_action_data_t& data); -void to_json(json& j, const CONTEXT_SWITCH_DEFS__change_vdma_to_stream_mapping_data_t& data); -void to_json(json& j, const CONTEXT_SWITCH_DEFS__lcu_interrupt_data_t& data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__deactivate_vdma_channel_action_data_t &data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__validate_vdma_channel_action_data_t &data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__activate_boundary_input_data_t &data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__activate_inter_context_input_data_t &data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__activate_ddr_buffer_input_data_t &data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__activate_boundary_output_data_t &data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__activate_inter_context_output_data_t &data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__activate_ddr_buffer_output_data_t &data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__enable_lcu_action_default_data_t& data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__enable_lcu_action_non_default_data_t& data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__disable_lcu_action_data_t& data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__fetch_cfg_channel_descriptors_action_data_t& data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__change_vdma_to_stream_mapping_data_t& data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__fetch_data_action_data_t &data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__wait_dma_idle_data_t &data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__vdma_dataflow_interrupt_data_t &data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__lcu_interrupt_data_t& data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__activate_cfg_channel_t &data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__deactivate_cfg_channel_t &data); +void to_json(json &j, const CONTEXT_SWITCH_DEFS__add_ddr_pair_info_action_data_t &data); #endif /* _HAILO_DOWNLOAD_ACTION_LIST_COMMAND_HPP_ */ diff --git a/hailort/hailortcli/fw_config_serializer.cpp b/hailort/hailortcli/fw_config_serializer.cpp index 81230e9..a78981a 100644 --- a/hailort/hailortcli/fw_config_serializer.cpp +++ b/hailort/hailortcli/fw_config_serializer.cpp @@ -12,6 +12,7 @@ #include "hailo/hailort.h" #include "user_config_common.h" #include "common/file_utils.hpp" +#include "common/string_utils.hpp" #include #include @@ -266,16 +267,8 @@ Expected FwConfigJsonSerializer::deserialize_mac_address(uint8_t *entry_va CHECK_AS_EXPECTED((MAC_ADDRESS_LENGTH == size), HAILO_INTERNAL_FAILURE, "Mac address length is invalid. Length recieved is: {}, length expected: {}", size, MAC_ADDRESS_LENGTH); - std::stringstream ss; - for (size_t i = 0; i < MAC_ADDRESS_LENGTH; i++) { - if (i != 0) { - ss << ':'; - } - ss.width(2); - ss.fill('0'); - ss << std::uppercase << std::hex << (int)(entry_value[i]); - } - return json(ss.str()); + const bool UPPERCASE = true; + return json(StringUtils::to_hex_string(entry_value, size, UPPERCASE, ":")); } Expected FwConfigJsonSerializer::deserialize_supported_aspm_states(uint8_t *entry_value, uint32_t size) diff --git a/hailort/hailortcli/fw_control_command.cpp b/hailort/hailortcli/fw_control_command.cpp index 5124600..757df33 100644 --- a/hailort/hailortcli/fw_control_command.cpp +++ b/hailort/hailortcli/fw_control_command.cpp @@ -9,9 +9,10 @@ #include "fw_control_command.hpp" #include "firmware_header_utils.h" +#include "common/string_utils.hpp" -static const char *NOT_CONFIGURED_ATTR = ""; +static const char *NOT_CONFIGURED_ATTR = ""; #define MHz (1000 * 1000) @@ -55,14 +56,8 @@ static std::string extended_device_information_supported_features(hailo_device_s static void extended_device_information_print_array(uint8_t *array_for_print, size_t array_length, std::string splitter) { - uint32_t i = 0; - for(i = 0; i < array_length; i++) { - std::cout << std::setfill('0') << std::setw(2) << std::uppercase << std::hex << static_cast(array_for_print[i]); - if(array_length != (i+1)) { - std::cout << splitter; - } - } - std::cout << std::endl; + const bool UPPERCASE = true; + std::cout << StringUtils::to_hex_string(array_for_print, array_length, UPPERCASE, splitter) << std::endl; } static bool extended_device_information_is_array_not_empty(uint8_t *array_for_print, size_t array_length) @@ -135,8 +130,10 @@ static std::string fw_version_string(const hailo_device_identity_t &identity) static std::string identity_arch_string(const hailo_device_identity_t &identity) { switch (identity.device_architecture) { - case HAILO_ARCH_HAILO8_B0: - return "HAILO8_B0"; + case HAILO_ARCH_HAILO8: + return "HAILO8"; + case HAILO_ARCH_HAILO8L: + return "HAILO8L"; case HAILO_ARCH_MERCURY_CA: return "MERCURY_CA"; case HAILO_ARCH_MERCURY_VPU: diff --git a/hailort/hailortcli/graph_printer.cpp b/hailort/hailortcli/graph_printer.cpp index 5aba8f9..f58fa9b 100644 --- a/hailort/hailortcli/graph_printer.cpp +++ b/hailort/hailortcli/graph_printer.cpp @@ -206,55 +206,60 @@ void PipelineGraphNode::set_visited() m_visited = true; } -hailo_status GraphPrinter::write_dot_file(const std::map> &input_vstreams_per_network, - const std::map> &output_vstreams_per_network, const std::string &graph_title, +hailo_status GraphPrinter::write_dot_file(const std::vector>>> &input_vstreams_per_network_group, + const std::vector>>> &output_vstreams_per_network_group, const std::string &graph_title, const std::string &output_path, bool write_pipeline_stats) { - PipelineGraph graph(input_vstreams_per_network, output_vstreams_per_network, graph_title, write_pipeline_stats); + PipelineGraph graph(input_vstreams_per_network_group, output_vstreams_per_network_group, graph_title, write_pipeline_stats); return graph.write_dot_file(output_path); } -GraphPrinter::PipelineGraph::PipelineGraph(const std::map> &input_vstreams_per_network, - const std::map> &output_vstreams_per_network, +GraphPrinter::PipelineGraph::PipelineGraph(const std::vector>>> &input_vstreams_per_network_group, + const std::vector>>> &output_vstreams_per_network_group, const std::string &graph_title, bool write_pipeline_stats) : m_graph(true, create_graph_title_label(graph_title, DefaultNodeAttrs::MAIN_LABEL_FONT_SIZE)), m_elems_in_graph() { - size_t total_inputs_count = 0; - for (auto &input_vstreams_pair : input_vstreams_per_network) { - total_inputs_count += input_vstreams_pair.second.size(); - } - size_t total_outputs_count = 0; - for (auto &output_vstreams_pair : output_vstreams_per_network) { - total_outputs_count += output_vstreams_pair.second.size(); - } - // Set the graph "graph title" label to be on top m_graph.GetAttributes().SetLabelLoc(DotWriter::LabelLoc::T); // Set the graph direction from left to right m_graph.GetAttributes().SetRankDir(DotWriter::RankDir::LR); - m_graph.GetAttributes().SetPackMode(format_pack_mode(total_outputs_count + total_inputs_count)); - // Note: This order is important (input pipelines will be printed above output pipelines) - for (const auto &output_vstreams_pair : output_vstreams_per_network) { - for (const auto &vstream : output_vstreams_pair.second) { - update_graph_nodes(vstream.get_pipeline(), write_pipeline_stats); + assert(input_vstreams_per_network_group.size() == output_vstreams_per_network_group.size()); + + for (size_t network_group_index = 0; network_group_index < input_vstreams_per_network_group.size(); network_group_index++) { + size_t total_inputs_count = 0; + for (auto &input_vstreams_pair : input_vstreams_per_network_group[network_group_index]) { + total_inputs_count += input_vstreams_pair.second.size(); } - } - for (const auto &input_vstreams_pair : input_vstreams_per_network) { - for (const auto &vstream : input_vstreams_pair.second) { - update_graph_nodes(vstream.get_pipeline(), write_pipeline_stats); + size_t total_outputs_count = 0; + for (auto &output_vstreams_pair : output_vstreams_per_network_group[network_group_index]) { + total_outputs_count += output_vstreams_pair.second.size(); } - } - for (const auto &output_vstreams_pair : output_vstreams_per_network) { - for (const auto &vstream : output_vstreams_pair.second) { - update_edges_in_graph(vstream.get_pipeline(), "HW", "user_output"); + + m_graph.GetAttributes().SetPackMode(format_pack_mode(total_outputs_count + total_inputs_count)); + + // Note: This order is important (input pipelines will be printed above output pipelines) + for (const auto &output_vstreams_pair : output_vstreams_per_network_group[network_group_index]) { + for (const auto &vstream : output_vstreams_pair.second) { + update_graph_nodes(vstream.get().get_pipeline(), write_pipeline_stats); + } } - } - for (const auto &input_vstreams_pair : input_vstreams_per_network) { - for (const auto &vstream : input_vstreams_pair.second) { - update_edges_in_graph(vstream.get_pipeline(), "user_input", "HW"); + for (const auto &input_vstreams_pair : input_vstreams_per_network_group[network_group_index]) { + for (const auto &vstream : input_vstreams_pair.second) { + update_graph_nodes(vstream.get().get_pipeline(), write_pipeline_stats); + } + } + for (const auto &output_vstreams_pair : output_vstreams_per_network_group[network_group_index]) { + for (const auto &vstream : output_vstreams_pair.second) { + update_edges_in_graph(vstream.get().get_pipeline(), "HW", "user_output"); + } + } + for (const auto &input_vstreams_pair : input_vstreams_per_network_group[network_group_index]) { + for (const auto &vstream : input_vstreams_pair.second) { + update_edges_in_graph(vstream.get().get_pipeline(), "user_input", "HW"); + } } } } diff --git a/hailort/hailortcli/graph_printer.hpp b/hailort/hailortcli/graph_printer.hpp index f0be525..4443642 100644 --- a/hailort/hailortcli/graph_printer.hpp +++ b/hailort/hailortcli/graph_printer.hpp @@ -121,16 +121,16 @@ class GraphPrinter { public: GraphPrinter() = delete; - static hailo_status write_dot_file(const std::map> &input_vstreams_per_network, - const std::map> &output_vstreams_per_network, + static hailo_status write_dot_file(const std::vector>>> &input_vstreams_per_network_group, + const std::vector>>> &output_vstreams_per_network_group, const std::string &graph_title, const std::string &output_path, bool write_pipeline_stats); private: class PipelineGraph final { public: - PipelineGraph(const std::map> &input_vstreams_per_network, - const std::map> &output_vstreams_per_network, + PipelineGraph(const std::vector>>> &input_vstreams_per_network_group, + const std::vector>>> &output_vstreams_per_network_group, const std::string &graph_title, bool write_pipeline_stats); hailo_status write_dot_file(const std::string &output_path); diff --git a/hailort/hailortcli/hailortcli.cpp b/hailort/hailortcli/hailortcli.cpp index 5493222..c3b589b 100644 --- a/hailort/hailortcli/hailortcli.cpp +++ b/hailort/hailortcli/hailortcli.cpp @@ -19,6 +19,7 @@ #include "fw_config_command.hpp" #include "fw_logger_command.hpp" #include "benchmark_command.hpp" +#include "mon_command.hpp" #if defined(__GNUC__) #include "udp_rate_limiter_command.hpp" #endif @@ -42,148 +43,123 @@ #include #include -static Expected get_pcie_device_info(const hailo_pcie_params &pcie_params) -{ - if (pcie_params.pcie_bdf.empty()) { - auto scan_result = Device::scan_pcie(); - if (!scan_result) { - std::cerr << "Hailo PCIe scan failed (maybe pcie device not exists). status=" << scan_result.status() << std::endl; - return make_unexpected(scan_result.status()); - } - if (scan_result->size() == 0) { - std::cerr << "Hailo PCIe not found.." << std::endl; - return make_unexpected(HAILO_INTERNAL_FAILURE); - } - return std::move(scan_result->at(0)); - } else { - auto device_info_expected = Device::parse_pcie_device_info(pcie_params.pcie_bdf); - if (!device_info_expected) { - std::cerr << "Invalid pcie bdf format" << std::endl; - return make_unexpected(device_info_expected.status()); - } - return device_info_expected.release(); +Expected> get_device_ids(const hailo_device_params &device_params) +{ + if (device_params.device_ids.empty() || contains(device_params.device_ids, std::string("*"))) { + // No device id given, using all devices in the system. + return Device::scan(); + } + else { + return std::vector(device_params.device_ids); } } -Expected> create_pcie_device(const hailo_pcie_params &pcie_params) +Expected>> create_devices(const hailo_device_params &device_params) { - auto device_info = get_pcie_device_info(pcie_params); - if (!device_info) { - return make_unexpected(device_info.status()); - } + std::vector> res; + + auto device_ids = get_device_ids(device_params); + CHECK_EXPECTED(device_ids); - auto device = Device::create_pcie(device_info.value()); - if (!device) { - std::cerr << "Failed create pcie device. status=" << device.status() << std::endl; - return make_unexpected(device.status()); + for (auto device_id : device_ids.value()) { + auto device = Device::create(device_id); + CHECK_EXPECTED(device); + res.emplace_back(device.release()); } - return Expected>(device.release()); + return res; } -static Expected> create_eth_device(const hailo_eth_params ð_params) -{ - auto device = Device::create_eth(eth_params.ip_addr); - if (!device) { - std::cerr << "Failed create ethernet device. status=" << device.status() << std::endl; - return make_unexpected(device.status()); - } +class BDFValidator : public CLI::Validator { + public: + BDFValidator(bool support_asterisk) : Validator("BDF") { + func_ = [support_asterisk](std::string &bdf) { + if (support_asterisk && (bdf == "*")) { + return std::string(); + } - return Expected>(device.release()); -} + auto pcie_device_info = Device::parse_pcie_device_info(bdf); + if (pcie_device_info.has_value()) { + return std::string(); + } + else { + return std::string("Invalid PCIe BDF " + bdf); + } + }; + } +}; -Expected> create_device(const hailo_device_params &device_params) +void add_vdevice_options(CLI::App *app, hailo_vdevice_params &vdevice_params) { - switch (device_params.device_type) { - case DeviceType::PCIE: - return create_pcie_device(device_params.pcie_params); - case DeviceType::ETH: - return create_eth_device(device_params.eth_params); - case DeviceType::DEFAULT: - // If core driver is loaded (we are running on Mercury) then the default is core device; else, pcie device - if (Device::is_core_driver_loaded()) { - return Device::create_core_device(); - } else { - return create_pcie_device(device_params.pcie_params); + add_device_options(app, vdevice_params.device_params, false); + auto group = app->add_option_group("VDevice Options"); + auto device_count_option = group->add_option("--device-count", vdevice_params.device_count, "VDevice device count") + ->check(CLI::PositiveNumber); + auto multi_process_option = group->add_flag("--multi-process-service", vdevice_params.multi_process_service, + "VDevice multi process service"); + group->add_option("--group-id", vdevice_params.group_id, "VDevice group id")->needs(multi_process_option); + group->parse_complete_callback([&vdevice_params, device_count_option](){ + if (vdevice_params.device_params.device_ids.size() > 0) { + // Check either device_count or device_id + PARSE_CHECK(device_count_option->empty(), + "Passing " + device_count_option->get_name() + " in combination with device-ids is not allowed"); + + // Fill device_count with real value + vdevice_params.device_count = static_cast(vdevice_params.device_params.device_ids.size()); } - default: - std::cerr << "Invalid device type" << std::endl; - return make_unexpected(HAILO_INVALID_ARGUMENT); - } + }); } -void add_device_options(CLI::App *app, hailo_device_params &device_params) +void add_device_options(CLI::App *app, hailo_device_params &device_params, bool support_asterisk) { - // Initialize the device type to default - device_params.device_type = DeviceType::DEFAULT; - auto group = app->add_option_group("Device Options"); - - const HailoCheckedTransformer device_type_transformer({ - { "pcie", DeviceType::PCIE }, - { "eth", DeviceType::ETH }, - }); - auto *device_type_option = group->add_option("-d,--device-type,--target", device_params.device_type, - "Device type to use\n" - "Default is pcie.") - ->transform(device_type_transformer); + + // General device id + auto *device_id_option = group->add_option("-s,--device-id", device_params.device_ids, + std::string("Device id, same as returned from `hailortcli scan` command. ") + + std::string("For multiple devices, use space as separator.\n") + + (support_asterisk ? + std::string("In order to run on all devices connected to the machine one-by-one, use '*' (instead of device id).") : + std::string(""))); // PCIe options - auto *pcie_bdf_option = group->add_option("-s,--bdf", device_params.pcie_params.pcie_bdf, - "Device id ([]::., same as in lspci command).\n" \ - "In order to run on all PCIe devices connected to the machine one-by-one, use '*' (instead of device id).") - ->default_val(""); + auto *pcie_bdf_option = group->add_option("--bdf", device_params.device_ids, + std::string("Device bdf ([]::., same as in lspci command).\n") + + std::string("For multiple BDFs, use space as separator.\n") + + (support_asterisk ? + std::string("In order to run on all devices connected to the machine one-by-one, use '*' (instead of device id).") : + std::string(""))) + ->check(BDFValidator(support_asterisk)); // Ethernet options - auto *ip_option = group->add_option("--ip", device_params.eth_params.ip_addr, "IP address of the target") - ->default_val("") + auto *ip_option = group->add_option("--ip", device_params.device_ids, "IP address of the target") ->check(CLI::ValidIPV4); - group->parse_complete_callback([&device_params, device_type_option, pcie_bdf_option, ip_option](){ - // The user didn't put target, we can figure it ourself - if (device_type_option->empty()) { - if (!ip_option->empty()) { - // User gave IP, target is eth - device_params.device_type = DeviceType::ETH; - } else if (!pcie_bdf_option->empty()) { - // User gave bdf, target is pcie - device_params.device_type = DeviceType::PCIE; - } - else { - device_params.device_type = DeviceType::DEFAULT; - } - } - - if (ip_option->empty() && device_params.device_type == DeviceType::ETH) { - throw CLI::ParseError("IP address is not set", CLI::ExitCodes::InvalidError); - } - - if (!ip_option->empty() && device_params.device_type != DeviceType::ETH) { - throw CLI::ParseError("IP address is set on non eth device", CLI::ExitCodes::InvalidError); - } - - if (!pcie_bdf_option->empty() && device_params.device_type != DeviceType::PCIE) { - throw CLI::ParseError("bdf (-s) is set on non pcie device", CLI::ExitCodes::InvalidError); - } - }); -} + auto *device_type_option = group->add_option("-d,--device-type,--target", "ignored."); -void add_vdevice_options(CLI::App *app, hailo_device_params &device_params) { - auto group = app->add_option_group("VDevice Options"); - - // VDevice options - auto *device_count_option = group->add_option("--device-count", device_params.vdevice_params.device_count, "VDevice device count") - ->default_val(HAILO_DEFAULT_DEVICE_COUNT) - ->check(CLI::PositiveNumber); + std::vector actions{ + std::make_shared(device_type_option), + }; + hailo_deprecate_options(app, actions, false); - group->parse_complete_callback([&device_params, device_count_option](){ - // The user gave device_count - if (!device_count_option->empty()) { - if ((device_params.vdevice_params.device_count > 1) && - ((DeviceType::ETH == device_params.device_type) || (DeviceType::PCIE == device_params.device_type && !device_params.pcie_params.pcie_bdf.empty()))) { - throw CLI::ParseError("Device type must not be specified when using multiple devices", CLI::ExitCodes::InvalidError); - } + group->parse_complete_callback([&device_params, device_id_option, pcie_bdf_option, ip_option, support_asterisk]() + { + // Check that only one device id param is given + const std::string device_id_options_names = device_id_option->get_name(true, true) + ", " + + pcie_bdf_option->get_name(true, true) + ", " + + ip_option->get_name(true, true); + + const auto dev_id_options_parsed = + static_cast(!device_id_option->empty()) + + static_cast(!pcie_bdf_option->empty()) + + static_cast(!ip_option->empty()); + PARSE_CHECK(dev_id_options_parsed <= 1, + "Only one of " + device_id_options_names + " Can bet set"); + + if (contains(device_params.device_ids, std::string("*"))) { + PARSE_CHECK(support_asterisk, "Passing * is not allowed in this command"); + PARSE_CHECK(device_params.device_ids.size() == 1, "passing '*' in combination with other device ids is not allowed"); } }); } @@ -235,6 +211,7 @@ public: add_subcommand(); add_subcommand(); add_subcommand(); + add_subcommand(); #if defined(__GNUC__) add_subcommand(); #endif diff --git a/hailort/hailortcli/hailortcli.hpp b/hailort/hailortcli/hailortcli.hpp index 5c61bf6..29f5c25 100644 --- a/hailort/hailortcli/hailortcli.hpp +++ b/hailort/hailortcli/hailortcli.hpp @@ -27,35 +27,21 @@ using namespace hailort; } \ } while (0) -struct hailo_pcie_params { - std::string pcie_bdf; // if empty use the first scanned. if '*', run on all devices on the machine one-by-one -}; - -struct hailo_eth_params { - std::string ip_addr; +struct hailo_device_params { + std::vector device_ids; }; struct hailo_vdevice_params { - uint32_t device_count; -}; - -enum class DeviceType { - PCIE = 0, - ETH, - DEFAULT + hailo_device_params device_params; + uint32_t device_count = HAILO_DEFAULT_DEVICE_COUNT; + std::string group_id; + bool multi_process_service = false; }; -struct hailo_device_params { - DeviceType device_type; - hailo_pcie_params pcie_params; - hailo_eth_params eth_params; - hailo_vdevice_params vdevice_params; -}; - -void add_device_options(CLI::App *app, hailo_device_params &device_params); -void add_vdevice_options(CLI::App *app, hailo_device_params &device_params); -Expected> create_device(const hailo_device_params &device_params); -Expected> create_pcie_device(const hailo_pcie_params &pcie_params); +void add_vdevice_options(CLI::App *app, hailo_vdevice_params &vdevice_params); +void add_device_options(CLI::App *app, hailo_device_params &device_params, bool support_asterisk=true); +Expected>> create_devices(const hailo_device_params &device_params); +Expected> get_device_ids(const hailo_device_params &device_params); /** * CLI11 transformer object, converting enum argument from string. @@ -101,7 +87,7 @@ using DeprecationActionPtr = std::shared_ptr; class OptionDeprecation : public DeprecationAction { public: - OptionDeprecation(CLI::Option *opt, const std::string &replacement) : + OptionDeprecation(CLI::Option *opt, const std::string &replacement = std::string()) : DeprecationAction(), m_opt(opt), m_replacement(replacement) @@ -115,7 +101,12 @@ public: virtual std::string deprecate(bool message_inline) override { std::stringstream message; - message << "'" << m_opt->get_name() << "' is deprecated, please use '" << m_replacement << "' instead." << std::endl; + message << "'" << m_opt->get_name() << "' is deprecated"; + if (!m_replacement.empty()) { + std::cout << ", please use " << m_replacement << "' instead."; + } + message << std::endl; + CLI::Validator deprecate_warning( [message = message.str()](std::string &) { std::cout << message; diff --git a/hailort/hailortcli/infer_stats_printer.cpp b/hailort/hailortcli/infer_stats_printer.cpp index 3f91c1c..ee9feb2 100644 --- a/hailort/hailortcli/infer_stats_printer.cpp +++ b/hailort/hailortcli/infer_stats_printer.cpp @@ -10,6 +10,7 @@ #include "infer_stats_printer.hpp" #include "run_command.hpp" #include "common.hpp" +#include "pipeline.hpp" #include #include @@ -103,20 +104,22 @@ InferStatsPrinter::InferStatsPrinter(const inference_runner_params ¶ms, hail output_status = HAILO_SUCCESS; } -void InferStatsPrinter::print(const std::string &network_group_name, Expected& inference_result) +void InferStatsPrinter::print(const std::vector &network_groups_names, Expected &inference_result) { if (m_results_csv_file.is_open()) { std::cout << "> Writing inference results to '" << m_results_csv_path << "'... "; - print_csv(network_group_name, inference_result); + print_csv(network_groups_names, inference_result); std::cout << "done." << std::endl; } - if (m_pipeline_stats_csv_file.is_open()) { + if (m_pipeline_stats_csv_file.is_open() && (inference_result)) { std::cout << "> Writing pipeline statistics to '" << m_pipeline_stats_csv_path << "'... "; m_pipeline_stats_csv_file << "net_name,vstream_name,param_type,element,mean,min,max,var,sd,mean_sd,index" << std::endl; - print_pipeline_elem_stats_csv(network_group_name, inference_result->m_fps_accumulators); - print_pipeline_elem_stats_csv(network_group_name, inference_result->m_latency_accumulators); - print_pipeline_elem_stats_csv(network_group_name, inference_result->m_queue_size_accumulators); - print_entire_pipeline_stats_csv(network_group_name, inference_result->m_pipeline_latency_accumulators); + for (auto &network_group_results : inference_result->network_group_results()) { + print_pipeline_elem_stats_csv(network_group_results.network_group_name(), network_group_results.m_fps_accumulators); + print_pipeline_elem_stats_csv(network_group_results.network_group_name(), network_group_results.m_latency_accumulators); + print_pipeline_elem_stats_csv(network_group_results.network_group_name(), network_group_results.m_queue_size_accumulators); + print_entire_pipeline_stats_csv(network_group_results.network_group_name(), network_group_results.m_pipeline_latency_accumulators); + } std::cout << "done." << std::endl; } print_stdout(inference_result); @@ -132,100 +135,106 @@ void InferStatsPrinter::print_benchmark_csv_header() m_results_csv_file << "net_name,fps,hw_only_fps,num_of_frames,num_of_frames_hw_only,hw_latency,overall_latency,min_power,average_power,max_power" << std::endl; } -void InferStatsPrinter::print_csv(const std::string &network_group_name, Expected& inference_result) +void InferStatsPrinter::print_csv(const std::vector &network_groups_names, Expected &inference_result) { auto status_description = hailo_get_status_message(inference_result.status()); - m_results_csv_file << network_group_name << "," << static_cast(inference_result.status()) << "," << status_description; - if (!inference_result) { - m_results_csv_file << ",,,,,,,,,,,"; - } - else { - m_results_csv_file << ","; - - if (auto fps = inference_result->fps()) { - m_results_csv_file << fps.value(); + if (HAILO_SUCCESS != inference_result.status()) { + for (auto &network_group_name : network_groups_names) { + m_results_csv_file << network_group_name << "," << static_cast(inference_result.status()) << "," << status_description; + if (!inference_result) { + m_results_csv_file << ",,,,,,,,,,,"; + } } - m_results_csv_file << ","; + } else { + for (auto &results : inference_result->network_group_results()) { + m_results_csv_file << results.network_group_name() << "," << static_cast(inference_result.status()) << "," << status_description; + m_results_csv_file << ","; - if (auto frames_count = inference_result->frames_count()) { - m_results_csv_file << frames_count.value(); - } - m_results_csv_file << ","; + if (auto fps = results.fps()) { + m_results_csv_file << fps.value(); + } + m_results_csv_file << ","; - if (auto send_data_rate = inference_result->send_data_rate_mbit_sec()) { - m_results_csv_file << send_data_rate.value(); - } - m_results_csv_file << ","; + if (auto frames_count = results.frames_count()) { + m_results_csv_file << frames_count.value(); + } + m_results_csv_file << ","; - if (auto recv_data_rate = inference_result->recv_data_rate_mbit_sec()) { - m_results_csv_file << recv_data_rate.value(); - } - m_results_csv_file << ","; + if (auto send_data_rate = results.send_data_rate_mbit_sec()) { + m_results_csv_file << send_data_rate.value(); + } + m_results_csv_file << ","; - if (auto hw_latency = inference_result->hw_latency()) { - m_results_csv_file << InferResultsFormatUtils::latency_result_to_ms(hw_latency.value()); - } - m_results_csv_file << ","; + if (auto recv_data_rate = results.recv_data_rate_mbit_sec()) { + m_results_csv_file << recv_data_rate.value(); + } + m_results_csv_file << ","; - if (auto overall_latency = inference_result->overall_latency()) { - m_results_csv_file << InferResultsFormatUtils::latency_result_to_ms(overall_latency.value()); - } + if (auto hw_latency = results.hw_latency()) { + m_results_csv_file << InferResultsFormatUtils::latency_result_to_ms(hw_latency.value()); + } + m_results_csv_file << ","; - // TODO HRT-5363 support multiple devices (Currently assumes 1 device in the map) - if (1 == inference_result->m_power_measurements.size()) { - for (const auto &pair : inference_result->m_power_measurements) { - if (nullptr != pair.second) { - m_results_csv_file << ","; - m_results_csv_file << pair.second->data().min_value; - m_results_csv_file << ","; - m_results_csv_file << pair.second->data().average_value; - m_results_csv_file << ","; - m_results_csv_file << pair.second->data().max_value; - } else { - m_results_csv_file << ",,,"; + if (auto overall_latency = results.overall_latency()) { + m_results_csv_file << InferResultsFormatUtils::latency_result_to_ms(overall_latency.value()); + } + + // TODO HRT-5363 support multiple devices (Currently assumes 1 device in the map) + if (1 == inference_result->m_power_measurements.size()) { + for (const auto &pair : inference_result->m_power_measurements) { + if (nullptr != pair.second) { + m_results_csv_file << ","; + m_results_csv_file << pair.second->data().min_value; + m_results_csv_file << ","; + m_results_csv_file << pair.second->data().average_value; + m_results_csv_file << ","; + m_results_csv_file << pair.second->data().max_value; + } else { + m_results_csv_file << ",,,"; + } } + } else { + m_results_csv_file << ",,,"; } - } else { - m_results_csv_file << ",,,"; - } - // TODO HRT-5363 support multiple devices (Currently assumes 1 device in the map) - if (1 == inference_result->m_current_measurements.size()) { - for (const auto &pair : inference_result->m_current_measurements) { - if (nullptr != pair.second) { - m_results_csv_file << ","; - m_results_csv_file << pair.second->data().min_value; - m_results_csv_file << ","; - m_results_csv_file << pair.second->data().average_value; - m_results_csv_file << ","; - m_results_csv_file << pair.second->data().max_value; - } else { - m_results_csv_file << ",,,"; + // TODO HRT-5363 support multiple devices (Currently assumes 1 device in the map) + if (1 == inference_result->m_current_measurements.size()) { + for (const auto &pair : inference_result->m_current_measurements) { + if (nullptr != pair.second) { + m_results_csv_file << ","; + m_results_csv_file << pair.second->data().min_value; + m_results_csv_file << ","; + m_results_csv_file << pair.second->data().average_value; + m_results_csv_file << ","; + m_results_csv_file << pair.second->data().max_value; + } else { + m_results_csv_file << ",,,"; + } } + } else { + m_results_csv_file << ",,,"; } - } else { - m_results_csv_file << ",,,"; - } - // TODO HRT-5363 support multiple devices (Currently assumes 1 device in the map) - if (1 == inference_result->m_temp_measurements.size()) { - for (const auto &pair : inference_result->m_temp_measurements) { - if (nullptr != pair.second) { - m_results_csv_file << ","; - m_results_csv_file << pair.second->min_value; - m_results_csv_file << ","; - m_results_csv_file << pair.second->average_value; - m_results_csv_file << ","; - m_results_csv_file << pair.second->max_value; - } else { - m_results_csv_file << ",,,"; + // TODO HRT-5363 support multiple devices (Currently assumes 1 device in the map) + if (1 == inference_result->m_temp_measurements.size()) { + for (const auto &pair : inference_result->m_temp_measurements) { + if (nullptr != pair.second) { + m_results_csv_file << ","; + m_results_csv_file << pair.second->min_value; + m_results_csv_file << ","; + m_results_csv_file << pair.second->average_value; + m_results_csv_file << ","; + m_results_csv_file << pair.second->max_value; + } else { + m_results_csv_file << ",,,"; + } } + } else { + m_results_csv_file << ",,,"; } - } else { - m_results_csv_file << ",,,"; + m_results_csv_file << std::endl; } } - m_results_csv_file << std::endl; } void InferStatsPrinter::print_pipeline_elem_stats_csv(const std::string &network_group_name, @@ -273,59 +282,72 @@ void InferStatsPrinter::print_entire_pipeline_stats_csv(const std::string &netwo } } -void InferStatsPrinter::print_benchmark_csv(const std::string &network_group_name, const NetworkGroupInferResult &hw_inference_result, - const NetworkGroupInferResult &streaming_inference_result, const NetworkGroupInferResult &hw_latency_result) +void InferStatsPrinter::print_benchmark_csv(InferResult &hw_inference_result, + InferResult &streaming_inference_result, InferResult &hw_latency_result) { - m_results_csv_file << network_group_name << ","; + assert(hw_inference_result.network_group_results().size() == streaming_inference_result.network_group_results().size()); + assert(hw_latency_result.network_group_results().size() == streaming_inference_result.network_group_results().size()); + + for (auto &hw_res : hw_inference_result.network_group_results()) { + auto network_group_name = hw_res.network_group_name(); - if (auto fps = streaming_inference_result.fps()) { - m_results_csv_file << fps.value(); - } - m_results_csv_file << ","; + auto streaming_res = std::find_if(streaming_inference_result.network_group_results().begin(), streaming_inference_result.network_group_results().end(), + [network_group_name] (NetworkGroupInferResult &infer_results) { return (infer_results.network_group_name() == network_group_name); }); - if (auto hw_only_fps = hw_inference_result.fps()) { - m_results_csv_file << hw_only_fps.value(); - } - m_results_csv_file << ","; + auto latency_res = std::find_if(hw_latency_result.network_group_results().begin(), hw_latency_result.network_group_results().end(), + [network_group_name] (NetworkGroupInferResult &infer_results) { return (infer_results.network_group_name() == network_group_name); }); - if (auto frames_count = streaming_inference_result.frames_count()) { - m_results_csv_file << frames_count.value(); - } - m_results_csv_file << ","; + m_results_csv_file << network_group_name << ","; - if (auto frames_count = hw_inference_result.frames_count()) { - m_results_csv_file << frames_count.value(); - } - m_results_csv_file << ","; + if (auto fps = streaming_res->fps()) { + m_results_csv_file << fps.value(); + } + m_results_csv_file << ","; - if (auto hw_latency = hw_latency_result.hw_latency()) { - m_results_csv_file << InferResultsFormatUtils::latency_result_to_ms(hw_latency.value()); - } - m_results_csv_file << ","; + if (auto hw_only_fps = hw_res.fps()) { + m_results_csv_file << hw_only_fps.value(); + } + m_results_csv_file << ","; - if (auto overall_latency = hw_latency_result.overall_latency()) { - m_results_csv_file << InferResultsFormatUtils::latency_result_to_ms(overall_latency.value()); - } + if (auto frames_count = streaming_res->frames_count()) { + m_results_csv_file << frames_count.value(); + } + m_results_csv_file << ","; - // TODO HRT-5363 support multiple devices (Currently assumes 1 device in the map) - if (1 == streaming_inference_result.m_power_measurements.size()) { - for (const auto &pair : streaming_inference_result.m_power_measurements) { - if (nullptr != pair.second) { - m_results_csv_file << ","; - m_results_csv_file << pair.second->data().min_value; - m_results_csv_file << ","; - m_results_csv_file << pair.second->data().average_value; - m_results_csv_file << ","; - m_results_csv_file << pair.second->data().max_value; - } else { - m_results_csv_file << ",,,"; + if (auto frames_count = hw_res.frames_count()) { + m_results_csv_file << frames_count.value(); + } + m_results_csv_file << ","; + + if (auto hw_latency = latency_res->hw_latency()) { + m_results_csv_file << InferResultsFormatUtils::latency_result_to_ms(hw_latency.value()); + } + m_results_csv_file << ","; + + if (auto overall_latency = latency_res->overall_latency()) { + m_results_csv_file << InferResultsFormatUtils::latency_result_to_ms(overall_latency.value()); + } + + // TODO HRT-5363 support multiple devices (Currently assumes 1 device in the map) + if (1 == streaming_inference_result.m_power_measurements.size()) { + for (const auto &pair : streaming_inference_result.m_power_measurements) { + if (nullptr != pair.second) { + m_results_csv_file << ","; + m_results_csv_file << pair.second->data().min_value; + m_results_csv_file << ","; + m_results_csv_file << pair.second->data().average_value; + m_results_csv_file << ","; + m_results_csv_file << pair.second->data().max_value; + } else { + m_results_csv_file << ",,,"; + } } + } else { + m_results_csv_file << ",,,"; } - } else { - m_results_csv_file << ",,,"; - } - m_results_csv_file << std::endl; + m_results_csv_file << std::endl; + } } template< typename T> void InferStatsPrinter::print_stdout_single_element(const T &results, size_t frames_count) @@ -351,13 +373,14 @@ void InferStatsPrinter::print_stdout_single_element(const T &results, size_t fra if (auto hw_latency = results.hw_latency()) { std::cout << " HW Latency: " << InferResultsFormatUtils::latency_result_to_ms(hw_latency.value()) << " ms" << std::endl; } + if (auto overall_latency = results.overall_latency()) { std::cout << " Overall Latency: " << InferResultsFormatUtils::latency_result_to_ms(overall_latency.value()) << " ms" << std::endl; } } -void InferStatsPrinter::print_stdout(Expected& inference_result) +void InferStatsPrinter::print_stdout(Expected &inference_result) { if (!inference_result) { return; @@ -369,31 +392,36 @@ void InferStatsPrinter::print_stdout(Expected& inferenc std::cout << std::setprecision(2) << std::fixed; std::cout << FORMAT_CLEAR_LINE << "> Inference result:" << std::endl; - if (1 < inference_result->m_result_per_network.size()) { - // If there is more than 1 network, we print results per network, and than sum of bandwith - for (auto &network_result_pair : inference_result->m_result_per_network) { - std::cout << " Network: " << network_result_pair.first << std::endl; - auto frames_count = (m_print_frame_count) ? network_result_pair.second.m_frames_count : 0; - print_stdout_single_element(network_result_pair.second, frames_count); - } - std::stringstream bandwidth_stream; - bandwidth_stream << std::setprecision(2) << std::fixed; - if (auto send_data_rate = inference_result->send_data_rate_mbit_sec()) { - bandwidth_stream << " Send Rate: " << send_data_rate.value() << " Mbit/s" << std::endl; - } + for (auto &network_group_results : inference_result.value().network_group_results()) { + std::cout << " Network group: " << network_group_results.network_group_name() << std::endl; + if (1 < network_group_results.m_result_per_network.size()) { + // If there is more than 1 network, we print results per network, and than sum of bandwith + for (auto &network_result_pair : network_group_results.m_result_per_network) { + std::cout << " Network: " << network_result_pair.first << std::endl; + auto frames_count = (m_print_frame_count) ? network_result_pair.second.m_frames_count : 0; + print_stdout_single_element(network_result_pair.second, frames_count); + } + std::stringstream bandwidth_stream; + bandwidth_stream << std::setprecision(2) << std::fixed; + if (auto send_data_rate = network_group_results.send_data_rate_mbit_sec()) { + bandwidth_stream << " Send Rate: " << send_data_rate.value() << " Mbit/s" << std::endl; + } - if (auto recv_data_rate = inference_result->recv_data_rate_mbit_sec()) { - bandwidth_stream << " Recv Rate: " << recv_data_rate.value() << " Mbit/s" << std::endl; - } + if (auto recv_data_rate = network_group_results.recv_data_rate_mbit_sec()) { + bandwidth_stream << " Recv Rate: " << recv_data_rate.value() << " Mbit/s" << std::endl; + } - if (0 != bandwidth_stream.rdbuf()->in_avail()) { - std::cout << " Total bandwidth: " << std::endl; - std::cout << bandwidth_stream.rdbuf(); + if (0 != bandwidth_stream.rdbuf()->in_avail()) { + std::cout << " Total bandwidth: " << std::endl; + std::cout << bandwidth_stream.rdbuf(); + } + std::cout << std::endl; + } else { + auto frames_count_exp = network_group_results.frames_count(); + auto frames_count = ((frames_count_exp) && (m_print_frame_count)) ? frames_count_exp.value() : 0; + print_stdout_single_element(network_group_results, frames_count); + std::cout << std::endl; } - } else { - auto frames_count_exp = inference_result->frames_count(); - auto frames_count = ((frames_count_exp) && (m_print_frame_count)) ? frames_count_exp.value() : 0; - print_stdout_single_element(inference_result.value(), frames_count); } if ((inference_result->m_power_measurements.size() != inference_result->m_current_measurements.size()) || @@ -419,9 +447,9 @@ void InferStatsPrinter::print_stdout(Expected& inferenc } auto temp_measure_iter = inference_result->m_temp_measurements.find(pair.first); if ((temp_measure_iter != inference_result->m_temp_measurements.end()) && (nullptr != temp_measure_iter->second)) { - measurement_stream << " Minimum chip temperature: " << temp_measure_iter->second->min_value << "°C" << std::endl; - measurement_stream << " Average chip temperature: " << temp_measure_iter->second->average_value << "°C" << std::endl; - measurement_stream << " Maximum chip temperature: " << temp_measure_iter->second->max_value << "°C" << std::endl; + measurement_stream << " Minimum chip temperature: " << temp_measure_iter->second->min_value << "C" << std::endl; + measurement_stream << " Average chip temperature: " << temp_measure_iter->second->average_value << "C" << std::endl; + measurement_stream << " Maximum chip temperature: " << temp_measure_iter->second->max_value << "C" << std::endl; } if (0 != measurement_stream.rdbuf()->in_avail()) { std::cout << " Device: " << pair.first << std::endl; @@ -439,6 +467,8 @@ void InferStatsPrinter::write_accumulator_results(std::ofstream &output_stream, { const auto &accumulator_result = accumulator->get(); if ((!accumulator_result.count()) || (accumulator_result.count().value() == 0)) { + LOGGER__WARNING("No {} data has been collected for element '{}' (vstream '{}'). Collection begins after the element has processed {} frames...", + accumulator->get_data_type(), elem_name, vstream_name, DEFAULT_NUM_FRAMES_BEFORE_COLLECTION_START); return; } diff --git a/hailort/hailortcli/infer_stats_printer.hpp b/hailort/hailortcli/infer_stats_printer.hpp index 3400067..ac0e950 100644 --- a/hailort/hailortcli/infer_stats_printer.hpp +++ b/hailort/hailortcli/infer_stats_printer.hpp @@ -29,9 +29,9 @@ public: class InferStatsPrinter final { public: static Expected create(const inference_runner_params ¶ms, bool print_running_info = true); - void print(const std::string &network_name, Expected& inference_result); - void print_benchmark_csv(const std::string &network_name, const NetworkGroupInferResult &hw_inference_result, - const NetworkGroupInferResult &streaming_inference_result, const NetworkGroupInferResult &hw_latency_result); + void print(const std::vector &network_groups_names, Expected &inference_result); + void print_benchmark_csv(InferResult &hw_inference_result, + InferResult &streaming_inference_result, InferResult &hw_latency_result); void print_csv_header(); void print_benchmark_csv_header(); @@ -39,14 +39,14 @@ private: static constexpr uint32_t NO_INDEX = std::numeric_limits::max(); InferStatsPrinter(const inference_runner_params ¶ms, hailo_status &output_status, bool print_running_info = true); - void print_csv(const std::string &network_name, Expected& inference_result); + void print_csv(const std::vector &network_groups_names, Expected &inference_result); void print_pipeline_elem_stats_csv(const std::string &network_name, const std::map> &inference_result); void print_pipeline_elem_stats_csv(const std::string &network_name, const std::map>> &inference_result); void print_entire_pipeline_stats_csv(const std::string &network_name, const std::map &inference_result); - void print_stdout(Expected& inference_result); + void print_stdout(Expected &inference_result); template void print_stdout_single_element(const T &results, size_t frames_count); diff --git a/hailort/hailortcli/inference_progress.cpp b/hailort/hailortcli/inference_progress.cpp index 75161a9..9a3092b 100644 --- a/hailort/hailortcli/inference_progress.cpp +++ b/hailort/hailortcli/inference_progress.cpp @@ -13,33 +13,59 @@ #include #include -InferProgress::InferProgress(ConfiguredNetworkGroup &configured_network_group, const inference_runner_params ¶ms, - std::chrono::duration print_interval) : - m_configured_network_group(configured_network_group), m_params(params), - m_print_interval(print_interval), m_networks_progress(), m_stop(true) {} +Expected> InferProgress::create(const inference_runner_params ¶ms, + std::chrono::milliseconds print_interval) +{ + auto status = HAILO_UNINITIALIZED; + + auto progress_bar_ptr = std::shared_ptr(new (std::nothrow) InferProgress(params, print_interval, status)); + CHECK_AS_EXPECTED((nullptr != progress_bar_ptr), HAILO_OUT_OF_HOST_MEMORY); + + return progress_bar_ptr; +} + +InferProgress::InferProgress(const inference_runner_params ¶ms, + std::chrono::milliseconds print_interval, hailo_status &status) : + m_params(params), m_print_interval(print_interval), m_networks_progress(), + m_stop_event(Event::create_shared(Event::State::not_signalled)), m_finished(false) +{ + if (nullptr == m_stop_event) { + LOGGER__ERROR("Failed to create event for progress bar"); + status = HAILO_OUT_OF_HOST_MEMORY; + return; + } + status = HAILO_SUCCESS; +} void InferProgress::start() { - m_stop = false; m_print_thread = std::thread([this] () { - while (!m_stop.load()) { + while (true) { print_progress(true); - std::this_thread::sleep_for(m_print_interval); + auto status = m_stop_event->wait(m_print_interval); + if (HAILO_TIMEOUT != status) { + break; + } } }); } void InferProgress::finish(bool should_print_progress) { - m_stop = true; - m_print_thread.join(); + (void)m_stop_event->signal(); + if (m_print_thread.joinable()) { + m_print_thread.join(); + } + if (should_print_progress) { print_progress(false); } + m_finished = true; } void InferProgress::print_progress(bool should_reset_cursor) { + std::unique_lock lock(m_mutex); for (auto &network_progress_bar : m_networks_progress) { std::cout << network_progress_bar->get_progress_text() << std::endl; } @@ -50,15 +76,15 @@ void InferProgress::print_progress(bool should_reset_cursor) InferProgress::~InferProgress() { - if (!m_stop.load()) { + if (!m_finished) { finish(false); } } -Expected> InferProgress::create_network_progress_bar(const std::string &network_name) +Expected> InferProgress::create_network_progress_bar(std::shared_ptr network_group, const std::string &network_name) { std::shared_ptr network_progress_ber = - make_shared_nothrow(m_configured_network_group, m_params, network_name); + make_shared_nothrow(network_group, m_params, network_name); CHECK_NOT_NULL_AS_EXPECTED(network_progress_ber, HAILO_OUT_OF_HOST_MEMORY); { @@ -71,7 +97,7 @@ Expected> InferProgress::create_network_prog return prog_bar_cpy; } -NetworkProgressBar::NetworkProgressBar(ConfiguredNetworkGroup &configured_network_group, +NetworkProgressBar::NetworkProgressBar(std::shared_ptr configured_network_group, const inference_runner_params ¶ms, const std::string &network_name) : m_network_name(network_name), m_configured_network_group(configured_network_group), m_params(params), m_progress_count(0), m_start(std::chrono::steady_clock::now()) // NetworkProgressBar sets start time to its creation time @@ -110,7 +136,7 @@ std::string NetworkProgressBar::get_progress_text() res << " | FPS: " << fps; } else { double avg_hw_latency = 0; - auto latency_expected = m_configured_network_group.get_latency_measurement(m_network_name); + auto latency_expected = m_configured_network_group->get_latency_measurement(m_network_name); if (latency_expected) { avg_hw_latency = InferResultsFormatUtils::latency_result_to_ms(latency_expected.release().avg_hw_latency); } diff --git a/hailort/hailortcli/inference_progress.hpp b/hailort/hailortcli/inference_progress.hpp index c0854f6..0aaff6a 100644 --- a/hailort/hailortcli/inference_progress.hpp +++ b/hailort/hailortcli/inference_progress.hpp @@ -19,14 +19,14 @@ class NetworkProgressBar final { public: - NetworkProgressBar(ConfiguredNetworkGroup &configured_network_group, + NetworkProgressBar(std::shared_ptr configured_network_group, const inference_runner_params ¶ms, const std::string &network_name); void make_progress(); std::string get_progress_text(); private: const std::string m_network_name; - ConfiguredNetworkGroup &m_configured_network_group; + std::shared_ptr m_configured_network_group; const inference_runner_params m_params; std::atomic m_progress_count; std::chrono::time_point m_start; @@ -34,25 +34,29 @@ private: class InferProgress final { public: - InferProgress(ConfiguredNetworkGroup &configured_network_group, - const inference_runner_params ¶ms, std::chrono::duration print_interval); + static Expected> create(const inference_runner_params ¶ms, + std::chrono::milliseconds print_interval); ~InferProgress(); - Expected> create_network_progress_bar(const std::string &network_name); + Expected> create_network_progress_bar(std::shared_ptr network_group, + const std::string &network_name); void start(); void finish(bool should_print_progress = true); + + InferProgress(const inference_runner_params ¶ms, std::chrono::milliseconds print_interval, hailo_status &status); private: void print_progress(bool should_reset_cursor); - ConfiguredNetworkGroup &m_configured_network_group; + std::vector> m_configured_network_groups; const inference_runner_params m_params; - std::chrono::duration m_print_interval; + std::chrono::milliseconds m_print_interval; std::vector> m_networks_progress; - std::atomic_bool m_stop; + EventPtr m_stop_event; std::thread m_print_thread; std::mutex m_mutex; + std::atomic_bool m_finished; }; #endif /* _HAILO_INFERENCE_PROGRESS_HPP_ */ \ No newline at end of file diff --git a/hailort/hailortcli/inference_result.hpp b/hailort/hailortcli/inference_result.hpp index 9e5ce11..c1134d7 100644 --- a/hailort/hailortcli/inference_result.hpp +++ b/hailort/hailortcli/inference_result.hpp @@ -47,7 +47,7 @@ public: Expected send_data_rate_mbit_sec() const { - if (!m_infer_duration) { + if (!m_infer_duration || !m_total_send_frame_size) { return make_unexpected(HAILO_NOT_AVAILABLE); } return (static_cast(m_frames_count * m_total_send_frame_size) / *m_infer_duration) * MBIT_PER_BYTE; @@ -55,7 +55,7 @@ public: Expected recv_data_rate_mbit_sec() const { - if (!m_infer_duration) { + if (!m_infer_duration || !m_total_recv_frame_size) { return make_unexpected(HAILO_NOT_AVAILABLE); } return (static_cast(m_frames_count * m_total_recv_frame_size) / *m_infer_duration) * MBIT_PER_BYTE; @@ -84,25 +84,28 @@ public: size_t m_total_recv_frame_size; // TODO: change to optional - std::unique_ptr m_infer_duration; - std::unique_ptr m_hw_latency; - std::unique_ptr m_overall_latency; + std::shared_ptr m_infer_duration; + std::shared_ptr m_hw_latency; + std::shared_ptr m_overall_latency; }; struct NetworkGroupInferResult { public: - NetworkGroupInferResult(std::map &&result_per_network = {}) : + NetworkGroupInferResult(const std::string &network_group_name, std::map &&result_per_network = {}) : + m_network_group_name(network_group_name), m_result_per_network(std::move(result_per_network)), - m_power_measurements(), - m_current_measurements(), - m_temp_measurements(), m_fps_accumulators(), m_latency_accumulators(), m_queue_size_accumulators(), m_pipeline_latency_accumulators() {} + std::string network_group_name() + { + return m_network_group_name; + } + Expected infer_duration(const std::string &network_name = "") const { if (network_name.empty()) { @@ -228,15 +231,15 @@ public: return frames_count_cpy; } + const std::map &results_per_network() const + { + return m_result_per_network; + } + + std::string m_network_group_name; // std::map m_result_per_network; - // - // TODO: create a struct contianing all device measurements, and keep only one map - std::map> m_power_measurements; - std::map> m_current_measurements; - std::map> m_temp_measurements; - // std::map> m_fps_accumulators; std::map> m_latency_accumulators; @@ -274,19 +277,43 @@ public: void update_pipeline_stats(const std::map>> &/*inputs_per_network*/, const std::map>> &/*outputs_per_network*/) { - // Overloading fow hw_object inference - not using any pipelines so nothing to update + // Overloading fow hw_only inference - not using any pipelines so nothing to update + } + +private: + template + static void update_accumulator_map(std::map &map, + const K &key, const V &value) + { + if (value.size() == 0) { + return; + } + + map.emplace(key, value); + } +}; + +struct InferResult +{ +public: + InferResult(std::vector &&network_groups_results) : m_network_group_results(std::move(network_groups_results)) + {} + + std::vector &network_group_results() + { + return m_network_group_results; } void initialize_measurements(const std::vector> &devices) { for (const auto &device : devices) { - m_power_measurements.emplace(device.get().get_dev_id(), std::unique_ptr{}); - m_current_measurements.emplace(device.get().get_dev_id(), std::unique_ptr{}); - m_temp_measurements.emplace(device.get().get_dev_id(), std::unique_ptr{}); + m_power_measurements.emplace(device.get().get_dev_id(), std::shared_ptr{}); + m_current_measurements.emplace(device.get().get_dev_id(), std::shared_ptr{}); + m_temp_measurements.emplace(device.get().get_dev_id(), std::shared_ptr{}); } } - hailo_status set_power_measurement(const std::string &device_id, std::unique_ptr &&power_measure) + hailo_status set_power_measurement(const std::string &device_id, std::shared_ptr &&power_measure) { auto iter = m_power_measurements.find(device_id); CHECK(m_power_measurements.end() != iter, HAILO_INVALID_ARGUMENT); @@ -294,7 +321,7 @@ public: return HAILO_SUCCESS; } - hailo_status set_current_measurement(const std::string &device_id, std::unique_ptr &¤t_measure) + hailo_status set_current_measurement(const std::string &device_id, std::shared_ptr &¤t_measure) { auto iter = m_current_measurements.find(device_id); CHECK(m_current_measurements.end() != iter, HAILO_INVALID_ARGUMENT); @@ -302,7 +329,7 @@ public: return HAILO_SUCCESS; } - hailo_status set_temp_measurement(const std::string &device_id, std::unique_ptr &&temp_measure) + hailo_status set_temp_measurement(const std::string &device_id, std::shared_ptr &&temp_measure) { auto iter = m_temp_measurements.find(device_id); CHECK(m_temp_measurements.end() != iter, HAILO_INVALID_ARGUMENT); @@ -310,17 +337,14 @@ public: return HAILO_SUCCESS; } -private: - template - static void update_accumulator_map(std::map &map, - const K &key, const V &value) - { - if (value.size() == 0) { - return; - } + // + // TODO: create a struct containing all device measurements, and keep only one map + std::map> m_power_measurements; + std::map> m_current_measurements; + std::map> m_temp_measurements; - map.emplace(key, value); - } +private: + std::vector m_network_group_results; }; #endif /* _HAILO_INFER_RESULT_ */ \ No newline at end of file diff --git a/hailort/hailortcli/mon_command.cpp b/hailort/hailortcli/mon_command.cpp new file mode 100644 index 0000000..38daf1d --- /dev/null +++ b/hailort/hailortcli/mon_command.cpp @@ -0,0 +1,165 @@ +/** + * Copyright (c) 2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) +**/ +/** + * @file mon_command.cpp + * @brief Monitor of networks - Presents information about the running networks + **/ + +#include "mon_command.hpp" +#include "common.hpp" +#include "hailo/hailort.h" +#include "common/filesystem.hpp" + +#include + +namespace hailort +{ + +// TODO: Deal with longer networks names - should use HAILO_MAX_NETWORK_NAME_SIZE but its too long for one line +constexpr size_t NETWORK_NAME_WIDTH = 40; +constexpr size_t STREAM_NAME_WIDTH = 60; +constexpr size_t NUMBER_WIDTH = 15; +constexpr size_t LINE_LENGTH = 125; + +MonCommand::MonCommand(CLI::App &parent_app) : + Command(parent_app.add_subcommand("monitor", "Monitor of networks - Presents information about the running networks. " \ + "To enable monitor, set in the application process the environment variable '" + std::string(SCHEDULER_MON_ENV_VAR) + "' to 1.")) +{} + +hailo_status MonCommand::execute() +{ +#ifdef _WIN32 + LOGGER__ERROR("hailortcli `monitor` command is not supported on Windows"); + return HAILO_NOT_IMPLEMENTED; +#else + return print_table(); +#endif +} + +size_t MonCommand::print_networks_info_header() +{ + std::cout << + std::setw(NETWORK_NAME_WIDTH) << std::left << "Network" << + std::setw(NUMBER_WIDTH) << std::left << "FPS" << + std::setw(NUMBER_WIDTH) << std::left << "Core%" << + std::setw(NUMBER_WIDTH) << std::left << "PID" << + "\n" << std::left << std::string(LINE_LENGTH, '-') << "\n"; + static const uint32_t header_lines_count = 2; + + return header_lines_count; +} + +size_t MonCommand::print_networks_info_table(const ProtoMon &mon_message) +{ + const std::string &pid = mon_message.pid(); + for (auto net_info : mon_message.networks_infos()) { + auto &net_name = net_info.network_name(); + auto fps = net_info.fps(); + auto core = net_info.core_utilization(); + + std::cout << std::setprecision(1) << std::fixed << + std::setw(NETWORK_NAME_WIDTH) << std::left << net_name << + std::setw(NUMBER_WIDTH) << std::left << fps << + std::setw(NUMBER_WIDTH) << std::left << core << + std::setw(NUMBER_WIDTH) << std::left << pid << "\n"; + } + + return mon_message.networks_infos().size(); +} + +size_t MonCommand::print_frames_header() +{ + std::cout << + std::setw(NETWORK_NAME_WIDTH) << std::left << "Network" << + std::setw(STREAM_NAME_WIDTH) << std::left << "Stream" << + std::setw(NUMBER_WIDTH) << std::left << "Direction" << + std::setw(NUMBER_WIDTH) << std::left << "Frames" << + "\n" << std::left << std::string(LINE_LENGTH, '-') << "\n"; + static const size_t header_lines_count = 2; + return header_lines_count; +} + +size_t MonCommand::print_frames_table(const ProtoMon &mon_message) +{ + size_t table_lines_count = 0; + for (auto &net_info : mon_message.net_frames_infos()) { + auto &net_name = net_info.network_name(); + table_lines_count += net_info.streams_frames_infos().size(); + for (auto &streams_frames : net_info.streams_frames_infos()) { + auto &stream_name = streams_frames.stream_name(); + auto stream_direction = (streams_frames.stream_direction() == PROTO__STREAM_DIRECTION__HOST_TO_DEVICE) ? "H2D" : "D2H"; + + std::string frames; + if (SCHEDULER_MON_NAN_VAL == streams_frames.buffer_frames_size() || SCHEDULER_MON_NAN_VAL == streams_frames.pending_frames_count()) { + frames = "NaN"; + } else { + frames = std::to_string(streams_frames.pending_frames_count()) + "/" + std::to_string(streams_frames.buffer_frames_size()); + } + + std::cout << + std::setw(NETWORK_NAME_WIDTH) << std::left << net_name << + std::setw(STREAM_NAME_WIDTH) << std::left << stream_name << + std::setw(NUMBER_WIDTH) << std::left << stream_direction << + std::setw(NUMBER_WIDTH) << std::left << frames << "\n"; + } + } + + return table_lines_count; +} + +#if defined(__GNUC__) +hailo_status MonCommand::print_table() +{ + while (true) { + auto epsilon = std::chrono::milliseconds(500); + std::chrono::milliseconds time_interval = DEFAULT_SCHEDULER_MON_INTERVAL + epsilon; + auto scheduler_mon_files = Filesystem::get_latest_files_in_dir_flat(SCHEDULER_MON_TMP_DIR, time_interval); + if (HAILO_SUCCESS != scheduler_mon_files.status() || scheduler_mon_files->empty()) { + LOGGER__WARNING("Getting scheduler monitor files failed. Please check the application is running and environment variable '{}' is set to 1.", + SCHEDULER_MON_ENV_VAR); + return HAILO_NOT_FOUND; + } + + std::vector mon_messages; + mon_messages.reserve(scheduler_mon_files->size()); + for (const auto &mon_file : scheduler_mon_files.value()) { + auto file = LockedFile::create(mon_file, "r"); + if (HAILO_SUCCESS != file.status()) { + LOGGER__ERROR("Failed to open and lock file {}, with status: {}", mon_file, file.status()); + continue; + } + + ProtoMon mon_message; + if (!mon_message.ParseFromFileDescriptor(file->get_fd())) { + LOGGER__WARNING("Failed to ParseFromFileDescriptor monitor file {} with errno {}", mon_file, errno); + continue; + } + + mon_messages.emplace_back(std::move(mon_message)); + } + + size_t total_lines_count = print_networks_info_header(); + for (auto &mon_message : mon_messages) { + total_lines_count += print_networks_info_table(mon_message); + } + + std::cout << "\n\n"; + total_lines_count += 2; + + total_lines_count += print_frames_header(); + for (auto &mon_message : mon_messages) { + total_lines_count += print_frames_table(mon_message); + } + CliCommon::reset_cursor(total_lines_count); + + std::this_thread::sleep_for(DEFAULT_SCHEDULER_MON_INTERVAL); + } + + return HAILO_SUCCESS; +} +#endif + +} /* namespace hailort */ + diff --git a/hailort/hailortcli/mon_command.hpp b/hailort/hailortcli/mon_command.hpp new file mode 100644 index 0000000..3bfd700 --- /dev/null +++ b/hailort/hailortcli/mon_command.hpp @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) +**/ +/** + * @file mon_command.hpp + * @brief Monitor of networks - Presents information about the running networks + **/ + +#ifndef _HAILO_MON_COMMAND_HPP_ +#define _HAILO_MON_COMMAND_HPP_ + +#include "hailo/hailort.h" +#include "hailortcli.hpp" +#include "command.hpp" +#include "scheduler_mon.hpp" + +#include "CLI/CLI.hpp" + +namespace hailort +{ + +class MonCommand : public Command +{ +public: + explicit MonCommand(CLI::App &parent_app); + + virtual hailo_status execute() override; + +private: + hailo_status print_table(); + size_t print_networks_info_header(); + size_t print_frames_header(); + size_t print_networks_info_table(const ProtoMon &mon_message); + size_t print_frames_table(const ProtoMon &mon_message); +}; + +} /* namespace hailort */ + +#endif /* _HAILO_MON_COMMAND_HPP_ */ + diff --git a/hailort/hailortcli/run_command.cpp b/hailort/hailortcli/run_command.cpp index f623b17..8eff84f 100644 --- a/hailort/hailortcli/run_command.cpp +++ b/hailort/hailortcli/run_command.cpp @@ -36,7 +36,7 @@ std::condition_variable wait_for_exit_cv; /* The SIGUSR1 and SIGUSR2 signals are set aside for you to use any way you want. - They’re useful for simple interprocess communication. */ + They're useful for simple interprocess communication. */ #define USER_SIGNAL (SIGUSR1) constexpr size_t OVERALL_LATENCY_TIMESTAMPS_LIST_LENGTH (512); @@ -63,7 +63,7 @@ hailo_status wait_for_exit_with_timeout(std::chrono::seconds time_to_run) { #if defined(__linux__) sighandler_t prev_handler = signal(USER_SIGNAL, user_signal_handler_func); - CHECK(prev_handler != SIG_ERR, HAILO_INVALID_OPERATION, "signal failed, errno = {}", errno); + CHECK(prev_handler != SIG_ERR, HAILO_INVALID_OPERATION, "signal failed, errno = {}", errno); std::mutex mutex; std::unique_lock condition_variable_lock(mutex); wait_for_exit_cv.wait_for(condition_variable_lock, time_to_run); @@ -83,6 +83,12 @@ bool should_measure_pipeline_stats(const inference_runner_params& params) return std::any_of(measure_flags.cbegin(), measure_flags.cend(), [](bool x){ return x; }); } +bool use_batch_to_measure_opt(const inference_runner_params& params) +{ + return params.runtime_data.collect_runtime_data && + (params.runtime_data.batch_to_measure != RUNTIME_DATA_IGNORE_BATCH_TO_MEASURE_OPT); +} + static void add_run_command_params(CLI::App *run_subcommand, inference_runner_params& params) { // TODO: init values in RunCommand ctor @@ -95,8 +101,7 @@ static void add_run_command_params(CLI::App *run_subcommand, inference_runner_pa params.frames_count = 0; params.measure_temp = false; - add_device_options(run_subcommand, params.device_params); - add_vdevice_options(run_subcommand, params.device_params); + add_vdevice_options(run_subcommand, params.vdevice_params); auto hef_new = run_subcommand->add_option("hef", params.hef_path, "An existing HEF file/directory path") ->check(CLI::ExistingFile | CLI::ExistingDirectory); @@ -123,7 +128,7 @@ static void add_run_command_params(CLI::App *run_subcommand, inference_runner_pa ->excludes(total_batch_size) ->excludes(frames_count); - run_subcommand->add_option("--power-mode", params.power_mode, + CLI::Option *power_mode_option = run_subcommand->add_option("--power-mode", params.power_mode, "Core power mode (PCIE only; ignored otherwise)") ->transform(HailoCheckedTransformer({ { "performance", hailo_power_mode_t::HAILO_POWER_MODE_PERFORMANCE }, @@ -175,12 +180,12 @@ static void add_run_command_params(CLI::App *run_subcommand, inference_runner_pa ->default_val("auto"); auto *measure_stats_subcommand = run_subcommand->add_subcommand("measure-stats", "Pipeline Statistics Measurements"); - measure_stats_subcommand->add_flag("--elem-fps", params.pipeline_stats.measure_elem_fps, + CLI::Option *elem_fps_option = measure_stats_subcommand->add_flag("--elem-fps", params.pipeline_stats.measure_elem_fps, "Measure the fps of each pipeline element separately"); - measure_stats_subcommand->add_flag("--elem-latency", params.pipeline_stats.measure_elem_latency, + CLI::Option *elem_latency_option = measure_stats_subcommand->add_flag("--elem-latency", params.pipeline_stats.measure_elem_latency, "Measure the latency of each pipeline element separately") ->group(""); // --elem-latency will be hidden in the --help print. - measure_stats_subcommand->add_flag("--elem-queue-size", params.pipeline_stats.measure_elem_queue_size, + CLI::Option *elem_queue_size_option = measure_stats_subcommand->add_flag("--elem-queue-size", params.pipeline_stats.measure_elem_queue_size, "Measure the queue size of each pipeline element separately"); // TODO (HRT-4522): Remove comment-out @@ -207,10 +212,12 @@ static void add_run_command_params(CLI::App *run_subcommand, inference_runner_pa params.runtime_data.runtime_data_output_path, "Runtime data output file path") ->default_val("runtime_data.json") ->check(FileSuffixValidator(JSON_SUFFIX)); - static const uint32_t DEFAULT_BATCH_TO_MEASURE = 2; + std::stringstream batch_to_measure_help; + batch_to_measure_help << "Batch to be measured " << std::endl + << "The last batch will be measured if " << RUNTIME_DATA_IGNORE_BATCH_TO_MEASURE_OPT << " is provided"; collect_runtime_data_subcommand->add_option("--batch-to-measure", - params.runtime_data.batch_to_measure, "Batch to be measured") - ->default_val(DEFAULT_BATCH_TO_MEASURE); + params.runtime_data.batch_to_measure, batch_to_measure_help.str()) + ->default_val(RUNTIME_DATA_DEFAULT_BATCH_TO_MEASURE_OPT); collect_runtime_data_subcommand->parse_complete_callback([¶ms]() { // If this subcommand was parsed, then we need to download runtime_data params.runtime_data.collect_runtime_data = true; @@ -225,7 +232,18 @@ static void add_run_command_params(CLI::App *run_subcommand, inference_runner_pa PowerMeasurementSubcommand::init_sampling_period_option(power_sampling_period); PowerMeasurementSubcommand::init_averaging_factor_option(power_averaging_factor); - run_subcommand->add_flag("--measure-temp", params.measure_temp, "Measure chip temperature"); + CLI::Option *measure_temp_option = run_subcommand->add_flag("--measure-temp", params.measure_temp, "Measure chip temperature"); + + CLI::Option *multi_process_option = run_subcommand->get_option("--multi-process-service"); + multi_process_option->excludes(measure_power_opt) + ->excludes(measure_current_opt) + ->excludes(power_mode_option) + ->excludes(power_sampling_period) + ->excludes(power_averaging_factor) + ->excludes(measure_temp_option) + ->excludes(elem_fps_option) + ->excludes(elem_latency_option) + ->excludes(elem_queue_size_option); run_subcommand->parse_complete_callback([¶ms, hef_new, power_sampling_period, power_averaging_factor, measure_power_opt, measure_current_opt]() { @@ -242,24 +260,29 @@ static void add_run_command_params(CLI::App *run_subcommand, inference_runner_pa PARSE_CHECK(((0 != params.time_to_run) || (0 == (params.frames_count % params.batch_size))), "--batch-size should be a divisor of --frames-count if provided"); // TODO HRT-5363 support multiple devices - PARSE_CHECK((params.device_params.vdevice_params.device_count == 1) || params.csv_output.empty() || + PARSE_CHECK((params.vdevice_params.device_count == 1) || params.csv_output.empty() || !(params.power_measurement.measure_power || params.power_measurement.measure_current || params.measure_temp), "Writing measurements in csv format is not supported for multiple devices"); - PARSE_CHECK(("*" != params.device_params.pcie_params.pcie_bdf), - "Passing '*' as BDF is not supported for 'run' command. for multiple devices inference see '--device-count'"); - if ((0 == params.time_to_run) && (0 == params.frames_count)) { // Use default params.time_to_run = DEFAULT_TIME_TO_RUN_SECONDS; } - if (params.runtime_data.collect_runtime_data) { - if ((0 != params.frames_count) && (params.frames_count < params.runtime_data.batch_to_measure)) { - LOGGER__WARNING("--frames-count ({}) is smaller than --batch-to-measure ({}), " - "hence timestamps will not be updated in runtime data", params.frames_count, - params.runtime_data.batch_to_measure); - } + PARSE_CHECK(((!params.runtime_data.collect_runtime_data) || (params.vdevice_params.device_count == 1)), + "Passing runtime data is not supported for multiple devices"); + + PARSE_CHECK((!(params.runtime_data.collect_runtime_data && params.vdevice_params.multi_process_service)), + "Passing runtime data is not supported for multi process service"); + + PARSE_CHECK(!(params.vdevice_params.multi_process_service && is_hw_only), + "--hw-only mode is not supported for multi process service"); + + if (use_batch_to_measure_opt(params) && + (0 != params.frames_count) && (params.frames_count < params.runtime_data.batch_to_measure)) { + LOGGER__WARNING("--frames-count ({}) is smaller than --batch-to-measure ({}), " + "hence timestamps will not be updated in runtime data", params.frames_count, + params.runtime_data.batch_to_measure); } }); } @@ -341,12 +364,12 @@ static size_t total_recv_frame_size(const std::vector hailo_status send_loop(const inference_runner_params ¶ms, SendObject &send_object, - std::map &input_dataset, Barrier &barrier, LatencyMeter &overall_latency_meter, uint16_t batch_size) + const std::map &input_dataset, Barrier &barrier, LatencyMeter &overall_latency_meter, uint16_t batch_size) { assert(input_dataset.find(send_object.name()) != input_dataset.end()); - const Buffer &input_buffer = input_dataset.at(send_object.name()); - assert((input_buffer.size() % send_object.get_frame_size()) == 0); - const size_t frames_in_buffer = input_buffer.size() / send_object.get_frame_size(); + const BufferPtr &input_buffer = input_dataset.at(send_object.name()); + assert((input_buffer->size() % send_object.get_frame_size()) == 0); + const size_t frames_in_buffer = input_buffer->size() / send_object.get_frame_size(); // TODO: pass the correct batch (may be different between networks) uint32_t num_of_batches = (0 == params.time_to_run ? (params.frames_count / batch_size) : UINT32_MAX); for (uint32_t i = 0; i < num_of_batches; i++) { @@ -360,7 +383,7 @@ hailo_status send_loop(const inference_runner_params ¶ms, SendObject &send_o const size_t offset = (i % frames_in_buffer) * send_object.get_frame_size(); auto status = send_object.write(MemoryView( - const_cast(input_buffer.data()) + offset, + const_cast(input_buffer->data()) + offset, send_object.get_frame_size())); if (HAILO_STREAM_INTERNAL_ABORT == status) { LOGGER__DEBUG("Input stream was aborted!"); @@ -378,7 +401,7 @@ hailo_status send_loop(const inference_runner_params ¶ms, SendObject &send_o template hailo_status recv_loop(const inference_runner_params ¶ms, RecvObject &recv_object, std::shared_ptr progress_bar, Barrier &barrier, LatencyMeter &overall_latency_meter, - std::map &dst_data, std::atomic_size_t &received_frames_count, uint32_t output_idx, bool show_progress, + std::map &dst_data, std::atomic_size_t &received_frames_count, uint32_t output_idx, bool show_progress, uint16_t batch_size) { uint32_t num_of_batches = ((0 == params.time_to_run) ? (params.frames_count / batch_size) : UINT32_MAX); @@ -387,7 +410,7 @@ hailo_status recv_loop(const inference_runner_params ¶ms, RecvObject &recv_o barrier.arrive_and_wait(); } for (int j = 0; j < batch_size; j++) { - auto status = recv_object.read(MemoryView(dst_data[recv_object.name()])); + auto status = recv_object.read(MemoryView(*dst_data[recv_object.name()])); if (HAILO_SUCCESS != status) { return status; } @@ -405,22 +428,19 @@ hailo_status recv_loop(const inference_runner_params ¶ms, RecvObject &recv_o return HAILO_SUCCESS; } -hailo_status abort_low_level_streams(ConfiguredNetworkGroup &configured_net_group, const std::string &network_name = "") +template +hailo_status abort_streams(std::vector> &send_objects, + std::vector> &recv_objects) { - // If network_name is not given, all networks are addressed auto status = HAILO_SUCCESS; // Best effort - auto input_streams = configured_net_group.get_input_streams_by_network(network_name); - CHECK_EXPECTED_AS_STATUS(input_streams); - for (auto &input_stream : input_streams.release()) { + for (auto &input_stream : send_objects) { auto abort_status = input_stream.get().abort(); if (HAILO_SUCCESS != abort_status) { LOGGER__ERROR("Failed to abort input stream {}", input_stream.get().name()); status = abort_status; } } - auto output_streams = configured_net_group.get_output_streams_by_network(network_name); - CHECK_EXPECTED_AS_STATUS(output_streams); - for (auto &output_stream : output_streams.release()) { + for (auto &output_stream : recv_objects) { auto abort_status = output_stream.get().abort(); if (HAILO_SUCCESS != abort_status) { LOGGER__ERROR("Failed to abort output stream {}", output_stream.get().name()); @@ -502,13 +522,13 @@ Expected> // TODO: HRT-5177 create output buffers inside run_streaming template< typename RecvObject> -Expected> create_output_buffers( +Expected> create_output_buffers( std::map>> &recv_objects_per_network) { - std::map dst_data; + std::map dst_data; for (auto &recv_objects : recv_objects_per_network) { for (auto &recv_object : recv_objects.second) { - auto buffer = Buffer::create(recv_object.get().get_frame_size()); + auto buffer = Buffer::create_shared(recv_object.get().get_frame_size()); CHECK_EXPECTED(buffer); dst_data[recv_object.get().name()] = buffer.release(); } @@ -548,47 +568,52 @@ Expected> get_configure_params(con hailo_status status = hailo_init_configure_params(reinterpret_cast(&hef), interface, &config_params); CHECK_SUCCESS_AS_EXPECTED(status); - // TODO: SDK-14842, for now this function supports only one network_group /* params.batch_per_network is a partial list of networks. If a network is not in it, it gets the network_group_batch (params.batch_size) */ if (params.batch_per_network.empty()) { - config_params.network_group_params[0].batch_size = params.batch_size; + for (size_t network_group_idx = 0; network_group_idx < config_params.network_group_params_count; network_group_idx++) { + config_params.network_group_params[network_group_idx].batch_size = params.batch_size; + } } else { for (auto &name_to_batch_str : params.batch_per_network) { auto name_to_batch = get_network_to_batch(name_to_batch_str); auto network_name = name_to_batch.first; auto batch_size = name_to_batch.second; - bool found = false; - for (uint8_t network_idx = 0; network_idx < config_params.network_group_params[0].network_params_by_name_count; network_idx++) { - if (0 == strcmp(network_name.c_str(), config_params.network_group_params[0].network_params_by_name[network_idx].name)) { - config_params.network_group_params[0].network_params_by_name[network_idx].network_params.batch_size = batch_size; - found = true; + for (size_t network_group_idx = 0; network_group_idx < config_params.network_group_params_count; network_group_idx++) { + bool found = false; + for (uint8_t network_idx = 0; network_idx < config_params.network_group_params[network_group_idx].network_params_by_name_count; network_idx++) { + if (0 == strcmp(network_name.c_str(), config_params.network_group_params[network_group_idx].network_params_by_name[network_idx].name)) { + config_params.network_group_params[network_group_idx].network_params_by_name[network_idx].network_params.batch_size = batch_size; + found = true; + } } + CHECK_AS_EXPECTED(found, HAILO_INVALID_ARGUMENT, "Did not find any network named {}. Use 'parse-hef' option to see network names.", + network_name); } - CHECK_AS_EXPECTED(found, HAILO_INVALID_ARGUMENT, "Did not find any network named {}. Use 'parse-hef' option to see network names.", - network_name); } } - config_params.network_group_params[0].power_mode = params.power_mode; - configure_params.emplace(std::string(config_params.network_group_params[0].name), - ConfigureNetworkParams(config_params.network_group_params[0])); + for (size_t network_group_idx = 0; network_group_idx < config_params.network_group_params_count; network_group_idx++) { + config_params.network_group_params[network_group_idx].power_mode = params.power_mode; + configure_params.emplace(std::string(config_params.network_group_params[network_group_idx].name), + ConfigureNetworkParams(config_params.network_group_params[network_group_idx])); - if (params.measure_latency) { - configure_params[std::string(config_params.network_group_params[0].name)].latency |= HAILO_LATENCY_MEASURE; + if (params.measure_latency) { + configure_params[std::string(config_params.network_group_params[network_group_idx].name)].latency |= HAILO_LATENCY_MEASURE; + } } return configure_params; } template -static hailo_status run_streaming_impl(ConfiguredNetworkGroup &configured_net_group, - std::map &input_dataset, - std::map &output_buffers, +static hailo_status run_streaming_impl(std::shared_ptr configured_net_group, + const std::map &input_dataset, + std::map &output_buffers, const inference_runner_params ¶ms, std::vector> &send_objects, std::vector> &recv_objects, - const std::string network_name, - InferProgress &network_group_progress_bar, + const std::string &network_name, + InferProgress &progress_bar, NetworkInferResult &inference_result) { // latency resources init @@ -611,9 +636,9 @@ static hailo_status run_streaming_impl(ConfiguredNetworkGroup &configured_net_gr std::vector> results; - auto progress_bar_exp = network_group_progress_bar.create_network_progress_bar(network_name); - CHECK_EXPECTED_AS_STATUS(progress_bar_exp); - auto progress_bar = progress_bar_exp.release(); + auto network_progress_bar_exp = progress_bar.create_network_progress_bar(configured_net_group, network_name); + CHECK_EXPECTED_AS_STATUS(network_progress_bar_exp); + auto network_progress_bar = network_progress_bar_exp.release(); const auto start = std::chrono::high_resolution_clock::now(); // Launch async read/writes @@ -622,9 +647,9 @@ static hailo_status run_streaming_impl(ConfiguredNetworkGroup &configured_net_gr for (auto& recv_object : recv_objects) { auto &frames_recieved = frames_recieved_per_output[output_index]; results.emplace_back(std::make_unique>( - [progress_bar, params, &recv_object, &output_buffers, first, &barrier, &overall_latency_meter, + [network_progress_bar, params, &recv_object, &output_buffers, first, &barrier, &overall_latency_meter, &frames_recieved, output_index, batch_size]() { - auto res = recv_loop(params, recv_object.get(), progress_bar, barrier, overall_latency_meter, + auto res = recv_loop(params, recv_object.get(), network_progress_bar, barrier, overall_latency_meter, output_buffers, frames_recieved, output_index, first, batch_size); if (HAILO_SUCCESS != res) { barrier.terminate(); @@ -651,7 +676,7 @@ static hailo_status run_streaming_impl(ConfiguredNetworkGroup &configured_net_gr auto status = wait_for_exit_with_timeout(std::chrono::seconds(params.time_to_run)); CHECK_SUCCESS(status); - status = abort_low_level_streams(configured_net_group, network_name); + status = abort_streams(send_objects, recv_objects); barrier.terminate(); CHECK_SUCCESS(status); } @@ -678,16 +703,19 @@ static hailo_status run_streaming_impl(ConfiguredNetworkGroup &configured_net_gr inference_result.m_frames_count = min_frame_count_recieved; - auto network_input_streams = configured_net_group.get_input_streams_by_network(network_name); - CHECK_EXPECTED_AS_STATUS(network_input_streams); - inference_result.m_total_send_frame_size = total_send_frame_size(network_input_streams.value()); - auto network_output_streams = configured_net_group.get_output_streams_by_network(network_name); - CHECK_EXPECTED_AS_STATUS(network_output_streams); - inference_result.m_total_recv_frame_size = total_recv_frame_size(network_output_streams.value()); + // TODO: HRT-7798 + if (!params.vdevice_params.multi_process_service) { + auto network_input_streams = configured_net_group->get_input_streams_by_network(network_name); + CHECK_EXPECTED_AS_STATUS(network_input_streams); + inference_result.m_total_send_frame_size = total_send_frame_size(network_input_streams.value()); + auto network_output_streams = configured_net_group->get_output_streams_by_network(network_name); + CHECK_EXPECTED_AS_STATUS(network_output_streams); + inference_result.m_total_recv_frame_size = total_recv_frame_size(network_output_streams.value()); + } if (params.measure_latency) { - if (auto hw_latency = configured_net_group.get_latency_measurement(network_name)) { - auto hw_latency_p = make_unique_nothrow(hw_latency->avg_hw_latency); + if (auto hw_latency = configured_net_group->get_latency_measurement(network_name)) { + auto hw_latency_p = make_shared_nothrow(hw_latency->avg_hw_latency); CHECK_NOT_NULL(hw_latency_p, HAILO_OUT_OF_HOST_MEMORY); inference_result.m_hw_latency = std::move(hw_latency_p); } @@ -705,136 +733,189 @@ static hailo_status run_streaming_impl(ConfiguredNetworkGroup &configured_net_gr } template -static Expected run_streaming(ConfiguredNetworkGroup &configured_net_group, - std::map &input_dataset, - std::map &output_buffers, +static Expected run_streaming(const std::vector> &configured_net_groups, + const std::vector> &input_datasets, + std::vector> &output_buffers, const inference_runner_params ¶ms, - std::map>> &send_objects_per_network, - std::map>> &recv_objects_per_network) + std::vector>>> &send_objects_per_network_group, + std::vector>>> &recv_objects_per_network_group) { - CHECK_AS_EXPECTED(send_objects_per_network.size() == recv_objects_per_network.size(), HAILO_INTERNAL_FAILURE, - "Not all networks was parsed correctly."); + CHECK_AS_EXPECTED(send_objects_per_network_group.size() == recv_objects_per_network_group.size(), HAILO_INTERNAL_FAILURE, + "Not all network groups parsed correctly."); + CHECK_AS_EXPECTED(configured_net_groups.size() == recv_objects_per_network_group.size(), HAILO_INTERNAL_FAILURE, + "Not all network groups parsed correctly. configured_net_groups.size(): {}, recv_objects_per_network_group: {}", configured_net_groups.size(), recv_objects_per_network_group.size()); - // TODO: support AsyncThreadPtr for Expected, and use it instead of status - std::vector> networks_threads_status; - networks_threads_status.reserve(send_objects_per_network.size()); - std::map networks_results; + std::vector results_per_network_group; - // TODO (HRT-5789): instead of init this map and giving it to run_streaming_impl, change AsyncThread to return Expected - for (auto &network_name_pair : send_objects_per_network) { - networks_results.emplace(network_name_pair.first, NetworkInferResult()); - } + std::vector>> networks_threads_status; // Vector of threads for each network group + networks_threads_status.reserve(configured_net_groups.size()); + std::vector> networks_results; // Map of networks results for each network group + networks_results.reserve(configured_net_groups.size()); + + auto progress_bar_exp = InferProgress::create(params, std::chrono::seconds(1)); + CHECK_EXPECTED(progress_bar_exp); + auto progress_bar = progress_bar_exp.release(); + + for (size_t network_group_index = 0; network_group_index < configured_net_groups.size(); network_group_index++) { + networks_threads_status.emplace_back(); + networks_results.emplace_back(); + CHECK_AS_EXPECTED(send_objects_per_network_group[network_group_index].size() == recv_objects_per_network_group[network_group_index].size(), HAILO_INTERNAL_FAILURE, + "Not all networks parsed correctly in network group {}.", configured_net_groups[network_group_index]->name()); - InferProgress network_group_progress_bar(configured_net_group, params, std::chrono::seconds(1)); + // TODO: support AsyncThreadPtr for Expected, and use it instead of status + networks_threads_status[network_group_index].reserve(send_objects_per_network_group[network_group_index].size()); + + // TODO (HRT-5789): instead of init this map and giving it to run_streaming_impl, change AsyncThread to return Expected + for (auto &network_name_pair : send_objects_per_network_group[network_group_index]) { + networks_results[network_group_index].emplace(network_name_pair.first, NetworkInferResult()); + } + } if (params.show_progress) { - network_group_progress_bar.start(); - } - - for (auto &network_name_pair : send_objects_per_network) { - CHECK_AS_EXPECTED(contains(recv_objects_per_network, network_name_pair.first), HAILO_INTERNAL_FAILURE, - "Not all networks was parsed correctly."); - auto network_name = network_name_pair.first; - networks_threads_status.emplace_back(std::make_unique>( - [&configured_net_group, &input_dataset, &output_buffers, ¶ms, &send_objects_per_network, &recv_objects_per_network, network_name, - &network_group_progress_bar, &networks_results]() { - return run_streaming_impl(configured_net_group, input_dataset, output_buffers, params, - send_objects_per_network.at(network_name), - recv_objects_per_network.at(network_name), - network_name, network_group_progress_bar, networks_results.at(network_name)); - } - )); + progress_bar->start(); + } + + for (size_t network_group_index = 0; network_group_index < configured_net_groups.size(); network_group_index++) { + for (auto &network_name_pair : send_objects_per_network_group[network_group_index]) { + CHECK_AS_EXPECTED(contains(recv_objects_per_network_group[network_group_index], network_name_pair.first), HAILO_INTERNAL_FAILURE, + "Not all networks was parsed correctly."); + auto network_name = network_name_pair.first; + networks_threads_status[network_group_index].emplace_back(std::make_unique>( + [network_group_index, &configured_net_groups, &input_datasets, &output_buffers, ¶ms, &send_objects_per_network_group, + &recv_objects_per_network_group, network_name, &progress_bar, &networks_results]() { + return run_streaming_impl(configured_net_groups[network_group_index], input_datasets[network_group_index], + output_buffers[network_group_index], params, + send_objects_per_network_group[network_group_index].at(network_name), + recv_objects_per_network_group[network_group_index].at(network_name), + network_name, *progress_bar, networks_results[network_group_index].at(network_name)); + } + )); + } } - // Wait for all results - for (auto& status : networks_threads_status) { - auto network_status = status->get(); - CHECK_SUCCESS_AS_EXPECTED(network_status); + for (size_t network_group_index = 0; network_group_index < configured_net_groups.size(); network_group_index++) { + // Wait for all results + for (auto& status : networks_threads_status[network_group_index]) { + auto network_status = status->get(); + CHECK_SUCCESS_AS_EXPECTED(network_status); + } } if (params.show_progress) { - network_group_progress_bar.finish(); + progress_bar->finish(); } - // Update final_result struct - with all inferences results - NetworkGroupInferResult final_result(std::move(networks_results)); + for (size_t network_group_index = 0; network_group_index < configured_net_groups.size(); network_group_index++) { + NetworkGroupInferResult network_group_result(configured_net_groups[network_group_index]->name(), + std::move(networks_results[network_group_index])); - if (should_measure_pipeline_stats(params)) { - final_result.update_pipeline_stats(send_objects_per_network, recv_objects_per_network); + if (should_measure_pipeline_stats(params)) { + network_group_result.update_pipeline_stats(send_objects_per_network_group[network_group_index], + recv_objects_per_network_group[network_group_index]); + } + results_per_network_group.emplace_back(std::move(network_group_result)); } + // Update final_result struct - with all inferences results + InferResult final_result(std::move(results_per_network_group)); return final_result; } -static Expected run_inference(ConfiguredNetworkGroup &configured_net_group, - std::map &input_dataset, +static Expected run_inference(const std::vector> &configured_net_groups, + const std::vector> &input_datasets, const inference_runner_params ¶ms) { switch (params.mode) { case InferMode::STREAMING: { - auto in_vstreams = create_input_vstreams(configured_net_group, params); - CHECK_EXPECTED(in_vstreams); - - auto out_vstreams = create_output_vstreams(configured_net_group, params); - CHECK_EXPECTED(out_vstreams); - - // run_streaming function should get reference_wrappers to vstreams instead of the instances themselves - std::map>> input_refs_map; - for (auto &input_vstreams_per_network : in_vstreams.value()) { - std::vector> input_refs; - for (auto &input_vstream : input_vstreams_per_network.second) { - input_refs.emplace_back(input_vstream); + std::vector>>> input_vstreams; + input_vstreams.reserve(configured_net_groups.size()); + std::vector>>> output_vstreams; + output_vstreams.reserve(configured_net_groups.size()); + + std::vector>>> input_vstreams_refs(configured_net_groups.size(), + std::map>>()); + std::vector>>> output_vstreams_refs(configured_net_groups.size(), + std::map>>()); + + std::vector> output_buffers(configured_net_groups.size(), + std::map()); + + for (size_t network_group_index = 0; network_group_index < configured_net_groups.size(); network_group_index++) { + input_vstreams.emplace_back(); + output_vstreams.emplace_back(); + auto in_vstreams = create_input_vstreams(*configured_net_groups[network_group_index], params); + CHECK_EXPECTED(in_vstreams); + auto in_vstreams_ptr = make_shared_nothrow>>(in_vstreams.release()); + CHECK_NOT_NULL_AS_EXPECTED(in_vstreams_ptr, HAILO_OUT_OF_HOST_MEMORY); + input_vstreams[network_group_index] = in_vstreams_ptr; + + auto out_vstreams = create_output_vstreams(*configured_net_groups[network_group_index], params); + CHECK_EXPECTED(out_vstreams); + auto out_vstreams_ptr = make_shared_nothrow>>(out_vstreams.release()); + CHECK_NOT_NULL_AS_EXPECTED(out_vstreams_ptr, HAILO_OUT_OF_HOST_MEMORY); + output_vstreams[network_group_index] = out_vstreams_ptr; + + // run_streaming function should get reference_wrappers to vstreams instead of the instances themselves + for (auto &input_vstreams_per_network : *input_vstreams[network_group_index]) { + std::vector> input_refs; + for (auto &input_vstream : input_vstreams_per_network.second) { + input_refs.emplace_back(input_vstream); + } + input_vstreams_refs[network_group_index].emplace(input_vstreams_per_network.first, input_refs); } - input_refs_map.emplace(input_vstreams_per_network.first, input_refs); - } - std::map>> output_refs_map; - for (auto &output_vstreams_per_network : out_vstreams.value()) { - std::vector> output_refs; - for (auto &output_vstream : output_vstreams_per_network.second) { - output_refs.emplace_back(output_vstream); + for (auto &output_vstreams_per_network : *output_vstreams[network_group_index]) { + std::vector> output_refs; + for (auto &output_vstream : output_vstreams_per_network.second) { + output_refs.emplace_back(output_vstream); + } + output_vstreams_refs[network_group_index].emplace(output_vstreams_per_network.first, output_refs); } - output_refs_map.emplace(output_vstreams_per_network.first, output_refs); - } - auto output_buffers = create_output_buffers(output_refs_map); - CHECK_EXPECTED(output_buffers); + auto network_group_output_buffers = create_output_buffers(output_vstreams_refs[network_group_index]); + CHECK_EXPECTED(network_group_output_buffers); + output_buffers[network_group_index] = network_group_output_buffers.release(); + } - auto res = run_streaming(configured_net_group, input_dataset, - output_buffers.value(), params, input_refs_map, output_refs_map); + auto res = run_streaming(configured_net_groups, input_datasets, + output_buffers, params, input_vstreams_refs, output_vstreams_refs); if (!params.dot_output.empty()) { - const auto status = GraphPrinter::write_dot_file(in_vstreams.value(), out_vstreams.value(), params.hef_path, + const auto status = GraphPrinter::write_dot_file(input_vstreams_refs, output_vstreams_refs, params.hef_path, params.dot_output, should_measure_pipeline_stats(params)); CHECK_SUCCESS_AS_EXPECTED(status); } - // Note: In VStreams d'tor, low-level-streams clears their abort flag. In low-level-streams d'tor, 'flush()' is called. - // In order to avoid error logs on 'flush()', we re-set the abort flag in the low-level streams after vstreams d'tor. - // TODO: HRT-5177 fix that note - in_vstreams->clear(); - out_vstreams->clear(); + input_vstreams.clear(); + output_vstreams.clear(); - if (0 < params.time_to_run) { - auto status = abort_low_level_streams(configured_net_group); - CHECK_SUCCESS_AS_EXPECTED(status); - } CHECK_EXPECTED(res); return res; - } case InferMode::HW_ONLY: { - auto input_streams = create_input_streams(configured_net_group); - CHECK_EXPECTED(input_streams); - auto output_streams = create_output_streams(configured_net_group); - CHECK_EXPECTED(output_streams); - - auto output_buffers = create_output_buffers(output_streams.value()); - CHECK_EXPECTED(output_buffers); + std::vector>>> input_streams_refs(configured_net_groups.size(), + std::map>>()); + std::vector>>> output_streams_refs(configured_net_groups.size(), + std::map>>()); + + std::vector> output_buffers(configured_net_groups.size(), + std::map()); + + for (size_t network_group_index = 0; network_group_index < configured_net_groups.size(); network_group_index++) { + auto input_streams = create_input_streams(*configured_net_groups[network_group_index]); + CHECK_EXPECTED(input_streams); + input_streams_refs[network_group_index] = input_streams.release(); + auto output_streams = create_output_streams(*configured_net_groups[network_group_index]); + output_streams_refs[network_group_index] = output_streams.release(); + + auto network_group_output_buffers = create_output_buffers(output_streams_refs[network_group_index]); + CHECK_EXPECTED(network_group_output_buffers); + output_buffers[network_group_index] = network_group_output_buffers.release(); + } - return run_streaming(configured_net_group, input_dataset, output_buffers.value(), - params, input_streams.value(), output_streams.value()); + return run_streaming(configured_net_groups, input_datasets, output_buffers, + params, input_streams_refs, output_streams_refs); } default: return make_unexpected(HAILO_INVALID_OPERATION); @@ -854,43 +935,43 @@ static Expected> activate_network_group(C return activated_network_group; } -static Expected> create_constant_dataset( - const std::vector> &input_streams, const hailo_transform_params_t &trans_params) +static Expected> create_constant_dataset( + const std::vector &input_streams_infos, const hailo_transform_params_t &trans_params) { const uint8_t const_byte = 0xAB; - std::map dataset; - for (const auto &input_stream : input_streams) { - const auto frame_size = hailo_get_host_frame_size(&(input_stream.get().get_info()), &trans_params); - auto constant_buffer = Buffer::create(frame_size, const_byte); + std::map dataset; + for (const auto &input_stream_info : input_streams_infos) { + const auto frame_size = hailo_get_host_frame_size(&input_stream_info, &trans_params); + auto constant_buffer = Buffer::create_shared(frame_size, const_byte); if (!constant_buffer) { std::cerr << "Out of memory, tried to allocate " << frame_size << std::endl; return make_unexpected(constant_buffer.status()); } - dataset.emplace(input_stream.get().name(), constant_buffer.release()); + dataset.emplace(std::string(input_stream_info.name), constant_buffer.release()); } return dataset; } -static Expected> create_dataset_from_files( - const std::vector> &input_streams, const std::vector &input_files, +static Expected> create_dataset_from_files( + const std::vector &input_streams_infos, const std::vector &input_files, const hailo_transform_params_t &trans_params, InferMode mode) { - CHECK_AS_EXPECTED(input_streams.size() == input_files.size(), HAILO_INVALID_ARGUMENT, "Number of input files ({}) must be equal to the number of inputs ({})", input_files.size(), input_streams.size()); + CHECK_AS_EXPECTED(input_streams_infos.size() == input_files.size(), HAILO_INVALID_ARGUMENT, "Number of input files ({}) must be equal to the number of inputs ({})", input_files.size(), input_streams_infos.size()); std::map file_paths; - if ((input_streams.size() == 1) && (input_files[0].find("=") == std::string::npos)) { // Legacy single input format - file_paths.emplace(input_streams[0].get().name(), input_files[0]); + if ((input_streams_infos.size() == 1) && (input_files[0].find("=") == std::string::npos)) { // Legacy single input format + file_paths.emplace(std::string(input_streams_infos[0].name), input_files[0]); } else { file_paths = format_strings_to_key_value_pairs(input_files); } - std::map dataset; - for (const auto &input_stream : input_streams) { - const auto host_frame_size = hailo_get_host_frame_size(&(input_stream.get().get_info()), &trans_params); - const auto stream_name = std::string(input_stream.get().name()); + std::map dataset; + for (const auto &input_stream_info : input_streams_infos) { + const auto host_frame_size = hailo_get_host_frame_size(&input_stream_info, &trans_params); + const auto stream_name = std::string(input_stream_info.name); CHECK_AS_EXPECTED(stream_name.find("=") == std::string::npos, HAILO_INVALID_ARGUMENT, "stream inputs must not contain '=' characters: {}", stream_name); const auto file_path_it = file_paths.find(stream_name); @@ -903,17 +984,17 @@ static Expected> create_dataset_from_files( if (InferMode::HW_ONLY == mode) { const size_t frames_count = (host_buffer->size() / host_frame_size); - const size_t hw_frame_size = input_stream.get().get_frame_size(); + const size_t hw_frame_size = input_stream_info.hw_frame_size; const size_t hw_buffer_size = frames_count * hw_frame_size; - auto hw_buffer = Buffer::create(hw_buffer_size); + auto hw_buffer = Buffer::create_shared(hw_buffer_size); CHECK_EXPECTED(hw_buffer); - auto transform_context = InputTransformContext::create(input_stream.get().get_info(), trans_params); + auto transform_context = InputTransformContext::create(input_stream_info, trans_params); CHECK_EXPECTED(transform_context); for (size_t i = 0; i < frames_count; i++) { MemoryView host_data(static_cast(host_buffer->data() + (i*host_frame_size)), host_frame_size); - MemoryView hw_data(static_cast(hw_buffer->data() + (i*hw_frame_size)), hw_frame_size); + MemoryView hw_data(static_cast(hw_buffer.value()->data() + (i*hw_frame_size)), hw_frame_size); auto status = transform_context.value()->transform(host_data, hw_data); CHECK_SUCCESS_AS_EXPECTED(status); @@ -921,41 +1002,65 @@ static Expected> create_dataset_from_files( dataset[stream_name] = hw_buffer.release(); } else { - dataset[stream_name] = host_buffer.release(); + auto host_buffer_shared = make_shared_nothrow(host_buffer.release()); + CHECK_NOT_NULL_AS_EXPECTED(host_buffer_shared, HAILO_OUT_OF_HOST_MEMORY); + dataset[stream_name] = host_buffer_shared; } } return dataset; } -static Expected> create_dataset( - const std::vector> &input_streams, +static Expected>> create_dataset( + const std::vector> &network_groups, const inference_runner_params ¶ms) { + std::vector> results; + results.reserve(network_groups.size()); hailo_transform_params_t trans_params = {}; trans_params.transform_mode = (params.transform.transform ? HAILO_STREAM_TRANSFORM_COPY : HAILO_STREAM_NO_TRANSFORM); trans_params.user_buffer_format.order = HAILO_FORMAT_ORDER_AUTO; trans_params.user_buffer_format.flags = (params.transform.quantized ? HAILO_FORMAT_FLAGS_QUANTIZED : HAILO_FORMAT_FLAGS_NONE); trans_params.user_buffer_format.type = params.transform.format_type; - + std::vector> input_infos; + for (auto &network_group : network_groups) { + auto expected_all_streams_infos = network_group->get_all_stream_infos(); + CHECK_EXPECTED(expected_all_streams_infos); + auto &all_infos = expected_all_streams_infos.value(); + std::vector group_input_infos; + std::copy_if(all_infos.begin(), all_infos.end(), std::back_inserter(group_input_infos), [](auto &info) { + return info.direction == HAILO_H2D_STREAM; + }); + input_infos.push_back(group_input_infos); + } if (!params.inputs_name_and_file_path.empty()) { - return create_dataset_from_files(input_streams, params.inputs_name_and_file_path, trans_params, params.mode); + for (auto &group_input_infos : input_infos) { + auto network_group_dataset = create_dataset_from_files(group_input_infos, params.inputs_name_and_file_path, + trans_params, params.mode); + CHECK_EXPECTED(network_group_dataset); + results.emplace_back(network_group_dataset.release()); + } } else { - return create_constant_dataset(input_streams, trans_params); + for (auto &group_input_infos : input_infos) { + auto network_group_dataset = create_constant_dataset(group_input_infos, trans_params); + CHECK_EXPECTED(network_group_dataset); + results.emplace_back(network_group_dataset.release()); + } } + return results; } -Expected activate_network_group_and_run( +Expected activate_network_group_and_run( Device &device, - std::shared_ptr network_group, + const std::vector> &network_groups, const inference_runner_params ¶ms) { - auto activated_net_group = activate_network_group(*network_group); + CHECK_AS_EXPECTED(1 == network_groups.size(), HAILO_INVALID_OPERATION, "Inference is not supported on HEFs with multiple network groups"); + auto activated_net_group = activate_network_group(*network_groups[0]); CHECK_EXPECTED(activated_net_group, "Failed activate network_group"); - auto input_streams = network_group->get_input_streams(); - auto input_dataset = create_dataset(input_streams, params); + auto input_dataset = create_dataset(network_groups, params); CHECK_EXPECTED(input_dataset, "Failed creating input dataset"); hailo_power_measurement_types_t measurement_type = HAILO_POWER_MEASUREMENT_TYPES__MAX_ENUM; @@ -968,13 +1073,13 @@ Expected activate_network_group_and_run( should_measure_power = true; } - std::unique_ptr long_power_measurement = nullptr; + std::shared_ptr long_power_measurement = nullptr; if (should_measure_power) { auto long_power_measurement_exp = PowerMeasurementSubcommand::start_power_measurement(device, HAILO_DVM_OPTIONS_AUTO, measurement_type, params.power_measurement.sampling_period, params.power_measurement.averaging_factor); CHECK_EXPECTED(long_power_measurement_exp); - long_power_measurement = make_unique_nothrow(long_power_measurement_exp.release()); + long_power_measurement = make_shared_nothrow(long_power_measurement_exp.release()); CHECK_NOT_NULL_AS_EXPECTED(long_power_measurement, HAILO_OUT_OF_HOST_MEMORY); } @@ -985,10 +1090,10 @@ Expected activate_network_group_and_run( CHECK_SUCCESS_AS_EXPECTED(status, "Failed to get chip's temperature"); } - auto infer_result = run_inference(*network_group, input_dataset.value(), params); + auto infer_result = run_inference(network_groups, input_dataset.value(), params); CHECK_EXPECTED(infer_result, "Error failed running inference"); - NetworkGroupInferResult inference_result(infer_result.release()); + InferResult inference_result(infer_result.release()); std::vector> device_refs; device_refs.push_back(device); inference_result.initialize_measurements(device_refs); @@ -1008,7 +1113,7 @@ Expected activate_network_group_and_run( if (should_measure_temp) { temp_measure.stop_measurement(); - auto temp_measure_p = make_unique_nothrow(temp_measure.get_data()); + auto temp_measure_p = make_shared_nothrow(temp_measure.get_data()); CHECK_NOT_NULL_AS_EXPECTED(temp_measure_p, HAILO_OUT_OF_HOST_MEMORY); auto status = inference_result.set_temp_measurement(device.get_dev_id(), std::move(temp_measure_p)); CHECK_SUCCESS_AS_EXPECTED(status); @@ -1017,61 +1122,102 @@ Expected activate_network_group_and_run( return inference_result; } -Expected run_command_hef_single_device(const inference_runner_params ¶ms) +Expected get_min_inferred_frames_count(InferResult &inference_result) { - auto device = create_device(params.device_params); - CHECK_EXPECTED(device, "Failed creating device"); + size_t min_frames_count = UINT32_MAX; + for (auto &network_group_results : inference_result.network_group_results()) { + for (const auto &network_results_pair : network_group_results.results_per_network()) { + auto frames_count = network_group_results.frames_count(network_results_pair.first); + CHECK_EXPECTED(frames_count); + min_frames_count = std::min(frames_count.value(), min_frames_count); + } + } + return min_frames_count; +} + +Expected run_command_hef_single_device(const inference_runner_params ¶ms) +{ + auto devices = create_devices(params.vdevice_params.device_params); + CHECK_EXPECTED(devices, "Failed creating device"); + /* This function supports controls for multiple devices. + We validate there is only 1 device generated as we are on a single device flow */ + CHECK_AS_EXPECTED(1 == devices->size(), HAILO_INTERNAL_FAILURE); + auto &device = devices.value()[0]; auto hef = Hef::create(params.hef_path.c_str()); CHECK_EXPECTED(hef, "Failed reading hef file {}", params.hef_path); - auto interface = device.value()->get_default_streams_interface(); + auto interface = device->get_default_streams_interface(); CHECK_EXPECTED(interface, "Failed to get default streams interface"); auto configure_params = get_configure_params(params, hef.value(), interface.value()); CHECK_EXPECTED(configure_params); - auto network_group_list = device.value()->configure(hef.value(), configure_params.value()); + auto network_group_list = device->configure(hef.value(), configure_params.value()); CHECK_EXPECTED(network_group_list, "Failed configure device from hef"); - #if defined(__GNUC__) +#if defined(__GNUC__) // TODO: Support on windows (HRT-5919) - if (params.runtime_data.collect_runtime_data) { - DownloadActionListCommand::set_batch_to_measure(*device.value(), params.runtime_data.batch_to_measure); + if (use_batch_to_measure_opt(params)) { + auto status = DownloadActionListCommand::set_batch_to_measure(*device, params.runtime_data.batch_to_measure); + CHECK_SUCCESS_AS_EXPECTED(status); } - #endif +#endif - // TODO: SDK-14842, for now this function supports only one network_group - auto network_group = network_group_list.value()[0]; - auto inference_result = activate_network_group_and_run(*device.value().get(), network_group, params); + auto inference_result = activate_network_group_and_run(*device, network_group_list.value(), params); - #if defined(__GNUC__) +#if defined(__GNUC__) // TODO: Support on windows (HRT-5919) - if (params.runtime_data.collect_runtime_data) { - if ((0 == params.frames_count) && inference_result) { - const auto frames_count = inference_result->frames_count(); - if (frames_count && (frames_count.value() < params.runtime_data.batch_to_measure)) { - LOGGER__WARNING("Number of frames sent ({}) is smaller than --batch-to-measure ({}), " - "hence timestamps will not be updated in runtime data", frames_count.value(), - params.runtime_data.batch_to_measure); - } + if (use_batch_to_measure_opt(params) && (0 == params.frames_count) && inference_result) { + auto min_frames_count = get_min_inferred_frames_count(inference_result.value()); + CHECK_EXPECTED(min_frames_count); + if (min_frames_count.value() < params.runtime_data.batch_to_measure) { + LOGGER__WARNING("Number of frames sent ({}) is smaller than --batch-to-measure ({}), " + "hence timestamps will not be updated in runtime data", min_frames_count.value() , + params.runtime_data.batch_to_measure); } + } - DownloadActionListCommand::execute(*device.value(), params.runtime_data.runtime_data_output_path, + if (params.runtime_data.collect_runtime_data) { + DownloadActionListCommand::execute(*device, params.runtime_data.runtime_data_output_path, network_group_list.value(), params.hef_path); } - #endif + +#endif CHECK_EXPECTED(inference_result); return inference_result; } -Expected run_command_hef_vdevice(const inference_runner_params ¶ms) +Expected run_command_hef_vdevice(const inference_runner_params ¶ms) { auto hef = Hef::create(params.hef_path.c_str()); CHECK_EXPECTED(hef, "Failed reading hef file {}", params.hef_path); + auto network_groups_infos = hef->get_network_groups_infos(); + CHECK_EXPECTED(network_groups_infos); + bool scheduler_is_used = (1 < network_groups_infos->size()) || params.vdevice_params.multi_process_service; + hailo_vdevice_params_t vdevice_params = {}; - vdevice_params.device_count = params.device_params.vdevice_params.device_count; + auto status = hailo_init_vdevice_params(&vdevice_params); + CHECK_SUCCESS_AS_EXPECTED(status); + if (params.vdevice_params.device_count != HAILO_DEFAULT_DEVICE_COUNT) { + vdevice_params.device_count = params.vdevice_params.device_count; + } + std::vector dev_ids; + if (!params.vdevice_params.device_params.device_ids.empty()) { + auto dev_ids_exp = get_device_ids(params.vdevice_params.device_params); + CHECK_EXPECTED(dev_ids_exp); + + auto dev_ids_struct_exp = HailoRTCommon::to_device_ids_vector(dev_ids_exp.value()); + CHECK_EXPECTED(dev_ids_struct_exp); + dev_ids = dev_ids_struct_exp.release(); + + vdevice_params.device_ids = dev_ids.data(); + vdevice_params.device_count = static_cast(dev_ids.size()); + } + vdevice_params.scheduling_algorithm = (scheduler_is_used) ? HAILO_SCHEDULING_ALGORITHM_ROUND_ROBIN : HAILO_SCHEDULING_ALGORITHM_NONE; + vdevice_params.group_id = params.vdevice_params.group_id.c_str(); + vdevice_params.multi_process_service = params.vdevice_params.multi_process_service; auto vdevice = VDevice::create(vdevice_params); CHECK_EXPECTED(vdevice, "Failed creating vdevice"); @@ -1082,13 +1228,14 @@ Expected run_command_hef_vdevice(const inference_runner auto network_group_list = vdevice.value()->configure(hef.value(), configure_params.value()); CHECK_EXPECTED(network_group_list, "Failed configure vdevice from hef"); - // TODO: SDK-14842, for now this function supports only one network_group - auto network_group = network_group_list.value()[0]; - auto activated_net_group = activate_network_group(*network_group); - CHECK_EXPECTED(activated_net_group, "Failed activate network_group"); + std::unique_ptr activated_network_group; + if (!scheduler_is_used) { + auto activated_net_group_exp = activate_network_group(*network_group_list.value()[0]); + CHECK_EXPECTED(activated_net_group_exp, "Failed activate network_group"); + activated_network_group = activated_net_group_exp.release(); + } - auto input_streams = network_group->get_input_streams(); - auto input_dataset = create_dataset(input_streams, params); + auto input_dataset = create_dataset(network_group_list.value(), params); CHECK_EXPECTED(input_dataset, "Failed creating input dataset"); hailo_power_measurement_types_t measurement_type = HAILO_POWER_MEASUREMENT_TYPES__MAX_ENUM; @@ -1101,41 +1248,75 @@ Expected run_command_hef_vdevice(const inference_runner should_measure_power = true; } - auto physical_devices = vdevice.value()->get_physical_devices(); - CHECK_EXPECTED(physical_devices); + std::vector> physical_devices; + if (!params.vdevice_params.multi_process_service) { + auto expected_physical_devices = vdevice.value()->get_physical_devices(); + CHECK_EXPECTED(expected_physical_devices); + physical_devices = expected_physical_devices.value(); + } - std::map> power_measurements; + std::map> power_measurements; if (should_measure_power) { - for (auto &device : physical_devices.value()) { + for (auto &device : physical_devices) { auto long_power_measurement_exp = PowerMeasurementSubcommand::start_power_measurement(device, HAILO_DVM_OPTIONS_AUTO, measurement_type, params.power_measurement.sampling_period, params.power_measurement.averaging_factor); CHECK_EXPECTED(long_power_measurement_exp, "Failed starting power measurement on device {}", device.get().get_dev_id()); - auto long_power_measurement_p = make_unique_nothrow(long_power_measurement_exp.release()); + auto long_power_measurement_p = make_shared_nothrow(long_power_measurement_exp.release()); CHECK_NOT_NULL_AS_EXPECTED(long_power_measurement_p, HAILO_OUT_OF_HOST_MEMORY); power_measurements.emplace(device.get().get_dev_id(), std::move(long_power_measurement_p)); } } - std::map> temp_measurements; + std::map> temp_measurements; if (params.measure_temp) { - for (auto &device : physical_devices.value()) { - auto temp_measure = make_unique_nothrow(device); + for (auto &device : physical_devices) { + auto temp_measure = make_shared_nothrow(device); CHECK_NOT_NULL_AS_EXPECTED(temp_measure, HAILO_OUT_OF_HOST_MEMORY); - auto status = temp_measure->start_measurement(); + status = temp_measure->start_measurement(); CHECK_SUCCESS_AS_EXPECTED(status, "Failed starting temperature measurement on device {}", device.get().get_dev_id()); temp_measurements.emplace(device.get().get_dev_id(), std::move(temp_measure)); } } - auto infer_result = run_inference(*network_group, input_dataset.value(), params); +#if defined(__GNUC__) + for (auto &device : physical_devices) { + // TODO: Support on windows (HRT-5919) + if (use_batch_to_measure_opt(params)) { + status = DownloadActionListCommand::set_batch_to_measure(device.get(), params.runtime_data.batch_to_measure); + CHECK_SUCCESS_AS_EXPECTED(status); + } + } +#endif + + auto infer_result = run_inference(network_group_list.value(), input_dataset.value(), params); + +#if defined(__GNUC__) + for (auto &device : physical_devices) { + // TODO: Support on windows (HRT-5919) + if (use_batch_to_measure_opt(params) && (0 == params.frames_count) && infer_result) { + auto min_frames_count = get_min_inferred_frames_count(infer_result.value()); + CHECK_EXPECTED(min_frames_count); + if (min_frames_count.value() < params.runtime_data.batch_to_measure) { + LOGGER__WARNING("Number of frames sent ({}) is smaller than --batch-to-measure ({}), " + "hence timestamps will not be updated in runtime data", min_frames_count.value() , + params.runtime_data.batch_to_measure); + } + } + + if (params.runtime_data.collect_runtime_data) { + DownloadActionListCommand::execute(device.get(), params.runtime_data.runtime_data_output_path, + network_group_list.value(), params.hef_path); + } + } +#endif + CHECK_EXPECTED(infer_result, "Error failed running inference"); - NetworkGroupInferResult inference_result(infer_result.release()); - inference_result.initialize_measurements(physical_devices.value()); + InferResult inference_result(infer_result.release()); + inference_result.initialize_measurements(physical_devices); if (should_measure_power) { - auto status = HAILO_SUCCESS; for (auto &power_measure_pair : power_measurements) { auto measurement_status = power_measure_pair.second->stop(); if (HAILO_SUCCESS != measurement_status) { @@ -1161,9 +1342,9 @@ Expected run_command_hef_vdevice(const inference_runner if (params.measure_temp) { for(const auto &temp_measure_pair : temp_measurements) { temp_measure_pair.second->stop_measurement(); - auto temp_measure_p = make_unique_nothrow(temp_measure_pair.second->get_data()); + auto temp_measure_p = make_shared_nothrow(temp_measure_pair.second->get_data()); CHECK_NOT_NULL_AS_EXPECTED(temp_measure_p, HAILO_OUT_OF_HOST_MEMORY); - auto status = inference_result.set_temp_measurement(temp_measure_pair.first, std::move(temp_measure_p)); + status = inference_result.set_temp_measurement(temp_measure_pair.first, std::move(temp_measure_p)); CHECK_SUCCESS_AS_EXPECTED(status); } } @@ -1171,9 +1352,29 @@ Expected run_command_hef_vdevice(const inference_runner return inference_result; } -Expected run_command_hef(const inference_runner_params ¶ms) +Expected use_vdevice(const hailo_vdevice_params ¶ms) { - if (params.device_params.vdevice_params.device_count > 1) { + if (params.device_count > 1) { + return true; + } + + if (params.device_params.device_ids.empty()) { + // By default, if no device id was given, we assume vdevice is allowed. + return true; + } + + auto device_type = Device::get_device_type(params.device_params.device_ids[0]); + CHECK_EXPECTED(device_type); + + return device_type.value() != Device::Type::ETH; +} + +Expected run_command_hef(const inference_runner_params ¶ms) +{ + auto use_vdevice_expected = use_vdevice(params.vdevice_params); + CHECK_EXPECTED(use_vdevice_expected); + + if (use_vdevice_expected.value()) { return run_command_hef_vdevice(params); } else { @@ -1196,8 +1397,11 @@ static hailo_status run_command_hefs_dir(const inference_runner_params ¶ms, contains_hef = true; curr_params.hef_path = full_path; std::cout << std::string(80, '*') << std::endl << "Inferring " << full_path << ":"<< std::endl; + auto hef = Hef::create(full_path); + CHECK_EXPECTED_AS_STATUS(hef); + auto network_groups_names = hef->get_network_groups_names(); auto infer_stats = run_command_hef(curr_params); - printer.print(full_path, infer_stats); + printer.print(network_groups_names , infer_stats); if (!infer_stats) { overall_status = infer_stats.status(); @@ -1228,8 +1432,10 @@ hailo_status run_command(const inference_runner_params ¶ms) return run_command_hefs_dir(params, printer.value()); } else { auto infer_stats = run_command_hef(params); - // TODO: pass here network name without .hef - printer->print(params.hef_path, infer_stats); + auto hef = Hef::create(params.hef_path.c_str()); + CHECK_EXPECTED_AS_STATUS(hef); + auto network_groups_names = hef->get_network_groups_names(); + printer->print(network_groups_names, infer_stats); return infer_stats.status(); } } diff --git a/hailort/hailortcli/run_command.hpp b/hailort/hailortcli/run_command.hpp index 85f45d9..806ba05 100644 --- a/hailort/hailortcli/run_command.hpp +++ b/hailort/hailortcli/run_command.hpp @@ -44,6 +44,9 @@ struct pipeline_stats_measurement_params { std::string pipeline_stats_output_path; }; +static constexpr uint16_t RUNTIME_DATA_IGNORE_BATCH_TO_MEASURE_OPT = std::numeric_limits::max(); +static constexpr uint16_t RUNTIME_DATA_DEFAULT_BATCH_TO_MEASURE_OPT = 2; + struct runtime_data_params { bool collect_runtime_data; std::string runtime_data_output_path; @@ -51,7 +54,7 @@ struct runtime_data_params { }; struct inference_runner_params { - hailo_device_params device_params; + hailo_vdevice_params vdevice_params; std::string hef_path; uint32_t frames_count; uint32_t time_to_run; @@ -75,7 +78,7 @@ struct inference_runner_params { bool should_measure_pipeline_stats(const inference_runner_params& params); CLI::App* create_run_command(CLI::App& parent, inference_runner_params& params); hailo_status run_command(const inference_runner_params ¶ms); -Expected run_command_hef(const inference_runner_params ¶ms); +Expected run_command_hef(const inference_runner_params ¶ms); std::string format_type_to_string(hailo_format_type_t format_type); diff --git a/hailort/hailortcli/scan_command.cpp b/hailort/hailortcli/scan_command.cpp index 2d9e4e2..aedee0c 100644 --- a/hailort/hailortcli/scan_command.cpp +++ b/hailort/hailortcli/scan_command.cpp @@ -14,17 +14,13 @@ ScanSubcommand::ScanSubcommand(CLI::App &parent_app) : - Command(parent_app.add_subcommand("scan", "Shows all available devices")), - m_device_type(Device::Type::PCIE) + Command(parent_app.add_subcommand("scan", "Shows all available devices")) { - const HailoCheckedTransformer device_type_transformer({ - { "pcie", Device::Type::PCIE }, - { "eth", Device::Type::ETH }, - }); - auto *device_type_option = m_app->add_option("-d,--device-type,--target", m_device_type, - "Device type to use.") - ->transform(device_type_transformer) - ->default_val("pcie"); + auto *device_type_option = m_app->add_option("-d,--device-type,--target", "ignored."); + std::vector actions{ + std::make_shared(device_type_option), + }; + hailo_deprecate_options(m_app, actions, false); // Ethernet options auto *eth_options_group = m_app->add_option_group("Ethernet Device Options"); @@ -33,63 +29,37 @@ ScanSubcommand::ScanSubcommand(CLI::App &parent_app) : ->default_val("") ->check(CLI::ValidIPV4); - auto *interface_name_option = eth_options_group->add_option("--interface", m_interface_name, "Interface name to scan") + eth_options_group->add_option("--interface", m_interface_name, "Interface name to scan") ->default_val("") ->excludes(interface_ip_option); - - m_app->parse_complete_callback([this, device_type_option, interface_ip_option, interface_name_option]() { - bool eth_options_given = !interface_ip_option->empty() || !interface_name_option->empty(); - - // The user didn't put target, we can figure it ourself - if (device_type_option->empty()) { - if (eth_options_given) { - // User gave IP, target is eth - m_device_type = Device::Type::ETH; - } - else { - // Default is pcie - m_device_type = Device::Type::PCIE; - } - } - - if (!eth_options_given && (m_device_type == Device::Type::ETH)) { - throw CLI::ParseError("Ethernet options not set", CLI::ExitCodes::InvalidError); - } - - if (eth_options_given && (m_device_type != Device::Type::ETH)) { - throw CLI::ParseError("Ethernet options set on non eth device", CLI::ExitCodes::InvalidError); - } - }); } hailo_status ScanSubcommand::execute() { - switch (m_device_type) - { - case Device::Type::PCIE: - return scan_pcie(); - case Device::Type::ETH: - return scan_ethernet(m_interface_ip_addr, m_interface_name).status(); - default: - std::cerr << "Unkown target" << std::endl; - return HAILO_INVALID_ARGUMENT; + const bool request_for_eth_scan = !m_interface_name.empty() || !m_interface_ip_addr.empty(); + + if (!request_for_eth_scan) { + return scan(); + } + else { + auto res = scan_ethernet(m_interface_ip_addr, m_interface_name); + CHECK_EXPECTED_AS_STATUS(res); + return HAILO_SUCCESS; } } -hailo_status ScanSubcommand::scan_pcie() +hailo_status ScanSubcommand::scan() { - auto scan_result = Device::scan_pcie(); - CHECK_SUCCESS(scan_result.status(), "Error scan failed status = {}", scan_result.status()); + auto device_ids = Device::scan(); + CHECK_EXPECTED_AS_STATUS(device_ids); - if (scan_result->size() == 0) { - std::cout << "Hailo PCIe devices not found" << std::endl; + if (device_ids->size() == 0) { + std::cout << "Hailo devices not found" << std::endl; } else { - std::cout << "Hailo PCIe Devices:" << std::endl; - for (const auto& device_info : scan_result.value()) { - auto device_info_str = Device::pcie_device_info_to_string(device_info); - CHECK_EXPECTED_AS_STATUS(device_info_str); - std::cout << "[-] Device BDF: " << device_info_str.value() << std::endl; + std::cout << "Hailo Devices:" << std::endl; + for (const auto& device_id : device_ids.value()) { + std::cout << "[-] Device: " << device_id << std::endl; } } diff --git a/hailort/hailortcli/scan_command.hpp b/hailort/hailortcli/scan_command.hpp index be0c548..a50c124 100644 --- a/hailort/hailortcli/scan_command.hpp +++ b/hailort/hailortcli/scan_command.hpp @@ -26,9 +26,8 @@ public: const std::string &interface_name); private: - hailo_status scan_pcie(); - - Device::Type m_device_type; + // Scans any system device + hailo_status scan(); // Ethernet scan options std::string m_interface_ip_addr; diff --git a/hailort/libhailort/CMakeLists.txt b/hailort/libhailort/CMakeLists.txt index 5a51f00..2ef6154 100644 --- a/hailort/libhailort/CMakeLists.txt +++ b/hailort/libhailort/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.0.0) # set(CMAKE_C_CLANG_TIDY "clang-tidy;-checks=*") set(HAILORT_MAJOR_VERSION 4) -set(HAILORT_MINOR_VERSION 8) -set(HAILORT_REVISION_VERSION 1) +set(HAILORT_MINOR_VERSION 10) +set(HAILORT_REVISION_VERSION 0) # Add the cmake folder so the modules there are found set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) @@ -12,7 +12,7 @@ set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) protobuf_generate_cpp(PROTO_HEF_SRC PROTO_HEF_HEADR hef.proto) add_library(hef_proto ${PROTO_HEF_SRC} ${PROTO_HEF_HEADR}) -target_link_libraries(hef_proto libprotobuf) +target_link_libraries(hef_proto libprotobuf-lite) set_target_properties(hef_proto PROPERTIES CXX_STANDARD 14 GENERATED TRUE POSITION_INDEPENDENT_CODE ON) if(CMAKE_HOST_WIN32) # https://github.com/protocolbuffers/protobuf/tree/master/cmake#notes-on-compiler-warnings @@ -25,11 +25,26 @@ target_include_directories(hef_proto $ ) +protobuf_generate_cpp(PROTO_SCHEDULER_MON_SRC PROTO_SCHEDULER_MON_HEADR scheduler_mon.proto) +add_library(scheduler_mon_proto ${PROTO_SCHEDULER_MON_SRC} ${PROTO_SCHEDULER_MON_HEADR}) +target_link_libraries(scheduler_mon_proto libprotobuf-lite) +set_target_properties(scheduler_mon_proto PROPERTIES CXX_STANDARD 14 GENERATED TRUE POSITION_INDEPENDENT_CODE ON) +if(CMAKE_HOST_WIN32) + target_compile_options(scheduler_mon_proto PRIVATE /wd4244) +endif() +get_filename_component(PROTO_SCHEDULER_MON_HEADER_DIRECTORY ${PROTO_SCHEDULER_MON_HEADR} DIRECTORY) +target_include_directories(scheduler_mon_proto + PUBLIC + $ + $ +) + # Add readerwriterqueue as a header-only library add_library(readerwriterqueue INTERFACE) target_include_directories(readerwriterqueue INTERFACE ${HAILO_EXTERNAL_DIR}/readerwriterqueue) add_subdirectory(src) + if(HAILO_BUILD_EXAMPLES) add_subdirectory(examples) endif() diff --git a/hailort/libhailort/bindings/gstreamer/CMakeLists.txt b/hailort/libhailort/bindings/gstreamer/CMakeLists.txt index 87a0bc8..e5f782b 100644 --- a/hailort/libhailort/bindings/gstreamer/CMakeLists.txt +++ b/hailort/libhailort/bindings/gstreamer/CMakeLists.txt @@ -8,7 +8,7 @@ if(NOT CMAKE_HOST_UNIX) message(FATAL_ERROR "Only unix hosts are supported, stopping build") endif() -find_package(HailoRT 4.8.1 EXACT REQUIRED) +find_package(HailoRT 4.10.0 EXACT REQUIRED) # GST_PLUGIN_DEFINE needs PACKAGE to be defined set(GST_HAILO_PACKAGE_NAME "hailo") diff --git a/hailort/libhailort/bindings/gstreamer/gst-hailo/common.hpp b/hailort/libhailort/bindings/gstreamer/gst-hailo/common.hpp index 8e8759e..ff98230 100644 --- a/hailort/libhailort/bindings/gstreamer/gst-hailo/common.hpp +++ b/hailort/libhailort/bindings/gstreamer/gst-hailo/common.hpp @@ -48,7 +48,7 @@ using namespace hailort; #define DEFAULT_VDEVICE_KEY (0) #define MIN_VALID_VDEVICE_KEY (1) -#define HAILO_SUPPORTED_FORMATS "{ RGB, YUY2 }" +#define HAILO_SUPPORTED_FORMATS "{ RGB, RGBA, YUY2 }" #define HAILO_VIDEO_CAPS GST_VIDEO_CAPS_MAKE(HAILO_SUPPORTED_FORMATS) #define HAILO_DEFAULT_SCHEDULER_TIMEOUT_MS (0) @@ -70,6 +70,14 @@ using namespace hailort; } \ } while(0) +#define GST_CHECK_SUCCESS_AS_EXPECTED(status, element, domain, ...) \ + do { \ + if (HAILO_SUCCESS != (status)) { \ + GST_ELEMENT_ERROR((element), domain, FAILED, (__VA_ARGS__), (NULL)); \ + return make_unexpected(status); \ + } \ + } while(0) + #define GST_CHECK_EXPECTED(obj, element, domain, ...) \ do { \ if (!(obj)) { \ diff --git a/hailort/libhailort/bindings/gstreamer/gst-hailo/gsthailonet.cpp b/hailort/libhailort/bindings/gstreamer/gst-hailo/gsthailonet.cpp index b39e4e2..171a351 100644 --- a/hailort/libhailort/bindings/gstreamer/gst-hailo/gsthailonet.cpp +++ b/hailort/libhailort/bindings/gstreamer/gst-hailo/gsthailonet.cpp @@ -570,7 +570,7 @@ hailo_status HailoNetImpl::configure_network_group() GST_CHECK_EXPECTED_AS_STATUS(network_group_name, m_element, RESOURCE, "Could not get network group name from name %s, status = %d", m_props.m_network_name.get(), network_group_name.status()); - hailo_status status = m_net_group_handle->configure_network_group(network_group_name->c_str(), m_props.m_batch_size.get()); + hailo_status status = m_net_group_handle->configure_network_group(network_group_name->c_str(), m_props.m_scheduling_algorithm.get(), m_props.m_batch_size.get()); if (HAILO_SUCCESS != status) { return status; } @@ -585,7 +585,7 @@ hailo_status HailoNetImpl::configure_network_group() GST_CHECK_SUCCESS(status, m_element, RESOURCE, "Setting scheduler threshold failed, status = %d", status); } - auto vstreams = m_net_group_handle->create_vstreams(m_props.m_network_name.get(), m_output_formats); + auto vstreams = m_net_group_handle->create_vstreams(m_props.m_network_name.get(), m_props.m_scheduling_algorithm.get(), m_output_formats); GST_CHECK_EXPECTED_AS_STATUS(vstreams, m_element, RESOURCE, "Creating vstreams failed, status = %d", status); GST_HAILOSEND(m_hailosend)->impl->set_input_vstreams(std::move(vstreams->first)); diff --git a/hailort/libhailort/bindings/gstreamer/gst-hailo/gsthailosend.cpp b/hailort/libhailort/bindings/gstreamer/gst-hailo/gsthailosend.cpp index 3330b21..cebeee1 100644 --- a/hailort/libhailort/bindings/gstreamer/gst-hailo/gsthailosend.cpp +++ b/hailort/libhailort/bindings/gstreamer/gst-hailo/gsthailosend.cpp @@ -29,7 +29,10 @@ GST_DEBUG_CATEGORY_STATIC(gst_hailosend_debug_category); #define GST_CAT_DEFAULT gst_hailosend_debug_category #define RGB_FEATURES_SIZE (3) +#define RGBA_FEATURES_SIZE (4) #define YUY2_FEATURES_SIZE (2) +#define NV12_FEATURES_SIZE (3) +#define NV21_FEATURES_SIZE (3) static void gst_hailosend_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void gst_hailosend_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec); @@ -61,7 +64,7 @@ static void gst_hailosend_class_init(GstHailoSendClass *klass) gst_pad_template_new("sink", GST_PAD_SINK, GST_PAD_ALWAYS, gst_caps_from_string(HAILO_VIDEO_CAPS))); gst_element_class_set_static_metadata(GST_ELEMENT_CLASS(klass), - "hailosend element", "Hailo/Filter/Video", "Send RGB/YUY2 video to HailoRT", PLUGIN_AUTHOR); + "hailosend element", "Hailo/Filter/Video", "Send RGB/RGBA/YUY2 video to HailoRT", PLUGIN_AUTHOR); element_class->change_state = GST_DEBUG_FUNCPTR(gst_hailosend_change_state); @@ -173,7 +176,7 @@ hailo_status HailoSendImpl::write_to_vstreams(void *buf, size_t size) return HAILO_SUCCESS; } -GstCaps *HailoSendImpl::get_caps(GstBaseTransform */*trans*/, GstPadDirection /*direction*/, GstCaps */*caps*/, GstCaps */*filter*/) +GstCaps *HailoSendImpl::get_caps(GstBaseTransform */*trans*/, GstPadDirection /*direction*/, GstCaps *caps, GstCaps */*filter*/) { GST_DEBUG_OBJECT(m_element, "transform_caps"); @@ -190,6 +193,11 @@ GstCaps *HailoSendImpl::get_caps(GstBaseTransform */*trans*/, GstPadDirection /* const gchar *format = nullptr; switch (m_input_vstream_infos[0].format.order) { case HAILO_FORMAT_ORDER_NHWC: + if (m_input_vstream_infos[0].shape.features == RGBA_FEATURES_SIZE) { + format = "RGBA"; + break; + } + /* Fallthrough */ case HAILO_FORMAT_ORDER_NHCW: case HAILO_FORMAT_ORDER_FCR: case HAILO_FORMAT_ORDER_F8CR: @@ -204,6 +212,18 @@ GstCaps *HailoSendImpl::get_caps(GstBaseTransform */*trans*/, GstPadDirection /* "Features of input vstream %s is not %d for YUY2 format! (features=%d)", m_input_vstream_infos[0].name, YUY2_FEATURES_SIZE, m_input_vstream_infos[0].shape.features); break; + case HAILO_FORMAT_ORDER_NV12: + format = "NV12"; + GST_CHECK(NV12_FEATURES_SIZE == m_input_vstream_infos[0].shape.features, NULL, m_element, STREAM, + "Features of input vstream %s is not %d for NV12 format! (features=%d)", m_input_vstream_infos[0].name, NV12_FEATURES_SIZE, + m_input_vstream_infos[0].shape.features); + break; + case HAILO_FORMAT_ORDER_NV21: + format = "NV21"; + GST_CHECK(NV21_FEATURES_SIZE == m_input_vstream_infos[0].shape.features, NULL, m_element, STREAM, + "Features of input vstream %s is not %d for NV21 format! (features=%d)", m_input_vstream_infos[0].name, NV21_FEATURES_SIZE, + m_input_vstream_infos[0].shape.features); + break; default: GST_ELEMENT_ERROR(m_element, RESOURCE, FAILED, ("Input VStream %s has an unsupported format order! order = %d", m_input_vstream_infos[0].name, m_input_vstream_infos[0].format.order), (NULL)); @@ -211,11 +231,12 @@ GstCaps *HailoSendImpl::get_caps(GstBaseTransform */*trans*/, GstPadDirection /* } /* filter against set allowed caps on the pad */ - return gst_caps_new_simple("video/x-raw", + GstCaps *new_caps = gst_caps_new_simple("video/x-raw", "format", G_TYPE_STRING, format, "width", G_TYPE_INT, m_input_vstream_infos[0].shape.width, "height", G_TYPE_INT, m_input_vstream_infos[0].shape.height, NULL); + return gst_caps_intersect(caps, new_caps); } void HailoSendImpl::set_input_vstream_infos(std::vector &&input_vstream_infos) diff --git a/hailort/libhailort/bindings/gstreamer/gst-hailo/network_group_handle.cpp b/hailort/libhailort/bindings/gstreamer/gst-hailo/network_group_handle.cpp index 3453ebe..9808c59 100644 --- a/hailort/libhailort/bindings/gstreamer/gst-hailo/network_group_handle.cpp +++ b/hailort/libhailort/bindings/gstreamer/gst-hailo/network_group_handle.cpp @@ -22,14 +22,96 @@ #include #include -VDeviceManager NetworkGroupHandle::m_vdevice_manager; +std::unordered_set> NetworkGroupHandle::m_vdevices; NetworkGroupConfigManager NetworkGroupHandle::m_net_group_config_manager; NetworkGroupActivationManager NetworkGroupHandle::m_net_group_activation_manager; + +Expected> create_shared_vdevice(const void *element, const std::string &device_id, + uint32_t vdevice_key, hailo_scheduling_algorithm_t scheduling_algorithm) +{ + // If passing device_id, than device_count must be 1 + const auto device_count = 1; + + auto device_id_expected = HailoRTCommon::to_device_id(device_id); + GST_CHECK_EXPECTED(device_id_expected, element, RESOURCE, "Failed parsing device id, status = %d", device_id_expected.status()); + + hailo_vdevice_params_t params = {}; + auto status = hailo_init_vdevice_params(¶ms); + GST_CHECK_SUCCESS_AS_EXPECTED(status, element, RESOURCE, "Failed hailo_init_vdevice_params, status = %d", status); + + params.device_count = device_count; + params.device_ids = &(device_id_expected.value()); + params.scheduling_algorithm = scheduling_algorithm; + if (vdevice_key == DEFAULT_VDEVICE_KEY) { + params.group_id = HAILO_UNIQUE_VDEVICE_GROUP_ID; + } else { + auto key = std::to_string(vdevice_key); + params.group_id = key.c_str(); + } + auto vdevice = VDevice::create(params); + GST_CHECK_EXPECTED(vdevice, element, RESOURCE, "Failed creating vdevice, status = %d", vdevice.status()); + std::shared_ptr vdevice_ptr = std::move(vdevice.release()); + return vdevice_ptr; +} + +Expected> create_shared_vdevice(const void *element, uint16_t device_count, uint32_t vdevice_key, + hailo_scheduling_algorithm_t scheduling_algorithm) +{ + auto device_id = std::to_string(vdevice_key); + hailo_vdevice_params_t params = {}; + auto status = hailo_init_vdevice_params(¶ms); + GST_CHECK_SUCCESS_AS_EXPECTED(status, element, RESOURCE, "Failed hailo_init_vdevice_params, status = %d", status); + params.device_count = device_count; + params.scheduling_algorithm = scheduling_algorithm; + params.group_id = device_id.c_str(); + auto vdevice = VDevice::create(params); + GST_CHECK_EXPECTED(vdevice, element, RESOURCE, "Failed creating vdevice, status = %d", vdevice.status()); + std::shared_ptr vdevice_ptr = std::move(vdevice.release()); + return vdevice_ptr; +} + +Expected> create_unique_vdevice(const void *element, uint16_t device_count, + hailo_scheduling_algorithm_t scheduling_algorithm) +{ + hailo_vdevice_params_t params = {}; + auto status = hailo_init_vdevice_params(¶ms); + GST_CHECK_SUCCESS_AS_EXPECTED(status, element, RESOURCE, "Failed hailo_init_vdevice_params, status = %d", status); + + params.device_count = device_count; + params.scheduling_algorithm = scheduling_algorithm; + params.group_id = HAILO_UNIQUE_VDEVICE_GROUP_ID; + auto vdevice = VDevice::create(params); + GST_CHECK_EXPECTED(vdevice, element, RESOURCE, "Failed creating vdevice, status = %d", vdevice.status()); + std::shared_ptr vdevice_ptr = std::move(vdevice.release()); + return vdevice_ptr; +} + +Expected> NetworkGroupHandle::create_vdevice(const void *element, const std::string &device_id, uint16_t device_count, + uint32_t vdevice_key, hailo_scheduling_algorithm_t scheduling_algorithm) +{ + if (!device_id.empty()) { + auto result = create_shared_vdevice(element, device_id, vdevice_key, scheduling_algorithm); + GST_CHECK_EXPECTED(result, element, RESOURCE, "Failed creating vdevice, status = %d", result.status()); + m_vdevices.insert(result.value()); + return result; + } + if (DEFAULT_VDEVICE_KEY != vdevice_key) { + auto result = create_shared_vdevice(element, device_count, vdevice_key, scheduling_algorithm); + GST_CHECK_EXPECTED(result, element, RESOURCE, "Failed creating vdevice, status = %d", result.status()); + m_vdevices.insert(result.value()); + return result; + } + auto result = create_unique_vdevice(element, device_count, scheduling_algorithm); + GST_CHECK_EXPECTED(result, element, RESOURCE, "Failed creating vdevice, status = %d", result.status()); + m_vdevices.insert(result.value()); + return result; +} + Expected> NetworkGroupHandle::create_vdevice(const std::string &device_id, uint16_t device_count, uint32_t vdevice_key, hailo_scheduling_algorithm_t scheduling_algorithm) { - auto expected_device = m_vdevice_manager.create_vdevice(m_element, device_id, device_count, vdevice_key, scheduling_algorithm); + auto expected_device = create_vdevice(m_element, device_id, device_count, vdevice_key, scheduling_algorithm); GST_CHECK_EXPECTED(expected_device, m_element, RESOURCE, "Failed creating vdevice, status = %d", expected_device.status()); return expected_device; } @@ -65,12 +147,13 @@ hailo_status NetworkGroupHandle::set_hef(const char *device_id, uint16_t device_ return HAILO_SUCCESS; } -hailo_status NetworkGroupHandle::configure_network_group(const char *net_group_name, uint16_t batch_size) +hailo_status NetworkGroupHandle::configure_network_group(const char *net_group_name, hailo_scheduling_algorithm_t scheduling_algorithm, uint16_t batch_size) { auto net_groups_params_map = get_configure_params(*m_hef, net_group_name, batch_size); GST_CHECK_EXPECTED_AS_STATUS(net_groups_params_map, m_element, RESOURCE, "Failed getting configure params, status = %d", net_groups_params_map.status()); - auto expected_cng = m_net_group_config_manager.configure_network_group(m_element, m_shared_device_id, net_group_name, batch_size, m_vdevice, m_hef, net_groups_params_map.value()); + auto expected_cng = m_net_group_config_manager.configure_network_group(m_element, m_shared_device_id, scheduling_algorithm, + net_group_name, batch_size, m_vdevice, m_hef, net_groups_params_map.value()); GST_CHECK_EXPECTED_AS_STATUS(expected_cng, m_element, RESOURCE, "Failed configuring network, status = %d", expected_cng.status()); m_cng = expected_cng.release(); @@ -91,14 +174,16 @@ hailo_status NetworkGroupHandle::set_scheduler_threshold(const char *network_nam } Expected, std::vector>> NetworkGroupHandle::create_vstreams(const char *network_name, - const std::vector &output_formats) + hailo_scheduling_algorithm_t scheduling_algorithm, const std::vector &output_formats) { GST_CHECK(nullptr != network_name, make_unexpected(HAILO_INVALID_ARGUMENT), m_element, RESOURCE, "Got nullptr in network name!"); m_network_name = network_name; - hailo_status status = m_net_group_config_manager.add_network_to_shared_network_group(m_shared_device_id, m_network_name, m_element); - GST_CHECK(HAILO_SUCCESS == status, make_unexpected(status), m_element, RESOURCE, - "Inserting network name to configured networks has failed, status = %d", status); + if (scheduling_algorithm == HAILO_SCHEDULING_ALGORITHM_NONE) { + hailo_status status = m_net_group_config_manager.add_network_to_shared_network_group(m_shared_device_id, m_network_name, m_element); + GST_CHECK(HAILO_SUCCESS == status, make_unexpected(status), m_element, RESOURCE, + "Inserting network name to configured networks has failed, status = %d", status); + } auto input_params_map = m_cng->make_input_vstream_params(true, HAILO_FORMAT_TYPE_AUTO, HAILO_DEFAULT_VSTREAM_TIMEOUT_MS, HAILO_DEFAULT_VSTREAM_QUEUE_SIZE, m_network_name); @@ -151,7 +236,8 @@ Expected NetworkGroupHandle::get_configure_params(Hef &h hailo_status NetworkGroupHandle::activate_network_group() { - auto expected_ang = m_net_group_activation_manager.activate_network_group(m_element, m_shared_device_id, m_net_group_name.c_str(), m_batch_size, m_cng); + auto expected_ang = m_net_group_activation_manager.activate_network_group(m_element, m_shared_device_id, m_hef->hash(), + m_net_group_name.c_str(), m_batch_size, m_cng); GST_CHECK_EXPECTED_AS_STATUS(expected_ang, m_element, RESOURCE, "Failed activating network, status = %d", expected_ang.status()); m_ang = expected_ang.release(); return HAILO_SUCCESS; @@ -197,7 +283,8 @@ Expected NetworkGroupHandle::remove_network_group() // If use count is 2, it means the only references to the activated network group is in the manager and the one here, meaning that we can clear it // from the manager if (m_ang.use_count() == 2) { - hailo_status status = m_net_group_activation_manager.remove_activated_network(m_shared_device_id, m_net_group_name.c_str(), m_batch_size); + hailo_status status = m_net_group_activation_manager.remove_activated_network(m_shared_device_id, m_hef->hash(), + m_net_group_name.c_str(), m_batch_size); GST_CHECK(HAILO_SUCCESS == status, make_unexpected(status), m_element, RESOURCE, "Cound not find activated network group! status = %d", status); was_network_deactivated = true; @@ -209,129 +296,45 @@ Expected NetworkGroupHandle::remove_network_group() return was_network_deactivated; } - -Expected> VDeviceManager::create_vdevice(const void *element, const std::string &device_id, uint16_t device_count, - uint32_t vdevice_key, hailo_scheduling_algorithm_t scheduling_algorithm) -{ - std::unique_lock lock(m_mutex); - if (!device_id.empty()) { - return create_shared_vdevice(element, device_id, scheduling_algorithm); - } - if (DEFAULT_VDEVICE_KEY != vdevice_key) { - return create_shared_vdevice(element, device_count, vdevice_key, scheduling_algorithm); - } - return create_unique_vdevice(element, device_count, scheduling_algorithm); -} - -Expected> VDeviceManager::create_shared_vdevice(const void *element, const std::string &device_id, - hailo_scheduling_algorithm_t scheduling_algorithm) -{ - // If passing device_id, than device_count must be 1 - const auto device_count = 1; - - // If vdevice already exist, use it - auto found_vdevice = get_vdevice(device_id, scheduling_algorithm); - if (found_vdevice.status() != HAILO_NOT_FOUND) { - GST_CHECK_EXPECTED(found_vdevice, element, RESOURCE, "Failed using shared vdevice, status = %d", found_vdevice.status()); - return found_vdevice.release(); - } - - auto device_info_expected = Device::parse_pcie_device_info(device_id); - GST_CHECK_EXPECTED(device_info_expected, element, RESOURCE, "Failed parsing pcie device info, status = %d", device_info_expected.status()); - - hailo_vdevice_params_t params = {}; - params.device_count = device_count; - params.device_infos = &(device_info_expected.value()); - params.scheduling_algorithm = scheduling_algorithm; - auto vdevice = VDevice::create(params); - GST_CHECK_EXPECTED(vdevice, element, RESOURCE, "Failed creating vdevice, status = %d", vdevice.status()); - std::shared_ptr vdevice_ptr = std::move(vdevice.release()); - - m_shared_vdevices[device_id] = vdevice_ptr; - m_shared_vdevices_scheduling_algorithm[device_id] = scheduling_algorithm; - return vdevice_ptr; -} - -Expected> VDeviceManager::create_shared_vdevice(const void *element, uint16_t device_count, uint32_t vdevice_key, - hailo_scheduling_algorithm_t scheduling_algorithm) -{ - auto device_id = std::to_string(device_count) + "-" + std::to_string(vdevice_key); - - // If vdevice already exist, use it - auto found_vdevice = get_vdevice(device_id, scheduling_algorithm); - if (found_vdevice.status() != HAILO_NOT_FOUND) { - GST_CHECK_EXPECTED(found_vdevice, element, RESOURCE, "Failed using shared vdevice, status = %d", found_vdevice.status()); - return found_vdevice.release(); - } - - hailo_vdevice_params_t params = {}; - params.device_count = device_count; - params.device_infos = nullptr; - params.scheduling_algorithm = scheduling_algorithm; - auto vdevice = VDevice::create(params); - GST_CHECK_EXPECTED(vdevice, element, RESOURCE, "Failed creating vdevice, status = %d", vdevice.status()); - std::shared_ptr vdevice_ptr = std::move(vdevice.release()); - - m_shared_vdevices[device_id] = vdevice_ptr; - m_shared_vdevices_scheduling_algorithm[device_id] = scheduling_algorithm; - return vdevice_ptr; -} - -Expected> VDeviceManager::create_unique_vdevice(const void *element, uint16_t device_count, - hailo_scheduling_algorithm_t scheduling_algorithm) -{ - hailo_vdevice_params_t params = {}; - params.device_count = device_count; - params.device_infos = nullptr; - params.scheduling_algorithm = scheduling_algorithm; - auto vdevice = VDevice::create(params); - GST_CHECK_EXPECTED(vdevice, element, RESOURCE, "Failed creating vdevice, status = %d", vdevice.status()); - - // Unique vdevices are not saved in VDeviceManager, only in their hailonet - std::shared_ptr vdevice_ptr = std::move(vdevice.release()); - m_unique_vdevices.push_back(vdevice_ptr); - return vdevice_ptr; -} - -Expected> VDeviceManager::get_vdevice(const std::string &device_id, - hailo_scheduling_algorithm_t scheduling_algorithm) -{ - auto found = m_shared_vdevices.find(device_id); - if (found == m_shared_vdevices.end()) { - return make_unexpected(HAILO_NOT_FOUND); - } - - // shared_vdevice is found, verify the requested scheduling_algorithm - assert(m_shared_vdevices_scheduling_algorithm.end() != m_shared_vdevices_scheduling_algorithm.find(device_id)); - if (scheduling_algorithm != m_shared_vdevices_scheduling_algorithm[device_id]) { - auto status = HAILO_INVALID_OPERATION; - g_warning("Shared vdevice with the same credentials is already exists (%s) but with a different scheduling-algorithm (requested: %d, exists: %d), status = %d", - device_id.c_str(), scheduling_algorithm, m_shared_vdevices_scheduling_algorithm[device_id], status); - return make_unexpected(HAILO_INVALID_OPERATION); - } - - auto vdevice_cpy = found->second; - return vdevice_cpy; -} - Expected> NetworkGroupConfigManager::configure_network_group(const void *element, const std::string &device_id, - const char *network_group_name, uint16_t batch_size, std::shared_ptr &vdevice, std::shared_ptr hef, + hailo_scheduling_algorithm_t scheduling_algorithm, const char *network_group_name, uint16_t batch_size, std::shared_ptr &vdevice, std::shared_ptr hef, NetworkGroupsParamsMap &net_groups_params_map) { std::unique_lock lock(m_mutex); - std::shared_ptr found_cng = get_configured_network_group(device_id, network_group_name, batch_size); + std::shared_ptr found_cng = get_configured_network_group(device_id, hef->hash(), network_group_name, batch_size); if (nullptr != found_cng) { - return found_cng; + // If cng was already configured + auto infos = found_cng->get_network_infos(); + GST_CHECK_EXPECTED(infos, element, RESOURCE, "Failed getting network infos"); + if ((infos.release().size() > 1) || (scheduling_algorithm == HAILO_SCHEDULING_ALGORITHM_NONE)) { + // But hailonet is not running all networks in the cng (or if not using scheduler) - + // Do not use multiplexer! + return found_cng; + } } auto network_group_list = vdevice->configure(*hef, net_groups_params_map); GST_CHECK_EXPECTED(network_group_list, element, RESOURCE, "Failed configure device from hef, status = %d", network_group_list.status()); - m_configured_net_groups[get_configure_string(device_id, network_group_name, batch_size)] = network_group_list->at(0); + std::shared_ptr result = nullptr; + for (auto &network_group : network_group_list.value()) { + m_configured_net_groups[get_configure_string(device_id, hef->hash(), network_group->name().c_str(), batch_size)] = network_group; + if (std::string(network_group_name) == network_group->name()) { + result = network_group; + break; + } + } - return std::move(network_group_list->at(0)); + if (result) { + return result; + } else if (1 != network_group_list->size()) { + g_error("Configuring HEF with multiple network_groups without providing valid network_group name. passed name = %s, status = %d", network_group_name, HAILO_NOT_FOUND); + return make_unexpected(HAILO_NOT_FOUND); + } else { + return std::move(network_group_list->at(0)); + } } hailo_status NetworkGroupConfigManager::add_network_to_shared_network_group(const std::string &shared_device_id, const std::string &network_name, @@ -356,9 +359,9 @@ hailo_status NetworkGroupConfigManager::add_network_to_shared_network_group(cons } std::shared_ptr NetworkGroupConfigManager::get_configured_network_group(const std::string &device_id, - const char *network_group_name, uint16_t batch_size) + const std::string &hef_hash, const char *network_group_name, uint16_t batch_size) { - auto found = m_configured_net_groups.find(get_configure_string(device_id, network_group_name, batch_size)); + auto found = m_configured_net_groups.find(get_configure_string(device_id, hef_hash, network_group_name, batch_size)); if (found == m_configured_net_groups.end()) { return nullptr; } @@ -366,7 +369,8 @@ std::shared_ptr NetworkGroupConfigManager::get_configure return found->second; } -std::string NetworkGroupConfigManager::get_configure_string(const std::string &device_id, const char *network_group_name, uint16_t batch_size) +std::string NetworkGroupConfigManager::get_configure_string(const std::string &device_id, const std::string &hef_hash, + const char *network_group_name, uint16_t batch_size) { const char *EMPTY_FIELD = "NULL,"; std::ostringstream oss; @@ -377,6 +381,8 @@ std::string NetworkGroupConfigManager::get_configure_string(const std::string &d oss << device_id << ","; } + oss << hef_hash << ","; + if (nullptr == network_group_name) { oss << EMPTY_FIELD; } else { @@ -388,11 +394,11 @@ std::string NetworkGroupConfigManager::get_configure_string(const std::string &d } Expected> NetworkGroupActivationManager::activate_network_group(const void *element, const std::string &device_id, - const char *net_group_name, uint16_t batch_size, std::shared_ptr cng) + const std::string &hef_hash, const char *net_group_name, uint16_t batch_size, std::shared_ptr cng) { std::unique_lock lock(m_mutex); - std::shared_ptr found_ang = get_activated_network_group(device_id, net_group_name, batch_size); + std::shared_ptr found_ang = get_activated_network_group(device_id, hef_hash, net_group_name, batch_size); if (nullptr != found_ang) { return found_ang; } @@ -402,15 +408,15 @@ Expected> NetworkGroupActivationManager:: activated_network_group.status()); std::shared_ptr ang = std::move(activated_network_group.release()); - m_activated_net_groups[NetworkGroupConfigManager::get_configure_string(device_id, net_group_name, batch_size)] = ang; + m_activated_net_groups[NetworkGroupConfigManager::get_configure_string(device_id, hef_hash, net_group_name, batch_size)] = ang; return ang; } std::shared_ptr NetworkGroupActivationManager::get_activated_network_group(const std::string &device_id, - const char *net_group_name, uint16_t batch_size) + const std::string &hef_hef, const char *net_group_name, uint16_t batch_size) { - auto found = m_activated_net_groups.find(NetworkGroupConfigManager::get_configure_string(device_id, net_group_name, batch_size)); + auto found = m_activated_net_groups.find(NetworkGroupConfigManager::get_configure_string(device_id, hef_hef, net_group_name, batch_size)); if (found == m_activated_net_groups.end()) { return nullptr; } @@ -418,12 +424,12 @@ std::shared_ptr NetworkGroupActivationManager::get_activa return found->second; } -hailo_status NetworkGroupActivationManager::remove_activated_network(const std::string &device_id, const char *net_group_name, - uint16_t batch_size) +hailo_status NetworkGroupActivationManager::remove_activated_network(const std::string &device_id, const std::string &hef_hash, + const char *net_group_name, uint16_t batch_size) { std::unique_lock lock(m_mutex); - auto found = m_activated_net_groups.find(NetworkGroupConfigManager::get_configure_string(device_id, net_group_name, batch_size)); + auto found = m_activated_net_groups.find(NetworkGroupConfigManager::get_configure_string(device_id, hef_hash, net_group_name, batch_size)); if (found == m_activated_net_groups.end()) { return HAILO_NOT_FOUND; } diff --git a/hailort/libhailort/bindings/gstreamer/gst-hailo/network_group_handle.hpp b/hailort/libhailort/bindings/gstreamer/gst-hailo/network_group_handle.hpp index f402678..93f86b1 100644 --- a/hailort/libhailort/bindings/gstreamer/gst-hailo/network_group_handle.hpp +++ b/hailort/libhailort/bindings/gstreamer/gst-hailo/network_group_handle.hpp @@ -26,32 +26,7 @@ #include #include - - -class VDeviceManager final -{ -public: - VDeviceManager() : m_shared_vdevices(), m_shared_vdevices_scheduling_algorithm(), m_unique_vdevices() {} - - Expected> create_vdevice(const void *element, const std::string &device_id, uint16_t device_count, uint32_t vdevice_key, - hailo_scheduling_algorithm_t scheduling_algorithm); - -private: - Expected> create_shared_vdevice(const void *element, const std::string &device_id, - hailo_scheduling_algorithm_t scheduling_algorithm); - Expected> create_shared_vdevice(const void *element, uint16_t device_count, uint32_t vdevice_key, - hailo_scheduling_algorithm_t scheduling_algorithm); - Expected> create_unique_vdevice(const void *element, uint16_t device_count, - hailo_scheduling_algorithm_t scheduling_algorithm); - Expected> get_vdevice(const std::string &device_id, hailo_scheduling_algorithm_t scheduling_algorithm); - - /* Contains only the shared vdevices (either created by bdf, or with device-count && vdevice-key) - Keys are either "" or "-" */ - std::unordered_map> m_shared_vdevices; - std::unordered_map m_shared_vdevices_scheduling_algorithm; // Used to check that 2 shared vdevices gets the same scheduling-algorithm - std::vector> m_unique_vdevices; - std::mutex m_mutex; -}; +#include using device_id_t = std::string; using network_name_t = std::string; @@ -62,17 +37,18 @@ class NetworkGroupConfigManager final public: NetworkGroupConfigManager() : m_configured_net_groups() {} Expected> configure_network_group(const void *element, const std::string &device_id, - const char *network_group_name, uint16_t batch_size, std::shared_ptr &vdevice, std::shared_ptr hef, + hailo_scheduling_algorithm_t scheduling_algorithm, const char *network_group_name, uint16_t batch_size, std::shared_ptr &vdevice, std::shared_ptr hef, NetworkGroupsParamsMap &net_groups_params_map); hailo_status add_network_to_shared_network_group(const std::string &shared_device_id, const std::string &network_name, const GstElement *owner_element); private: - static std::string get_configure_string(const std::string &device_id, const char *network_group_name, uint16_t batch_size); + static std::string get_configure_string(const std::string &device_id, const std::string &hef_hash, + const char *network_group_name, uint16_t batch_size); friend class NetworkGroupActivationManager; - std::shared_ptr get_configured_network_group(const std::string &device_id, const char *net_group_name, - uint16_t batch_size); + std::shared_ptr get_configured_network_group(const std::string &device_id, const std::string &hef_hash, + const char *net_group_name, uint16_t batch_size); // TODO: change this map to store only the shared network_groups (used by multiple hailonets with the same vdevices) std::unordered_map> m_configured_net_groups; @@ -85,12 +61,12 @@ class NetworkGroupActivationManager final public: NetworkGroupActivationManager() : m_activated_net_groups() {} Expected> activate_network_group(const void *element, const std::string &device_id, - const char *net_group_name, uint16_t batch_size, std::shared_ptr cng); - hailo_status remove_activated_network(const std::string &device_id, const char *net_group_name, uint16_t batch_size); + const std::string &hef_hash, const char *net_group_name, uint16_t batch_size, std::shared_ptr cng); + hailo_status remove_activated_network(const std::string &device_id, const std::string &hef_hash, const char *net_group_name, uint16_t batch_size); private: - std::shared_ptr get_activated_network_group(const std::string &device_id, const char *net_group_name, - uint16_t batch_size); + std::shared_ptr get_activated_network_group(const std::string &device_id, const std::string &hef_hash, + const char *net_group_name, uint16_t batch_size); // TODO: change this map to store only the shared network_groups (used by multiple hailonets with the same vdevices) std::unordered_map> m_activated_net_groups; @@ -105,9 +81,9 @@ public: hailo_status set_hef(const char *device_id, uint16_t device_count, uint32_t vdevice_key, hailo_scheduling_algorithm_t scheduling_algorithm, const char *hef_path); - hailo_status configure_network_group(const char *net_group_name, uint16_t batch_size); + hailo_status configure_network_group(const char *net_group_name, hailo_scheduling_algorithm_t scheduling_algorithm, uint16_t batch_size); Expected, std::vector>> create_vstreams(const char *network_name, - const std::vector &output_formats); + hailo_scheduling_algorithm_t scheduling_algorithm, const std::vector &output_formats); hailo_status activate_network_group(); hailo_status abort_streams(); Expected remove_network_group(); @@ -123,10 +99,12 @@ public: private: Expected get_configure_params(Hef &hef, const char *net_group_name, uint16_t batch_size); + static Expected> create_vdevice(const void *element, const std::string &device_id, uint16_t device_count, + uint32_t vdevice_key, hailo_scheduling_algorithm_t scheduling_algorithm); Expected> create_vdevice(const std::string &device_id, uint16_t device_count, uint32_t vdevice_key, hailo_scheduling_algorithm_t scheduling_algorithm); - static VDeviceManager m_vdevice_manager; + static std::unordered_set> m_vdevices; static NetworkGroupConfigManager m_net_group_config_manager; static NetworkGroupActivationManager m_net_group_activation_manager; const GstElement *m_element; diff --git a/hailort/libhailort/bindings/python/examples/hef_infer_pipeline_vstream.py b/hailort/libhailort/bindings/python/examples/hef_infer_pipeline_vstream.py index c1896df..c5bfbeb 100644 --- a/hailort/libhailort/bindings/python/examples/hef_infer_pipeline_vstream.py +++ b/hailort/libhailort/bindings/python/examples/hef_infer_pipeline_vstream.py @@ -19,14 +19,16 @@ def main(): network_group = network_groups[0] network_group_params = network_group.create_params() input_vstreams_params = InputVStreamParams.make(network_group, quantized=False, format_type=FormatType.FLOAT32) - output_vstreams_params = OutputVStreamParams.make(network_group, quantized=True, format_type=FormatType.UINT8) + output_vstreams_params = OutputVStreamParams.make(network_group, quantized=True, format_type=FormatType.AUTO) with InferVStreams(network_group, input_vstreams_params, output_vstreams_params) as infer_pipeline: input_names_to_shape = {vstream_info.name: vstream_info.shape for vstream_info in hef.get_input_vstream_infos()} input_data = {name : 1 + np.ndarray([args.num_frames] + list(shape), dtype=np.float32) for name, shape in input_names_to_shape.items()} with network_group.activate(network_group_params): _ = infer_pipeline.infer(input_data) + fps = args.num_frames / infer_pipeline.get_hw_time() print('Inference ran successfully') + print(f'FPS: {fps}') if __name__ == '__main__': main() \ No newline at end of file diff --git a/hailort/libhailort/bindings/python/platform/hailo_platform/__init__.py b/hailort/libhailort/bindings/python/platform/hailo_platform/__init__.py index 610f878..4e84909 100644 --- a/hailort/libhailort/bindings/python/platform/hailo_platform/__init__.py +++ b/hailort/libhailort/bindings/python/platform/hailo_platform/__init__.py @@ -24,8 +24,9 @@ from hailo_platform.pyhailort.pyhailort import (HEF, ConfigureParams, Endianness, HailoStreamInterface, InputVStreamParams, OutputVStreamParams, InputVStreams, OutputVStreams, - InferVStreams, HailoStreamDirection, HailoFormatFlags, HailoCpuId, VDevice, - DvmTypes, PowerMeasurementTypes, SamplingPeriod, AveragingFactor, MeasurementBufferIndex) + InferVStreams, HailoStreamDirection, HailoFormatFlags, HailoCpuId, Device, VDevice, + DvmTypes, PowerMeasurementTypes, SamplingPeriod, AveragingFactor, MeasurementBufferIndex, + HailoRTException) def _verify_pyhailort_lib_exists(): python_version = "".join(str(i) for i in sys.version_info[:2]) @@ -61,4 +62,4 @@ __all__ = ['EthernetDevice', 'DvmTypes', 'PowerMeasurementTypes', 'MipiIspImageInOrder', 'MipiIspImageOutDataType', 'join_drivers_path', 'IspLightFrequency', 'HailoPowerMode', 'Endianness', 'HailoStreamInterface', 'InputVStreamParams', 'OutputVStreamParams', 'InputVStreams', 'OutputVStreams', 'InferVStreams', 'HailoStreamDirection', 'HailoFormatFlags', 'HailoCpuId', - 'VDevice'] + 'Device', 'VDevice', 'HailoRTException'] diff --git a/hailort/libhailort/bindings/python/platform/hailo_platform/pyhailort/control_object.py b/hailort/libhailort/bindings/python/platform/hailo_platform/pyhailort/control_object.py index f4b3e20..879f817 100644 --- a/hailort/libhailort/bindings/python/platform/hailo_platform/pyhailort/control_object.py +++ b/hailort/libhailort/bindings/python/platform/hailo_platform/pyhailort/control_object.py @@ -1,19 +1,11 @@ #!/usr/bin/env python """Control operations for the Hailo hardware device.""" -import struct -import sys -import signal -from builtins import object -from abc import ABCMeta, abstractmethod -from future.utils import with_metaclass - -from hailo_platform.common.logger.logger import default_logger - -from hailo_platform.pyhailort.hailo_control_protocol import BoardInformation, CoreInformation, DeviceArchitectureTypes, ExtendedDeviceInformation, HealthInformation -from hailo_platform.pyhailort.power_measurement import SamplingPeriod, AveragingFactor, DvmTypes, PowerMeasurementTypes, MeasurementBufferIndex, _get_buffer_index_enum_member -from hailo_platform.pyhailort.pyhailort import InternalPcieDevice, ExceptionWrapper +from hailo_platform.pyhailort.pyhailort import (Control, InternalPcieDevice, ExceptionWrapper, BoardInformation, # noqa F401 + CoreInformation, DeviceArchitectureTypes, ExtendedDeviceInformation, # noqa F401 + HealthInformation, SamplingPeriod, AveragingFactor, DvmTypes, # noqa F401 + PowerMeasurementTypes, MeasurementBufferIndex) # noqa F401 import hailo_platform.pyhailort._pyhailort as _pyhailort @@ -27,655 +19,12 @@ class FirmwareUpdateException(Exception): pass -class HailoControl(with_metaclass(ABCMeta, object)): +class HailoControl(Control): """Control object that sends control operations to a Hailo hardware device.""" - def __init__(self): - """Initializes a new HailoControl object.""" - self._logger = default_logger() - self._device = None - - if sys.platform != "win32": - signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGWINCH]) - - @abstractmethod - def open(self): - """Initializes the resources needed for using a control device.""" - pass - - @abstractmethod - def close(self): - """Releases the resources that were allocated for the control device.""" - pass - - def configure(self, hef, configure_params_by_name={}): - """ - Configures device from HEF object. - - Args: - hef (:class:`~hailo_platform.pyhailort.pyhailort.HEF`): HEF to configure the - device from. - configure_params_by_name (dict, optional): Maps between each net_group_name to - configure_params. In case of a mismatch with net_groups_names, default params will - be used. - """ - with ExceptionWrapper(): - return self._device.configure(hef._hef, configure_params_by_name) - - @abstractmethod - def chip_reset(self): - """Resets the device (chip reset).""" - pass - - @abstractmethod - def read_memory(self, address, data_length): - """Reads memory from the Hailo chip. - Byte order isn't changed. The core uses little-endian byte order. - - Args: - address (int): Physical address to read from. - data_length (int): Size to read in bytes. - - Returns: - list of str: Memory read from the chip, each index in the list is a byte. - """ - pass - - @abstractmethod - def write_memory(self, address, data_buffer): - """Write memory to Hailo chip. - Byte order isn't changed. The core uses little-endian byte order. - - Args: - address (int): Physical address to write to. - data_buffer (list of str): Data to write. - """ - pass - - class HcpControl(HailoControl): """Control object that uses the HCP protocol for controlling the device.""" - WORD_SIZE = 4 - - - def __init__(self): - super(HcpControl, self).__init__() - - @property - def device_id(self): - """Getter for the device_id. - - Returns: - str: A string ID of the device. BDF for PCIe devices, IP address for Ethernet devices, "Core" for core devices. - """ - return self._device_id - - def open(self): - """Initializes the resources needed for using a control device.""" - pass - - def close(self): - """Releases the resources that were allocated for the control device.""" - pass - - def chip_reset(self): - """Resets the device (chip reset).""" - with ExceptionWrapper(): - return self._device.reset(_pyhailort.ResetDeviceMode.CHIP) - - def nn_core_reset(self): - """Resets the nn_core.""" - with ExceptionWrapper(): - return self._device.reset(_pyhailort.ResetDeviceMode.NN_CORE) - - def soft_reset(self): - """reloads the device firmware (soft reset)""" - with ExceptionWrapper(): - return self._device.reset(_pyhailort.ResetDeviceMode.SOFT) - - def forced_soft_reset(self): - """reloads the device firmware (forced soft reset)""" - with ExceptionWrapper(): - return self._device.reset(_pyhailort.ResetDeviceMode.FORCED_SOFT) - - def read_memory(self, address, data_length): - """Reads memory from the Hailo chip. Byte order isn't changed. The core uses little-endian - byte order. - - Args: - address (int): Physical address to read from. - data_length (int): Size to read in bytes. - - Returns: - list of str: Memory read from the chip, each index in the list is a byte - """ - with ExceptionWrapper(): - return self._device.read_memory(address, data_length) - - def write_memory(self, address, data_buffer): - """Write memory to Hailo chip. Byte order isn't changed. The core uses little-endian byte - order. - - Args: - address (int): Physical address to write to. - data_buffer (list of str): Data to write. - """ - with ExceptionWrapper(): - return self._device.write_memory(address, data_buffer, len(data_buffer)) - - def power_measurement(self, dvm=DvmTypes.AUTO, measurement_type=PowerMeasurementTypes.AUTO): - """Perform a single power measurement on an Hailo chip. It works with the default settings - where the sensor returns a new value every 2.2 ms without averaging the values. - - Args: - dvm (:class:`~hailo_platform.pyhailort.pyhailort.DvmTypes`): - Which DVM will be measured. Default (:class:`~hailo_platform.pyhailort.pyhailort.DvmTypes.AUTO`) will be different according to the board: \n - Default (:class:`~hailo_platform.pyhailort.pyhailort.DvmTypes.AUTO`) for EVB is an approximation to the total power consumption of the chip in PCIe setups. - It sums :class:`~hailo_platform.pyhailort.pyhailort.DvmTypes.VDD_CORE`, - :class:`~hailo_platform.pyhailort.pyhailort.DvmTypes.MIPI_AVDD` and :class:`~hailo_platform.pyhailort.pyhailort.DvmTypes.AVDD_H`. - Only :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes.POWER` can measured with this option. \n - Default (:class:`~hailo_platform.pyhailort.pyhailort.DvmTypes.AUTO`) for platforms supporting current monitoring (such as M.2 and mPCIe): :class:`~hailo_platform.pyhailort.pyhailort.DvmTypes.OVERCURRENT_PROTECTION` - measurement_type - (:class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes`): - The type of the measurement. - - Returns: - float: The measured power. \n - For :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes`: \n - - :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes.SHUNT_VOLTAGE`: Unit is mV. \n - - :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes.BUS_VOLTAGE`: Unit is mV. \n - - :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes.POWER`: Unit is W. \n - - :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes.CURRENT`: Unit is mA. \n - - - Note: - This function can perform measurements for more than just power. For all supported - measurement types, please look at - :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes`. - """ - if self.device_id.device_architecture != DeviceArchitectureTypes.HAILO8_B0: - raise ControlObjectException("Invalid device architecture: {}".format(self.device_id.device_architecture)) - with ExceptionWrapper(): - return self._device.power_measurement(dvm, measurement_type) - - def start_power_measurement(self, delay=None, averaging_factor=AveragingFactor.AVERAGE_256, sampling_period=SamplingPeriod.PERIOD_1100us): - """Start performing a long power measurement. - - Args: - delay: Unused parameter. Will be removed in future versions. - averaging_factor (:class:`~hailo_platform.pyhailort.pyhailort.AveragingFactor`): - Number of samples per time period, sensor configuration value. - sampling_period (:class:`~hailo_platform.pyhailort.pyhailort.SamplingPeriod`): - Related conversion time, sensor configuration value. The sensor samples the power - every ``sampling_period`` [ms] and averages every ``averaging_factor`` samples. The - sensor provides a new value every: 2 * sampling_period * averaging_factor [ms]. The - firmware wakes up every ``delay`` [ms] and checks the sensor. If there is a new` - value to read from the sensor, the firmware reads it. Note that the average - calculated by the firmware is "average of averages", because it averages values - that have already been averaged by the sensor. - """ - # TODO: Remove deprecated arg - if delay is not None: - self._logger.warning("Passing 'delay' to 'start_power_measurement()' is deprecated and will be removed in future versions") - with ExceptionWrapper(): - return self._device.start_power_measurement(averaging_factor, sampling_period) - - def stop_power_measurement(self): - """Stop performing a long power measurement. Deletes all saved results from the firmware. - Calling the function eliminates the start function settings for the averaging the samples, - and returns to the default values, so the sensor will return a new value every 2.2 ms - without averaging values. - """ - with ExceptionWrapper(): - return self._device.stop_power_measurement() - - def set_power_measurement(self, buffer_index=MeasurementBufferIndex.MEASUREMENT_BUFFER_INDEX_0, dvm=DvmTypes.AUTO, measurement_type=PowerMeasurementTypes.AUTO): - """Set parameters for long power measurement on an Hailo chip. - - Args: - buffer_index (:class:`~hailo_platform.pyhailort.pyhailort.MeasurementBufferIndex`): Index of the buffer on the firmware the data would be saved at. - Default is :class:`~hailo_platform.pyhailort.pyhailort.MeasurementBufferIndex.MEASUREMENT_BUFFER_INDEX_0` - dvm (:class:`~hailo_platform.pyhailort.pyhailort.DvmTypes`): - Which DVM will be measured. Default (:class:`~hailo_platform.pyhailort.pyhailort.DvmTypes.AUTO`) will be different according to the board: \n - Default (:class:`~hailo_platform.pyhailort.pyhailort.DvmTypes.AUTO`) for EVB is an approximation to the total power consumption of the chip in PCIe setups. - It sums :class:`~hailo_platform.pyhailort.pyhailort.DvmTypes.VDD_CORE`, - :class:`~hailo_platform.pyhailort.pyhailort.DvmTypes.MIPI_AVDD` and :class:`~hailo_platform.pyhailort.pyhailort.DvmTypes.AVDD_H`. - Only :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes.POWER` can measured with this option. \n - Default (:class:`~hailo_platform.pyhailort.pyhailort.DvmTypes.AUTO`) for platforms supporting current monitoring (such as M.2 and mPCIe): :class:`~hailo_platform.pyhailort.pyhailort.DvmTypes.OVERCURRENT_PROTECTION` - measurement_type - (:class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes`): - The type of the measurement. - - Note: - This function can perform measurements for more than just power. For all supported measurement types - view :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes` - """ - # TODO: Remove deprecated arg - if isinstance(buffer_index, int): - self._logger.warning("Passing integer as 'buffer_index' to 'set_power_measurement()' is deprecated. One should pass " - ":class:`~hailo_platform.pyhailort.pyhailort.MeasurementBufferIndex` as 'buffer_index' instead.") - buffer_index = _get_buffer_index_enum_member(buffer_index) - with ExceptionWrapper(): - return self._device.set_power_measurement(buffer_index, dvm, measurement_type) - - def get_power_measurement(self, buffer_index=MeasurementBufferIndex.MEASUREMENT_BUFFER_INDEX_0, should_clear=True): - """Read measured power from a long power measurement - - Args: - buffer_index (:class:`~hailo_platform.pyhailort.pyhailort.MeasurementBufferIndex`): Index of the buffer on the firmware the data would be saved at. - Default is :class:`~hailo_platform.pyhailort.pyhailort.MeasurementBufferIndex.MEASUREMENT_BUFFER_INDEX_0` - should_clear (bool): Flag indicating if the results saved at the firmware will be deleted after reading. - - Returns: - :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementData`: - Object containing measurement data \n - For :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes`: \n - - :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes.SHUNT_VOLTAGE`: Unit is mV. \n - - :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes.BUS_VOLTAGE`: Unit is mV. \n - - :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes.POWER`: Unit is W. \n - - :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes.CURRENT`: Unit is mA. \n - - Note: - This function can perform measurements for more than just power. - For all supported measurement types view - :class:`~hailo_platform.pyhailort.pyhailort.PowerMeasurementTypes`. - """ - if self.device_id.device_architecture != DeviceArchitectureTypes.HAILO8_B0: - raise ControlObjectException("Invalid device architecture: {}".format(self.device_id.device_architecture)) - # TODO: Remove deprecated arg - if isinstance(buffer_index, int): - self._logger.warning("Passing integer as 'buffer_index' to 'get_power_measurement()' is deprecated. One should pass " - ":class:`~hailo_platform.pyhailort.pyhailort.MeasurementBufferIndex` as 'buffer_index' instead.") - buffer_index = _get_buffer_index_enum_member(buffer_index) - with ExceptionWrapper(): - return self._device.get_power_measurement(buffer_index, should_clear) - - def _examine_user_config(self): - with ExceptionWrapper(): - return self._device.examine_user_config() - - def read_user_config(self): - """Read the user configuration section as binary data. - - Returns: - str: User config as a binary buffer. - """ - with ExceptionWrapper(): - return self._device.read_user_config() - - def write_user_config(self, configuration): - """Write the user configuration. - - Args: - configuration (str): A binary representation of a Hailo device configuration. - """ - with ExceptionWrapper(): - return self._device.write_user_config(configuration) - - def _erase_user_config(self): - with ExceptionWrapper(): - return self._device.erase_user_config() - - def read_board_config(self): - """Read the board configuration section as binary data. - - Returns: - str: Board config as a binary buffer. - """ - with ExceptionWrapper(): - return self._device.read_board_config() - - def write_board_config(self, configuration): - """Write the static confuration. - - Args: - configuration (str): A binary representation of a Hailo device configuration. - """ - with ExceptionWrapper(): - return self._device.write_board_config(configuration) - - def identify(self): - """Gets the Hailo chip identification. - - Returns: - class HailoIdentifyResponse with Protocol version. - """ - with ExceptionWrapper(): - response = self._device.identify() - board_information = BoardInformation(response.protocol_version, response.fw_version.major, - response.fw_version.minor, response.fw_version.revision, response.logger_version, - response.board_name, response.is_release, int(response.device_architecture), response.serial_number, - response.part_number, response.product_name) - return board_information - - def core_identify(self): - """Gets the Core Hailo chip identification. - - Returns: - class HailoIdentifyResponse with Protocol version. - """ - with ExceptionWrapper(): - response = self._device.core_identify() - core_information = CoreInformation(response.fw_version.major, response.fw_version.minor, - response.fw_version.revision, response.is_release) - return core_information - - def set_fw_logger(self, level, interface_mask): - """Configure logger level and interface of sending. - - Args: - level (FwLoggerLevel): The minimum logger level. - interface_mask (int): Output interfaces (mix of FwLoggerInterface). - """ - with ExceptionWrapper(): - return self._device.set_fw_logger(level, interface_mask) - - def set_throttling_state(self, should_activate): - """Change throttling state of temperature protection component. - - Args: - should_activate (bool): Should be true to enable or false to disable. - """ - with ExceptionWrapper(): - return self._device.set_throttling_state(should_activate) - - def get_throttling_state(self): - """Get the current throttling state of temperature protection component. - - Returns: - bool: true if temperature throttling is enabled, false otherwise. - """ - with ExceptionWrapper(): - return self._device.get_throttling_state() - - def _set_overcurrent_state(self, should_activate): - """Control whether the overcurrent protection is enabled or disabled. - - Args: - should_activate (bool): Should be true to enable or false to disable. - """ - with ExceptionWrapper(): - return self._device._set_overcurrent_state(should_activate) - - def _get_overcurrent_state(self): - """Get the overcurrent protection state. - - Returns: - bool: true if overcurrent protection is enabled, false otherwise. - """ - with ExceptionWrapper(): - return self._device._get_overcurrent_state() - - @staticmethod - def _create_c_i2c_slave(pythonic_slave): - c_slave = _pyhailort.I2CSlaveConfig() - c_slave.endianness = pythonic_slave.endianness - c_slave.slave_address = pythonic_slave.slave_address - c_slave.register_address_size = pythonic_slave.register_address_size - c_slave.bus_index = pythonic_slave.bus_index - return c_slave - - def i2c_write(self, slave, register_address, data): - """Write data to an I2C slave. - - Args: - slave (:class:`hailo_platform.pyhailort.i2c_slaves.I2CSlave`): I2C slave - configuration. - register_address (int): The address of the register to which the data will be written. - data (str): The data that will be written. - """ - c_slave = HcpControl._create_c_i2c_slave(slave) - with ExceptionWrapper(): - return self._device.i2c_write(c_slave, register_address, data, len(data)) - - def i2c_read(self, slave, register_address, data_length): - """Read data from an I2C slave. - - Args: - slave (:class:`hailo_platform.pyhailort.i2c_slaves.I2CSlave`): I2C slave - configuration. - register_address (int): The address of the register from which the data will be read. - data_length (int): The number of bytes to read. - - Returns: - str: Data read from the I2C slave. - """ - c_slave = HcpControl._create_c_i2c_slave(slave) - with ExceptionWrapper(): - return self._device.i2c_read(c_slave, register_address, data_length) - - def read_register(self, address): - """Read the value of a register from a given address. - - Args: - address (int): Address to read register from. - - Returns: - int: Value of the register - """ - register_value, = struct.unpack('!I', self.read_memory(address, type(self).WORD_SIZE)) - return register_value - - def set_bit(self, address, bit_index): - """Set (turn on) a specific bit at a register from a given address. - - Args: - address (int) : Address of the register to modify. - bit_index (int) : Index of the bit that would be set. - """ - register_value = self.read_register(address) - register_value |= 1 << bit_index - self.write_memory(address, struct.pack('!I', register_value)) - - def reset_bit(self, address, bit_index): - """Reset (turn off) a specific bit at a register from a given address. - - Args: - address (int) : Address of the register to modify. - bit_index (int) : Index of the bit that would be reset. - """ - register_value = self.read_register(address) - register_value &= ~(1 << bit_index) - self.write_memory(address, struct.pack('!I', register_value)) - - def firmware_update(self, firmware_binary, should_reset=True): - """Update firmware binary on the flash. - - Args: - firmware_binary (bytes): firmware binary stream. - should_reset (bool): Should a reset be performed after the update (to load the new firmware) - """ - with ExceptionWrapper(): - return self._device.firmware_update(firmware_binary, len(firmware_binary), should_reset) - - def second_stage_update(self, second_stage_binary): - """Update second stage binary on the flash - - Args: - second_stage_binary (bytes): second stage binary stream. - """ - with ExceptionWrapper(): - return self._device.second_stage_update(second_stage_binary, len(second_stage_binary)) - - def store_sensor_config(self, section_index, reset_data_size, sensor_type, config_file_path, - config_height=0, config_width=0, config_fps=0, config_name=None): - - """Store sensor configuration to Hailo chip flash memory. - - Args: - section_index (int): Flash section index to write to. [0-6] - reset_data_size (int): Size of reset configuration. - sensor_type (:class:`~hailo_platform.pyhailort.pyhailort.SensorConfigTypes`): Sensor type. - config_file_path (str): Sensor configuration file path. - config_height (int): Configuration resolution height. - config_width (int): Configuration resolution width. - config_fps (int): Configuration FPS. - config_name (str): Sensor configuration name. - """ - if config_name is None: - config_name = "UNINITIALIZED" - - with ExceptionWrapper(): - return self._device.sensor_store_config(section_index, reset_data_size, sensor_type, config_file_path, - config_height, config_width, config_fps, config_name) - - def store_isp_config(self, reset_config_size, isp_static_config_file_path, isp_runtime_config_file_path, - config_height=0, config_width=0, config_fps=0, config_name=None): - """Store sensor isp configuration to Hailo chip flash memory. - - Args: - reset_config_size (int): Size of reset configuration. - isp_static_config_file_path (str): Sensor isp static configuration file path. - isp_runtime_config_file_path (str): Sensor isp runtime configuration file path. - config_height (int): Configuration resolution height. - config_width (int): Configuration resolution width. - config_fps (int): Configuration FPS. - config_name (str): Sensor configuration name. - """ - if config_name is None: - config_name = "UNINITIALIZED" - - with ExceptionWrapper(): - return self._device.store_isp_config(reset_config_size, config_height, config_width, - config_fps, isp_static_config_file_path, isp_runtime_config_file_path, config_name) - - def get_sensor_sections_info(self): - """Get sensor sections info from Hailo chip flash memory. - - Returns: - Sensor sections info read from the chip flash memory. - """ - with ExceptionWrapper(): - return self._device.sensor_get_sections_info() - - def sensor_set_generic_i2c_slave(self, slave_address, register_address_size, bus_index, should_hold_bus, endianness): - """Set a generic I2C slave for sensor usage. - - Args: - sequence (int): Request/response sequence. - slave_address (int): The address of the I2C slave. - register_address_size (int): The size of the offset (in bytes). - bus_index (int): The number of the bus the I2C slave is behind. - should_hold_bus (bool): Hold the bus during the read. - endianness (:class:`~hailo_platform.pyhailort.pyhailort.Endianness`): - Big or little endian. - """ - with ExceptionWrapper(): - return self._device.sensor_set_generic_i2c_slave(slave_address, register_address_size, bus_index, should_hold_bus, endianness) - - def set_sensor_i2c_bus_index(self, sensor_type, i2c_bus_index): - """Set the I2C bus to which the sensor of the specified type is connected. - - Args: - sensor_type (:class:`~hailo_platform.pyhailort.pyhailort.SensorConfigTypes`): The sensor type. - i2c_bus_index (int): The I2C bus index of the sensor. - """ - with ExceptionWrapper(): - return self._device.sensor_set_i2c_bus_index(sensor_type, i2c_bus_index) - - def load_and_start_sensor(self, section_index): - """Load the configuration with I2C in the section index. - - Args: - section_index (int): Flash section index to load config from. [0-6] - """ - with ExceptionWrapper(): - return self._device.sensor_load_and_start_config(section_index) - - def reset_sensor(self, section_index): - """Reset the sensor that is related to the section index config. - - Args: - section_index (int): Flash section index to reset. [0-6] - """ - with ExceptionWrapper(): - return self._device.sensor_reset(section_index) - - def wd_enable(self, cpu_id): - """Enable firmware watchdog. - - Args: - cpu_id (:class:`~hailo_platform.pyhailort.pyhailort.HailoCpuId`): 0 for App CPU, 1 for Core CPU. - """ - with ExceptionWrapper(): - return self._device.wd_enable(cpu_id) - - def wd_disable(self, cpu_id): - """Disable firmware watchdog. - - Args: - cpu_id (:class:`~hailo_platform.pyhailort.pyhailort.HailoCpuId`): 0 for App CPU, 1 for Core CPU. - """ - with ExceptionWrapper(): - return self._device.wd_disable(cpu_id) - - def wd_config(self, cpu_id, wd_cycles, wd_mode): - """Configure a firmware watchdog. - - Args: - cpu_id (:class:`~hailo_platform.pyhailort.pyhailort.HailoCpuId`): 0 for App CPU, 1 for Core CPU. - wd_cycles (int): number of cycles until watchdog is triggered. - wd_mode (int): 0 - HW/SW mode, 1 - HW only mode - """ - with ExceptionWrapper(): - return self._device.wd_config(cpu_id, wd_cycles, wd_mode) - - def previous_system_state(self, cpu_id): - """Read the FW previous system state. - - Args: - cpu_id (:class:`~hailo_platform.pyhailort.pyhailort.HailoCpuId`): 0 for App CPU, 1 for Core CPU. - """ - with ExceptionWrapper(): - return self._device.previous_system_state(cpu_id) - - def get_chip_temperature(self): - """Returns the latest temperature measurements from the 2 internal temperature sensors of the Hailo chip. - - Returns: - :class:`~hailo_platform.pyhailort.pyhailort.TemperatureInfo`: - Temperature in celsius of the 2 internal temperature sensors (TS), and a sample - count (a running 16-bit counter) - """ - with ExceptionWrapper(): - return self._device.get_chip_temperature() - - def get_extended_device_information(self): - with ExceptionWrapper(): - response = self._device.get_extended_device_information() - device_information = ExtendedDeviceInformation(response.neural_network_core_clock_rate, - response.supported_features, response.boot_source, response.lcs, response.soc_id, response.eth_mac_address , response.unit_level_tracking_id, response.soc_pm_values) - return device_information - - def _get_health_information(self): - with ExceptionWrapper(): - response = self._device._get_health_information() - health_information = HealthInformation(response.overcurrent_protection_active, response.current_overcurrent_zone, response.red_overcurrent_threshold, - response.orange_overcurrent_threshold, response.temperature_throttling_active, response.current_temperature_zone, response.current_temperature_throttling_level, - response.temperature_throttling_levels, response.orange_temperature_threshold, response.orange_hysteresis_temperature_threshold, - response.red_temperature_threshold, response.red_hysteresis_temperature_threshold) - return health_information - - def set_pause_frames(self, rx_pause_frames_enable): - """Enable/Disable Pause frames. - - Args: - rx_pause_frames_enable (bool): False for disable, True for enable. - """ - with ExceptionWrapper(): - return self._device.set_pause_frames(rx_pause_frames_enable) - - def test_chip_memories(self): - """test all chip memories using smart BIST - - """ - with ExceptionWrapper(): - return self._device.test_chip_memories() - - def _get_device_handle(self): - return self._device - class UdpHcpControl(HcpControl): """Control object that uses a HCP over UDP controller interface.""" @@ -688,58 +37,32 @@ class UdpHcpControl(HcpControl): response_timeout_seconds (float, optional): Number of seconds to wait until a response is received. ignore_socket_errors (bool, optional): Ignore socket error (might be usefull for debugging). """ - super(UdpHcpControl, self).__init__() - # In the C API we define the total amount of attempts, instead of the amount of retries. max_number_of_attempts = retries + 1 response_timeout_milliseconds = int(response_timeout_seconds * 1000) if device is None: with ExceptionWrapper(): - self.device = _pyhailort.Device.create_eth(remote_ip, remote_control_port, + device = _pyhailort.Device.create_eth(remote_ip, remote_control_port, response_timeout_milliseconds, max_number_of_attempts) else: - self._device = device.device - self._device_id = self.identify() + # Needs to get the _pyhailort.Device object + device = device.device + super().__init__(device) class PcieHcpControl(HcpControl): """Control object that uses a HCP over PCIe controller interface.""" def __init__(self, device=None, device_info=None): """Initializes a new HailoPcieController object.""" - super(PcieHcpControl, self).__init__() - if device_info is None: device_info = InternalPcieDevice.scan_devices()[0] if device is None: with ExceptionWrapper(): - self._device = _pyhailort.Device.create_pcie(device_info) + device = _pyhailort.Device.create_pcie(device_info) else: - self._device = device.device - self._device_id = self.identify() - - def set_notification_callback(self, callback_func, notification_id, opaque): - """Set a callback function to be called when a notification is received. - - Args: - callback_func (function): Callback function with the parameters (device, notification, opaque). - Note that throwing exceptions is not supported and will cause the program to terminate with an error! - notification_id (NotificationId): Notification ID to register the callback to. - opauqe (object): User defined data. + # Needs to get the _pyhailort.Device object + device = device.device - Note: - The notifications thread is started and closed in the use_device() context, so - notifications can only be received there. - """ - with ExceptionWrapper(): - return self._device.set_notification_callback(callback_func, notification_id, opaque) - - def remove_notification_callback(self, notification_id): - """Remove a notification callback which was already set. - - Args: - notification_id (NotificationId): Notification ID to remove the callback from. - """ - with ExceptionWrapper(): - return self._device.remove_notification_callback(notification_id) + super().__init__(device) diff --git a/hailort/libhailort/bindings/python/platform/hailo_platform/pyhailort/hailo_control_protocol.py b/hailort/libhailort/bindings/python/platform/hailo_platform/pyhailort/hailo_control_protocol.py index a85e698..30cf537 100644 --- a/hailort/libhailort/bindings/python/platform/hailo_platform/pyhailort/hailo_control_protocol.py +++ b/hailort/libhailort/bindings/python/platform/hailo_platform/pyhailort/hailo_control_protocol.py @@ -4,320 +4,9 @@ :synopsis: Implements a Hailo Control Protocol message. """ -from builtins import object -from enum import Enum, IntEnum - -import struct - -# Supported protocol and Firmware version of current SDK. -SUPPORTED_PROTOCOL_VERSION = 2 -SUPPORTED_FW_MAJOR = 4 -SUPPORTED_FW_MINOR = 8 -SUPPORTED_FW_REVISION = 1 - -MEGA_MULTIPLIER = 1000.0 * 1000.0 - - -class HailoControlProtocolException(Exception): - pass - - -class DeviceArchitectureTypes(IntEnum): - HAILO8_A0 = 0 - HAILO8_B0 = 1 - MERCURY_CA = 2 - - def __str__(self): - return self.name - -class BoardInformation(object): - def __init__(self, protocol_version, fw_version_major, fw_version_minor, fw_version_revision, - logger_version, board_name, is_release, device_architecture, serial_number, part_number, product_name): - self.protocol_version = protocol_version - self.firmware_version = HailoFirmwareVersion.construct_from_params(fw_version_major, fw_version_minor, fw_version_revision, is_release, HailoFirmwareType.APP) - self.logger_version = logger_version - self.board_name = board_name - self.is_release = is_release - self.device_architecture = DeviceArchitectureTypes(device_architecture) - self.serial_number = serial_number - self.part_number = part_number - self.product_name = product_name - - def _string_field_str(self, string_field): - # Return if the string field is empty - return string_field.rstrip('\x00') or "" - - def __str__(self): - """Returns: - str: Human readable string. - """ - return 'Control Protocol Version: {}\n' \ - 'Firmware Version: {}\n' \ - 'Logger Version: {}\n' \ - 'Board Name: {}\n' \ - 'Device Architecture: {}\n' \ - 'Serial Number: {}\n' \ - 'Part Number: {}\n' \ - 'Product Name: {}\n'.format( - self.protocol_version, - self.firmware_version, - self.logger_version, - self.board_name.rstrip('\x00'), - str(self.device_architecture), - self._string_field_str(self.serial_number), - self._string_field_str(self.part_number), - self._string_field_str(self.product_name)) - - def __repr__(self): - """Returns: - str: Human readable string. - """ - return self.__str__() - - @staticmethod - def get_hw_arch_str(device_arch): - if device_arch == DeviceArchitectureTypes.HAILO8_B0: - return 'hailo8' - elif device_arch == DeviceArchitectureTypes.MERCURY_CA: - return 'mercury' - else: - raise HailoControlProtocolException("Unsupported device architecture.") - -class CoreInformation(object): - def __init__(self, fw_version_major, fw_version_minor, fw_version_revision, is_release): - self.firmware_version = HailoFirmwareVersion.construct_from_params(fw_version_major, fw_version_minor, fw_version_revision, is_release, HailoFirmwareType.CORE) - self.is_release = is_release - - def __str__(self): - """Returns: - str: Human readable string. - """ - return 'Core Firmware Version: {}'.format( - self.firmware_version) - - def __repr__(self): - """Returns: - str: Human readable string. - """ - return self.__str__() - -class TemperatureThrottlingLevel(object): - def __init__(self, level_number, temperature_threshold, hysteresis_temperature_threshold, throttling_nn_clock_freq): - self.level_number = level_number - self.temperature_threshold = temperature_threshold - self.hysteresis_temperature_threshold = hysteresis_temperature_threshold - self.throttling_nn_clock_freq = throttling_nn_clock_freq - - def __str__(self): - """Returns: - str: Human readable string. - """ - return 'Temperature Throttling Level {}: \n' \ - 'Temperature Threshold: {}\n' \ - 'Hysteresis Temperature Threshold: {}\n' \ - 'Throttling NN Clock Frequency: {}\n' \ - .format(self.level_number, self.temperature_threshold, self.hysteresis_temperature_threshold, self.throttling_nn_clock_freq) - - def __repr__(self): - return self.__str__() - -class HealthInformation(object): - def __init__(self, overcurrent_protection_active, current_overcurrent_zone, red_overcurrent_threshold, orange_overcurrent_threshold, - temperature_throttling_active, current_temperature_zone, current_temperature_throttling_level, - temperature_throttling_levels, orange_temperature_threshold, orange_hysteresis_temperature_threshold, - red_temperature_threshold, red_hysteresis_temperature_threshold): - self.overcurrent_protection_active = overcurrent_protection_active - self.current_overcurrent_zone = current_overcurrent_zone - self.red_overcurrent_threshold = red_overcurrent_threshold - self.orange_overcurrent_threshold = orange_overcurrent_threshold - self.temperature_throttling_active = temperature_throttling_active - self.current_temperature_zone = current_temperature_zone - self.current_temperature_throttling_level = current_temperature_throttling_level - self.orange_temperature_threshold = orange_temperature_threshold - self.orange_hysteresis_temperature_threshold = orange_hysteresis_temperature_threshold - self.red_temperature_threshold = red_temperature_threshold - self.red_hysteresis_temperature_threshold = red_hysteresis_temperature_threshold - - # Add TemperatureThrottlingLevel in case it has new throttling_nn_clock_freq. level_number can be used as only last - # levels can be with the same freq - self.temperature_throttling_levels = [] - if self.temperature_throttling_active: - throttling_nn_clock_frequencies = [] - for level_number, temperature_throttling_level in enumerate(temperature_throttling_levels): - if temperature_throttling_level.throttling_nn_clock_freq not in throttling_nn_clock_frequencies: - throttling_nn_clock_frequencies.append(temperature_throttling_level.throttling_nn_clock_freq) - self.temperature_throttling_levels.append(TemperatureThrottlingLevel(level_number, - temperature_throttling_level.temperature_threshold, - temperature_throttling_level.hysteresis_temperature_threshold, - temperature_throttling_level.throttling_nn_clock_freq)) - def __repr__(self): - return self.__str__() - - def __str__(self): - """Returns: - str: Human readable string. - """ - temperature_throttling_levels_str = "\n".join(["\n\n{}\n".format(str(temperature_throttling_level)) for temperature_throttling_level in self.temperature_throttling_levels]) \ - if self.temperature_throttling_active else "" - return 'Overcurrent Protection Active: {}\n' \ - 'Overcurrent Protection Current Overcurrent Zone: {}\n' \ - 'Overcurrent Protection Red Threshold: {}\n' \ - 'Overcurrent Protection Orange Threshold: {}\n' \ - 'Temperature Protection Red Threshold: {}\n' \ - 'Temperature Protection Red Hysteresis Threshold: {}\n' \ - 'Temperature Protection Orange Threshold: {}\n' \ - 'Temperature Protection Orange Hysteresis Threshold: {}\n' \ - 'Temperature Protection Throttling State: {}\n' \ - 'Temperature Protection Current Zone: {}\n' \ - 'Temperature Protection Current Throttling Level: {}\n' \ - 'Temperature Protection Throttling Levels: {}' \ - .format(self.overcurrent_protection_active, self.current_overcurrent_zone, self.red_overcurrent_threshold, - self.orange_overcurrent_threshold, self.red_temperature_threshold, - self.red_hysteresis_temperature_threshold, self.orange_temperature_threshold, - self.orange_hysteresis_temperature_threshold, self.temperature_throttling_active, - self.current_temperature_zone, self.current_temperature_throttling_level, temperature_throttling_levels_str) - -class ExtendedDeviceInformation(object): - def __init__(self, neural_network_core_clock_rate, supported_features, boot_source, lcs, soc_id, eth_mac_address, unit_level_tracking_id, soc_pm_values): - self.neural_network_core_clock_rate = neural_network_core_clock_rate - self.supported_features = SupportedFeatures(supported_features) - self.boot_source = boot_source - self.lcs = lcs - self.soc_id = soc_id - self.eth_mac_address = eth_mac_address - self.unit_level_tracking_id = unit_level_tracking_id - self.soc_pm_values = soc_pm_values - - def __str__(self): - """Returns: - str: Human readable string. - """ - string = 'Neural Network Core Clock Rate: {}MHz\n' \ - '{}' \ - 'Boot source: {}\n' \ - 'LCS: {}\n'.format( - self.neural_network_core_clock_rate / MEGA_MULTIPLIER, - str(self.supported_features), - str(self.boot_source.name), - str(self.lcs)) - if any(self.soc_id): - string += 'SoC ID: ' + (self.soc_id.hex()) - - if any(self.eth_mac_address): - string += '\nMAC Address: ' + (":".join("{:02X}".format(i) for i in self.eth_mac_address)) - - if any(self.unit_level_tracking_id): - string += '\nULT ID: ' + (self.unit_level_tracking_id.hex()) - - if any(self.soc_pm_values): - string += '\nPM Values: ' + (self.soc_pm_values.hex()) - - - return string - - def __repr__(self): - """Returns: - str: Human readable string. - """ - return self.__str__() - -class HailoFirmwareMode(Enum): - """Indication that firmware version is stable and official """ - DEVELOP = 'develop' - RELEASE = 'release' - - -class HailoFirmwareType(Enum): - """Indication the firmware type """ - CORE = 'core' - APP = 'app' - - -class HailoFirmwareVersion(object): - """Represents a Hailo chip firmware version.""" - DEV_BIT = 0x80000000 - CORE_BIT = 0x08000000 - FW_VERSION_FORMAT = ' if the string field is empty + return string_field.rstrip('\x00') or "" + + def __str__(self): + """Returns: + str: Human readable string. + """ + return 'Control Protocol Version: {}\n' \ + 'Firmware Version: {}\n' \ + 'Logger Version: {}\n' \ + 'Board Name: {}\n' \ + 'Device Architecture: {}\n' \ + 'Serial Number: {}\n' \ + 'Part Number: {}\n' \ + 'Product Name: {}\n'.format( + self.protocol_version, + self.firmware_version, + self.logger_version, + self.board_name.rstrip('\x00'), + str(self.device_architecture), + self._string_field_str(self.serial_number), + self._string_field_str(self.part_number), + self._string_field_str(self.product_name)) + + def __repr__(self): + """Returns: + str: Human readable string. + """ + return self.__str__() + + @staticmethod + def get_hw_arch_str(device_arch): + if ((device_arch == DeviceArchitectureTypes.HAILO8) or + (device_arch == DeviceArchitectureTypes.HAILO8L)): + return 'hailo8' + elif device_arch == DeviceArchitectureTypes.MERCURY_CA: + return 'mercury' + else: + raise HailoRTException("Unsupported device architecture.") + +class CoreInformation(object): + def __init__(self, fw_version_major, fw_version_minor, fw_version_revision, is_release): + self.firmware_version = HailoFirmwareVersion.construct_from_params(fw_version_major, fw_version_minor, fw_version_revision, is_release, HailoFirmwareType.CORE) + self.is_release = is_release + + def __str__(self): + """Returns: + str: Human readable string. + """ + return 'Core Firmware Version: {}'.format( + self.firmware_version) + + def __repr__(self): + """Returns: + str: Human readable string. + """ + return self.__str__() + +class TemperatureThrottlingLevel(object): + def __init__(self, level_number, temperature_threshold, hysteresis_temperature_threshold, throttling_nn_clock_freq): + self.level_number = level_number + self.temperature_threshold = temperature_threshold + self.hysteresis_temperature_threshold = hysteresis_temperature_threshold + self.throttling_nn_clock_freq = throttling_nn_clock_freq + + def __str__(self): + """Returns: + str: Human readable string. + """ + return 'Temperature Throttling Level {}: \n' \ + 'Temperature Threshold: {}\n' \ + 'Hysteresis Temperature Threshold: {}\n' \ + 'Throttling NN Clock Frequency: {}\n' \ + .format(self.level_number, self.temperature_threshold, self.hysteresis_temperature_threshold, self.throttling_nn_clock_freq) + + def __repr__(self): + return self.__str__() + +class HealthInformation(object): + def __init__(self, overcurrent_protection_active, current_overcurrent_zone, red_overcurrent_threshold, overcurrent_throttling_active, + temperature_throttling_active, current_temperature_zone, current_temperature_throttling_level, + temperature_throttling_levels, orange_temperature_threshold, orange_hysteresis_temperature_threshold, + red_temperature_threshold, red_hysteresis_temperature_threshold, requested_overcurrent_clock_freq, requested_temperature_clock_freq): + self.overcurrent_protection_active = overcurrent_protection_active + self.current_overcurrent_zone = current_overcurrent_zone + self.red_overcurrent_threshold = red_overcurrent_threshold + self.overcurrent_throttling_active = overcurrent_throttling_active + self.temperature_throttling_active = temperature_throttling_active + self.current_temperature_zone = current_temperature_zone + self.current_temperature_throttling_level = current_temperature_throttling_level + self.orange_temperature_threshold = orange_temperature_threshold + self.orange_hysteresis_temperature_threshold = orange_hysteresis_temperature_threshold + self.red_temperature_threshold = red_temperature_threshold + self.red_hysteresis_temperature_threshold = red_hysteresis_temperature_threshold + self.requested_overcurrent_clock_freq = requested_overcurrent_clock_freq + self.requested_temperature_clock_freq = requested_temperature_clock_freq + + # Add TemperatureThrottlingLevel in case it has new throttling_nn_clock_freq. level_number can be used as only last + # levels can be with the same freq + self.temperature_throttling_levels = [] + if self.temperature_throttling_active: + throttling_nn_clock_frequencies = [] + for level_number, temperature_throttling_level in enumerate(temperature_throttling_levels): + if temperature_throttling_level.throttling_nn_clock_freq not in throttling_nn_clock_frequencies: + throttling_nn_clock_frequencies.append(temperature_throttling_level.throttling_nn_clock_freq) + self.temperature_throttling_levels.append(TemperatureThrottlingLevel(level_number, + temperature_throttling_level.temperature_threshold, + temperature_throttling_level.hysteresis_temperature_threshold, + temperature_throttling_level.throttling_nn_clock_freq)) + def __repr__(self): + return self.__str__() + + def __str__(self): + """Returns: + str: Human readable string. + """ + temperature_throttling_levels_str = "\n".join(["\n\n{}\n".format(str(temperature_throttling_level)) for temperature_throttling_level in self.temperature_throttling_levels]) \ + if self.temperature_throttling_active else "" + return 'Overcurrent Protection Active: {}\n' \ + 'Overcurrent Protection Current Overcurrent Zone: {}\n' \ + 'Overcurrent Protection Red Threshold: {}\n' \ + 'Overcurrent Protection Throttling State: {}\n' \ + 'Temperature Protection Red Threshold: {}\n' \ + 'Temperature Protection Red Hysteresis Threshold: {}\n' \ + 'Temperature Protection Orange Threshold: {}\n' \ + 'Temperature Protection Orange Hysteresis Threshold: {}\n' \ + 'Temperature Protection Throttling State: {}\n' \ + 'Temperature Protection Current Zone: {}\n' \ + 'Temperature Protection Current Throttling Level: {}\n' \ + 'Temperature Protection Throttling Levels: {}' \ + .format(self.overcurrent_protection_active, self.current_overcurrent_zone, self.red_overcurrent_threshold, + self.overcurrent_throttling_active, self.red_temperature_threshold, + self.red_hysteresis_temperature_threshold, self.orange_temperature_threshold, + self.orange_hysteresis_temperature_threshold, self.temperature_throttling_active, + self.current_temperature_zone, self.current_temperature_throttling_level, temperature_throttling_levels_str) + +class ExtendedDeviceInformation(object): + def __init__(self, neural_network_core_clock_rate, supported_features, boot_source, lcs, soc_id, eth_mac_address, unit_level_tracking_id, soc_pm_values): + self.neural_network_core_clock_rate = neural_network_core_clock_rate + self.supported_features = SupportedFeatures(supported_features) + self.boot_source = boot_source + self.lcs = lcs + self.soc_id = soc_id + self.eth_mac_address = eth_mac_address + self.unit_level_tracking_id = unit_level_tracking_id + self.soc_pm_values = soc_pm_values + + def __str__(self): + """Returns: + str: Human readable string. + """ + string = 'Neural Network Core Clock Rate: {}MHz\n' \ + '{}' \ + 'Boot source: {}\n' \ + 'LCS: {}\n'.format( + self.neural_network_core_clock_rate / MEGA_MULTIPLIER, + str(self.supported_features), + str(self.boot_source.name), + str(self.lcs)) + if any(self.soc_id): + string += 'SoC ID: ' + (self.soc_id.hex()) + + if any(self.eth_mac_address): + string += '\nMAC Address: ' + (":".join("{:02X}".format(i) for i in self.eth_mac_address)) + + if any(self.unit_level_tracking_id): + string += '\nULT ID: ' + (self.unit_level_tracking_id.hex()) + + if any(self.soc_pm_values): + string += '\nPM Values: ' + (self.soc_pm_values.hex()) + + + return string + + def __repr__(self): + """Returns: + str: Human readable string. + """ + return self.__str__() + +class HailoFirmwareMode(Enum): + """Indication that firmware version is stable and official """ + DEVELOP = 'develop' + RELEASE = 'release' + + +class HailoFirmwareType(Enum): + """Indication the firmware type """ + CORE = 'core' + APP = 'app' + + +class HailoFirmwareVersion(object): + """Represents a Hailo chip firmware version.""" + DEV_BIT = 0x80000000 + CORE_BIT = 0x08000000 + FW_VERSION_FORMAT = ' str: + return f'Device({self._device_id!r})' + + def release(self): + """Release the allocated resources of the device. This function should be called when working with the device not as context-manager.""" + + if self._device is not None: + with ExceptionWrapper(): + self._device.release() + self._device = None + + @property + def device_id(self): + """Getter for the device_id. + + Returns: + str: A string ID of the device. BDF for PCIe devices, IP address for Ethernet devices, "Core" for core devices. + """ + return self._device_id + + def configure(self, hef, configure_params_by_name={}): + """Configures target device from HEF object. + + Args: + hef (:class:`~hailo_platform.pyhailort.pyhailort.HEF`): HEF to configure the vdevice from + configure_params_by_name (dict, optional): Maps between each net_group_name to configure_params. If not provided, default params will be applied + """ + if self._creation_pid != os.getpid(): + raise HailoRTException("VDevice can only be configured from the process it was created in.") + with ExceptionWrapper(): + configured_apps = self._device.configure(hef._hef, configure_params_by_name) + configured_networks = [ConfiguredNetwork(configured_app, self, hef) for configured_app in configured_apps] + self._loaded_network_groups.extend(configured_networks) + return configured_networks + + @property + def control(self): + """ + Returns: + :class:`~hailo_platform.pyhailort.pyhailort.Control`: the control object of this device, which + implements the control API of the Hailo device. + + .. attention:: Use the low level control API with care. + """ + return self._control_object class VDevice(object): """Hailo virtual device representation.""" - def __init__( - self, - params=None, device_infos=None): + def __init__(self, params=None, device_infos=None, *, device_ids=None): """Create the Hailo virtual device object. Args: params (:obj:`hailo_platform.pyhailort.pyhailort.VDeviceParams`, optional): VDevice params, call - :func:`VDevice.create_params` to get default params. Excludes 'device_infos'. - device_infos (list of :obj:`hailo_platform.pyhailort.pyhailort.PcieDeviceInfo`, optional): pcie devices infos to create VDevice from, - call :func:`PcieDevice.scan_devices` to get list of all available devices. Excludes 'params'. + :func:`VDevice.create_params` to get default params. Excludes 'device_ids' and 'device_infos'. + device_infos (list of :obj:`hailo_platform.pyhailort.pyhailort.PcieDeviceInfo`, optional): + Deprecated - one should use device_ids instead. If given - pcie devices infos to create VDevice from, + call :func:`PcieDevice.scan_devices` to get list of all available devices. Excludes 'params' and 'device_ids. + device_ids (list of str, optional): devices ids to create VDevice from, call :func:`_Device.scan` to get + list of all available devices. Excludes 'params' and 'device_infos'. """ gc.collect() self._id = "VDevice" self._params = params - self._device_infos = device_infos - if self._device_infos is not None: + + # Convert device_infos to device_ids + if device_infos is not None: + if device_ids is not None: + raise HailoRTException("VDevice can by ids from either device_ids or device_infos. Both parameters were passed to the c'tor") + + logger = default_logger() + logger.warning("Warning - passing device_infos is deprecated. One should pass device_ids.") + device_ids = [str(device_info) for device_info in device_infos] + device_infos = None + + self._device_ids = device_ids + if self._device_ids is not None: if self._params is not None: - raise HailoRTException("VDevice can be created from params or device_infos. Both parameters was passed to the c'tor") + raise HailoRTException("VDevice can be created from params or device ids/infos. Both parameters were passed to the c'tor") + self._vdevice = None self._loaded_network_groups = [] self._open_vdevice() @@ -1255,9 +2328,9 @@ class VDevice(object): self._creation_pid = os.getpid() def _open_vdevice(self): - if self._device_infos is not None: + if self._device_ids is not None: with ExceptionWrapper(): - self._vdevice = _pyhailort.VDevice.create_from_infos(self._device_infos) + self._vdevice = _pyhailort.VDevice.create_from_ids(self._device_ids) else: if self._params is None: self._params = VDevice.create_params() @@ -1304,25 +2377,32 @@ class VDevice(object): """Gets the underlying physical devices. Return: - list of :obj:`~hailo_platform.pyhailort.hw_object.PcieDevice`: The underlying physical devices. + list of :obj:`~hailo_platform.pyhailort.pyhailort.Device`: The underlying physical devices. """ - with ExceptionWrapper(): - phys_dev_infos = self._vdevice.get_physical_devices_infos() - pythonic_dev_infos = [PcieDeviceInfo(dev_info.bus, dev_info.device, dev_info.func, dev_info.domain) - for dev_info in phys_dev_infos] - - from hailo_platform.pyhailort.hw_object import PcieDevice - return [PcieDevice(info) for info in pythonic_dev_infos] + phys_dev_infos = self.get_physical_devices_ids() + return [Device(dev_id) for dev_id in phys_dev_infos] def get_physical_devices_infos(self): - """Gets the physical devices infos. + """Deprecated: :func:`VDevice.get_physical_devices_infos` is deprecated. One should use + (:func:`VDevice.get_physical_devices_ids`) instead. + + Gets the physical devices infos. Return: list of :obj:`~hailo_platform.pyhailort.pyhailort.PcieDeviceInfo`: The underlying physical devices infos. """ - with ExceptionWrapper(): - return self._vdevice.get_physical_devices_infos() + logger = default_logger() + logger.warning("Warning - VDevice.get_physical_devices_infos() is deprecated. One should use VDevice.get_physical_devices_ids.") + return [PcieDeviceInfo.from_string(dev_id) for dev_id in self.get_physical_devices_ids()] + + def get_physical_devices_ids(self): + """Gets the physical devices ids. + Return: + list of :obj:`str`: The underlying physical devices infos. + """ + with ExceptionWrapper(): + return self._vdevice.get_physical_devices_ids() class InputVStreamParams(object): """Parameters of an input virtual stream (host to device).""" @@ -1409,14 +2489,14 @@ class OutputVStreamParams(object): Args: configured_network (:class:`ConfiguredNetwork`): The configured network group for which the params are created. - quantized (bool): Whether the data fed into the chip is already quantized. True means - the data is already quantized. False means it's HailoRT's responsibility to quantize - (scale) the data. Defaults to True. + quantized (bool): Whether the data returned from the chip should be quantized. True means + the data is still quantized. False means it's HailoRT's responsibility to de-quantize + (rescale) the data. Defaults to True. format_type (:class:`~hailo_platform.pyhailort.pyhailort.FormatType`): The default format type of the data for all output virtual streams. If quantized is False, the default is :attr:`~hailo_platform.pyhailort.pyhailort.FormatType.FLOAT32`. Otherwise, the default is :attr:`~hailo_platform.pyhailort.pyhailort.FormatType.AUTO`, - which means the data is fed in the same format expected by the device (usually + which means the returned data is in the same format returned from the device (usually uint8). timeout_ms (int): The default timeout in milliseconds for all output virtual streams. Defaults to DEFAULT_VSTREAM_TIMEOUT_MS. In case of timeout, :class:`HailoRTTimeout` will be raised. diff --git a/hailort/libhailort/bindings/python/platform/hailo_platform/tools/hailocli/config_definitions.json b/hailort/libhailort/bindings/python/platform/hailo_platform/tools/hailocli/config_definitions.json index f1efdca..46b395f 100644 --- a/hailort/libhailort/bindings/python/platform/hailo_platform/tools/hailocli/config_definitions.json +++ b/hailort/libhailort/bindings/python/platform/hailo_platform/tools/hailocli/config_definitions.json @@ -45,7 +45,8 @@ "temperature_orange_threshold": {"size": 1, "deserialize_as": "int"}, "temperature_orange_hysteresis_threshold": {"size": 1, "deserialize_as": "int"}, "temperature_throttling_enable": {"size": 1, "deserialize_as": "bool"}, - "overcurrent_monitoring_orange_threshold_enable": {"size": 1, "deserialize_as": "bool"} + "deprecated__overcurrent_monitoring_orange_threshold_enable": {"size": 1, "deserialize_as": "bool"}, + "overcurrent_throttling_enable": {"size": 1, "deserialize_as": "bool"} } }, "control": diff --git a/hailort/libhailort/bindings/python/platform/hailo_platform/tools/hailocli/hailocli_commands.py b/hailort/libhailort/bindings/python/platform/hailo_platform/tools/hailocli/hailocli_commands.py index d3a62fd..bc27899 100644 --- a/hailort/libhailort/bindings/python/platform/hailo_platform/tools/hailocli/hailocli_commands.py +++ b/hailort/libhailort/bindings/python/platform/hailo_platform/tools/hailocli/hailocli_commands.py @@ -1,5 +1,11 @@ +import os +import subprocess +import pathlib +import sys +import hailo_platform from hailo_platform.tools.hailocli.base_utils import HailortCliUtil +import pkg_resources """ HailoRTCLI matching commands in Hailo-CLI tool. @@ -67,4 +73,63 @@ class SSBUpdaterCLI(HailortCliUtil): class UDPRateLimiterCLI(HailortCliUtil): """CLI tool for UDP rate limitation.""" def __init__(self, parser): - super().__init__(parser, 'udp-rate-limiter') \ No newline at end of file + super().__init__(parser, 'udp-rate-limiter') + + +class TutorialRequired(Exception): + pass + + +class TutorialRunnerCLI(): + + TUTORIALS_DIR = os.path.join(pathlib.Path(hailo_platform.__file__).parent.parent, 'hailo_tutorials/notebooks/') + TUTORIALS_REQUIREMENTS = ["jupyter"] + ERROR_MSG = """ + Jupyter or one of its dependencies are not installed in this Python environment. These packages + are not mandatory for pyHailoRT itself, but they are required in order to run the Python API tutorial + notebooks. Please run the following command to install the missing Python packages: + """ + + def __init__(self, parser): + parser.add_argument('--ip', type=str, default=None, help='the ip parameter passed to jupyter-notebook.') + parser.add_argument('--port', type=str, default=None, help='the port parameter passed to jupyter-notebook.') + parser.set_defaults(func=self.run) + + def _check_requirements(self): + missing_pkgs = [] + working_set = pkg_resources.WorkingSet() + for req in self.TUTORIALS_REQUIREMENTS: + try: + working_set.require(req) + except pkg_resources.DistributionNotFound: + missing_pkgs.append(req) + + if missing_pkgs: + sys.tracebacklimit = 0 + raise TutorialRequired(f"\n{self.ERROR_MSG}\n {'; '.join([f'pip install {pkg}' for pkg in missing_pkgs])}") + + def run(self, args): + self._check_requirements() + exec_string = f'jupyter-notebook {self.TUTORIALS_DIR}' + if args.ip is not None: + exec_string += f" --ip={args.ip}" + + if args.port is not None: + exec_string += f" --port={args.port}" + + return subprocess.run( + exec_string, + shell=True, + check=True, + env=os.environ.copy(), + ) + + +class ParseHEFCommandCLI(HailortCliUtil): + def __init__(self, parser): + super().__init__(parser, 'parse-hef') + + +class MonitorCommandCLI(HailortCliUtil): + def __init__(self, parser): + super().__init__(parser, 'monitor') diff --git a/hailort/libhailort/bindings/python/platform/hailo_platform/tools/hailocli/main.py b/hailort/libhailort/bindings/python/platform/hailo_platform/tools/hailocli/main.py index 0e329c4..2feca7e 100644 --- a/hailort/libhailort/bindings/python/platform/hailo_platform/tools/hailocli/main.py +++ b/hailort/libhailort/bindings/python/platform/hailo_platform/tools/hailocli/main.py @@ -8,7 +8,8 @@ import hailo_platform from hailo_platform.tools.hailocli.base_utils import HailortCliUtil, Helper, HailortCliUtilError from hailo_platform.tools.hailocli.hailocli_commands import (FWUpdaterCLI, SSBUpdaterCLI, ControlCommandCLI, ScanCommandCLI, LoggerCommandCLI, MeasurePowerCommandCLI, RunCommandCLI, SensorConfigCommandCLI, - FWConfigCommandCLI, BenchmarkCommandCLI, UDPRateLimiterCLI) + FWConfigCommandCLI, BenchmarkCommandCLI, UDPRateLimiterCLI, MonitorCommandCLI, ParseHEFCommandCLI, TutorialRunnerCLI) +from hailo_platform.common.logger.logger import default_logger # Note: PlatformCommands are external dependencies in phase2-sdk/demos repo; don't change! class PlatformCommands: @@ -18,19 +19,23 @@ class PlatformCommands: 'fw-update': ('Firmware update tool', FWUpdaterCLI), 'ssb-update': ('Second stage boot update tool', SSBUpdaterCLI), 'fw-config': ('Firmware configuration tool', FWConfigCommandCLI), - 'udp-limiter': ('UDP rate limitation tool', UDPRateLimiterCLI), - 'udp': ('Alias to udp-limiter', UDPRateLimiterCLI), + 'udp-limiter': ('Alias to udp-rate-limiter. Deprecated.', UDPRateLimiterCLI), + 'udp': ('Alias to udp-rate-limiter. Deprecated.', UDPRateLimiterCLI), + 'udp-rate-limiter': ('Limit UDP rate', UDPRateLimiterCLI), 'fw-control': ('Useful firmware control operations', ControlCommandCLI), 'fw-logger': ('Download fw logs to a file', LoggerCommandCLI), 'scan': ('Scans for devices (Ethernet or PCIE)', ScanCommandCLI), 'sensor-config': ('Sensor configuration tool', SensorConfigCommandCLI), 'run': ('Run a compiled network', RunCommandCLI), 'benchmark': ('Measure basic performance on compiled network', BenchmarkCommandCLI), + 'monitor': ("Monitor of networks - Presents information about the running networks. To enable monitor, set in the application process the environment variable 'SCHEDULER_MONITOR' to 1.", MonitorCommandCLI), + 'parse-hef': (' Parse HEF to get information about its components', ParseHEFCommandCLI), 'measure-power': ('Measures power consumption', MeasurePowerCommandCLI), + 'tutorial': ('Runs the tutorials in jupyter notebook', TutorialRunnerCLI), } def __init__(self): - self.parser = argparse.ArgumentParser(description=self._get_description()) + self.parser = argparse.ArgumentParser(description=self._get_generic_description()) self.subparsers = self.parser.add_subparsers(help='Hailo utilities aimed to help with everything you need') self.COMMANDS = {} self.COMMANDS.update(type(self).PLATFORM_COMMANDS) @@ -46,6 +51,10 @@ class PlatformCommands: def _get_description(): return 'Hailo Platform SW v{} command line utilities'.format(hailo_platform.__version__) + @staticmethod + def _get_generic_description(): + return 'Hailo Command Line Utility' + def run(self): argv = sys.argv[1:] return self._run(argv) @@ -70,6 +79,10 @@ class PlatformCommands: return self.INVALID_COMMAND_EXIT_CODE command_name = argv[0] + # TODO: Remove deprecation warning + if command_name in ['udp', 'udp-limiter']: + logger = default_logger() + logger.warning("Warning - running '{}' command is deprecated, support will be removed in future versions. Use 'udp-rate-limiter' instead.".format(command_name)) if (command_name in commands) and isinstance(commands[command_name], HailortCliUtil): # HailortCliUtil just passes the rest of the argv to hailortcli try : diff --git a/hailort/libhailort/bindings/python/platform/hailo_tutorials/notebooks/HRT_0_Inference_Tutorial.ipynb b/hailort/libhailort/bindings/python/platform/hailo_tutorials/notebooks/HRT_0_Inference_Tutorial.ipynb index 2f7d477..3d82e61 100644 --- a/hailort/libhailort/bindings/python/platform/hailo_tutorials/notebooks/HRT_0_Inference_Tutorial.ipynb +++ b/hailort/libhailort/bindings/python/platform/hailo_tutorials/notebooks/HRT_0_Inference_Tutorial.ipynb @@ -24,7 +24,7 @@ "core HW, using HailoRT. This way we can use the Hailo hardware without Tensorflow, and\n", "even without the Hailo SDK (after the HEF is built).\n", "\n", - "An HEF is Hailo’s binary format for neural networks. The HEF files contain:\n", + "An HEF is Hailo's binary format for neural networks. The HEF files contain:\n", "\n", "* Target HW configuration\n", "* Weights\n", diff --git a/hailort/libhailort/bindings/python/platform/setup.py b/hailort/libhailort/bindings/python/platform/setup.py index c165824..fc24266 100644 --- a/hailort/libhailort/bindings/python/platform/setup.py +++ b/hailort/libhailort/bindings/python/platform/setup.py @@ -68,6 +68,6 @@ if __name__ == "__main__": "linux_aarch64", ], url="https://hailo.ai/", - version="4.8.1", + version="4.10.0", zip_safe=False, ) diff --git a/hailort/libhailort/bindings/python/src/CMakeLists.txt b/hailort/libhailort/bindings/python/src/CMakeLists.txt index e1147b4..945b319 100644 --- a/hailort/libhailort/bindings/python/src/CMakeLists.txt +++ b/hailort/libhailort/bindings/python/src/CMakeLists.txt @@ -19,6 +19,7 @@ pybind11_add_module(_pyhailort device_api.cpp hef_api.cpp vstream_api.cpp + quantization_api.cpp ${HAILORT_COMMON_CPP_SOURCES} ) diff --git a/hailort/libhailort/bindings/python/src/device_api.cpp b/hailort/libhailort/bindings/python/src/device_api.cpp index d363ed4..047427b 100644 --- a/hailort/libhailort/bindings/python/src/device_api.cpp +++ b/hailort/libhailort/bindings/python/src/device_api.cpp @@ -14,6 +14,20 @@ namespace hailort { +std::vector DeviceWrapper::scan() +{ + auto device_ids = Device::scan(); + VALIDATE_EXPECTED(device_ids); + return device_ids.release(); +} + +DeviceWrapper DeviceWrapper::create(const std::string &device_id) +{ + auto device = Device::create(device_id); + VALIDATE_EXPECTED(device); + return DeviceWrapper(device.release()); +} + DeviceWrapper DeviceWrapper::create_pcie(hailo_pcie_device_info_t &device_info) { auto device = Device::create_pcie(device_info); @@ -22,7 +36,7 @@ DeviceWrapper DeviceWrapper::create_pcie(hailo_pcie_device_info_t &device_info) return DeviceWrapper(device.release()); } -DeviceWrapper DeviceWrapper::create_eth(std::string &device_address, uint16_t port, +DeviceWrapper DeviceWrapper::create_eth(const std::string &device_address, uint16_t port, uint32_t timeout_milliseconds, uint8_t max_number_of_attempts) { hailo_eth_device_info_t device_info = {}; @@ -52,14 +66,6 @@ DeviceWrapper DeviceWrapper::create_eth(std::string &device_address, uint16_t po return DeviceWrapper(device.release()); } -DeviceWrapper DeviceWrapper::create_core() -{ - auto device = Device::create_core_device(); - VALIDATE_EXPECTED(device); - - return DeviceWrapper(device.release()); -} - void DeviceWrapper::release() { m_device.reset(); @@ -458,13 +464,21 @@ py::bytes DeviceWrapper::direct_read_memory(uint32_t address, uint32_t size) return py::bytes(buffer_str); } +const char *DeviceWrapper::get_dev_id() const +{ + return device().get_dev_id(); +} + void DeviceWrapper::add_to_python_module(py::module &m) { py::class_(m, "Device") + // Scan + .def("scan", &DeviceWrapper::scan) + // C'tors + .def("create", &DeviceWrapper::create) .def("create_pcie", &DeviceWrapper::create_pcie) .def("create_eth", &DeviceWrapper::create_eth) - .def("create_core", &DeviceWrapper::create_core) .def("release", &DeviceWrapper::release) //HEF @@ -514,6 +528,7 @@ void DeviceWrapper::add_to_python_module(py::module &m) .def("_get_overcurrent_state", &DeviceWrapper::get_overcurrent_state) .def("direct_write_memory", &DeviceWrapper::direct_write_memory) .def("direct_read_memory", &DeviceWrapper::direct_read_memory) + .def_property_readonly("device_id", &DeviceWrapper::get_dev_id) .def("read_log", &DeviceWrapper::read_log, py::return_value_policy::move) .def("set_notification_callback", &DeviceWrapper::set_notification_callback) diff --git a/hailort/libhailort/bindings/python/src/device_api.hpp b/hailort/libhailort/bindings/python/src/device_api.hpp index 24f85e0..e55d222 100644 --- a/hailort/libhailort/bindings/python/src/device_api.hpp +++ b/hailort/libhailort/bindings/python/src/device_api.hpp @@ -44,10 +44,11 @@ class DeviceWrapper final { public: + static std::vector scan(); + static DeviceWrapper create(const std::string &device_id); static DeviceWrapper create_pcie(hailo_pcie_device_info_t &device_info); - static DeviceWrapper create_eth(std::string &device_address, uint16_t port, + static DeviceWrapper create_eth(const std::string &device_address, uint16_t port, uint32_t timeout_milliseconds, uint8_t max_number_of_attempts); - static DeviceWrapper create_core(); void release(); Device& device() @@ -56,6 +57,12 @@ public: return *(m_device.get()); } + const Device& device() const + { + VALIDATE_NOT_NULL(m_device); + return *(m_device.get()); + } + Device& operator*() // Used for control_internals { return device(); @@ -117,6 +124,7 @@ public: py::bytes read_log(size_t byte_count, hailo_cpu_id_t cpu_id); void direct_write_memory(uint32_t address, py::bytes buffer); py::bytes direct_read_memory(uint32_t address, uint32_t size); + const char *get_dev_id() const; static void add_to_python_module(py::module &m); diff --git a/hailort/libhailort/bindings/python/src/hef_api.cpp b/hailort/libhailort/bindings/python/src/hef_api.cpp index d31c486..f940f31 100644 --- a/hailort/libhailort/bindings/python/src/hef_api.cpp +++ b/hailort/libhailort/bindings/python/src/hef_api.cpp @@ -257,7 +257,7 @@ void HefWrapper::initialize_python_module(py::module &m) py::class_(m, "ConfiguredNetworkGroup") .def("get_name", [](ConfiguredNetworkGroup& self) { - return self.get_network_group_name(); + return self.name(); }) .def("get_default_streams_interface", [](ConfiguredNetworkGroup& self) { diff --git a/hailort/libhailort/bindings/python/src/internal/CMakeLists.txt b/hailort/libhailort/bindings/python/src/internal/CMakeLists.txt index c36310b..a03aa79 100644 --- a/hailort/libhailort/bindings/python/src/internal/CMakeLists.txt +++ b/hailort/libhailort/bindings/python/src/internal/CMakeLists.txt @@ -21,7 +21,17 @@ target_include_directories(_pyhailort_internal $ ) -target_link_libraries(_pyhailort_internal PRIVATE libhailort hef_proto spdlog::spdlog readerwriterqueue microprofile) +target_link_libraries(_pyhailort_internal PRIVATE + libhailort + hef_proto + spdlog::spdlog + readerwriterqueue + microprofile + scheduler_mon_proto) +if(HAILO_BUILD_SERVICE) + target_link_libraries(_pyhailort_internal PRIVATE grpc++_unsecure hailort_rpc_grpc_proto) +endif() + if(WIN32) target_link_libraries(_pyhailort_internal PRIVATE Ws2_32 Iphlpapi Shlwapi) endif() diff --git a/hailort/libhailort/bindings/python/src/internal/pyhailort_internal.cpp b/hailort/libhailort/bindings/python/src/internal/pyhailort_internal.cpp index 620a89c..3b27ea0 100644 --- a/hailort/libhailort/bindings/python/src/internal/pyhailort_internal.cpp +++ b/hailort/libhailort/bindings/python/src/internal/pyhailort_internal.cpp @@ -183,7 +183,7 @@ PYBIND11_MODULE(_pyhailort_internal, m) { return self.format.order; }) .def_readonly("direction", &LayerInfo::direction) - .def_readonly("sys_index", &LayerInfo::index) + .def_readonly("sys_index", &LayerInfo::stream_index) .def_readonly("name", &LayerInfo::name) .def_readonly("quant_info", &LayerInfo::quant_info) // For backwards compatibility (accessing qp through layer_info directly) @@ -207,8 +207,14 @@ PYBIND11_MODULE(_pyhailort_internal, m) { .def_readonly("height_gcd", &LayerInfo::height_gcd) .def_readonly("height_ratios", &LayerInfo::height_ratios) .def_readonly("buffer_indices", &LayerInfo::buffer_indices) - .def_readonly("core_bytes_per_buffer", &LayerInfo::core_bytes_per_buffer) - .def_readonly("core_buffers_per_frame", &LayerInfo::core_buffers_per_frame) + .def_property_readonly("core_bytes_per_buffer", [](LayerInfo& self) + { + return self.nn_stream_config.core_bytes_per_buffer; + }) + .def_property_readonly("core_buffers_per_frame", [](LayerInfo& self) + { + return self.nn_stream_config.core_buffers_per_frame; + }) .def_readonly("network_name", &LayerInfo::network_name) ; } diff --git a/hailort/libhailort/bindings/python/src/pyhailort.cpp b/hailort/libhailort/bindings/python/src/pyhailort.cpp index b679830..6ad0150 100644 --- a/hailort/libhailort/bindings/python/src/pyhailort.cpp +++ b/hailort/libhailort/bindings/python/src/pyhailort.cpp @@ -14,6 +14,7 @@ using namespace std; #include "vstream_api.hpp" #include "vdevice_api.hpp" #include "device_api.hpp" +#include "quantization_api.hpp" #include "utils.hpp" #include "utils.h" @@ -73,7 +74,13 @@ std::list UdpScan::scan_devices(char* interface_name, uint32_t time return device_addresses; } -std::vector scan_pcie_devices(void) +class PcieScan { +public: + PcieScan() = default; + std::vector scan_devices(void); +}; + +std::vector PcieScan::scan_devices(void) { auto scan_result = Device::scan_pcie(); VALIDATE_EXPECTED(scan_result); @@ -81,242 +88,6 @@ std::vector scan_pcie_devices(void) return scan_result.release(); } -// Quantization -void dequantize_output_buffer_from_uint8(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &dst_dtype, - uint32_t shape_size, const hailo_quant_info_t &quant_info) -{ - switch (dst_dtype) { - case HAILO_FORMAT_TYPE_UINT8: - Quantization::dequantize_output_buffer(static_cast(src_buffer.mutable_data()), - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - case HAILO_FORMAT_TYPE_UINT16: - Quantization::dequantize_output_buffer(static_cast(src_buffer.mutable_data()), - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - case HAILO_FORMAT_TYPE_FLOAT32: - Quantization::dequantize_output_buffer(static_cast(src_buffer.mutable_data()), - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - default: - LOGGER__ERROR("Output quantization isn't supported from src format type uint8 to dst format type = {}", - HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); - THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); - break; - } -} - -void dequantize_output_buffer_from_uint16(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &dst_dtype, - uint32_t shape_size, const hailo_quant_info_t &quant_info) -{ - switch (dst_dtype) { - case HAILO_FORMAT_TYPE_UINT16: - Quantization::dequantize_output_buffer(static_cast(src_buffer.mutable_data()), - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - case HAILO_FORMAT_TYPE_FLOAT32: - Quantization::dequantize_output_buffer(static_cast(src_buffer.mutable_data()), - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - default: - LOGGER__ERROR("Output quantization isn't supported from src dormat type uint16 to dst format type = {}", - HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); - THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); - break; - } -} - -void dequantize_output_buffer_from_float32(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &dst_dtype, - uint32_t shape_size, const hailo_quant_info_t &quant_info) -{ - switch (dst_dtype) { - case HAILO_FORMAT_TYPE_FLOAT32: - Quantization::dequantize_output_buffer(static_cast(src_buffer.mutable_data()), - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - default: - LOGGER__ERROR("Output quantization isn't supported from src format type float32 to dst format type = {}", - HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); - THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); - break; - } -} - -void dequantize_output_buffer_from_uint8_in_place(py::array dst_buffer, const hailo_format_type_t &dst_dtype, - uint32_t shape_size, const hailo_quant_info_t &quant_info) -{ - switch (dst_dtype) { - case HAILO_FORMAT_TYPE_UINT8: - Quantization::dequantize_output_buffer_in_place( - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - case HAILO_FORMAT_TYPE_UINT16: - Quantization::dequantize_output_buffer_in_place( - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - case HAILO_FORMAT_TYPE_FLOAT32: - Quantization::dequantize_output_buffer_in_place( - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - default: - LOGGER__ERROR("Output quantization isn't supported from src format type uint8 to dst format type = {}", - HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); - THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); - break; - } -} - -void dequantize_output_buffer_from_uint16_in_place(py::array dst_buffer, const hailo_format_type_t &dst_dtype, - uint32_t shape_size, const hailo_quant_info_t &quant_info) -{ - switch (dst_dtype) { - case HAILO_FORMAT_TYPE_UINT16: - Quantization::dequantize_output_buffer_in_place( - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - case HAILO_FORMAT_TYPE_FLOAT32: - Quantization::dequantize_output_buffer_in_place( - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - default: - LOGGER__ERROR("Output quantization isn't supported from src dormat type uint16 to dst format type = {}", - HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); - THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); - break; - } -} - -void dequantize_output_buffer_from_float32_in_place(py::array dst_buffer, const hailo_format_type_t &dst_dtype, - uint32_t shape_size, const hailo_quant_info_t &quant_info) -{ - switch (dst_dtype) { - case HAILO_FORMAT_TYPE_FLOAT32: - Quantization::dequantize_output_buffer_in_place( - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - default: - LOGGER__ERROR("Output quantization isn't supported from src format type float32 to dst format type = {}", - HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); - THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); - break; - } -} - -void dequantize_output_buffer_in_place(py::array dst_buffer, const hailo_format_type_t &src_dtype, - const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info) -{ - switch (src_dtype) { - case HAILO_FORMAT_TYPE_UINT8: - dequantize_output_buffer_from_uint8_in_place(dst_buffer, dst_dtype, shape_size, quant_info); - break; - case HAILO_FORMAT_TYPE_UINT16: - dequantize_output_buffer_from_uint16_in_place(dst_buffer, dst_dtype, shape_size, quant_info); - break; - case HAILO_FORMAT_TYPE_FLOAT32: - dequantize_output_buffer_from_float32_in_place(dst_buffer, dst_dtype, shape_size, quant_info); - break; - default: - LOGGER__ERROR("Unsupported src format type = {}", HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); - THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); - break; - } -} - -void dequantize_output_buffer(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &src_dtype, - const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info) -{ - switch (src_dtype) { - case HAILO_FORMAT_TYPE_UINT8: - dequantize_output_buffer_from_uint8(src_buffer, dst_buffer, dst_dtype, shape_size, quant_info); - break; - case HAILO_FORMAT_TYPE_UINT16: - dequantize_output_buffer_from_uint16(src_buffer, dst_buffer, dst_dtype, shape_size, quant_info); - break; - case HAILO_FORMAT_TYPE_FLOAT32: - dequantize_output_buffer_from_float32(src_buffer, dst_buffer, dst_dtype, shape_size, quant_info); - break; - default: - LOGGER__ERROR("Unsupported src format type = {}", HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); - THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); - break; - } -} - -void quantize_input_buffer_from_uint8(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &dst_dtype, - uint32_t shape_size, const hailo_quant_info_t &quant_info) -{ - switch (dst_dtype) { - case HAILO_FORMAT_TYPE_UINT8: - Quantization::quantize_input_buffer(static_cast(src_buffer.mutable_data()), - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - default: - LOGGER__ERROR("Input quantization isn't supported from src format type uint8 to dst format type = {}", HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); - THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); - break; - } -} - -void quantize_input_buffer_from_uint16(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &dst_dtype, - uint32_t shape_size, const hailo_quant_info_t &quant_info) -{ - switch (dst_dtype) { - case HAILO_FORMAT_TYPE_UINT8: - Quantization::quantize_input_buffer(static_cast(src_buffer.mutable_data()), - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - case HAILO_FORMAT_TYPE_UINT16: - Quantization::quantize_input_buffer(static_cast(src_buffer.mutable_data()), - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - default: - LOGGER__ERROR("Input quantization isn't supported from src format type uint16 to dst format type = {}", - HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); - THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); - break; - } -} - -void quantize_input_buffer_from_float32(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &dst_dtype, - uint32_t shape_size, const hailo_quant_info_t &quant_info) -{ - switch (dst_dtype) { - case HAILO_FORMAT_TYPE_UINT8: - Quantization::quantize_input_buffer(static_cast(src_buffer.mutable_data()), - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - case HAILO_FORMAT_TYPE_UINT16: - Quantization::quantize_input_buffer(static_cast(src_buffer.mutable_data()), - static_cast(dst_buffer.mutable_data()), shape_size, quant_info); - break; - default: - LOGGER__ERROR("Input quantization isn't supported from src format type float32 to dst format type = {}", - HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); - THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); - break; - } -} - -void quantize_input_buffer(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &src_dtype, - const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info) -{ - switch (src_dtype) { - case HAILO_FORMAT_TYPE_UINT8: - quantize_input_buffer_from_uint8(src_buffer, dst_buffer, dst_dtype, shape_size, quant_info); - break; - case HAILO_FORMAT_TYPE_UINT16: - quantize_input_buffer_from_uint16(src_buffer, dst_buffer, dst_dtype, shape_size, quant_info); - break; - case HAILO_FORMAT_TYPE_FLOAT32: - quantize_input_buffer_from_float32(src_buffer, dst_buffer, dst_dtype, shape_size, quant_info); - break; - default: - LOGGER__ERROR("Input quantization isn't supported for src format type = {}", HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); - THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); - break; - } -} - std::string get_status_message(uint32_t status_in) { auto status_str = hailo_get_status_message((hailo_status)status_in); @@ -413,10 +184,9 @@ PYBIND11_MODULE(_pyhailort, m) { validate_versions_match(); m.def("get_status_message", &get_status_message); - m.def("scan_pcie_devices", &scan_pcie_devices); - m.def("dequantize_output_buffer_in_place", &dequantize_output_buffer_in_place); - m.def("dequantize_output_buffer", &dequantize_output_buffer); - m.def("quantize_input_buffer", &quantize_input_buffer); + m.def("dequantize_output_buffer_in_place", &QuantizationBindings::dequantize_output_buffer_in_place); + m.def("dequantize_output_buffer", &QuantizationBindings::dequantize_output_buffer); + m.def("quantize_input_buffer", &QuantizationBindings::quantize_input_buffer); m.def("get_format_data_bytes", &HailoRTCommon::get_format_data_bytes); m.def("get_dtype", &HailoRTBindingsCommon::get_dtype); @@ -443,6 +213,10 @@ PYBIND11_MODULE(_pyhailort, m) { .def(py::init<>()) .def("scan_devices", &UdpScan::scan_devices) ; + py::class_(m, "PcieScan") + .def(py::init<>()) + .def("scan_devices", &PcieScan::scan_devices) + ; py::class_(m, "PowerMeasurementData") .def_readonly("average_value", &PowerMeasurementData::m_average_value, "float, The average value of the samples that were sampled") @@ -456,7 +230,8 @@ PYBIND11_MODULE(_pyhailort, m) { py::enum_(m, "DeviceArchitecture") .value("HAILO8_A0", HAILO_ARCH_HAILO8_A0) - .value("HAILO8_B0", HAILO_ARCH_HAILO8_B0) + .value("HAILO8", HAILO_ARCH_HAILO8) + .value("HAILO8L", HAILO_ARCH_HAILO8L) .value("MERCURY_CA", HAILO_ARCH_MERCURY_CA) ; @@ -530,8 +305,7 @@ PYBIND11_MODULE(_pyhailort, m) { ; py::enum_(m, "OvercurrentAlertState") - .value("OVERCURRENT_ZONE_NONE", HAILO_OVERCURRENT_PROTECTION_OVERCURRENT_ZONE__NONE) - .value("OVERCURRENT_ZONE_ORANGE", HAILO_OVERCURRENT_PROTECTION_OVERCURRENT_ZONE__ORANGE) + .value("OVERCURRENT_ZONE_GREEN", HAILO_OVERCURRENT_PROTECTION_OVERCURRENT_ZONE__GREEN) .value("OVERCURRENT_ZONE_RED", HAILO_OVERCURRENT_PROTECTION_OVERCURRENT_ZONE__RED) ; @@ -572,7 +346,7 @@ PYBIND11_MODULE(_pyhailort, m) { py::class_(m, "HealthMonitorOvercurrentAlertNotificationMessage") .def_readonly("overcurrent_zone", &hailo_health_monitor_overcurrent_alert_notification_message_t::overcurrent_zone) .def_readonly("exceeded_alert_threshold", &hailo_health_monitor_overcurrent_alert_notification_message_t::exceeded_alert_threshold) - .def_readonly("sampled_current_during_alert", &hailo_health_monitor_overcurrent_alert_notification_message_t::sampled_current_during_alert) + .def_readonly("is_last_overcurrent_violation_reached", &hailo_health_monitor_overcurrent_alert_notification_message_t::is_last_overcurrent_violation_reached) ; py::class_(m, "HealthMonitorLcuEccErrorNotificationMessage") @@ -997,11 +771,29 @@ PYBIND11_MODULE(_pyhailort, m) { }); ; - py::class_(m, "VDeviceParams") + py::class_(m, "VDeviceParams") .def(py::init<>()) - .def_readwrite("device_count", &hailo_vdevice_params_t::device_count) + .def_property("device_count", + [](const VDeviceParamsWrapper& params) -> uint32_t { + return params.orig_params.device_count; + }, + [](VDeviceParamsWrapper& params, const uint32_t& device_count) { + params.orig_params.device_count = device_count; + } + ) + .def_property("group_id", + [](const VDeviceParamsWrapper& params) -> py::str { + return std::string(params.orig_params.group_id); + }, + [](VDeviceParamsWrapper& params, const std::string& group_id) { + params.group_id_str = group_id; + params.orig_params.group_id = params.group_id_str.c_str(); + } + ) .def_static("default", []() { - return HailoRTDefaults::get_vdevice_params(); + auto orig_params = HailoRTDefaults::get_vdevice_params(); + VDeviceParamsWrapper params_wrapper{orig_params, ""}; + return params_wrapper; }); ; @@ -1070,7 +862,7 @@ PYBIND11_MODULE(_pyhailort, m) { .def_readonly("overcurrent_protection_active", &hailo_health_info_t::overcurrent_protection_active) .def_readonly("current_overcurrent_zone", &hailo_health_info_t::current_overcurrent_zone) .def_readonly("red_overcurrent_threshold", &hailo_health_info_t::red_overcurrent_threshold) - .def_readonly("orange_overcurrent_threshold", &hailo_health_info_t::orange_overcurrent_threshold) + .def_readonly("overcurrent_throttling_active", &hailo_health_info_t::overcurrent_throttling_active) .def_readonly("temperature_throttling_active", &hailo_health_info_t::temperature_throttling_active) .def_readonly("current_temperature_zone", &hailo_health_info_t::current_temperature_zone) .def_readonly("current_temperature_throttling_level", &hailo_health_info_t::current_temperature_throttling_level) @@ -1086,6 +878,8 @@ PYBIND11_MODULE(_pyhailort, m) { .def_readonly("orange_hysteresis_temperature_threshold", &hailo_health_info_t::orange_hysteresis_temperature_threshold) .def_readonly("red_temperature_threshold", &hailo_health_info_t::red_temperature_threshold) .def_readonly("red_hysteresis_temperature_threshold", &hailo_health_info_t::red_hysteresis_temperature_threshold) + .def_readonly("requested_overcurrent_clock_freq", &hailo_health_info_t::requested_overcurrent_clock_freq) + .def_readonly("requested_temperature_clock_freq", &hailo_health_info_t::requested_temperature_clock_freq) ; py::class_(m, "ExtendedDeviceInformation") @@ -1148,10 +942,6 @@ PYBIND11_MODULE(_pyhailort, m) { .value("CPU1", HAILO_CPU_ID_1) ; - py::enum_(m, "BootloaderVersion") - .value("UNSIGNED", BOOTLOADER_VERSION_HAILO8_B0_UNSIGNED) - .value("SIGNED", BOOTLOADER_VERSION_HAILO8_B0_SIGNED) - ; py::class_(m, "HailoRTDefaults") .def_static("HAILO_INFINITE", []() { return HAILO_INFINITE;} ) @@ -1160,6 +950,7 @@ PYBIND11_MODULE(_pyhailort, m) { .def_static("DEVICE_BASE_INPUT_STREAM_PORT", []() { return HailoRTCommon::ETH_INPUT_BASE_PORT;} ) .def_static("DEVICE_BASE_OUTPUT_STREAM_PORT", []() { return HailoRTCommon::ETH_OUTPUT_BASE_PORT;} ) .def_static("PCIE_ANY_DOMAIN", []() { return HAILO_PCIE_ANY_DOMAIN;} ) + .def_static("HAILO_UNIQUE_VDEVICE_GROUP_ID", []() { return std::string(HAILO_UNIQUE_VDEVICE_GROUP_ID); } ) ; py::class_(m, "NetworkGroupInfo", py::module_local()) diff --git a/hailort/libhailort/bindings/python/src/quantization_api.cpp b/hailort/libhailort/bindings/python/src/quantization_api.cpp new file mode 100644 index 0000000..12658d8 --- /dev/null +++ b/hailort/libhailort/bindings/python/src/quantization_api.cpp @@ -0,0 +1,251 @@ +/** + * Copyright (c) 2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) +**/ +/** + * @file quantization_api.cpp + * @brief Quantization python bindings functions + **/ + +#include "quantization_api.hpp" +#include "bindings_common.hpp" + +namespace hailort +{ + +void QuantizationBindings::dequantize_output_buffer_from_uint8(py::array src_buffer, py::array dst_buffer, + const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info) +{ + switch (dst_dtype) { + case HAILO_FORMAT_TYPE_UINT8: + Quantization::dequantize_output_buffer(static_cast(src_buffer.mutable_data()), + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + case HAILO_FORMAT_TYPE_UINT16: + Quantization::dequantize_output_buffer(static_cast(src_buffer.mutable_data()), + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + case HAILO_FORMAT_TYPE_FLOAT32: + Quantization::dequantize_output_buffer(static_cast(src_buffer.mutable_data()), + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + default: + LOGGER__ERROR("Output quantization isn't supported from src format type uint8 to dst format type = {}", + HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); + THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); + break; + } +} + +void QuantizationBindings::dequantize_output_buffer_from_uint16(py::array src_buffer, py::array dst_buffer, + const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info) +{ + switch (dst_dtype) { + case HAILO_FORMAT_TYPE_UINT16: + Quantization::dequantize_output_buffer(static_cast(src_buffer.mutable_data()), + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + case HAILO_FORMAT_TYPE_FLOAT32: + Quantization::dequantize_output_buffer(static_cast(src_buffer.mutable_data()), + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + default: + LOGGER__ERROR("Output quantization isn't supported from src dormat type uint16 to dst format type = {}", + HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); + THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); + break; + } +} + +void QuantizationBindings::dequantize_output_buffer_from_float32(py::array src_buffer, py::array dst_buffer, + const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info) +{ + switch (dst_dtype) { + case HAILO_FORMAT_TYPE_FLOAT32: + Quantization::dequantize_output_buffer(static_cast(src_buffer.mutable_data()), + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + default: + LOGGER__ERROR("Output quantization isn't supported from src format type float32 to dst format type = {}", + HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); + THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); + break; + } +} + +void QuantizationBindings::dequantize_output_buffer_from_uint8_in_place(py::array dst_buffer, const hailo_format_type_t &dst_dtype, + uint32_t shape_size, const hailo_quant_info_t &quant_info) +{ + switch (dst_dtype) { + case HAILO_FORMAT_TYPE_UINT8: + Quantization::dequantize_output_buffer_in_place( + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + case HAILO_FORMAT_TYPE_UINT16: + Quantization::dequantize_output_buffer_in_place( + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + case HAILO_FORMAT_TYPE_FLOAT32: + Quantization::dequantize_output_buffer_in_place( + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + default: + LOGGER__ERROR("Output quantization isn't supported from src format type uint8 to dst format type = {}", + HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); + THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); + break; + } +} + +void QuantizationBindings::dequantize_output_buffer_from_uint16_in_place(py::array dst_buffer, const hailo_format_type_t &dst_dtype, + uint32_t shape_size, const hailo_quant_info_t &quant_info) +{ + switch (dst_dtype) { + case HAILO_FORMAT_TYPE_UINT16: + Quantization::dequantize_output_buffer_in_place( + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + case HAILO_FORMAT_TYPE_FLOAT32: + Quantization::dequantize_output_buffer_in_place( + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + default: + LOGGER__ERROR("Output quantization isn't supported from src dormat type uint16 to dst format type = {}", + HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); + THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); + break; + } +} + +void QuantizationBindings::dequantize_output_buffer_from_float32_in_place(py::array dst_buffer, const hailo_format_type_t &dst_dtype, + uint32_t shape_size, const hailo_quant_info_t &quant_info) +{ + switch (dst_dtype) { + case HAILO_FORMAT_TYPE_FLOAT32: + Quantization::dequantize_output_buffer_in_place( + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + default: + LOGGER__ERROR("Output quantization isn't supported from src format type float32 to dst format type = {}", + HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); + THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); + break; + } +} + +void QuantizationBindings::dequantize_output_buffer_in_place(py::array dst_buffer, const hailo_format_type_t &src_dtype, + const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info) +{ + switch (src_dtype) { + case HAILO_FORMAT_TYPE_UINT8: + QuantizationBindings::dequantize_output_buffer_from_uint8_in_place(dst_buffer, dst_dtype, shape_size, quant_info); + break; + case HAILO_FORMAT_TYPE_UINT16: + QuantizationBindings::dequantize_output_buffer_from_uint16_in_place(dst_buffer, dst_dtype, shape_size, quant_info); + break; + case HAILO_FORMAT_TYPE_FLOAT32: + QuantizationBindings::dequantize_output_buffer_from_float32_in_place(dst_buffer, dst_dtype, shape_size, quant_info); + break; + default: + LOGGER__ERROR("Unsupported src format type = {}", HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); + THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); + break; + } +} + +void QuantizationBindings::dequantize_output_buffer(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &src_dtype, + const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info) +{ + switch (src_dtype) { + case HAILO_FORMAT_TYPE_UINT8: + QuantizationBindings::dequantize_output_buffer_from_uint8(src_buffer, dst_buffer, dst_dtype, shape_size, quant_info); + break; + case HAILO_FORMAT_TYPE_UINT16: + QuantizationBindings::dequantize_output_buffer_from_uint16(src_buffer, dst_buffer, dst_dtype, shape_size, quant_info); + break; + case HAILO_FORMAT_TYPE_FLOAT32: + QuantizationBindings::dequantize_output_buffer_from_float32(src_buffer, dst_buffer, dst_dtype, shape_size, quant_info); + break; + default: + LOGGER__ERROR("Unsupported src format type = {}", HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); + THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); + break; + } +} + +void QuantizationBindings::quantize_input_buffer_from_uint8(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &dst_dtype, + uint32_t shape_size, const hailo_quant_info_t &quant_info) +{ + switch (dst_dtype) { + case HAILO_FORMAT_TYPE_UINT8: + Quantization::quantize_input_buffer(static_cast(src_buffer.mutable_data()), + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + default: + LOGGER__ERROR("Input quantization isn't supported from src format type uint8 to dst format type = {}", HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); + THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); + break; + } +} + +void QuantizationBindings::quantize_input_buffer_from_uint16(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &dst_dtype, + uint32_t shape_size, const hailo_quant_info_t &quant_info) +{ + switch (dst_dtype) { + case HAILO_FORMAT_TYPE_UINT8: + Quantization::quantize_input_buffer(static_cast(src_buffer.mutable_data()), + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + case HAILO_FORMAT_TYPE_UINT16: + Quantization::quantize_input_buffer(static_cast(src_buffer.mutable_data()), + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + default: + LOGGER__ERROR("Input quantization isn't supported from src format type uint16 to dst format type = {}", + HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); + THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); + break; + } +} + +void QuantizationBindings::quantize_input_buffer_from_float32(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &dst_dtype, + uint32_t shape_size, const hailo_quant_info_t &quant_info) +{ + switch (dst_dtype) { + case HAILO_FORMAT_TYPE_UINT8: + Quantization::quantize_input_buffer(static_cast(src_buffer.mutable_data()), + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + case HAILO_FORMAT_TYPE_UINT16: + Quantization::quantize_input_buffer(static_cast(src_buffer.mutable_data()), + static_cast(dst_buffer.mutable_data()), shape_size, quant_info); + break; + default: + LOGGER__ERROR("Input quantization isn't supported from src format type float32 to dst format type = {}", + HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); + THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); + break; + } +} + +void QuantizationBindings::quantize_input_buffer(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &src_dtype, + const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info) +{ + switch (src_dtype) { + case HAILO_FORMAT_TYPE_UINT8: + QuantizationBindings::quantize_input_buffer_from_uint8(src_buffer, dst_buffer, dst_dtype, shape_size, quant_info); + break; + case HAILO_FORMAT_TYPE_UINT16: + QuantizationBindings::quantize_input_buffer_from_uint16(src_buffer, dst_buffer, dst_dtype, shape_size, quant_info); + break; + case HAILO_FORMAT_TYPE_FLOAT32: + QuantizationBindings::quantize_input_buffer_from_float32(src_buffer, dst_buffer, dst_dtype, shape_size, quant_info); + break; + default: + LOGGER__ERROR("Input quantization isn't supported for src format type = {}", HailoRTBindingsCommon::convert_format_type_to_string(dst_dtype)); + THROW_STATUS_ERROR(HAILO_INVALID_ARGUMENT); + break; + } +} + +} /* namespace hailort */ diff --git a/hailort/libhailort/bindings/python/src/quantization_api.hpp b/hailort/libhailort/bindings/python/src/quantization_api.hpp new file mode 100644 index 0000000..85242d7 --- /dev/null +++ b/hailort/libhailort/bindings/python/src/quantization_api.hpp @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) +**/ +/** + * @file quantization_api.hpp + * @brief Quantization python bindings functions + **/ + +#ifndef _HAILO_QUANTIZATION_API_HPP_ +#define _HAILO_QUANTIZATION_API_HPP_ + +#include "hailo/hailort.hpp" +#include "utils.hpp" + +#include +#include + +namespace hailort +{ + +class QuantizationBindings +{ +public: + static void quantize_input_buffer(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &src_dtype, + const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info); + static void dequantize_output_buffer_in_place(py::array dst_buffer, const hailo_format_type_t &src_dtype, + const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info); + static void dequantize_output_buffer(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &src_dtype, + const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info); +private: + static void dequantize_output_buffer_from_uint8(py::array src_buffer, py::array dst_buffer, + const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info); + static void dequantize_output_buffer_from_uint16(py::array src_buffer, py::array dst_buffer, + const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info); + static void dequantize_output_buffer_from_float32(py::array src_buffer, py::array dst_buffer, + const hailo_format_type_t &dst_dtype, uint32_t shape_size, const hailo_quant_info_t &quant_info); + + static void dequantize_output_buffer_from_uint8_in_place(py::array dst_buffer, const hailo_format_type_t &dst_dtype, + uint32_t shape_size, const hailo_quant_info_t &quant_info); + static void dequantize_output_buffer_from_uint16_in_place(py::array dst_buffer, const hailo_format_type_t &dst_dtype, + uint32_t shape_size, const hailo_quant_info_t &quant_info); + static void dequantize_output_buffer_from_float32_in_place(py::array dst_buffer, const hailo_format_type_t &dst_dtype, + uint32_t shape_size, const hailo_quant_info_t &quant_info); + + static void quantize_input_buffer_from_uint8(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &dst_dtype, + uint32_t shape_size, const hailo_quant_info_t &quant_info); + static void quantize_input_buffer_from_uint16(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &dst_dtype, + uint32_t shape_size, const hailo_quant_info_t &quant_info); + static void quantize_input_buffer_from_float32(py::array src_buffer, py::array dst_buffer, const hailo_format_type_t &dst_dtype, + uint32_t shape_size, const hailo_quant_info_t &quant_info); +}; + +} /* namespace hailort */ + +#endif /* _HAILO_QUANTIZATION_API_HPP_ */ diff --git a/hailort/libhailort/bindings/python/src/vdevice_api.hpp b/hailort/libhailort/bindings/python/src/vdevice_api.hpp index 48e7d7a..0468b76 100644 --- a/hailort/libhailort/bindings/python/src/vdevice_api.hpp +++ b/hailort/libhailort/bindings/python/src/vdevice_api.hpp @@ -30,6 +30,11 @@ namespace hailort { +struct VDeviceParamsWrapper { + hailo_vdevice_params_t orig_params; + std::string group_id_str; +}; + class VDeviceWrapper { public: static VDeviceWrapper create(const hailo_vdevice_params_t ¶ms) @@ -37,13 +42,15 @@ public: return VDeviceWrapper(params); }; - static VDeviceWrapper create_from_infos(const std::vector &device_infos) + static VDeviceWrapper create(const VDeviceParamsWrapper ¶ms) { - hailo_vdevice_params_t params = {}; - params.device_count = static_cast(device_infos.size()); - params.device_infos = const_cast(device_infos.data()); - return VDeviceWrapper(params); - }; + return VDeviceWrapper(params.orig_params); + } + + static VDeviceWrapper create_from_ids(const std::vector &device_ids) + { + return VDeviceWrapper(device_ids); + } VDeviceWrapper(const hailo_vdevice_params_t ¶ms) { @@ -53,13 +60,21 @@ public: m_vdevice = vdevice_expected.release(); }; - py::list get_physical_devices_infos() + VDeviceWrapper(const std::vector &device_ids) { - auto phys_devs_infos = m_vdevice->get_physical_devices_infos(); - VALIDATE_EXPECTED(phys_devs_infos); + auto vdevice_expected = VDevice::create(device_ids); + VALIDATE_EXPECTED(vdevice_expected); - return py::cast(phys_devs_infos.value()); - }; + m_vdevice = vdevice_expected.release(); + } + + py::list get_physical_devices_ids() const + { + const auto phys_devs_ids = m_vdevice->get_physical_devices_ids(); + VALIDATE_EXPECTED(phys_devs_ids); + + return py::cast(phys_devs_ids.value()); + } py::list configure(const HefWrapper &hef, const NetworkGroupsParamsMap &configure_params={}) @@ -88,9 +103,10 @@ private: void VDevice_api_initialize_python_module(py::module &m) { py::class_(m, "VDevice") - .def("create", &VDeviceWrapper::create) - .def("create_from_infos", &VDeviceWrapper::create_from_infos) - .def("get_physical_devices_infos", &VDeviceWrapper::get_physical_devices_infos) + .def("create", py::overload_cast(&VDeviceWrapper::create)) + .def("create", py::overload_cast(&VDeviceWrapper::create)) + .def("create_from_ids", &VDeviceWrapper::create_from_ids) + .def("get_physical_devices_ids", &VDeviceWrapper::get_physical_devices_ids) .def("configure", &VDeviceWrapper::configure) .def("release", &VDeviceWrapper::release) ; diff --git a/hailort/libhailort/bindings/python/src/vstream_api.cpp b/hailort/libhailort/bindings/python/src/vstream_api.cpp index 2ae7a98..8d88b84 100644 --- a/hailort/libhailort/bindings/python/src/vstream_api.cpp +++ b/hailort/libhailort/bindings/python/src/vstream_api.cpp @@ -56,7 +56,8 @@ InputVStreamsWrapper InputVStreamsWrapper::create(ConfiguredNetworkGroup &net_gr std::unordered_map> input_vstreams; for (auto &input : input_vstreams_expected.value()) { - input_vstreams.emplace(input.name(), make_shared_nothrow(std::move(input))); + auto input_name = input.name(); + input_vstreams.emplace(input_name, make_shared_nothrow(std::move(input))); } return InputVStreamsWrapper(input_vstreams); } @@ -168,7 +169,8 @@ OutputVStreamsWrapper OutputVStreamsWrapper::create(ConfiguredNetworkGroup &net_ std::unordered_map> output_vstreams; for (auto &output : output_vstreams_expected.value()) { - output_vstreams.emplace(output.name(), make_shared_nothrow(std::move(output))); + auto output_name = output.name(); + output_vstreams.emplace(output_name, make_shared_nothrow(std::move(output))); } return OutputVStreamsWrapper(output_vstreams); } diff --git a/hailort/libhailort/cmake/toolchains/linux.armv7l.cmake b/hailort/libhailort/cmake/toolchains/linux.armv7l.cmake index 207cbe6..2ecb6fc 100644 --- a/hailort/libhailort/cmake/toolchains/linux.armv7l.cmake +++ b/hailort/libhailort/cmake/toolchains/linux.armv7l.cmake @@ -9,4 +9,4 @@ set(CMAKE_LINKER arm-linux-gnueabi-ld) add_compile_options(-march=armv7-a) # pybind is not supported in this platform -set(HAILO_BUILD_PYBIND 0) +set(HAILO_BUILD_PYBIND "OFF" CACHE STRING "hailo_build_pybind" FORCE) diff --git a/hailort/libhailort/cmake/toolchains/linux.armv7lhf.cmake b/hailort/libhailort/cmake/toolchains/linux.armv7lhf.cmake index 6c51638..a5e5105 100644 --- a/hailort/libhailort/cmake/toolchains/linux.armv7lhf.cmake +++ b/hailort/libhailort/cmake/toolchains/linux.armv7lhf.cmake @@ -9,4 +9,4 @@ set(CMAKE_LINKER arm-linux-gnueabihf-ld) add_compile_options(-march=armv7-a) # pybind is not supported in this platform -set(HAILO_BUILD_PYBIND 0) +set(HAILO_BUILD_PYBIND "OFF" CACHE STRING "hailo_build_pybind" FORCE) diff --git a/hailort/libhailort/cmake/toolchains/qnx.aarch64.cmake b/hailort/libhailort/cmake/toolchains/qnx.aarch64.cmake index 721da10..75c7421 100644 --- a/hailort/libhailort/cmake/toolchains/qnx.aarch64.cmake +++ b/hailort/libhailort/cmake/toolchains/qnx.aarch64.cmake @@ -47,6 +47,10 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--build-id=md5 -lang-c set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--build-id=md5 -lang-c++ -lsocket ${EXTRA_CMAKE_LINKER_FLAGS}" CACHE STRING "so_linker_flags") # pybind is not supported in this platform -set(HAILO_BUILD_PYBIND 0) +set(HAILO_BUILD_PYBIND "OFF" CACHE STRING "hailo_build_pybind" FORCE) # GStreamer does not work on QNX currently -set(HAILO_BUILD_GSTREAMER 0) \ No newline at end of file +set(HAILO_BUILD_GSTREAMER "OFF" CACHE STRING "hailo_build_gstreamer" FORCE) +# Hailort service does not work on QNX currently +set(HAILO_BUILD_SERVICE "OFF" CACHE STRING "hailo_build_service" FORCE) +# Set little endian flag for protobuf to work correctly on QNX +add_definitions("-D__LITTLE_ENDIAN__") \ No newline at end of file diff --git a/hailort/libhailort/cmake/toolchains/qnx.x86_64.cmake b/hailort/libhailort/cmake/toolchains/qnx.x86_64.cmake index 1b885de..9ba24e5 100644 --- a/hailort/libhailort/cmake/toolchains/qnx.x86_64.cmake +++ b/hailort/libhailort/cmake/toolchains/qnx.x86_64.cmake @@ -47,6 +47,10 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--build-id=md5 -lang-c set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--build-id=md5 -lang-c++ -lsocket ${EXTRA_CMAKE_LINKER_FLAGS}" CACHE STRING "so_linker_flags") # pybind is not supported in this platform -set(HAILO_BUILD_PYBIND 0) +set(HAILO_BUILD_PYBIND "OFF" CACHE STRING "hailo_build_pybind" FORCE) # GStreamer does not work on QNX currently -set(HAILO_BUILD_GSTREAMER 0) \ No newline at end of file +set(HAILO_BUILD_GSTREAMER "OFF" CACHE STRING "hailo_build_gstreamer" FORCE) +# Hailort service does not work on QNX currently +set(HAILO_BUILD_SERVICE "OFF" CACHE STRING "hailo_build_service" FORCE) +# Set little endian flag for protobuf to work correctly on QNX +add_definitions("-D__LITTLE_ENDIAN__") \ No newline at end of file diff --git a/hailort/libhailort/examples/CMakeLists.txt b/hailort/libhailort/examples/CMakeLists.txt index 40cb002..aae9461 100644 --- a/hailort/libhailort/examples/CMakeLists.txt +++ b/hailort/libhailort/examples/CMakeLists.txt @@ -5,7 +5,7 @@ project(hailort-examples) find_package(Threads REQUIRED) set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package(HailoRT 4.8.1 EXACT REQUIRED) +find_package(HailoRT 4.10.0 EXACT REQUIRED) add_library(example_base INTERFACE) target_link_libraries(example_base INTERFACE HailoRT::libhailort Threads::Threads) diff --git a/hailort/libhailort/examples/README.md b/hailort/libhailort/examples/README.md index fd42924..768b080 100644 --- a/hailort/libhailort/examples/README.md +++ b/hailort/libhailort/examples/README.md @@ -37,12 +37,14 @@ The following examples are provided, demonstrating the HailoRT API: - `infer_pipeline_example` - Basic inference of a shortcut network using inference pipeline (blocking) api. - same as `infer_pipeline_example` C example, uses HailoRT C++ api. - `raw_streams_example` - Basic inference of a shortcut network, same as `raw_streams_example` C example, uses HailoRT C++ api. + - `multi_process_example` - Demonstrates how to work with HailoRT as a service and using the HailoRT scheduler for network groups switching. + Using the script `multi_process_example.sh` one can specify the number of processes to run each hef, see `multi_process_example.sh -h` for more information. ## Compiling with CMake Examples are configured and compiled using the following commands: ```sh -cmake -H. -Bbuild -cmake --build build +cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release +cmake --build build --config release ``` > **_NOTE:_** Write permissions are required to compile the examples from their current directory. If this is not the case, copy the examples directory to another location with the required permissions. diff --git a/hailort/libhailort/examples/c/infer_pipeline_example.c b/hailort/libhailort/examples/c/infer_pipeline_example.c index b5d16be..aca18fc 100644 --- a/hailort/libhailort/examples/c/infer_pipeline_example.c +++ b/hailort/libhailort/examples/c/infer_pipeline_example.c @@ -44,7 +44,7 @@ hailo_status infer(hailo_configured_network_group configured_network_group, src_data = malloc(input_buffer.raw_buffer.size); REQUIRE_ACTION(src_data != NULL, status = HAILO_OUT_OF_HOST_MEMORY, l_free_buffers, "Failed to allocate input buffer"); // Prepare src data here - for (size_t j = 0; j < frame_size; j++) { + for (size_t j = 0; j < input_buffer.raw_buffer.size; j++) { src_data[j] = (uint8_t)(rand() % 256); } diff --git a/hailort/libhailort/examples/c/multi_device_example.c b/hailort/libhailort/examples/c/multi_device_example.c index a631e5f..787168b 100644 --- a/hailort/libhailort/examples/c/multi_device_example.c +++ b/hailort/libhailort/examples/c/multi_device_example.c @@ -1,14 +1,6 @@ /** - * Copyright 2020 (C) Hailo Technologies Ltd. - * All rights reserved. - * - * Hailo Technologies Ltd. ("Hailo") disclaims any warranties, including, but not limited to, - * the implied warranties of merchantability and fitness for a particular purpose. - * This software is provided on an "AS IS" basis, and Hailo has no obligation to provide maintenance, - * support, updates, enhancements, or modifications. - * - * You may use this software in the development of any project. - * You shall not reproduce, modify or distribute this software without prior written permission. + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) **/ /** * @file multi_device_example.c @@ -23,7 +15,7 @@ #define INFER_FRAME_COUNT (100) #define MAX_EDGE_LAYERS (16) -#define MAX_PCIE_DEVICES (16) +#define MAX_DEVICES (16) #define HEF_FILE ("hefs/shortcut_net.hef") @@ -136,8 +128,8 @@ int main() { hailo_status status = HAILO_UNINITIALIZED; hailo_vdevice vdevice = NULL; - hailo_pcie_device_info_t device_infos[MAX_PCIE_DEVICES]; - size_t actual_count = 0; + hailo_device_id_t device_ids[MAX_DEVICES]; + size_t actual_count = MAX_DEVICES; hailo_vdevice_params_t params = {0}; hailo_hef hef = NULL; hailo_configure_params_t config_params = {0}; @@ -151,8 +143,8 @@ int main() hailo_input_vstream input_vstreams[MAX_EDGE_LAYERS] = {NULL}; hailo_output_vstream output_vstreams[MAX_EDGE_LAYERS] = {NULL}; - status = hailo_scan_pcie_devices(device_infos, MAX_PCIE_DEVICES, &actual_count); - REQUIRE_SUCCESS(status, l_exit, "Failed to scan pcie_device"); + status = hailo_scan_devices(NULL, device_ids, &actual_count); + REQUIRE_SUCCESS(status, l_exit, "Failed to scan devices"); status = hailo_init_vdevice_params(¶ms); REQUIRE_SUCCESS(status, l_exit, "Failed init vdevice_params"); diff --git a/hailort/libhailort/examples/c/power_measurement_example.c b/hailort/libhailort/examples/c/power_measurement_example.c index b4c75ea..03a0ff1 100644 --- a/hailort/libhailort/examples/c/power_measurement_example.c +++ b/hailort/libhailort/examples/c/power_measurement_example.c @@ -18,7 +18,7 @@ #define MEASUREMENT_BUFFER_INDEX (HAILO_MEASUREMENT_BUFFER_INDEX_0) #define MEASUREMENTS_DURATION_SECS (5) -#define MAX_PCIE_DEVICES (16) +#define MAX_DEVICES (16) #define MEASUREMENT_UNITS(__type) \ ((HAILO_POWER_MEASUREMENT_TYPES__POWER == __type) ? ("W") : ("mA")) @@ -80,17 +80,17 @@ int main(int argc, char **argv) { hailo_status status = HAILO_UNINITIALIZED; hailo_vdevice vdevice = NULL; - hailo_pcie_device_info_t device_infos[MAX_PCIE_DEVICES]; - size_t actual_device_count = 0; + hailo_device_id_t device_ids[MAX_DEVICES]; + size_t actual_device_count = MAX_DEVICES; hailo_vdevice_params_t params = {0}; - hailo_device physical_devices[MAX_PCIE_DEVICES]; - hailo_power_measurement_data_t measurement_result[MAX_PCIE_DEVICES] = {0}; + hailo_device physical_devices[MAX_DEVICES]; + hailo_power_measurement_data_t measurement_result[MAX_DEVICES] = {0}; hailo_power_measurement_types_t measurement_type = {0}; parse_arguments(argc, argv, &measurement_type); - status = hailo_scan_pcie_devices(device_infos, MAX_PCIE_DEVICES, &actual_device_count); - REQUIRE_SUCCESS(status, l_exit, "Failed to scan pcie_device"); + status = hailo_scan_devices(NULL, device_ids, &actual_device_count); + REQUIRE_SUCCESS(status, l_exit, "Failed to scan devices"); status = hailo_init_vdevice_params(¶ms); REQUIRE_SUCCESS(status, l_exit, "Failed to init vdevice_params"); diff --git a/hailort/libhailort/examples/c/raw_streams_example.c b/hailort/libhailort/examples/c/raw_streams_example.c index 33592bf..41fc88d 100644 --- a/hailort/libhailort/examples/c/raw_streams_example.c +++ b/hailort/libhailort/examples/c/raw_streams_example.c @@ -5,7 +5,7 @@ /** * @file raw_streams_example.c * This example demonstrates basic usage of HailoRT streaming api. - * It loads an HEF network with multiple inputs and multiple outputs into a Hailo PCIe device and performs a + * It loads an HEF network with multiple inputs and multiple outputs into a Hailo device and performs a * short inference. **/ @@ -170,6 +170,8 @@ l_cleanup: int main() { hailo_status status = HAILO_UNINITIALIZED; + hailo_device_id_t device_id = {0}; + size_t actual_devices_count = 1; hailo_device device = NULL; hailo_hef hef = NULL; hailo_configure_params_t configure_params = {0}; @@ -184,13 +186,13 @@ int main() size_t number_output_streams = 0; size_t index = 0; - status = hailo_create_pcie_device(NULL, &device); - /* - For simplicity, passing NULL as `device_info` - This function will fail in case more than one PCIe device is present. - See `hailo_scan_pcie_devices` and `hailo_create_pcie_device` functions documentation. - */ - REQUIRE_SUCCESS(status, l_exit, "Failed to create pcie_device"); + status = hailo_scan_devices(NULL, &device_id, &actual_devices_count); + REQUIRE_SUCCESS(status, l_exit, "Failed to scan devices"); + REQUIRE_ACTION(1 == actual_devices_count, status = HAILO_INVALID_OPERATION, l_exit, + "Only 1 device on the system is supported on the example"); + status = hailo_create_device_by_id(&device_id, &device); + REQUIRE_SUCCESS(status, l_exit, "Failed to create device"); status = hailo_create_hef_file(&hef, HEF_FILE); REQUIRE_SUCCESS(status, l_release_device, "Failed creating hef file %s", HEF_FILE); diff --git a/hailort/libhailort/examples/c/switch_network_groups_example.c b/hailort/libhailort/examples/c/switch_network_groups_example.c index e3f258c..e1ac03f 100644 --- a/hailort/libhailort/examples/c/switch_network_groups_example.c +++ b/hailort/libhailort/examples/c/switch_network_groups_example.c @@ -5,7 +5,7 @@ /** * @file switch_network_groups_example.c * This example demonstrates basic usage of HailoRT streaming api over multiple network groups, using VStreams. - * It loads several network_groups (via several HEFs) into a Hailo PCIe VDevice and performs a inferences on all of them in parallel. + * It loads several network_groups (via several HEFs) into a Hailo VDevice and performs a inferences on all of them in parallel. * The network_groups switching is performed automatically by the HailoRT scheduler. **/ diff --git a/hailort/libhailort/examples/c/switch_single_io_network_groups_manually_example.c b/hailort/libhailort/examples/c/switch_single_io_network_groups_manually_example.c index 3d09f95..190d087 100644 --- a/hailort/libhailort/examples/c/switch_single_io_network_groups_manually_example.c +++ b/hailort/libhailort/examples/c/switch_single_io_network_groups_manually_example.c @@ -5,7 +5,7 @@ /** * @file switch_single_io_network_groups_manually_example.c * This example demonstrates basic usage of HailoRT streaming api over multiple network groups, using vstreams. - * It loads several HEF networks with a single input and a single output into a Hailo PCIe VDevice and performs a inference on each one. + * It loads several HEF networks with a single input and a single output into a Hailo VDevice and performs a inference on each one. * After inference is finished, the example switches to the next HEF and start inference again. **/ diff --git a/hailort/libhailort/examples/cpp/CMakeLists.txt b/hailort/libhailort/examples/cpp/CMakeLists.txt index 47d2913..60d822c 100644 --- a/hailort/libhailort/examples/cpp/CMakeLists.txt +++ b/hailort/libhailort/examples/cpp/CMakeLists.txt @@ -24,6 +24,9 @@ target_link_libraries(cpp_multi_device_example PRIVATE example_base) add_executable(cpp_power_measurement_example power_measurement_example.cpp) target_link_libraries(cpp_power_measurement_example PRIVATE example_base) +add_executable(cpp_multi_process_example multi_process_example.cpp) +target_link_libraries(cpp_multi_process_example PRIVATE example_base) + set(EXAMPLES_CPP_TARGETS cpp_vstreams_example cpp_infer_pipeline_example @@ -33,4 +36,5 @@ set(EXAMPLES_CPP_TARGETS cpp_switch_network_groups_manually_example cpp_multi_device_example cpp_power_measurement_example + cpp_multi_process_example PARENT_SCOPE) diff --git a/hailort/libhailort/examples/cpp/infer_pipeline_example.cpp b/hailort/libhailort/examples/cpp/infer_pipeline_example.cpp index 0b03790..3327bf8 100644 --- a/hailort/libhailort/examples/cpp/infer_pipeline_example.cpp +++ b/hailort/libhailort/examples/cpp/infer_pipeline_example.cpp @@ -3,9 +3,9 @@ * Distributed under the MIT license (https://opensource.org/licenses/MIT) **/ /** - * @file vstreams_example + * @file infer_pipeline_example.cpp * This example demonstrates the basic data-path on HailoRT using the high level API - Virtual Stream Pipeline. - * The program creates a device according to the provdied IP address, generates a random dataset, + * The program creates a device according to the provided IP address, generates a random dataset, * and runs it through the device with virtual streams pipeline. **/ diff --git a/hailort/libhailort/examples/cpp/multi_device_example.cpp b/hailort/libhailort/examples/cpp/multi_device_example.cpp index 2b1e2df..02ee32a 100644 --- a/hailort/libhailort/examples/cpp/multi_device_example.cpp +++ b/hailort/libhailort/examples/cpp/multi_device_example.cpp @@ -1,14 +1,6 @@ /** - * Copyright 2020 (C) Hailo Technologies Ltd. - * All rights reserved. - * - * Hailo Technologies Ltd. ("Hailo") disclaims any warranties, including, but not limited to, - * the implied warranties of merchantability and fitness for a particular purpose. - * This software is provided on an "AS IS" basis, and Hailo has no obligation to provide maintenance, - * support, updates, enhancements, or modifications. - * - * You may use this software in the development of any project. - * You shall not reproduce, modify or distribute this software without prior written permission. + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) **/ /** * @file multi_device_example.cpp @@ -134,9 +126,9 @@ hailo_status infer(std::vector &input_streams, std::vectorsize()) { - std::cerr << "Invalid amount of networks in group " << net_group.get_network_group_name() << std::endl; + std::cerr << "Invalid amount of networks in group " << net_group.name() << std::endl; return make_unexpected(networks_infos.status()); } diff --git a/hailort/libhailort/examples/cpp/multi_process_example.cpp b/hailort/libhailort/examples/cpp/multi_process_example.cpp new file mode 100644 index 0000000..6040507 --- /dev/null +++ b/hailort/libhailort/examples/cpp/multi_process_example.cpp @@ -0,0 +1,177 @@ +/** + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + **/ +/** + * @file multi_process_example.cpp + * This example demonstrates the basic usage of HailoRT as a service. + * The program creates a virtual device with multi_process_service flag and uses the HailoRT API to run inference using VStreams. + * The network_groups switching is performed automatically by the HailoRT scheduler. + * + **/ + +#include "hailo/hailort.hpp" + +#include + +constexpr size_t FRAMES_COUNT = 100; +constexpr bool QUANTIZED = true; +constexpr hailo_format_type_t FORMAT_TYPE = HAILO_FORMAT_TYPE_AUTO; +constexpr size_t MAX_LAYER_EDGES = 16; +constexpr uint32_t DEVICE_COUNT = 1; + +using namespace hailort; + +Expected> configure_network_group(const std::string &hef_path, VDevice &vdevice) +{ + auto hef = Hef::create(hef_path); + if (!hef) { + return make_unexpected(hef.status()); + } + + auto configure_params = hef->create_configure_params(HAILO_STREAM_INTERFACE_PCIE); + if (!configure_params) { + return make_unexpected(configure_params.status()); + } + + auto network_groups = vdevice.configure(hef.value(), configure_params.value()); + if (!network_groups) { + return make_unexpected(network_groups.status()); + } + + if (1 != network_groups->size()) { + std::cerr << "Invalid amount of network groups" << std::endl; + return make_unexpected(HAILO_INTERNAL_FAILURE); + } + + return std::move(network_groups->at(0)); +} + +void write_all(InputVStream &input, hailo_status &status) +{ + std::vector data(input.get_frame_size()); + for (size_t i = 0; i < FRAMES_COUNT; i++) { + status = input.write(MemoryView(data.data(), data.size())); + if (HAILO_SUCCESS != status) { + return; + } + } + + status = HAILO_SUCCESS; + return; +} + +void read_all(OutputVStream &output, hailo_status &status) +{ + std::vector data(output.get_frame_size()); + for (size_t i = 0; i < FRAMES_COUNT; i++) { + status = output.read(MemoryView(data.data(), data.size())); + if (HAILO_SUCCESS != status) { + return; + } + } + status = HAILO_SUCCESS; + return; +} + +hailo_status infer(std::vector &input_streams, std::vector &output_streams, const std::string &hef_path) +{ + hailo_status status = HAILO_SUCCESS; // Success oriented + hailo_status input_status[MAX_LAYER_EDGES] = {HAILO_UNINITIALIZED}; + hailo_status output_status[MAX_LAYER_EDGES] = {HAILO_UNINITIALIZED}; + std::unique_ptr input_threads[MAX_LAYER_EDGES]; + std::unique_ptr output_threads[MAX_LAYER_EDGES]; + size_t input_thread_index = 0; + size_t output_thread_index = 0; + + // Create read threads + for (output_thread_index = 0 ; output_thread_index < output_streams.size(); output_thread_index++) { + output_threads[output_thread_index] = std::make_unique(read_all, + std::ref(output_streams[output_thread_index]), std::ref(output_status[output_thread_index])); + } + + // Create write threads + for (input_thread_index = 0 ; input_thread_index < input_streams.size(); input_thread_index++) { + input_threads[input_thread_index] = std::make_unique(write_all, + std::ref(input_streams[input_thread_index]), std::ref(input_status[input_thread_index])); + } + + // Join write threads + for (size_t i = 0; i < input_thread_index; i++) { + input_threads[i]->join(); + if (HAILO_SUCCESS != input_status[i]) { + status = input_status[i]; + } + } + + // Join read threads + for (size_t i = 0; i < output_thread_index; i++) { + output_threads[i]->join(); + if (HAILO_SUCCESS != output_status[i]) { + status = output_status[i]; + } + } + + if (HAILO_SUCCESS == status) { + std::cout << "Inference finished successfully on " << hef_path << std::endl; + } + + return status; +} + +Expected> create_vdevice() +{ + hailo_vdevice_params_t params; + auto status = hailo_init_vdevice_params(¶ms); + if (HAILO_SUCCESS != status) { + std::cerr << "Failed init vdevice_params, status = " << status << std::endl; + return make_unexpected(status); + } + params.scheduling_algorithm = HAILO_SCHEDULING_ALGORITHM_ROUND_ROBIN; + params.device_count = DEVICE_COUNT; + params.multi_process_service = true; + params.group_id = "SHARED"; + + return VDevice::create(params); +} + +int main(int argc, char **argv) +{ + if (2 > argc) { + std::cerr << "Missing HEF file path!\nUsage: example " << std::endl; + return HAILO_INVALID_ARGUMENT; + } + auto hef_path = argv[1]; + + auto vdevice = create_vdevice(); + if (!vdevice) { + std::cerr << "Failed create vdevice, status = " << vdevice.status() << std::endl; + return vdevice.status(); + } + + auto network_group = configure_network_group(hef_path, *vdevice.value()); + if (!network_group) { + std::cerr << "Failed to configure network group " << hef_path << std::endl; + return network_group.status(); + } + + auto vstreams = VStreamsBuilder::create_vstreams(*network_group.value(), QUANTIZED, FORMAT_TYPE); + if (!vstreams) { + std::cerr << "Failed creating vstreams " << vstreams.status() << std::endl; + return vstreams.status(); + } + + if (vstreams->first.size() > MAX_LAYER_EDGES || vstreams->second.size() > MAX_LAYER_EDGES) { + std::cerr << "Trying to infer network with too many input/output virtual streams, Maximum amount is " << + MAX_LAYER_EDGES << " (either change HEF or change the definition of MAX_LAYER_EDGES)"<< std::endl; + return HAILO_INVALID_OPERATION; + } + + auto status = infer(vstreams->first, vstreams->second, hef_path); + if (HAILO_SUCCESS != status) { + std::cerr << "Inference on " << hef_path << " failed with status " << status << std::endl; + return status; + } + + return HAILO_SUCCESS; +} diff --git a/hailort/libhailort/examples/cpp/multi_process_example.sh b/hailort/libhailort/examples/cpp/multi_process_example.sh new file mode 100755 index 0000000..7d1386b --- /dev/null +++ b/hailort/libhailort/examples/cpp/multi_process_example.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +readonly first_hef="hefs/multi_network_shortcut_net.hef" +readonly second_hef="hefs/shortcut_net.hef" +readonly max_processes_count=8 +readonly default_processes_count=1 + +function print_usage { + echo "Usage: [-h help] [-n] [-m]" + echo " -h Print usage and exit" + echo " -n Number of processes to run example with $first_hef. Max is $max_processes_count (defualt is $default_processes_count)" + echo " -m Number of processes to run example with $second_hef. Max is $max_processes_count (defualt is $default_processes_count)" +} + +first_hef_count=1 +second_hef_count=1 +while getopts "hn:m:" opt; do + case "${opt}" in + n) first_hef_count=${OPTARG} ;; + m) second_hef_count=${OPTARG} ;; + \?) echo "Try -h' for more information." ; exit 1 ;; + h) print_usage; exit 0 ;; + esac +done + +if (( $first_hef_count > $max_processes_count )) +then + echo "Max processes to run each hef is $max_processes_count! Given $first_hef_count for $first_hef" + exit 1 +fi + +if (( $second_hef_count > $max_processes_count )) +then + echo "Max processes to run each hef is $max_processes_count! Given $second_hef_count for $second_hef" + exit 1 +fi + +# Check service is enabled +service hailort status | grep 'disabled;' > /dev/null 2>&1 +if [ $? == 0 ] +then + echo "HailoRT service is not enabled." + echo "To enable and start the service run the following command: 'sudo systemctl enable --now hailort.service'" + exit 1 +fi + +# Check service is active +service hailort status | grep 'active (running)' > /dev/null 2>&1 +if [ $? != 0 ] +then + echo "HailoRT service is not active." + echo "To start the service run the following command: 'sudo systemctl start hailort.service'" + exit 1 +fi + +max=$(( $first_hef_count > $second_hef_count ? $first_hef_count : $second_hef_count )) +for i in $(seq 0 $max) +do + if (( $i < $first_hef_count)) + then + ./build/cpp/cpp_multi_process_example $first_hef & + fi + + if (( $i < $second_hef_count)) + then + ./build/cpp/cpp_multi_process_example $second_hef & + fi +done + +wait +exit 0 \ No newline at end of file diff --git a/hailort/libhailort/examples/cpp/raw_streams_example.cpp b/hailort/libhailort/examples/cpp/raw_streams_example.cpp index df2c37e..76fe911 100644 --- a/hailort/libhailort/examples/cpp/raw_streams_example.cpp +++ b/hailort/libhailort/examples/cpp/raw_streams_example.cpp @@ -26,7 +26,12 @@ Expected> configure_network_group(Device return make_unexpected(hef.status()); } - auto configure_params = hef->create_configure_params(HAILO_STREAM_INTERFACE_PCIE); + auto stream_interface = device.get_default_streams_interface(); + if (!stream_interface) { + return make_unexpected(stream_interface.status()); + } + + auto configure_params = hef->create_configure_params(stream_interface.value()); if (!configure_params) { return make_unexpected(configure_params.status()); } @@ -143,13 +148,13 @@ hailo_status infer(InputStreamRefVector &input_streams, OutputStreamRefVector &o int main() { - auto device = Device::create_pcie(); /* - For simplicity, not passing `device_info` - This function will fail in case more than one PCIe device is present. - See `hailort::Device::scan_pcie` and `hailort::Device::create_pcie` functions documentation. + For simplicity, not passing `device_id` - This function will fail in case more than one device is present. + See `hailort::Device::scan_devices` and `hailort::Device::create` functions documentation. */ + auto device = Device::create(); if (!device) { - std::cerr << "Failed create_pcie " << device.status() << std::endl; + std::cerr << "Failed create device " << device.status() << std::endl; return device.status(); } diff --git a/hailort/libhailort/examples/cpp/switch_network_groups_example.cpp b/hailort/libhailort/examples/cpp/switch_network_groups_example.cpp index a0a067d..b100228 100644 --- a/hailort/libhailort/examples/cpp/switch_network_groups_example.cpp +++ b/hailort/libhailort/examples/cpp/switch_network_groups_example.cpp @@ -5,7 +5,7 @@ /** * @file switch_network_groups_example.cpp * This example demonstrates basic usage of HailoRT streaming api over multiple network groups, using VStreams. - * It loads several network_groups (via several HEFs) into a Hailo PCIe VDevice and performs a inferences on all of them in parallel. + * It loads several network_groups (via several HEFs) into a Hailo VDevice and performs a inferences on all of them in parallel. * The network_groups switching is performed automatically by the HailoRT scheduler. **/ diff --git a/hailort/libhailort/examples/cpp/switch_network_groups_manually_example.cpp b/hailort/libhailort/examples/cpp/switch_network_groups_manually_example.cpp index aabfe67..7155e91 100644 --- a/hailort/libhailort/examples/cpp/switch_network_groups_manually_example.cpp +++ b/hailort/libhailort/examples/cpp/switch_network_groups_manually_example.cpp @@ -5,7 +5,7 @@ /** * @file switch_network_groups_manually_example.cpp * This example demonstrates basic usage of HailoRT streaming api over multiple networks, using vstreams. - * It loads several HEF networks with single/multiple inputs and single/multiple outputs into a Hailo PCIe VDevice and performs a + * It loads several HEF networks with single/multiple inputs and single/multiple outputs into a Hailo VDevice and performs a * short inference on each one. * After inference is finished, the example switches to the next HEF and start inference again. **/ diff --git a/hailort/libhailort/hef.proto b/hailort/libhailort/hef.proto index f629ed7..80aa2e8 100644 --- a/hailort/libhailort/hef.proto +++ b/hailort/libhailort/hef.proto @@ -1,5 +1,7 @@ syntax = "proto3"; +option optimize_for = LITE_RUNTIME; + message ProtoHEFHef { ProtoHEFHeader header = 1; repeated ProtoHEFNetworkGroup network_groups = 2; @@ -13,7 +15,7 @@ message ProtoHEFIncludedFeatures { bool abbale = 1; bool posted_writes = 2; bool ddr = 3; - uint32 number_of_contexts = 4; + bool is_multi_context = 4; bool compressed_params = 5; bool transpose_component = 6; bool padded_ddr_buffers = 7; @@ -33,7 +35,7 @@ enum ProtoHEFExtensionType { IS_NMS_MULTI_CONTEXT = 9; OFFLOAD_ARGMAX = 10; HW_PADDING = 11; - PRELIMINARY_RUN_ASAP = 12; + KO_RUN_ASAP = 12; UNUSED = 0XFFFF; } @@ -42,7 +44,7 @@ message ProtoHEFExtension { string name = 2; } -message ProtoHEFOptionalExtension { +message ProtoHEFOptionalExtension { uint32 type_index = 1; string name = 2; } @@ -66,6 +68,7 @@ enum ProtoHEFHwArch { PROTO__HW_ARCH__HAILO8 = 0; PROTO__HW_ARCH__HAILO8P = 1; PROTO__HW_ARCH__HAILO8R = 2; + PROTO__HW_ARCH__HAILO8L = 3; // Reserving low numbers to public hw archs PROTO__HW_ARCH__SAGE_A0 = 100; @@ -76,6 +79,16 @@ enum ProtoHEFHwArch { PROTO__HW_ARCH__LAVENDER = 105; } + +message ProtoHEFPhysicalLayout { + uint32 partial_clusters_layout_bitmap = 1; +}; + +message ProtoHEFPartialNetworkGroup { + ProtoHEFNetworkGroup network_group = 1; + ProtoHEFPhysicalLayout layout = 2; +}; + message ProtoHEFNetworkGroup { // Metadata describing the network_group ProtoHEFNetworkGroupMetadata network_group_metadata = 1; @@ -94,6 +107,15 @@ message ProtoHEFNetworkGroup { // Connected component names ordered by index repeated string networks_names = 6; + + // Partial network groups configuration + repeated ProtoHEFPartialNetworkGroup partial_network_groups = 7; +} + +message ProtoHEFCfgChannelConfig { + uint32 cfg_channel_index = 1; + // which dma feeds this cfg channel + uint32 engine_id = 2; } message ProtoHEFNetworkGroupMetadata { @@ -109,8 +131,14 @@ message ProtoHEFNetworkGroupMetadata { // The bottleneck post placement fps double bottleneck_fps = 4; + // The latency from simulation + double latency = 6; + // Number of pcie channels being used for config uint32 cfg_channels_count = 5; + + // config information regarding the cfg channels + repeated ProtoHEFCfgChannelConfig cfg_channels_config = 7; } message ProtoHEFFusedLayersMetadata { @@ -211,6 +239,7 @@ message ProtoHEFAction { ProtoHEFActionNone none = 9; ProtoHEFActionAllowInputDataflow allow_input_dataflow = 10; ProtoHEFActionWaitForModuleConfigDone wait_for_module_config_done = 11; + ProtoHEFActionDebugSleep debug_sleep = 12; } } @@ -236,6 +265,11 @@ message ProtoHEFActionWriteCompressedData { bytes data = 2; } +message ProtoHEFActionDebugSleep { + // Debug action that asks FW to sleep for some duration + uint64 duration_in_usec = 1; +} + message InitialL3 { // L3 cut index sequencer should start from uint32 initial_l3_index = 1; @@ -439,6 +473,7 @@ message ProtoHEFConnectedContextInfo { uint32 index = 1; string name = 2; uint32 sys_index = 3; + uint32 engine_id = 4; } message ProtoHEFResourceIndices { @@ -464,6 +499,7 @@ message ProtoHEFEdgeLayerBase { repeated ProtoHEFResourceIndices buffer_indices = 13; bool host_argmax = 14; uint32 max_shmifo_size = 15; + uint32 engine_id = 16; } // Additional information for specific layer types @@ -512,4 +548,4 @@ message ProtoHEFHwPackageInfo { uint32 dense_alignment_size = 1; uint32 axi_width = 2; uint32 memory_width = 3; -} +} \ No newline at end of file diff --git a/hailort/libhailort/include/hailo/buffer.hpp b/hailort/libhailort/include/hailo/buffer.hpp index 9bacefb..734386a 100644 --- a/hailort/libhailort/include/hailo/buffer.hpp +++ b/hailort/libhailort/include/hailo/buffer.hpp @@ -21,6 +21,9 @@ namespace hailort { +class Buffer; +using BufferPtr = std::shared_ptr; + class HAILORTAPI Buffer final { public: @@ -55,15 +58,20 @@ public: /** * Create functions, may fail be due to out of memory */ - // Crates a buffer size bytes long, without setting the memory + // Creates a buffer size bytes long, without setting the memory static Expected create(size_t size); - // Crates a buffer size bytes long, setting the memory to default_value + // Creates a buffer size bytes long, setting the memory to default_value static Expected create(size_t size, uint8_t default_value); // Creates a copy of the data pointed to by src, size bytes long static Expected create(const uint8_t *src, size_t size); // Creates a new buffer with the contents of the initializer_list static Expected create(std::initializer_list init); + // Creates a buffer size bytes long, without setting the memory + static Expected create_shared(size_t size); + // Creates a buffer size bytes long, setting the memory to default_value + static Expected create_shared(size_t size, uint8_t default_value); + // Moves the data pointed to by other into the lvalue: // * other is invalidated. // * The previous data pointed to by the lvalue is freed diff --git a/hailort/libhailort/include/hailo/device.hpp b/hailort/libhailort/include/hailo/device.hpp index 90eba66..2fb1521 100644 --- a/hailort/libhailort/include/hailo/device.hpp +++ b/hailort/libhailort/include/hailo/device.hpp @@ -24,10 +24,6 @@ namespace hailort { -typedef enum { - BOOTLOADER_VERSION_HAILO8_B0_UNSIGNED = 0, - BOOTLOADER_VERSION_HAILO8_B0_SIGNED -} hailo_bootloader_version_t; /** @defgroup group_type_definitions HailoRT CPP API definitions * @{ @@ -50,6 +46,17 @@ public: CORE }; + /** + * Returns the device_id string on all available devices in the system. + * The device id is a unique identitier for the device on the system. + * + * @return Upon success, returns Expected of a vector of std::string containing the information. + * Otherwise, returns Unexpected of ::hailo_status error. + * @note ethernet devices are not considered "devices in the system", so they are not scanned in this function. + * use :scan_eth for ethernet devices. + */ + static Expected> scan(); + /** * Returns information on all available pcie devices in the system. * @@ -80,6 +87,26 @@ public: static Expected> scan_eth_by_host_address(const std::string &host_address, std::chrono::milliseconds timeout); + /** + * Creates a device if there is only one system device detected in the system. + * + * @return Upon success, returns Expected of a unique_ptr to Device object. + * Otherwise, returns Unexpected of ::hailo_status error. + */ + static Expected> create(); + + /** + * Creates a device by the given device id. + * + * @param[in] device_id Device id string, can represent several device types: + * [-] for pcie devices - pcie bdf (XXXX:XX:XX.X) + * [-] for ethernet devices - ip address (xxx.xxx.xxx.xxx) + * + * @return Upon success, returns Expected of a unique_ptr to Device object. + * Otherwise, returns Unexpected of ::hailo_status error. + */ + static Expected> create(const std::string &device_id); + /** * Creates pcie device if there is only one pcie device connected * @@ -133,9 +160,14 @@ public: */ static Expected pcie_device_info_to_string(const hailo_pcie_device_info_t &device_info); - // For internal use only - static bool is_core_driver_loaded(); - static Expected> create_core_device(); + /** + * Returns the device type of the given device id string. + * + * @param[in] device_id A std::string device id to check. + * @return Upon success, returns Expected of the device type. + * Otherwise, returns Unexpected of ::hailo_status error. + */ + static Expected get_device_type(const std::string &device_id); /** * Configure the device from an hef. @@ -192,7 +224,9 @@ public: hailo_status set_fw_logger(hailo_fw_logger_level_t level, uint32_t interface_mask); /** - * Change throttling state of temperature protection component. + * Change throttling state of temperature protection and overcurrent protection components. + * In case that change throttling state of temperature protection didn't succeed, + * the change throttling state of overcurrent protection is executed. * * @param[in] should_activate Should be true to enable or false to disable. * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns an ::hailo_status error. @@ -218,7 +252,8 @@ public: hailo_status read_memory(uint32_t address, MemoryView &data); /** - * Get current throttling state of temperature protection component. + * Get current throttling state of temperature protection and overcurrent protection components. + * If any throttling is enabled, the function return true. * * @return Upon success, returns Expected of @a bool, indicates weather the throttling state is active or not. * Otherwise, returns Unexpected of ::hailo_status error. @@ -320,7 +355,7 @@ public: * averaging_factor samples. The sensor provides a new value every: 2 * sampling_period * averaging_factor {ms}. * The firmware wakes up every interval_milliseconds {ms} and checks the sensor. * If there is a new value to read from the sensor, the firmware reads it. - * Note that the average calculated by the firmware is “average of averages”, + * Note that the average calculated by the firmware is 'average of averages', * because it averages values that have already been averaged by the sensor. * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. * @note This function is deprecated. One should use 'Device::start_power_measurement(hailo_averaging_factor_t averaging_factor, hailo_sampling_period_t sampling_period)' @@ -363,7 +398,7 @@ public: * averaging_factor samples. The sensor provides a new value every: 2 * sampling_period * averaging_factor {ms}. * The firmware wakes up every interval_milliseconds {ms} and checks the sensor. * If there is a new value to read from the sensor, the firmware reads it. - * Note that the average calculated by the firmware is “average of averages”, + * Note that the average calculated by the firmware is 'average of averages', * because it averages values that have already been averaged by the sensor. * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. */ @@ -672,6 +707,8 @@ public: protected: Device(Type type); + static Expected> create_core(); + virtual hailo_status wait_for_wakeup() = 0; virtual void increment_control_sequence() = 0; hailo_status fw_interact(uint8_t *request_buffer, size_t request_size, uint8_t *response_buffer, size_t *response_size); diff --git a/hailort/libhailort/include/hailo/expected.hpp b/hailort/libhailort/include/hailo/expected.hpp index cae0ea9..c77c2a0 100644 --- a/hailort/libhailort/include/hailo/expected.hpp +++ b/hailort/libhailort/include/hailo/expected.hpp @@ -300,7 +300,7 @@ public: * * NOTE: std::enable_if_t used because the variadic constructor can sometimes have a better cv-qualifier match than * the other constructors. For example, candidate is: Expected::operator bool() const [with T = int] - * while conversion from ‘Expected’ to ‘int’. + * while conversion from 'Expected' to 'int'. * See https://stackoverflow.com/questions/51937519/class-constructor-precedence-with-a-variadic-template-constructor-for-a-value-wr */ template ::value, int> = 0> @@ -314,7 +314,7 @@ public: * * NOTE: std::enable_if_t used because the variadic constructor can sometimes have a better cv-qualifier match than * the other constructors. For example, candidate is: Expected::operator bool() const [with T = int] - * while conversion from ‘Expected’ to ‘int’. + * while conversion from 'Expected' to 'int'. * See https://stackoverflow.com/questions/51937519/class-constructor-precedence-with-a-variadic-template-constructor-for-a-value-wr * * NOTE: ExpectedKey can only be constructed from Expected. This way Expected will be the only class that diff --git a/hailort/libhailort/include/hailo/hailort.h b/hailort/libhailort/include/hailo/hailort.h index 3519e7c..746e374 100644 --- a/hailort/libhailort/include/hailo/hailort.h +++ b/hailort/libhailort/include/hailo/hailort.h @@ -65,6 +65,10 @@ extern "C" { #define HAILO_SOC_PM_VALUES_BYTES_LENGTH (24) #define HAILO_MAX_TEMPERATURE_THROTTLING_LEVELS_NUMBER (4) +#define HAILO_UNIQUE_VDEVICE_GROUP_ID ("UNIQUE") +#define HAILO_DEFAULT_VDEVICE_GROUP_ID HAILO_UNIQUE_VDEVICE_GROUP_ID + + typedef float float32_t; typedef double float64_t; typedef uint16_t nms_bbox_counter_t; @@ -136,7 +140,7 @@ typedef uint16_t nms_bbox_counter_t; HAILO_STATUS__X(62, HAILO_STREAM_ABORTED /*!< Stream aborted due to an external event */)\ HAILO_STATUS__X(63, HAILO_STREAM_INTERNAL_ABORT /*!< Stream recv/send was aborted */)\ HAILO_STATUS__X(64, HAILO_PCIE_DRIVER_NOT_INSTALLED /*!< Pcie driver is not installed */)\ - HAILO_STATUS__X(65, HAILO_NOT_AVAILABLE /*!< Componenet is not available */)\ + HAILO_STATUS__X(65, HAILO_NOT_AVAILABLE /*!< Component is not available */)\ HAILO_STATUS__X(66, HAILO_TRAFFIC_CONTROL_FAILURE /*!< Traffic control failure */)\ HAILO_STATUS__X(67, HAILO_INVALID_SECOND_STAGE /*!< Second stage bin is invalid */)\ HAILO_STATUS__X(68, HAILO_INVALID_PIPELINE /*!< Pipeline is invalid */)\ @@ -147,7 +151,9 @@ typedef uint16_t nms_bbox_counter_t; HAILO_STATUS__X(73, HAILO_DEVICE_IN_USE /*!< The device is already in use */)\ HAILO_STATUS__X(74, HAILO_OUT_OF_PHYSICAL_DEVICES /*!< There are not enough physical devices */)\ HAILO_STATUS__X(75, HAILO_INVALID_DEVICE_ARCHITECTURE /*!< Invalid device architecture */)\ - HAILO_STATUS__X(76, HAILO_INVALID_DRIVER_VERSION /*!< Invalid driver version*/)\ + HAILO_STATUS__X(76, HAILO_INVALID_DRIVER_VERSION /*!< Invalid driver version */)\ + HAILO_STATUS__X(77, HAILO_RPC_FAILED /*!< RPC failed */)\ + HAILO_STATUS__X(78, HAILO_INVALID_SERVICE_VERSION /*!< Invalid service version */)\ typedef enum { #define HAILO_STATUS__X(value, name) name = value, @@ -320,6 +326,9 @@ typedef struct { uint32_t total_number_of_samples; } hailo_power_measurement_data_t; +/** Additional scan params, for future compatibility */ +typedef struct _hailo_scan_devices_params_t hailo_scan_devices_params_t; + /** Ethernet device information */ typedef struct { struct sockaddr_in host_address; @@ -337,6 +346,21 @@ typedef struct { uint32_t func; } hailo_pcie_device_info_t; +/** Hailo device ID string - BDF for PCIe devices, IP address for Ethernet devices. **/ +typedef struct { + char id[HAILO_MAX_DEVICE_ID_LENGTH]; +} hailo_device_id_t; + +/** Hailo device type */ +typedef enum { + HAILO_DEVICE_TYPE_PCIE, + HAILO_DEVICE_TYPE_ETH, + HAILO_DEVICE_TYPE_CORE, + + /** Max enum value to maintain ABI Integrity */ + HAILO_DEVICE_TYPE_MAX_ENUM = HAILO_MAX_ENUM +} hailo_device_type_t; + /** Scheduler algorithm */ typedef enum hailo_scheduling_algorithm_e { /** Scheduling disabled */ @@ -350,18 +374,41 @@ typedef enum hailo_scheduling_algorithm_e { /** Virtual device parameters */ typedef struct { - /** Requested number of physical devices. if @a device_infos is not NULL, represents the number of ::hailo_pcie_device_info_t in @a device_infos */ + /** + * Requested number of physical devices. if @a device_ids is not NULL, or @a device_infos is not NULL represents + * the number of ::hailo_device_id_t in @a device_ids or the number of ::hailo_pcie_device_info_t in + * @a device_infos. + */ uint32_t device_count; - /** Specific physical devices information to create the vdevice from. If NULL, the vdevice will try to occupy devices from the available pool */ - hailo_pcie_device_info_t *device_infos; + + /** + * This param is deprecated. One should use 'device_ids'. + * Specific physical devices information to create the vdevice from. If NULL, the vdevice will try to occupy + * devices from the available pool. + * This parameter cannot be used together with @a device_ids. + */ + hailo_pcie_device_info_t *device_infos DEPRECATED("'device_infos' param is deprecated. One should use 'device_ids'"); + + /** + * Specific device ids to create the vdevice from. If NULL, the vdevice the vdevice will try to occupy + * devices from the available pool. + * This parameter cannot be used together with @a device_infos. + */ + hailo_device_id_t *device_ids; + /** The scheduling algorithm to use for network group scheduling */ hailo_scheduling_algorithm_t scheduling_algorithm; + /** Key for using a shared VDevice. To create a unique VDevice, use HAILO_UNIQUE_VDEVICE_GROUP_ID */ + const char *group_id; + /** Flag specifies whether to create the VDevice in HailoRT service or not. Defaults to false */ + bool multi_process_service; } hailo_vdevice_params_t; /** Device architecture */ typedef enum hailo_device_architecture_e { HAILO_ARCH_HAILO8_A0 = 0, - HAILO_ARCH_HAILO8_B0, + HAILO_ARCH_HAILO8, + HAILO_ARCH_HAILO8L, HAILO_ARCH_MERCURY_CA, HAILO_ARCH_MERCURY_VPU, @@ -610,6 +657,32 @@ typedef enum { */ HAILO_FORMAT_ORDER_YUY2 = 12, + /** + * YUV format, encoding 8 pixels in 96 bits + * [Y0, Y1, Y2, Y3, Y4, Y5, Y6, Y7, U0, V0, U1, V1] represents + * [Y0, U0, V0], [Y1, U0, V0], [Y2, U0, V0], [Y3, U0, V0], [Y4, U1, V1], [Y5, U1, V1], [Y6, U1, V1], [Y7, U1, V1] + */ + HAILO_FORMAT_ORDER_NV12 = 13, + + /** + * YUV format, encoding 8 pixels in 96 bits + * [Y0, Y1, Y2, Y3, Y4, Y5, Y6, Y7, V0, U0, V1, U1] represents + * [Y0, V0, U0], [Y1, V0, U0], [Y2, V0, U0], [Y3, V0, U0], [Y4, V1, U1], [Y5, V1, U1], [Y6, V1, U1], [Y7, V1, U1] + */ + HAILO_FORMAT_ORDER_NV21 = 14, + + /** + * Internal implementation for HAILO_FORMAT_ORDER_NV12 format + * [Y0, Y1, Y2, Y3, Y4, Y5, Y6, Y7, U0, V0, U1, V1] is represented by [Y0, Y1, Y2, Y3, U0, V0, Y4, Y5, Y6, Y7, U1, V1] + */ + HAILO_FORMAT_ORDER_HAILO_YYUV = 15, + + /** + * Internal implementation for HAILO_FORMAT_ORDER_NV21 format + * [Y0, Y1, Y2, Y3, Y4, Y5, Y6, Y7, V0, U0, V1, U1] is represented by [Y0, Y1, Y2, Y3, V0, U0, Y4, Y5, Y6, Y7, V1, U1] + */ + HAILO_FORMAT_ORDER_HAILO_YYVU = 16, + /** Max enum value to maintain ABI Integrity */ HAILO_FORMAT_ORDER_MAX_ENUM = HAILO_MAX_ENUM } hailo_format_order_t; @@ -1210,11 +1283,6 @@ typedef struct { char name[HAILO_MAX_NETWORK_NAME_SIZE]; } hailo_network_info_t; -/** Hailo device ID string - BDF for PCIe devices, IP address for Ethernet devices, "Core" for core devices. **/ -typedef struct { - char id[HAILO_MAX_DEVICE_ID_LENGTH]; -} hailo_device_id_t; - /** Notification IDs, for each notification, one of the ::hailo_notification_message_parameters_t union will be set. */ typedef enum { /** Matches hailo_notification_message_parameters_t::rx_error_notification. */ @@ -1288,16 +1356,15 @@ typedef struct { } hailo_health_monitor_temperature_alarm_notification_message_t; typedef enum { - HAILO_OVERCURRENT_PROTECTION_OVERCURRENT_ZONE__NONE = 0, - HAILO_OVERCURRENT_PROTECTION_OVERCURRENT_ZONE__ORANGE = 1, - HAILO_OVERCURRENT_PROTECTION_OVERCURRENT_ZONE__RED = 2 + HAILO_OVERCURRENT_PROTECTION_OVERCURRENT_ZONE__GREEN = 0, + HAILO_OVERCURRENT_PROTECTION_OVERCURRENT_ZONE__RED = 1 } hailo_overcurrent_protection_overcurrent_zone_t; /** Health monitor - Overcurrent alert notification message */ typedef struct { hailo_overcurrent_protection_overcurrent_zone_t overcurrent_zone; float32_t exceeded_alert_threshold; - float32_t sampled_current_during_alert; + bool is_last_overcurrent_violation_reached; } hailo_health_monitor_overcurrent_alert_notification_message_t; /** Health monitor - LCU ECC error notification message */ @@ -1397,7 +1464,7 @@ typedef struct { bool overcurrent_protection_active; uint8_t current_overcurrent_zone; float32_t red_overcurrent_threshold; - float32_t orange_overcurrent_threshold; + bool overcurrent_throttling_active; bool temperature_throttling_active; uint8_t current_temperature_zone; int8_t current_temperature_throttling_level; @@ -1406,6 +1473,8 @@ typedef struct { int32_t orange_hysteresis_temperature_threshold; int32_t red_temperature_threshold; int32_t red_hysteresis_temperature_threshold; + uint32_t requested_overcurrent_clock_freq; + uint32_t requested_temperature_clock_freq; } hailo_health_info_t; typedef struct { @@ -1561,6 +1630,36 @@ HAILORTAPI const char* hailo_get_status_message(hailo_status status); * @{ */ +/** + * Returns information on all available devices in the system. + * + * @param[in] params Scan params, used for future compatibility, only NULL is allowed. + * @param[out] device_ids Array of ::hailo_device_id_t to be fetched from vdevice. + * @param[inout] device_ids_length As input - the size of @a device_ids array. As output - the number of + * device scanned. + * @note ethernet devices are not considered "devices in the system", so they are not scanned in this function. + * use :hailo_scan_ethernet_devices for ethernet devices. + * + * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. + */ +HAILORTAPI hailo_status hailo_scan_devices(hailo_scan_devices_params_t *params, hailo_device_id_t *device_ids, + size_t *device_ids_length); + +/** + * Creates a device by the given device id. + * + * @param[in] device_id Device id, can represent several device types: + * [-] for pcie devices - pcie bdf (XXXX:XX:XX.X or XX:XX.X) + * [-] for ethernet devices - ip address (xxx.xxx.xxx.xxx) + * If NULL is given and there is only one available system device, use this device. + * @param[out] device A pointer to a ::hailo_device that receives the allocated PCIe device. + * @return Upon success, returns Expected of a unique_ptr to Device object. + * Otherwise, returns Unexpected of ::hailo_status error. + * + * @note To release a device, call the ::hailo_release_device function with the returned ::hailo_device. + */ +HAILORTAPI hailo_status hailo_create_device_by_id(const hailo_device_id_t *device_id, hailo_device *device); + /** * Returns information on all available pcie devices in the system. * @@ -1634,6 +1733,16 @@ HAILORTAPI hailo_status hailo_create_ethernet_device(hailo_eth_device_info_t *de */ HAILORTAPI hailo_status hailo_release_device(hailo_device device); +/** + * Returns the device type of the given device id string. + * + * @param[in] device_id A :hailo_device_id_t device id to check. + * @param[out] device_type A :hailo_device_type_t returned device type. + * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. + */ +HAILORTAPI hailo_status hailo_device_get_type_by_device_id(const hailo_device_id_t *device_id, + hailo_device_type_t *device_type); + /** * Sends identify control to a Hailo device. * @@ -1673,7 +1782,9 @@ HAILORTAPI hailo_status hailo_set_fw_logger(hailo_device device, hailo_fw_logger uint32_t interface_mask); /** - * Change throttling state of temperature protection component. + * Change throttling state of temperature protection and overcurrent protection components. + * In case that change throttling state of temperature protection didn't succeed, + * the change throttling state of overcurrent protection is executed. * * @param[in] device A ::hailo_device object. * @param[in] should_activate Should be true to enable or false to disable. @@ -1682,10 +1793,11 @@ HAILORTAPI hailo_status hailo_set_fw_logger(hailo_device device, hailo_fw_logger HAILORTAPI hailo_status hailo_set_throttling_state(hailo_device device, bool should_activate); /** - * Get current throttling state of temperature protection component. + * Get current throttling state of temperature protection and overcurrent protection components. + * If any throttling is enabled, the function return true. * * @param[in] device A ::hailo_device object. - * @param[out] is_active A pointer to the temperature protection throttling state: true if active, false otherwise. + * @param[out] is_active A pointer to the temperature protection or overcurrent protection components throttling state: true if active, false otherwise. * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns an ::hailo_status error. */ HAILORTAPI hailo_status hailo_get_throttling_state(hailo_device device, bool *is_active); @@ -1981,8 +2093,22 @@ HAILORTAPI hailo_status hailo_get_physical_devices(hailo_vdevice vdevice, hailo_ * @param[inout] number_of_devices As input - the size of @a devices_infos array. As output - the number of physical devices under vdevice. * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. * @note The returned physical devices are held in the scope of @a vdevice. + * @note This function is deprecated. One should use 'hailo_vdevice_get_physical_devices_ids()' */ HAILORTAPI hailo_status hailo_get_physical_devices_infos(hailo_vdevice vdevice, hailo_pcie_device_info_t *devices_infos, + size_t *number_of_devices) DEPRECATED("'hailo_get_physical_devices_infos' is deprecated. One should use 'hailo_vdevice_get_physical_devices_ids()'."); + +/** + * Gets the physical devices' ids from a vdevice. + * + * @param[in] vdevice A @a hailo_vdevice object to fetch physical devices from. + * @param[out] devices_ids Array of ::hailo_device_id_t to be fetched from vdevice. + * @param[inout] number_of_devices As input - the size of @a devices_ids array. As output - the number of + * physical devices under vdevice. + * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. + * @note The returned physical devices are held in the scope of @a vdevice. + */ +HAILORTAPI hailo_status hailo_vdevice_get_physical_devices_ids(hailo_vdevice vdevice, hailo_device_id_t *devices_ids, size_t *number_of_devices); /** @@ -2027,7 +2153,7 @@ HAILORTAPI hailo_status hailo_power_measurement(hailo_device device, hailo_dvm_o * averaging_factor samples. The sensor provides a new value every: 2 * sampling_period * averaging_factor {ms}. * The firmware wakes up every interval_milliseconds {ms} and checks the sensor. * If there is a new value to read from the sensor, the firmware reads it. - * Note that the average calculated by the firmware is “average of averages”, + * Note that the average calculated by the firmware is 'average of averages', * because it averages values that have already been averaged by the sensor. * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. */ @@ -2456,6 +2582,7 @@ HAILORTAPI hailo_status hailo_get_latency_measurement(hailo_configured_network_g * @note Using this function is only allowed when scheduling_algorithm is not ::HAILO_SCHEDULING_ALGORITHM_NONE, and before the creation of any vstreams. * @note The default timeout is 0ms. * @note Currently, setting the timeout for a specific network is not supported. + * @note The timeout may be ignored to prevent idle time from the device. */ HAILORTAPI hailo_status hailo_set_scheduler_timeout(hailo_configured_network_group configured_network_group, uint32_t timeout_ms, const char *network_name); @@ -2473,6 +2600,7 @@ HAILORTAPI hailo_status hailo_set_scheduler_timeout(hailo_configured_network_gro * @note Using this function is only allowed when scheduling_algorithm is not ::HAILO_SCHEDULING_ALGORITHM_NONE, and before the creation of any vstreams. * @note The default threshold is 1. * @note Currently, setting the threshold for a specific network is not supported. + * @note The threshold may be ignored to prevent idle time from the device. */ HAILORTAPI hailo_status hailo_set_scheduler_threshold(hailo_configured_network_group configured_network_group, uint32_t threshold, const char *network_name); diff --git a/hailort/libhailort/include/hailo/hailort_common.hpp b/hailort/libhailort/include/hailo/hailort_common.hpp index a3b9fae..089b3fb 100644 --- a/hailort/libhailort/include/hailo/hailort_common.hpp +++ b/hailort/libhailort/include/hailo/hailort_common.hpp @@ -14,6 +14,7 @@ #include "hailo/expected.hpp" #include #include +#include namespace hailort { @@ -172,6 +173,14 @@ public: return "NCHW"; case HAILO_FORMAT_ORDER_YUY2: return "YUY2"; + case HAILO_FORMAT_ORDER_NV12: + return "NV12"; + case HAILO_FORMAT_ORDER_HAILO_YYUV: + return "YYUV"; + case HAILO_FORMAT_ORDER_NV21: + return "NV21"; + case HAILO_FORMAT_ORDER_HAILO_YYVU: + return "YYVU"; default: return "Nan"; } @@ -282,6 +291,14 @@ public: return get_frame_size(vstream_info.shape, format); } } + + static constexpr bool is_vdma_stream_interface(hailo_stream_interface_t stream_interface) + { + return (HAILO_STREAM_INTERFACE_PCIE == stream_interface) || (HAILO_STREAM_INTERFACE_CORE == stream_interface); + } + + static Expected to_device_id(const std::string &device_id); + static Expected> to_device_ids_vector(const std::vector &device_ids_str); }; #ifndef HAILO_EMULATOR @@ -290,7 +307,7 @@ constexpr std::chrono::milliseconds DEFAULT_TRANSFER_TIMEOUT(std::chrono::second constexpr std::chrono::milliseconds DEFAULT_TRANSFER_TIMEOUT(std::chrono::seconds(5000)); #endif /* ifndef HAILO_EMULATOR */ -constexpr std::chrono::milliseconds HAILO_INFINITE_TIMEOUT(std::chrono::milliseconds::max()); +constexpr std::chrono::milliseconds HAILO_INFINITE_TIMEOUT(UINT32_MAX); inline hailo_latency_measurement_flags_t operator|(hailo_latency_measurement_flags_t a, hailo_latency_measurement_flags_t b) diff --git a/hailort/libhailort/include/hailo/hef.hpp b/hailort/libhailort/include/hailo/hef.hpp index 54b25ad..94287de 100644 --- a/hailort/libhailort/include/hailo/hef.hpp +++ b/hailort/libhailort/include/hailo/hef.hpp @@ -38,6 +38,9 @@ struct ConfigureNetworkParams } } + bool operator==(const ConfigureNetworkParams &other); + bool operator!=(const ConfigureNetworkParams &other); + uint16_t batch_size; hailo_power_mode_t power_mode; hailo_latency_measurement_flags_t latency; @@ -424,6 +427,13 @@ public: */ Expected> get_network_infos(const std::string &net_group_name=""); + /** + * Returns a unique hash for the specific Hef. + * + * @return A unique string hash for the Hef. Hefs created based on identical files will return identical hashes. + */ + std::string hash() const; + ~Hef(); Hef(Hef &&); Hef &operator=(Hef &&); @@ -440,6 +450,10 @@ private: friend class PyhailortInternal; friend class ConfiguredNetworkGroupBase; +#ifdef HAILO_SUPPORT_MULTI_PROCESS + friend class HailoRtRpcClient; +#endif // HAILO_SUPPORT_MULTI_PROCESS + class Impl; Hef(std::unique_ptr pimpl); std::unique_ptr pimpl; diff --git a/hailort/libhailort/include/hailo/inference_pipeline.hpp b/hailort/libhailort/include/hailo/inference_pipeline.hpp index 1bbd7f0..556d65b 100644 --- a/hailort/libhailort/include/hailo/inference_pipeline.hpp +++ b/hailort/libhailort/include/hailo/inference_pipeline.hpp @@ -46,16 +46,16 @@ public: * * @param[in] input_data A mapping of vstream name to MemoryView containing input dataset for inference. * @param[out] output_data A mapping of vstream name to MemoryView containing the inference output data. - * @param[in] batch_size The amount of inferred frames. + * @param[in] frames_count The amount of inferred frames. * * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. * @note ConfiguredNetworkGroup must be activated before calling this function. * @note The size of each element in @a input_data and @a output_data must match the frame size - * of the matching vstream name multiplied by @a batch_size. + * of the matching vstream name multiplied by @a frames_count. * @note If at least one input/output of some network is present, all inputs and outputs of that network must also be present. */ hailo_status infer(const std::map& input_data, - std::map& output_data, size_t batch_size); + std::map& output_data, size_t frames_count); /** * Get InputVStream by name. @@ -91,21 +91,24 @@ public: m_outputs(std::move(other.m_outputs)), m_is_multi_context(std::move(other.m_is_multi_context)), m_network_name_to_input_count(std::move(other.m_network_name_to_input_count)), - m_network_name_to_output_count(std::move(other.m_network_name_to_output_count)) + m_network_name_to_output_count(std::move(other.m_network_name_to_output_count)), + m_batch_size(std::move(other.m_batch_size)) {}; private: - InferVStreams(std::vector &&inputs, std::vector &&outputs, bool is_multi_context); + InferVStreams(std::vector &&inputs, std::vector &&outputs, bool is_multi_context, uint16_t batch_size); hailo_status verify_network_inputs_and_outputs(const std::map& inputs_name_mem_view_map, const std::map& outputs_name_mem_view_map); hailo_status verify_memory_view_size(const std::map& inputs_name_mem_view_map, const std::map& outputs_name_mem_view_map, - size_t batch_count); + size_t frames_count); + hailo_status verify_frames_count(size_t frames_count); std::vector m_inputs; std::vector m_outputs; bool m_is_multi_context; std::map m_network_name_to_input_count; std::map m_network_name_to_output_count; + uint16_t m_batch_size; }; } /* namespace hailort */ diff --git a/hailort/libhailort/include/hailo/network_group.hpp b/hailort/libhailort/include/hailo/network_group.hpp index 3de2c47..80278c7 100644 --- a/hailort/libhailort/include/hailo/network_group.hpp +++ b/hailort/libhailort/include/hailo/network_group.hpp @@ -12,6 +12,7 @@ #include "hailo/stream.hpp" #include "hailo/runtime_statistics.hpp" +#include "hailo/hef.hpp" #include #include @@ -22,6 +23,9 @@ namespace hailort { +class InputVStream; +class OutputVStream; + /** @addtogroup group_type_definitions */ /*@{*/ @@ -64,6 +68,8 @@ public: virtual Expected get_intermediate_buffer(const IntermediateBufferKey &key) = 0; + virtual hailo_status set_keep_nn_config_during_reset(const bool keep_nn_config_during_reset) = 0; + /** * @return The number of invalid frames. */ @@ -86,7 +92,13 @@ public: /** * @return The network group name. */ - virtual const std::string &get_network_group_name() const = 0; + virtual const std::string &get_network_group_name() const + DEPRECATED("'get_network_group_name' is deprecated. One should use 'name()'.") = 0; + + /** + * @return The network group name. + */ + virtual const std::string &name() const = 0; /** * Gets the stream's default interface. @@ -338,9 +350,22 @@ public: */ virtual hailo_status set_scheduler_threshold(uint32_t threshold, const std::string &network_name="") = 0; + /** + * @return Is the network group multi-context or not. + */ + virtual bool is_multi_context() const = 0; + + /** + * @return The configuration parameters this network group was initialized with. + */ + virtual const ConfigureNetworkParams get_config_params() const = 0; + virtual AccumulatorPtr get_activation_time_accumulator() const = 0; virtual AccumulatorPtr get_deactivation_time_accumulator() const = 0; + virtual Expected> create_input_vstreams(const std::map &inputs_params) = 0; + virtual Expected> create_output_vstreams(const std::map &outputs_params) = 0; + protected: ConfiguredNetworkGroup() = default; diff --git a/hailort/libhailort/include/hailo/stream.hpp b/hailort/libhailort/include/hailo/stream.hpp index 41a7bc6..93aa815 100644 --- a/hailort/libhailort/include/hailo/stream.hpp +++ b/hailort/libhailort/include/hailo/stream.hpp @@ -96,7 +96,7 @@ public: /** * @returns A ::hailo_stream_info_t object containing the stream's info. */ - const hailo_stream_info_t &get_info() const + virtual const hailo_stream_info_t &get_info() const { return m_stream_info; } @@ -106,7 +106,7 @@ public: */ virtual inline size_t get_frame_size() const { - return m_stream_info.hw_frame_size; + return get_info().hw_frame_size; } /** @@ -114,7 +114,7 @@ public: */ std::string name() const { - return m_stream_info.name; + return get_info().name; } /** @@ -196,7 +196,7 @@ public: /** * @returns the stream's info. */ - const hailo_stream_info_t &get_info() const + virtual const hailo_stream_info_t &get_info() const { return m_stream_info; } @@ -206,7 +206,7 @@ public: */ virtual inline size_t get_frame_size() const { - return m_stream_info.hw_frame_size; + return get_info().hw_frame_size; } /** @@ -214,7 +214,7 @@ public: */ std::string name() const { - return m_stream_info.name; + return get_info().name; } /** diff --git a/hailort/libhailort/include/hailo/vdevice.hpp b/hailort/libhailort/include/hailo/vdevice.hpp index 4ae48cc..2c112c1 100644 --- a/hailort/libhailort/include/hailo/vdevice.hpp +++ b/hailort/libhailort/include/hailo/vdevice.hpp @@ -42,6 +42,15 @@ public: */ static Expected> create(); + /** + * Creates a vdevice from the given phyiscal device ids. + * + * @return Upon success, returns Expected of a unique_ptr to VDevice object. + * Otherwise, returns Unexpected of ::hailo_status error. + * @note calling this create method will apply default vdevice params. + */ + static Expected> create(const std::vector &device_ids); + /** * Configure the vdevice from an hef. * @@ -60,15 +69,25 @@ public: * Otherwise, returns Unexpected of ::hailo_status error. * @note The returned physical devices are held in the scope of @a vdevice. */ - virtual Expected>> get_physical_devices() = 0; + virtual Expected>> get_physical_devices() const = 0; /** * Gets the devices informations. * * @return Upon success, returns Expected of a vector of ::hailo_pcie_device_info_t objects. * Otherwise, returns Unexpected of ::hailo_status error. + * @note This function is deprecated. One should use 'get_physical_devices_ids()' + */ + Expected> get_physical_devices_infos() const + DEPRECATED("'VDevice::get_physical_devices_infos' is deprecated. One should use 'VDevice::get_physical_devices_ids()'."); + + /** + * Gets the physical device IDs. + * + * @return Upon success, returns Expected of a vector of std::string device ids objects. + * Otherwise, returns Unexpected of ::hailo_status error. */ - virtual Expected> get_physical_devices_infos() = 0; + virtual Expected> get_physical_devices_ids() const = 0; virtual ~VDevice() = default; VDevice(const VDevice &) = delete; diff --git a/hailort/libhailort/include/hailo/vstream.hpp b/hailort/libhailort/include/hailo/vstream.hpp index 3b62be9..d0299e4 100644 --- a/hailort/libhailort/include/hailo/vstream.hpp +++ b/hailort/libhailort/include/hailo/vstream.hpp @@ -18,16 +18,62 @@ namespace hailort { +class OutputVStreamInternal; +class InputVStreamInternal; class SinkElement; class PipelineElement; -/*! Virtual stream base class */ -class HAILORTAPI BaseVStream +class HAILORTAPI InputVStream { public: - BaseVStream(BaseVStream &&other) noexcept; - BaseVStream& operator=(BaseVStream &&other) noexcept; - virtual ~BaseVStream(); + static Expected create(const hailo_vstream_info_t &vstream_info, + const hailo_vstream_params_t &vstream_params, std::shared_ptr pipeline_entry, + std::shared_ptr pipeline_exit, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, EventPtr network_group_activated_event, + AccumulatorPtr pipeline_latency_accumulator); + InputVStream(InputVStream &&other) noexcept = default; + InputVStream &operator=(InputVStream &&other) noexcept = default; + virtual ~InputVStream() = default; + + /** + * Writes @a buffer to hailo device. + * + * @param[in] buffer The buffer containing the data to be sent to device. + * The buffer's format can be obtained by get_user_buffer_format(), + * and the buffer's shape can be obtained by calling get_info().shape. + * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. + */ + hailo_status write(const MemoryView &buffer); + + /** + * Flushes the vstream pipeline buffers. This will block until the vstream pipeline is clear. + * + * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. + */ + hailo_status flush(); + + /** + * Clears the vstreams' pipeline buffers. + * + * @param[in] vstreams The vstreams to be cleared. + * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. + */ + static hailo_status clear(std::vector &vstreams); + + /** + * Clears the vstreams' pipeline buffers. + * + * @param[in] vstreams The vstreams to be cleared. + * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. + */ + static hailo_status clear(std::vector> &vstreams); + + /** + * Aborts vstream in unrecoverable manner. + * + * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. + */ + hailo_status abort(); /** * @return the size of a virtual stream's frame on the host side in bytes. @@ -106,89 +152,19 @@ public: const std::vector> &get_pipeline() const; protected: - BaseVStream(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, - std::shared_ptr pipeline_entry, std::vector> &&pipeline, - std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, AccumulatorPtr pipeline_latency_accumulator, - EventPtr &&network_group_activated_event, hailo_status &output_status); + explicit InputVStream(std::shared_ptr vstream); + std::string get_pipeline_description() const; - virtual std::string get_pipeline_description() const = 0; hailo_status start_vstream(); hailo_status stop_vstream(); hailo_status stop_and_clear(); - hailo_vstream_info_t m_vstream_info; - hailo_vstream_params_t m_vstream_params; - bool m_measure_pipeline_latency; - std::shared_ptr m_entry_element; - std::vector> m_pipeline; - volatile bool m_is_activated; - std::shared_ptr> m_pipeline_status; - EventPtr m_shutdown_event; - EventPtr m_network_group_activated_event; - std::map m_fps_accumulators; - std::map m_latency_accumulators; - std::map> m_queue_size_accumulators; - AccumulatorPtr m_pipeline_latency_accumulator; -}; - -/*! Input virtual stream, used to stream data to device */ -class HAILORTAPI InputVStream : public BaseVStream -{ -public: - static Expected create(const hailo_vstream_info_t &vstream_info, - const hailo_vstream_params_t &vstream_params, std::shared_ptr pipeline_entry, - std::shared_ptr pipeline_exit, std::vector> &&pipeline, - std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, EventPtr network_group_activated_event, - AccumulatorPtr pipeline_latency_accumulator); - InputVStream(InputVStream &&other) noexcept = default; - InputVStream &operator=(InputVStream &&other) noexcept = default; - virtual ~InputVStream() = default; - - /** - * Writes @a buffer to hailo device. - * - * @param[in] buffer The buffer containing the data to be sent to device. - * The buffer's format can be obtained by get_user_buffer_format(), - * and the buffer's shape can be obtained by calling get_info().shape. - * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. - */ - hailo_status write(const MemoryView &buffer); - - /** - * Flushes the vstream pipeline buffers. This will block until the vstream pipeline is clear. - * - * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. - */ - hailo_status flush(); - - /** - * Clears the vstreams' pipeline buffers. - * - * @param[in] vstreams The vstreams to be cleared. - * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. - */ - static hailo_status clear(std::vector &vstreams); - - /** - * Clears the vstreams' pipeline buffers. - * - * @param[in] vstreams The vstreams to be cleared. - * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. - */ - static hailo_status clear(std::vector> &vstreams); - -private: - InputVStream(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, - std::shared_ptr pipeline_entry, std::vector> &&pipeline, - std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, AccumulatorPtr pipeline_latency_accumulator, - EventPtr network_group_activated_event, hailo_status &output_status); + std::shared_ptr m_vstream; - virtual std::string get_pipeline_description() const override; friend class VStreamsBuilderUtils; }; -/*! Output virtual stream, used to read data from device */ -class HAILORTAPI OutputVStream : public BaseVStream +class HAILORTAPI OutputVStream { public: static Expected create( @@ -212,32 +188,115 @@ public: /** * Clears the vstreams' pipeline buffers. - * + * * @param[in] vstreams The vstreams to be cleared. * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. - * @note If not all output vstreams from the same group are passed together, it will cause an undefined behavior . - * See ConfiguredNetworkGroup::get_output_vstream_groups, to get the output vstreams groups. */ static hailo_status clear(std::vector &vstreams); /** * Clears the vstreams' pipeline buffers. - * + * * @param[in] vstreams The vstreams to be cleared. * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. - * @note If not all output vstreams from the same group are passed together, it will cause an undefined behavior . - * See ConfiguredNetworkGroup::get_output_vstream_groups, to get the output vstreams groups. */ static hailo_status clear(std::vector> &vstreams); -private: - OutputVStream(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, - std::shared_ptr pipeline_entry, std::vector> &&pipeline, - std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, AccumulatorPtr pipeline_latency_accumulator, - EventPtr network_group_activated_event, hailo_status &output_status); + /** + * Aborts vstream in unrecoverable manner. + * + * @return Upon success, returns ::HAILO_SUCCESS. Otherwise, returns a ::hailo_status error. + */ + hailo_status abort(); + + /** + * @return the size of a virtual stream's frame on the host side in bytes. + * @note The size could be affected by the format type - using UINT16, or by the data not being quantized yet. + */ + size_t get_frame_size() const; + + /** + * @return ::hailo_vstream_info_t object containing the vstream info. + */ + const hailo_vstream_info_t &get_info() const; + + /** + * @return ::hailo_format_t object containing the user buffer format. + */ + const hailo_format_t &get_user_buffer_format() const; + + /** + * @return the virtual stream's name. + */ + std::string name() const; + + /** + * @return the virtual stream's network name. + */ + std::string network_name() const; + + /** + * Gets a reference to a map between pipeline element names to their respective fps accumulators. + * These accumulators measure the net throughput of each pipeline element. This means that the effects + * of queuing in the vstream pipeline (between elements) are not accounted for by these accumulators. + * + * @return A const reference to a map between pipeline element names to their respective fps accumulators. + * @note FPS accumulators are created for pipeline elements, if the vstream is created with the flag + * ::HAILO_PIPELINE_ELEM_STATS_MEASURE_FPS set under the @a pipeline_elements_stats_flags field of ::hailo_vstream_params_t. + */ + const std::map &get_fps_accumulators() const; + + /** + * Gets a reference to a map between pipeline element names to their respective latency accumulators. + * These accumulators measure the net latency of each pipeline element. This means that the effects + * of queuing in the vstream pipeline (between elements) are not accounted for by these accumulators. + * + * @return A const reference to a map between pipeline element names to their respective latency accumulators. + * @note Latency accumulators are created for pipeline elements, if the vstream is created with the flag + * ::HAILO_PIPELINE_ELEM_STATS_MEASURE_LATENCY set under the @a pipeline_elements_stats_flags field of ::hailo_vstream_params_t. + */ + const std::map &get_latency_accumulators() const; + + /** + * Gets a reference to a map between pipeline element names to their respective queue size accumulators. + * These accumulators measure the number of free buffers in the queue, right before a buffer is removed + * from the queue to be used. + * + * @return A const reference to a map between pipeline element names to their respective queue size accumulators. + * @note Queue size accumulators are created for pipeline elements, if the vstream is created with the flag + * ::HAILO_PIPELINE_ELEM_STATS_MEASURE_QUEUE_SIZE set under the @a pipeline_elements_stats_flags field of ::hailo_vstream_params_t. + */ + const std::map> &get_queue_size_accumulators() const; + + /** + * Gets a shared_ptr to the vstream's latency accumulator. This accumulator measures the time it takes for a frame to pass + * through an entire vstream pipeline. Specifically: + * * For InputVStream%s: The time it takes a frame from the call to InputVStream::write, until the frame is written to the HW. + * * For OutputVStream%s: The time it takes a frame from being read from the HW, until it's returned to the user via OutputVStream::read. + * + * @return A shared pointer to the vstream's latency accumulator. + * @note A pipeline-wide latency accumulator is created for the vstream, if the vstream is created with the flag + * ::HAILO_VSTREAM_STATS_MEASURE_LATENCY set under the @a vstream_stats_flags field of ::hailo_vstream_params_t. + */ + AccumulatorPtr get_pipeline_latency_accumulator() const; + + /** + * @return A const reference to the @a PipelineElement%s that this vstream is comprised of. + */ + const std::vector> &get_pipeline() const; + +protected: + explicit OutputVStream(std::shared_ptr vstream); + std::string get_pipeline_description() const; + + hailo_status start_vstream(); + hailo_status stop_vstream(); + hailo_status stop_and_clear(); + + std::shared_ptr m_vstream; - virtual std::string get_pipeline_description() const override; friend class VStreamsBuilderUtils; + friend class VdmaConfigNetworkGroup; }; /*! Contains the virtual streams creation functions */ diff --git a/hailort/libhailort/scheduler_mon.proto b/hailort/libhailort/scheduler_mon.proto new file mode 100644 index 0000000..25043a3 --- /dev/null +++ b/hailort/libhailort/scheduler_mon.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +message ProtoMonInfo { + string network_name = 1; + double fps = 2; + double core_utilization = 3; +} + +enum ProtoMonStreamDirection { + PROTO__STREAM_DIRECTION__HOST_TO_DEVICE = 0; + PROTO__STREAM_DIRECTION__DEVICE_TO_HOST = 1; +} + +message ProtoMonStreamFramesInfo { + string stream_name = 1; + ProtoMonStreamDirection stream_direction = 2; + int32 buffer_frames_size = 3; + int32 pending_frames_count = 4; +} + +message ProtoMonNetworkFrames { + string network_name = 1; + repeated ProtoMonStreamFramesInfo streams_frames_infos = 2; +} + +message ProtoMon { + string pid = 1; + repeated ProtoMonInfo networks_infos = 2; + repeated ProtoMonNetworkFrames net_frames_infos = 3; +} \ No newline at end of file diff --git a/hailort/libhailort/src/CMakeLists.txt b/hailort/libhailort/src/CMakeLists.txt index b60e1a6..dd7a9aa 100644 --- a/hailort/libhailort/src/CMakeLists.txt +++ b/hailort/libhailort/src/CMakeLists.txt @@ -4,7 +4,7 @@ find_package(Threads REQUIRED) include(GNUInstallDirs) include(CMakePackageConfigHelpers) -include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/common_compiler_options.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/common_compiler_options.cmake) FUNCTION(relative_to_absolute_paths output) SET(listVar "") @@ -48,7 +48,9 @@ set(HAILORT_CPP_SOURCES context_switch/network_group_wrapper.cpp context_switch/resource_manager.cpp context_switch/pipeline_multiplexer.cpp - intermediate_buffer.cpp + channel_allocator.cpp + inter_context_buffer.cpp + ddr_channels_pair.cpp config_buffer.cpp d2h_events_parser.cpp mipi_stream.cpp @@ -59,9 +61,11 @@ set(HAILORT_CPP_SOURCES vdma_device.cpp vdma_stream.cpp + vdma/vdma_mapped_buffer_impl.cpp vdma/mapped_buffer.cpp vdma/sg_buffer.cpp vdma/continuous_buffer.cpp + vdma/vdma_buffer.cpp pcie_device.cpp pcie_stream.cpp @@ -71,6 +75,7 @@ set(HAILORT_CPP_SOURCES vdevice.cpp vdevice_stream.cpp + vdevice_stream_wrapper.cpp control_protocol.cpp @@ -80,6 +85,10 @@ set(HAILORT_CPP_SOURCES network_group_scheduler.cpp ) +if(HAILO_BUILD_SERVICE) + set(HAILORT_CPP_SOURCES "${HAILORT_CPP_SOURCES}" hailort_rpc_client.cpp network_group_client.cpp) +endif() + set(common_dir "${PROJECT_SOURCE_DIR}/common/src") set(COMMON_C_SOURCES ${common_dir}/firmware_status.c @@ -124,9 +133,14 @@ endif() target_link_libraries(libhailort PRIVATE Threads::Threads) target_link_libraries(libhailort PRIVATE hef_proto) +target_link_libraries(libhailort PRIVATE scheduler_mon_proto) target_link_libraries(libhailort PRIVATE spdlog::spdlog) target_link_libraries(libhailort PRIVATE readerwriterqueue) target_link_libraries(libhailort PRIVATE microprofile) +if(HAILO_BUILD_SERVICE) + target_link_libraries(libhailort PRIVATE grpc++_unsecure) + target_link_libraries(libhailort PRIVATE hailort_rpc_grpc_proto) +endif() if(CMAKE_SYSTEM_NAME STREQUAL QNX) target_link_libraries(libhailort PRIVATE pevents pci) endif() @@ -179,6 +193,7 @@ target_include_directories(libhailort $ $ $ + $ ) target_compile_definitions(libhailort PUBLIC diff --git a/hailort/libhailort/src/buffer.cpp b/hailort/libhailort/src/buffer.cpp index 7cfe613..6d14bc8 100644 --- a/hailort/libhailort/src/buffer.cpp +++ b/hailort/libhailort/src/buffer.cpp @@ -12,6 +12,7 @@ #include "hailo/buffer.hpp" #include "common/logger_macros.hpp" #include "common/utils.hpp" +#include "common/string_utils.hpp" #include #include @@ -51,6 +52,24 @@ Expected Buffer::create(size_t size, uint8_t default_value) return buffer; } +Expected Buffer::create_shared(size_t size) +{ + auto buffer = Buffer::create(size); + CHECK_EXPECTED(buffer); + auto buffer_ptr = make_shared_nothrow(buffer.release()); + CHECK_NOT_NULL_AS_EXPECTED(buffer_ptr, HAILO_OUT_OF_HOST_MEMORY); + return buffer_ptr; +} + +Expected Buffer::create_shared(size_t size, uint8_t default_value) +{ + auto buffer = Buffer::create(size, default_value); + CHECK_EXPECTED(buffer); + auto buffer_ptr = make_shared_nothrow(buffer.release()); + CHECK_NOT_NULL_AS_EXPECTED(buffer_ptr, HAILO_OUT_OF_HOST_MEMORY); + return buffer_ptr; +} + Expected Buffer::create(const uint8_t *src, size_t size) { auto buffer = create(size); @@ -158,12 +177,9 @@ std::string Buffer::to_string() const // Note: This is a friend function std::ostream& operator<<(std::ostream& stream, const Buffer& buffer) { - for (size_t i = 0; i < buffer.size(); i++) { - // Without the cast, the numbers are printed as though they were chars - stream << std::hex << std::setfill('0') << std::setw(2) << static_cast(buffer[i]) << "\t"; - } + const bool UPPERCASE = true; + stream << StringUtils::to_hex_string(buffer.data(), buffer.size(), UPPERCASE, "\t"); stream << "\n[size = " << std::dec << buffer.size() << "]"; - return stream; } @@ -245,12 +261,9 @@ bool MemoryView::empty() const noexcept // Note: This is a friend function std::ostream& operator<<(std::ostream& stream, const MemoryView& buffer) { - for (size_t i = 0; i < buffer.size(); i++) { - // Without the cast, the numbers are printed as though they were chars - stream << std::hex << std::setfill('0') << std::setw(2) << static_cast(buffer.data()[i]) << "\t"; - } + const bool UPPERCASE = true; + stream << StringUtils::to_hex_string(buffer.data(), buffer.size(), UPPERCASE, "\t"); stream << "\n[size = " << std::dec << buffer.size() << "]"; - return stream; } diff --git a/hailort/libhailort/src/channel_allocator.cpp b/hailort/libhailort/src/channel_allocator.cpp new file mode 100644 index 0000000..45c776f --- /dev/null +++ b/hailort/libhailort/src/channel_allocator.cpp @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) +**/ +/** + * @file channel_allocator.cpp + * @brief Allocates vdma channel indexes, allows reusing non-boundary channels between contextes. + **/ + +#include "channel_allocator.hpp" + + +namespace hailort +{ + +ChannelAllocator::ChannelAllocator(size_t max_engines_count) : + m_max_engines_count(max_engines_count) +{} + +Expected ChannelAllocator::get_available_channel_id(const LayerIdentifier &layer_identifier, + VdmaChannel::Direction direction, uint8_t engine_index) +{ + CHECK_AS_EXPECTED(engine_index < m_max_engines_count, HAILO_INVALID_ARGUMENT, + "Invalid engine index {}, max is {}", engine_index, m_max_engines_count); + + const auto found_channel = m_allocated_channels.find(layer_identifier); + if (found_channel != m_allocated_channels.end()) { + CHECK_AS_EXPECTED(found_channel->second.engine_index == engine_index, HAILO_INTERNAL_FAILURE, + "Mismatch engine index"); + return Expected(found_channel->second); + } + + // If we reach here, we need to allocate channel index for that layer. + std::set currently_used_channel_indexes; + for (auto channel_id_pair : m_allocated_channels) { + currently_used_channel_indexes.insert(channel_id_pair.second); + } + + uint8_t min_channel_index = + (direction == VdmaChannel::Direction::H2D) ? MIN_H2D_CHANNEL_INDEX : MIN_D2H_CHANNEL_INDEX; + uint8_t max_channel_index = + (direction == VdmaChannel::Direction::H2D) ? MAX_H2D_CHANNEL_INDEX : MAX_D2H_CHANNEL_INDEX; + + for (uint8_t index = min_channel_index; index <= max_channel_index; ++index) { + const vdma::ChannelId channel_id = {engine_index, index}; + + // Check that the channel is not currently in use. + if (contains(currently_used_channel_indexes, channel_id)) { + continue; + } + + // In the case of boundary channels, if the channel id was used in previous context as fw-managed (and it + // was freed, so it doesn't appear in `currently_used_channel_index`), we can't reuse it. + if (std::get<0>(layer_identifier) == LayerType::BOUNDARY) { + if (contains(m_fw_managed_channel_ids, channel_id)) { + continue; + } + } + + // Found it + insert_new_channel_id(layer_identifier, channel_id); + return Expected(channel_id); + } + + LOGGER__ERROR("Failed to get available channel_index"); + return make_unexpected(HAILO_INTERNAL_FAILURE); +} + +hailo_status ChannelAllocator::free_channel_index(const LayerIdentifier &layer_identifier) +{ + auto layer_channel_pair = m_allocated_channels.find(layer_identifier); + CHECK(m_allocated_channels.end() != layer_channel_pair, HAILO_INTERNAL_FAILURE, "Failed to free channel"); + CHECK(std::get<0>(layer_channel_pair->first) != LayerType::BOUNDARY, HAILO_INTERNAL_FAILURE, + "Can't free boundary channels"); + + m_allocated_channels.erase(layer_channel_pair); + return HAILO_SUCCESS; +} + +const std::set &ChannelAllocator::get_boundary_channel_ids() const +{ + return m_boundary_channel_ids; +} + +const std::set &ChannelAllocator::get_fw_managed_channel_ids() const +{ + return m_fw_managed_channel_ids; +} + +void ChannelAllocator::insert_new_channel_id(const LayerIdentifier &layer_identifier, const vdma::ChannelId &channel_id) +{ + if (LayerType::BOUNDARY == std::get<0>(layer_identifier)) { + m_boundary_channel_ids.insert(channel_id); + } + else { + m_fw_managed_channel_ids.insert(channel_id); + } + + m_allocated_channels.emplace(layer_identifier, channel_id); +} + +} /* namespace hailort */ diff --git a/hailort/libhailort/src/channel_allocator.hpp b/hailort/libhailort/src/channel_allocator.hpp new file mode 100644 index 0000000..778ae8a --- /dev/null +++ b/hailort/libhailort/src/channel_allocator.hpp @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) +**/ +/** + * @file channel_allocator.hpp + * @brief Allocates vdma channel indexes, allows reusing non-boundary channels between contextes. + **/ + +#ifndef _HAILO_CHANNEL_ALLOCATOR_HPP_ +#define _HAILO_CHANNEL_ALLOCATOR_HPP_ + +#include "hailo/hailort.h" +#include "vdma_descriptor_list.hpp" +#include "vdma/channel_id.hpp" +#include "vdma_channel.hpp" +#include "hef_internal.hpp" + +#include + +namespace hailort +{ + +class ChannelAllocator final +{ +public: + explicit ChannelAllocator(size_t max_engines_count); + ChannelAllocator(ChannelAllocator &&other) = default; + + Expected get_available_channel_id(const LayerIdentifier &layer_identifier, + VdmaChannel::Direction direction, uint8_t engine_index); + hailo_status free_channel_index(const LayerIdentifier &layer_identifier); + + const std::set &get_boundary_channel_ids() const; + const std::set &get_fw_managed_channel_ids() const; + +private: + void insert_new_channel_id(const LayerIdentifier &layer_identifier, const vdma::ChannelId &channel_id); + + const size_t m_max_engines_count; + + // Contains all channels that are currently used. This channels are released in the free_channel_index. + std::map m_allocated_channels; + + // Contains all channels id allocated for the network group. This channels are never released. + std::set m_boundary_channel_ids; + std::set m_fw_managed_channel_ids; +}; + +} /* namespace hailort */ + +#endif /* _HAILO_CHANNEL_ALLOCATOR_HPP_ */ diff --git a/hailort/libhailort/src/config_buffer.cpp b/hailort/libhailort/src/config_buffer.cpp index 96cae2a..20f8d92 100644 --- a/hailort/libhailort/src/config_buffer.cpp +++ b/hailort/libhailort/src/config_buffer.cpp @@ -16,22 +16,23 @@ namespace hailort { -Expected ConfigBuffer::create(HailoRTDriver &driver, uint8_t channel_index, +Expected ConfigBuffer::create(HailoRTDriver &driver, vdma::ChannelId channel_id, const std::vector &cfg_sizes) { const auto buffer_size = std::accumulate(cfg_sizes.begin(), cfg_sizes.end(), 0); auto buffer_ptr = should_use_ccb(driver) ? create_ccb_buffer(driver, buffer_size) : - create_sg_buffer(driver, channel_index, cfg_sizes); + create_sg_buffer(driver, channel_id.channel_index, cfg_sizes); CHECK_EXPECTED(buffer_ptr); - return ConfigBuffer(buffer_ptr.release(), buffer_size); + return ConfigBuffer(buffer_ptr.release(), channel_id, buffer_size); } ConfigBuffer::ConfigBuffer(std::unique_ptr &&buffer, - size_t total_buffer_size) + vdma::ChannelId channel_id, size_t total_buffer_size) : m_buffer(std::move(buffer)), + m_channel_id(channel_id), m_total_buffer_size(total_buffer_size), m_acc_buffer_offset(0), m_acc_desc_count(0), m_current_buffer_size(0) {} @@ -65,11 +66,6 @@ size_t ConfigBuffer::get_total_cfg_size() return m_total_buffer_size; } -vdma::VdmaBuffer::Type ConfigBuffer::buffer_type() const -{ - return m_buffer->type(); -} - size_t ConfigBuffer::get_current_buffer_size() { return m_current_buffer_size; @@ -80,23 +76,17 @@ uint16_t ConfigBuffer::desc_page_size() const return m_buffer->desc_page_size(); } -uint64_t ConfigBuffer::dma_address() const -{ - return m_buffer->dma_address(); -} - -uint32_t ConfigBuffer::total_desc_count() const +CONTROL_PROTOCOL__config_channel_info_t ConfigBuffer::get_config_channel_info() const { - return m_buffer->descs_count(); -} - -uint32_t ConfigBuffer::acc_desc_count() const -{ - return m_acc_desc_count; + CONTROL_PROTOCOL__config_channel_info_t config_channel_info; + config_channel_info.config_buffer_info = m_buffer->get_host_buffer_info(m_acc_desc_count * m_buffer->desc_page_size()); + config_channel_info.engine_index = m_channel_id.engine_index; + config_channel_info.vdma_channel_index = m_channel_id.channel_index; + return config_channel_info; } Expected> ConfigBuffer::create_sg_buffer(HailoRTDriver &driver, - uint8_t channel_index, const std::vector &cfg_sizes) + uint8_t vdma_channel_index, const std::vector &cfg_sizes) { auto desc_sizes_pair = VdmaDescriptorList::get_desc_buffer_sizes_for_multiple_transfers(driver, 1, cfg_sizes); CHECK_EXPECTED(desc_sizes_pair); @@ -105,7 +95,7 @@ Expected> ConfigBuffer::create_sg_buffer(Hailo auto descs_count = desc_sizes_pair->second; auto buffer = vdma::SgBuffer::create(driver, descs_count, page_size, HailoRTDriver::DmaDirection::H2D, - channel_index); + vdma_channel_index); CHECK_EXPECTED(buffer); auto buffer_ptr = make_unique_nothrow(buffer.release()); @@ -134,10 +124,11 @@ bool ConfigBuffer::should_use_ccb(HailoRTDriver &driver) return false; case HailoRTDriver::DmaType::DRAM: return true; - default: - assert(true); - return false; } + + // Shouldn't reach here + assert(false); + return false; } } /* hailort */ \ No newline at end of file diff --git a/hailort/libhailort/src/config_buffer.hpp b/hailort/libhailort/src/config_buffer.hpp index 4a92e89..ade4708 100644 --- a/hailort/libhailort/src/config_buffer.hpp +++ b/hailort/libhailort/src/config_buffer.hpp @@ -19,7 +19,7 @@ namespace hailort { class ConfigBuffer final { public: - static Expected create(HailoRTDriver &driver, uint8_t channel_index, + static Expected create(HailoRTDriver &driver, vdma::ChannelId channel_id, const std::vector &cfg_sizes); // Write data to config channel @@ -34,24 +34,21 @@ public: since we might add NOPs to the data (Pre-fetch mode) */ size_t get_total_cfg_size(); - vdma::VdmaBuffer::Type buffer_type() const; uint16_t desc_page_size() const; - uint64_t dma_address() const; - uint32_t total_desc_count() const; - - uint32_t acc_desc_count() const; + CONTROL_PROTOCOL__config_channel_info_t get_config_channel_info() const; private: - ConfigBuffer(std::unique_ptr &&buffer, size_t total_buffer_size); + ConfigBuffer(std::unique_ptr &&buffer, vdma::ChannelId channel_id, size_t total_buffer_size); static Expected> create_sg_buffer(HailoRTDriver &driver, - uint8_t channel_index, const std::vector &cfg_sizes); + uint8_t vdma_channel_index, const std::vector &cfg_sizes); static Expected> create_ccb_buffer(HailoRTDriver &driver, uint32_t buffer_size); static bool should_use_ccb(HailoRTDriver &driver); std::unique_ptr m_buffer; + vdma::ChannelId m_channel_id; const size_t m_total_buffer_size; size_t m_acc_buffer_offset; uint32_t m_acc_desc_count; diff --git a/hailort/libhailort/src/context_switch/active_network_group_holder.hpp b/hailort/libhailort/src/context_switch/active_network_group_holder.hpp index e8d1da6..935c96e 100644 --- a/hailort/libhailort/src/context_switch/active_network_group_holder.hpp +++ b/hailort/libhailort/src/context_switch/active_network_group_holder.hpp @@ -11,7 +11,7 @@ #ifndef _HAILO_CONTEXT_SWITCH_ACTIVE_NETWORK_GROUP_HOLDER_HPP_ #define _HAILO_CONTEXT_SWITCH_ACTIVE_NETWORK_GROUP_HOLDER_HPP_ -// TODO: can’t we just have ActiveNetworkGroup ref under device? +// TODO: cant we just have ActiveNetworkGroup ref under device? #include "hailo/hailort.h" #include "common/utils.hpp" diff --git a/hailort/libhailort/src/context_switch/config_manager.hpp b/hailort/libhailort/src/context_switch/config_manager.hpp index 0911f66..caf0490 100644 --- a/hailort/libhailort/src/context_switch/config_manager.hpp +++ b/hailort/libhailort/src/context_switch/config_manager.hpp @@ -35,8 +35,7 @@ class ConfigManager public: virtual ~ConfigManager() {} virtual ConfigManagerType get_manager_type() = 0; - virtual Expected add_hef(Hef &hef, const std::map &configure_params, bool is_scheduler_used = false) = 0; + virtual Expected add_hef(Hef &hef, const std::map &configure_params) = 0; protected: hailo_status validate_boundary_streams_were_created(Hef &hef, const std::string &network_group_name, ConfiguredNetworkGroup &network_group) diff --git a/hailort/libhailort/src/context_switch/hcp_config_manager.cpp b/hailort/libhailort/src/context_switch/hcp_config_manager.cpp index d314347..8dd2b25 100644 --- a/hailort/libhailort/src/context_switch/hcp_config_manager.cpp +++ b/hailort/libhailort/src/context_switch/hcp_config_manager.cpp @@ -40,79 +40,102 @@ namespace hailort { Expected HcpConfigManager::add_hef(Hef &hef, - const NetworkGroupsParamsMap &configure_params, bool /*is_scheduler_used*/) + const NetworkGroupsParamsMap &configure_params) { + auto device_arch_exp = m_device.get_architecture(); + CHECK_EXPECTED(device_arch_exp); + auto device_arch = device_arch_exp.release(); + + auto partial_clusters_layout_bitmap_exp = Control::get_partial_clusters_layout_bitmap(m_device); + CHECK_EXPECTED(partial_clusters_layout_bitmap_exp); + auto partial_clusters_layout_bitmap = partial_clusters_layout_bitmap_exp.release(); + auto &hef_network_groups = hef.pimpl->network_groups(); auto current_net_group_index = static_cast(m_net_groups.size()); - - /* Validate that all network_groups are single context */ - for (const auto &net_group : hef_network_groups) { - CHECK_NOT_NULL_AS_EXPECTED(net_group, HAILO_INTERNAL_FAILURE); - CHECK(1 == net_group->contexts_size(), make_unexpected(HAILO_INTERNAL_FAILURE), - "Only single_context network_groups is supported!. Network group {} has {} contexts.", - net_group->network_group_metadata().network_group_name(), net_group->contexts_size()); - CHECK_AS_EXPECTED(!(Hef::Impl::contains_ddr_layers(*net_group)), HAILO_INVALID_OPERATION, - "DDR layers are only supported for PCIe device. Network group {} contains DDR layers.", - net_group->network_group_metadata().network_group_name()); - auto status = Hef::Impl::validate_net_group_unique_layer_names(net_group); - CHECK_SUCCESS_AS_EXPECTED(status); - } + ConfiguredNetworkGroupVector added_network_groups; + added_network_groups.reserve(hef_network_groups.size()); + auto configure_params_copy = configure_params; + auto hef_arch = hef.pimpl->get_device_arch(); // Reset FW state_machine status - auto status = Control::reset_context_switch_state_machine(m_device); + static const auto REMOVE_NN_CONFIG_DURING_RESET = false; + auto status = Control::reset_context_switch_state_machine(m_device, REMOVE_NN_CONFIG_DURING_RESET); CHECK_SUCCESS_AS_EXPECTED(status); - ConfiguredNetworkGroupVector added_network_groups; - added_network_groups.reserve(hef_network_groups.size()); - - /* Update preliminary_config and dynamic_contexts recepies */ - auto configure_params_copy = configure_params; + const ProtoHEFNetworkGroup *network_group_ptr = nullptr; for (const auto &net_group : hef_network_groups) { CHECK_NOT_NULL_AS_EXPECTED(net_group, HAILO_INTERNAL_FAILURE); - auto &proto_preliminary_config = net_group->preliminary_config(); + + if (ProtoHEFHwArch::PROTO__HW_ARCH__HAILO8L == hef_arch) { + // Hailo8 can work with Hailo8L configurations. in that case we choose one of the configurations + for (auto &partial_network_group : net_group->partial_network_groups()) { + if ((partial_clusters_layout_bitmap == partial_network_group.layout().partial_clusters_layout_bitmap()) || + ((HAILO_ARCH_HAILO8 == device_arch))) { + network_group_ptr = &partial_network_group.network_group(); + break; + } + } + CHECK_AS_EXPECTED(nullptr != network_group_ptr, HAILO_INTERNAL_FAILURE, "There is no matching partial_clusters_layout_bitmap configuration in the given HEF"); + } else { + network_group_ptr = net_group.get(); + } + CHECK_NOT_NULL_AS_EXPECTED(network_group_ptr, HAILO_INTERNAL_FAILURE); + + /* Validate that all network_groups are single context */ + CHECK(1 == network_group_ptr->contexts_size(), make_unexpected(HAILO_INTERNAL_FAILURE), + "Only single_context network_groups is supported!. Network group {} has {} contexts.", + network_group_ptr->network_group_metadata().network_group_name(), network_group_ptr->contexts_size()); + CHECK_AS_EXPECTED(!(Hef::Impl::contains_ddr_layers(*network_group_ptr)), HAILO_INVALID_OPERATION, + "DDR layers are only supported for PCIe device. Network group {} contains DDR layers.", + network_group_ptr->network_group_metadata().network_group_name()); + status = Hef::Impl::validate_net_group_unique_layer_names(*network_group_ptr); + CHECK_SUCCESS_AS_EXPECTED(status); + + /* Update preliminary_config and dynamic_contexts recepies */ + auto &proto_preliminary_config = network_group_ptr->preliminary_config(); auto net_group_config = Hef::Impl::create_single_context_network_group_config(proto_preliminary_config); CHECK_EXPECTED(net_group_config); ConfigureNetworkParams config_params = {}; - if (contains(configure_params_copy, net_group->network_group_metadata().network_group_name())) { - config_params = configure_params_copy.at(net_group->network_group_metadata().network_group_name()); - configure_params_copy.erase(net_group->network_group_metadata().network_group_name()); + if (contains(configure_params_copy, network_group_ptr->network_group_metadata().network_group_name())) { + config_params = configure_params_copy.at(network_group_ptr->network_group_metadata().network_group_name()); + configure_params_copy.erase(network_group_ptr->network_group_metadata().network_group_name()); } else { auto interface = m_device.get_default_streams_interface(); CHECK_EXPECTED(interface); - auto config_params_exp = hef.create_configure_params(interface.value(), net_group->network_group_metadata().network_group_name()); + auto config_params_exp = hef.create_configure_params(interface.value(), network_group_ptr->network_group_metadata().network_group_name()); CHECK_EXPECTED(config_params_exp); config_params = config_params_exp.release(); } - auto network_group_metadata = hef.pimpl->get_network_group_metadata(net_group->network_group_metadata().network_group_name()); + auto network_group_metadata = hef.pimpl->get_network_group_metadata(network_group_ptr->network_group_metadata().network_group_name()); CHECK_EXPECTED(network_group_metadata); auto single_context_app = HcpConfigNetworkGroup(m_device, m_active_net_group_holder, net_group_config.release(), config_params, current_net_group_index, network_group_metadata.release(), status); CHECK_SUCCESS_AS_EXPECTED(status); - auto net_group_ptr = make_shared_nothrow(std::move(single_context_app)); - CHECK_AS_EXPECTED(nullptr != net_group_ptr, HAILO_OUT_OF_HOST_MEMORY); - m_net_groups.emplace_back(net_group_ptr); + auto net_group_shared_ptr = make_shared_nothrow(std::move(single_context_app)); + CHECK_AS_EXPECTED(nullptr != net_group_shared_ptr, HAILO_OUT_OF_HOST_MEMORY); + m_net_groups.emplace_back(net_group_shared_ptr); - auto net_group_wrapper = ConfiguredNetworkGroupWrapper::create(net_group_ptr); + auto net_group_wrapper = ConfiguredNetworkGroupWrapper::create(net_group_shared_ptr); CHECK_EXPECTED(net_group_wrapper); auto net_group_wrapper_ptr = make_shared_nothrow(net_group_wrapper.release()); CHECK_AS_EXPECTED(nullptr != net_group_wrapper_ptr, HAILO_OUT_OF_HOST_MEMORY); - m_net_group_wrappers.emplace_back(net_group_wrapper_ptr); + m_net_group_wrappers.emplace_back(net_group_wrapper_ptr); added_network_groups.emplace_back(std::static_pointer_cast(net_group_wrapper_ptr)); current_net_group_index++; // TODO: move this func into HcpConfigNetworkGroup c'tor - status = net_group_ptr->create_streams_from_config_params(m_device); + status = net_group_shared_ptr->create_streams_from_config_params(m_device); CHECK_SUCCESS_AS_EXPECTED(status); // Check that all boundary streams were created - status = validate_boundary_streams_were_created(hef, net_group->network_group_metadata().network_group_name(), *net_group_ptr); + status = validate_boundary_streams_were_created(hef, network_group_ptr->network_group_metadata().network_group_name(), *net_group_shared_ptr); CHECK_SUCCESS_AS_EXPECTED(status); } std::string unmatched_keys = ""; diff --git a/hailort/libhailort/src/context_switch/hcp_config_network_group.cpp b/hailort/libhailort/src/context_switch/hcp_config_network_group.cpp index f48c993..22c9948 100644 --- a/hailort/libhailort/src/context_switch/hcp_config_network_group.cpp +++ b/hailort/libhailort/src/context_switch/hcp_config_network_group.cpp @@ -20,7 +20,7 @@ Expected> HcpConfigNetworkGroup::activate { auto start_time = std::chrono::steady_clock::now(); - auto activated_net_group = HcpConfigActivatedNetworkGroup::create(m_device, m_config, get_network_group_name(), network_group_params, + auto activated_net_group = HcpConfigActivatedNetworkGroup::create(m_device, m_config, name(), network_group_params, m_input_streams, m_output_streams, m_active_net_group_holder, m_config_params.power_mode, m_network_group_activated_event); CHECK_EXPECTED(activated_net_group); @@ -29,7 +29,7 @@ Expected> HcpConfigNetworkGroup::activate CHECK_AS_EXPECTED(nullptr != activated_net_group_ptr, HAILO_OUT_OF_HOST_MEMORY); auto elapsed_time_ms = std::chrono::duration(std::chrono::steady_clock::now() - start_time).count(); - LOGGER__INFO("Activating {} took {} milliseconds. Note that the function is asynchronous and thus the network is not fully activated yet.", get_network_group_name(), elapsed_time_ms); + LOGGER__INFO("Activating {} took {} milliseconds. Note that the function is asynchronous and thus the network is not fully activated yet.", name(), elapsed_time_ms); return activated_net_group_ptr; } @@ -39,13 +39,6 @@ Expected HcpConfigNetworkGroup::get_default_streams_in return m_device.get_default_streams_interface(); } -Expected HcpConfigNetworkGroup::get_boundary_channel_index(uint8_t stream_index, - hailo_stream_direction_t direction, const std::string &/* layer_name */) -{ - return (direction == HAILO_H2D_STREAM) ? stream_index : - static_cast(stream_index + OUTPUT_CHANNEL_INDEX_OFFSET); -} - hailo_status HcpConfigNetworkGroup::set_scheduler_timeout(const std::chrono::milliseconds &timeout, const std::string &network_name) { (void) timeout; @@ -60,7 +53,7 @@ hailo_status HcpConfigNetworkGroup::set_scheduler_threshold(uint32_t threshold, return HAILO_INVALID_OPERATION; } -Expected> HcpConfigNetworkGroup::get_latnecy_meters() +Expected> HcpConfigNetworkGroup::get_latency_meters() { /* hcp does not support latnecy. return empty map */ LatencyMetersMap empty_map; diff --git a/hailort/libhailort/src/context_switch/hef_metadata.cpp b/hailort/libhailort/src/context_switch/hef_metadata.cpp index 0a13864..c2b851b 100644 --- a/hailort/libhailort/src/context_switch/hef_metadata.cpp +++ b/hailort/libhailort/src/context_switch/hef_metadata.cpp @@ -300,7 +300,7 @@ hailo_status HEF_METADATA__add_read_vdma_action( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **action_data_current_offset, uint16_t descriptors_count, - uint8_t cfg_channel_handle, + uint8_t config_stream_index, bool is_repeated) { hailo_status status = HAILO_UNINITIALIZED; @@ -312,7 +312,7 @@ hailo_status HEF_METADATA__add_read_vdma_action( read_vdma_action.header.action_type = CONTROL_PROTOCOL__CONTEXT_SWITCH_ACTION_READ_VDMA; read_vdma_action.header.is_repeated = is_repeated; read_vdma_action.descriptors_count = descriptors_count; - read_vdma_action.cfg_channel_handle = cfg_channel_handle; + read_vdma_action.config_stream_index = config_stream_index; status = hef_metadata__update_slicing_info(context_info, action_data_current_offset, @@ -330,7 +330,7 @@ hailo_status HEF_METADATA__add_ccw_bursts_action( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **action_data_current_offset, uint16_t ccw_bursts, - uint8_t cfg_channel_handle, + uint8_t config_stream_index, bool is_repeated) { hailo_status status = HAILO_UNINITIALIZED; @@ -342,7 +342,7 @@ hailo_status HEF_METADATA__add_ccw_bursts_action( fetch_ccw_bursts.header.action_type = CONTROL_PROTOCOL__CONTEXT_SWITCH_ACTION_FETCH_CCW_BURSTS; fetch_ccw_bursts.header.is_repeated = is_repeated; fetch_ccw_bursts.ccw_bursts = ccw_bursts; - fetch_ccw_bursts.cfg_channel_handle = cfg_channel_handle; + fetch_ccw_bursts.config_stream_index = config_stream_index; status = hef_metadata__update_slicing_info(context_info, action_data_current_offset, @@ -548,15 +548,16 @@ void hef_metadata__add_edge_layer_header( *(edge_layer_current_offset) += sizeof(*edge_layer_header); } -void hef_metadata__fill_edge_layer_common_info( - CONTROL_PROTOCOL__edge_layer_common_info_t *edge_layer_common_info, - uint8_t stream_index, - uint8_t vdma_channel_index, - uint8_t network_index, - const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config) +static void hef_metadata__fill_edge_layer_common_info( + CONTROL_PROTOCOL__edge_layer_common_info_t *edge_layer_common_info, + vdma::ChannelId channel_id, + uint8_t stream_index, + uint8_t network_index, + const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config) { + edge_layer_common_info->engine_index = channel_id.engine_index; + edge_layer_common_info->vdma_channel_index = channel_id.channel_index; edge_layer_common_info->stream_index = stream_index; - edge_layer_common_info->vdma_channel_index = vdma_channel_index; edge_layer_common_info->network_index = network_index; edge_layer_common_info->nn_stream_config.core_bytes_per_buffer = BYTE_ORDER__htons(nn_stream_config.core_bytes_per_buffer); edge_layer_common_info->nn_stream_config.core_buffers_per_frame = BYTE_ORDER__htons(nn_stream_config.core_buffers_per_frame); @@ -567,14 +568,13 @@ void hef_metadata__fill_edge_layer_common_info( } hailo_status HEF_METADATA__add_network_boundary_output_edge_layer( - CONTROL_PROTOCOL__context_switch_context_info_t *context_info, - uint8_t **edge_layer_current_offset, - uint8_t stream_index, - uint8_t vdma_channel_index, - uint8_t network_index, - const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, - uint32_t frame_credits_in_bytes, - uint16_t desc_page_size) + CONTROL_PROTOCOL__context_switch_context_info_t *context_info, + uint8_t **edge_layer_current_offset, + vdma::ChannelId channel_id, + uint8_t stream_index, + uint8_t network_index, + const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, + const CONTROL_PROTOCOL__host_buffer_info_t &host_buffer_info) { hailo_status status = HAILO_UNINITIALIZED; CONTROL_PROTOCOL__network_boundary_output_t *edge_layer_info = nullptr; @@ -597,13 +597,12 @@ hailo_status HEF_METADATA__add_network_boundary_output_edge_layer( edge_layer_info = (CONTROL_PROTOCOL__network_boundary_output_t *)(*edge_layer_current_offset); hef_metadata__fill_edge_layer_common_info(&(edge_layer_info->common_info), - stream_index, - vdma_channel_index, - network_index, - nn_stream_config); + channel_id, + stream_index, + network_index, + nn_stream_config); - edge_layer_info->frame_credits_in_bytes = frame_credits_in_bytes; - edge_layer_info->desc_page_size = desc_page_size; + edge_layer_info->host_buffer_info = host_buffer_info; *(edge_layer_current_offset) += sizeof(*edge_layer_info); @@ -613,8 +612,8 @@ hailo_status HEF_METADATA__add_network_boundary_output_edge_layer( hailo_status HEF_METADATA__add_inter_context_output_edge_layer( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **edge_layer_current_offset, - uint8_t stream_index, - uint8_t vdma_channel_index, + vdma::ChannelId channel_id, + uint8_t stream_index, uint8_t network_index, const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, const CONTROL_PROTOCOL__host_buffer_info_t &host_buffer_info) @@ -640,10 +639,10 @@ hailo_status HEF_METADATA__add_inter_context_output_edge_layer( edge_layer_info = (CONTROL_PROTOCOL__inter_context_output_t *)(*edge_layer_current_offset); hef_metadata__fill_edge_layer_common_info(&(edge_layer_info->common_info), - stream_index, - vdma_channel_index, - network_index, - nn_stream_config); + channel_id, + stream_index, + network_index, + nn_stream_config); edge_layer_info->host_buffer_info = host_buffer_info; @@ -653,17 +652,14 @@ hailo_status HEF_METADATA__add_inter_context_output_edge_layer( } hailo_status HEF_METADATA__add_ddr_buffer_output_edge_layer( - CONTROL_PROTOCOL__context_switch_context_info_t *context_info, - uint8_t **edge_layer_current_offset, - uint8_t stream_index, - uint8_t vdma_channel_index, - uint8_t network_index, - const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, - uint32_t frame_credits_in_bytes, - uint64_t host_descriptors_base_address, - uint16_t desc_page_size, - uint8_t desc_list_depth, - uint32_t buffered_rows_count) + CONTROL_PROTOCOL__context_switch_context_info_t *context_info, + uint8_t **edge_layer_current_offset, + vdma::ChannelId channel_id, + uint8_t stream_index, + uint8_t network_index, + const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, + const CONTROL_PROTOCOL__host_buffer_info_t &host_buffer_info, + uint32_t buffered_rows_count) { hailo_status status = HAILO_UNINITIALIZED; CONTROL_PROTOCOL__ddr_buffer_output_t *edge_layer_info = nullptr; @@ -686,17 +682,14 @@ hailo_status HEF_METADATA__add_ddr_buffer_output_edge_layer( edge_layer_info = (CONTROL_PROTOCOL__ddr_buffer_output_t *)(*edge_layer_current_offset); hef_metadata__fill_edge_layer_common_info(&(edge_layer_info->common_info), - stream_index, - vdma_channel_index, - network_index, - nn_stream_config); + channel_id, + stream_index, + network_index, + nn_stream_config); *(edge_layer_current_offset) += sizeof(*edge_layer_info); - edge_layer_info->frame_credits_in_bytes = frame_credits_in_bytes; - edge_layer_info->host_desc_address_info.host_descriptors_base_address = host_descriptors_base_address; - edge_layer_info->desc_page_size = desc_page_size; - edge_layer_info->host_desc_address_info.desc_list_depth = desc_list_depth; + edge_layer_info->host_buffer_info = host_buffer_info; edge_layer_info->buffered_rows_count = buffered_rows_count; return HAILO_SUCCESS; @@ -705,11 +698,11 @@ hailo_status HEF_METADATA__add_ddr_buffer_output_edge_layer( hailo_status HEF_METADATA__add_network_boundary_input_edge_layer( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **edge_layer_current_offset, - uint8_t stream_index, - uint8_t vdma_channel_index, + vdma::ChannelId channel_id, + uint8_t stream_index, uint8_t network_index, const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, - uint16_t desc_page_size, + const CONTROL_PROTOCOL__host_buffer_info_t &host_buffer_info, uint32_t initial_credit_size) { hailo_status status = HAILO_UNINITIALIZED; @@ -733,12 +726,12 @@ hailo_status HEF_METADATA__add_network_boundary_input_edge_layer( edge_layer_info = (CONTROL_PROTOCOL__network_boundary_input_t *)(*edge_layer_current_offset); hef_metadata__fill_edge_layer_common_info(&(edge_layer_info->common_info), - stream_index, - vdma_channel_index, - network_index, - nn_stream_config); + channel_id, + stream_index, + network_index, + nn_stream_config); - edge_layer_info->desc_page_size = desc_page_size; + edge_layer_info->host_buffer_info = host_buffer_info; edge_layer_info->initial_credit_size = initial_credit_size; *(edge_layer_current_offset) += sizeof(*edge_layer_info); @@ -749,8 +742,8 @@ hailo_status HEF_METADATA__add_network_boundary_input_edge_layer( hailo_status HEF_METADATA__add_inter_context_input_edge_layer( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **edge_layer_current_offset, - uint8_t stream_index, - uint8_t vdma_channel_index, + vdma::ChannelId channel_id, + uint8_t stream_index, uint8_t network_index, const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, const CONTROL_PROTOCOL__host_buffer_info_t &host_buffer_info, @@ -777,10 +770,10 @@ hailo_status HEF_METADATA__add_inter_context_input_edge_layer( edge_layer_info = (CONTROL_PROTOCOL__inter_context_input_t *)(*edge_layer_current_offset); hef_metadata__fill_edge_layer_common_info(&(edge_layer_info->common_info), - stream_index, - vdma_channel_index, - network_index, - nn_stream_config); + channel_id, + stream_index, + network_index, + nn_stream_config); edge_layer_info->host_buffer_info = host_buffer_info; edge_layer_info->initial_credit_size = initial_credit_size; @@ -793,13 +786,13 @@ hailo_status HEF_METADATA__add_inter_context_input_edge_layer( hailo_status HEF_METADATA__add_ddr_buffer_input_edge_layer( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **edge_layer_current_offset, - uint8_t stream_index, - uint8_t vdma_channel_index, + vdma::ChannelId channel_id, + uint8_t stream_index, uint8_t network_index, const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, - uint64_t host_descriptors_base_address, - uint8_t desc_list_depth, - uint32_t initial_credit_size) + const CONTROL_PROTOCOL__host_buffer_info_t &host_buffer_info, + uint32_t initial_credit_size, + vdma::ChannelId connected_d2h_channel_id) { hailo_status status = HAILO_UNINITIALIZED; CONTROL_PROTOCOL__ddr_buffer_input_t *edge_layer_info = nullptr; @@ -822,14 +815,15 @@ hailo_status HEF_METADATA__add_ddr_buffer_input_edge_layer( edge_layer_info = (CONTROL_PROTOCOL__ddr_buffer_input_t *)(*edge_layer_current_offset); hef_metadata__fill_edge_layer_common_info(&(edge_layer_info->common_info), - stream_index, - vdma_channel_index, - network_index, - nn_stream_config); + channel_id, + stream_index, + network_index, + nn_stream_config); - edge_layer_info->host_desc_address_info.host_descriptors_base_address = host_descriptors_base_address; - edge_layer_info->host_desc_address_info.desc_list_depth = desc_list_depth; + edge_layer_info->host_buffer_info = host_buffer_info; edge_layer_info->initial_credit_size = initial_credit_size; + edge_layer_info->connected_d2h_engine_index = connected_d2h_channel_id.engine_index; + edge_layer_info->connected_d2h_channel_index = connected_d2h_channel_id.channel_index; *(edge_layer_current_offset) += sizeof(*edge_layer_info); @@ -837,13 +831,13 @@ hailo_status HEF_METADATA__add_ddr_buffer_input_edge_layer( } hailo_status HEF_METADATA__add_ddr_pair_info( - CONTROL_PROTOCOL__context_switch_context_info_t *context_info, - uint8_t **action_data_current_offset, - const uint8_t h2d_vdma_channel_index, - const uint8_t d2h_vdma_channel_index, - const uint32_t descriptors_per_frame, - const uint16_t programmed_descriptors_count, - bool is_repeated) + CONTROL_PROTOCOL__context_switch_context_info_t *context_info, + uint8_t **action_data_current_offset, + const vdma::ChannelId h2d_vdma_channel_id, + const vdma::ChannelId d2h_vdma_channel_id, + const uint32_t descriptors_per_frame, + const uint16_t programmed_descriptors_count, + bool is_repeated) { hailo_status status = HAILO_UNINITIALIZED; CONTROL_PROTOCOL__ADD_DDR_PAIR_ACTION_t ddr_pair_action{}; @@ -853,8 +847,10 @@ hailo_status HEF_METADATA__add_ddr_pair_info( ddr_pair_action.header.action_type = CONTROL_PROTOCOL__CONTEXT_SWITCH_ACTION_ADD_DDR_PAIR_INFO; ddr_pair_action.header.is_repeated = is_repeated; - ddr_pair_action.h2d_vdma_channel_index = h2d_vdma_channel_index; - ddr_pair_action.d2h_vdma_channel_index = d2h_vdma_channel_index; + ddr_pair_action.h2d_engine_index = h2d_vdma_channel_id.engine_index; + ddr_pair_action.h2d_vdma_channel_index = h2d_vdma_channel_id.channel_index; + ddr_pair_action.d2h_engine_index = d2h_vdma_channel_id.engine_index; + ddr_pair_action.d2h_vdma_channel_index = d2h_vdma_channel_id.channel_index; ddr_pair_action.descriptors_per_frame = descriptors_per_frame; ddr_pair_action.programmed_descriptors_count = programmed_descriptors_count; @@ -921,6 +917,33 @@ hailo_status HEF_METADATA__burst_credits_task_start( return HAILO_SUCCESS; } + +hailo_status HEF_METADATA__edge_layer_activation_actions_position_marker( + CONTROL_PROTOCOL__context_switch_context_info_t *context_info, + uint8_t **action_data_current_offset, + bool is_repeated) +{ + hailo_status status = HAILO_UNINITIALIZED; + CONTROL_PROTOCOL__EDGE_LAYER_ACTIVATION_ACTIONS_POSITION_MARKER_T marker{}; + + CHECK_ARG_NOT_NULL(action_data_current_offset); + CHECK_ARG_NOT_NULL(*action_data_current_offset); + + marker.header.action_type = CONTROL_PROTOCOL__CONTEXT_SWITCH_ACTION_EDGE_LAYER_ACTIVATION_ACTIONS_POSITION; + marker.header.is_repeated = is_repeated; + + status = hef_metadata__update_slicing_info(context_info, + action_data_current_offset, + sizeof(marker), + true); + CHECK_SUCCESS(status); + + memcpy((*action_data_current_offset), &marker, sizeof(marker)); + *(action_data_current_offset) += sizeof(marker); + + return HAILO_SUCCESS; +} + /* End of context switch info build functions */ } /* namespace hailort */ diff --git a/hailort/libhailort/src/context_switch/hef_metadata.hpp b/hailort/libhailort/src/context_switch/hef_metadata.hpp index d57f150..6a3b5de 100644 --- a/hailort/libhailort/src/context_switch/hef_metadata.hpp +++ b/hailort/libhailort/src/context_switch/hef_metadata.hpp @@ -5,6 +5,7 @@ #include "common/utils.hpp" #include "control_protocol.h" #include "control_protocol.hpp" +#include "vdma/channel_id.hpp" namespace hailort { @@ -83,7 +84,7 @@ hailo_status HEF_METADATA__add_trigger_to_trigger_group( * @param[in] context_info - struct holding all the context info * @param[out] action - pointer to the action * @param[in] descriptors_count - descriptors_count to fetch - * @param[in] cfg_channel_handle - index of the cfg channel (not the PCIe channel number!) + * @param[in] config_stream_index - index of the cfg channel (not the vDMA channel number!) * @param[in] is_repeated - 'true' if the action is part of a "repeated sequence" (a group of consecutive actions * with the same type) * @@ -92,7 +93,7 @@ hailo_status HEF_METADATA__add_read_vdma_action( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **action_data_current_offset, uint16_t descriptors_count, - uint8_t cfg_channel_handle, + uint8_t config_stream_index, bool is_repeated); /** @@ -101,7 +102,7 @@ hailo_status HEF_METADATA__add_read_vdma_action( * @param[in] context_info - struct holding all the context info * @param[out] action - pointer to the action * @param[in] ccw_bursts - ccw bursts to fetch - * @param[in] cfg_channel_handle - index of the cfg channel (not the PCIe channel number!) + * @param[in] config_stream_index - index of the cfg channel (not the vDMA channel number!) * @param[in] is_repeated - 'true' if the action is part of a "repeated sequence" (a group of consecutive actions * with the same type) * @@ -110,7 +111,7 @@ hailo_status HEF_METADATA__add_ccw_bursts_action( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **action_data_current_offset, uint16_t ccw_bursts, - uint8_t cfg_channel_handle, + uint8_t config_stream_index, bool is_repeated); /** @@ -276,31 +277,28 @@ hailo_status HEF_METADATA__add_wait_for_module_config_done_action( * * @param[in] context_info - struct holding all the context info * @param[out] edge_layer_current_offset - pointer to the location of the edge layer struct + * @param[in] channel_id - vdma channel id * @param[in] stream_index - stream index - * @param[in] vdma_channel_index - channel index * @param[in] network_index - network index * @param[in] nn_stream_config - * @param[in] frame_credits_in_bytes - context credits in bytes - * @param[in] desc_page_size - desc page size in bytes + * @param[in] host_buffer_info - info about host buffer * */ hailo_status HEF_METADATA__add_network_boundary_output_edge_layer( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **edge_layer_current_offset, + vdma::ChannelId channel_id, uint8_t stream_index, - uint8_t vdma_channel_index, uint8_t network_index, const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, - uint32_t frame_credits_in_bytes, - uint16_t desc_page_size); - + const CONTROL_PROTOCOL__host_buffer_info_t &host_buffer_info); /** * build edge layer - vdma intermediate buffer output * * @param[in] context_info - struct holding all the context info * @param[out] edge_layer_current_offset - pointer to the location of the edge layer struct + * @param[in] channel_id - vdma channel id * @param[in] stream_index - stream index - * @param[in] vdma_channel_index - channel index * @param[in] network_index - network index * @param[in] nn_stream_config * @param[in] host_buffer_info - info about host buffer @@ -308,8 +306,8 @@ hailo_status HEF_METADATA__add_network_boundary_output_edge_layer( hailo_status HEF_METADATA__add_inter_context_output_edge_layer( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **edge_layer_current_offset, - uint8_t stream_index, - uint8_t vdma_channel_index, + vdma::ChannelId channel_id, + uint8_t stream_index, uint8_t network_index, const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, const CONTROL_PROTOCOL__host_buffer_info_t &host_buffer_info); @@ -318,61 +316,55 @@ hailo_status HEF_METADATA__add_inter_context_output_edge_layer( * build edge layer - vdma DDR buffer output * * @param[in] context_info - struct holding all the context info - * @param[out] edge_layer_current_offset - pointer to the location of the edge layer struct + * @param[out] edge_layer_current_offset - pointer to the location of the edge layer struct + * @param[in] channel_id - vdma channel id * @param[in] stream_index - stream index - * @param[in] vdma_channel_index - channel index * @param[in] network_index - network index * @param[in] nn_stream_config - * @param[in] frame_credits_in_bytes - context credits in bytes - * @param[in] host_descriptors_base_address - host descritpors base address - * @param[in] desc_page_size - descriptor page_size in bytes - * @param[in] desc_list_depth - descriptor list depth + * @param[in] host_buffer_info - info about host buffer * @param[in] buffered_rows_count - amount of rows to buffer. * */ hailo_status HEF_METADATA__add_ddr_buffer_output_edge_layer( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **edge_layer_current_offset, + vdma::ChannelId channel_id, uint8_t stream_index, - uint8_t vdma_channel_index, uint8_t network_index, const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, - uint32_t frame_credits_in_bytes, - uint64_t host_descriptors_base_address, - uint16_t desc_page_size, - uint8_t desc_list_depth, + const CONTROL_PROTOCOL__host_buffer_info_t &host_buffer_info, uint32_t buffered_rows_count); /** * build edge layer - vdma network boundary input * * @param[in] context_info - struct holding all the context info - * @param[out] edge_layer_current_offset - pointer to the location of the edge layer struct + * @param[out] edge_layer_current_offset - pointer to the location of the edge layer struct + * @param[in] channel_id - vdma channel id * @param[in] stream_index - stream index - * @param[in] vdma_channel_index - channel index * @param[in] network_index - network index * @param[in] nn_stream_config - * @param[in] desc_page_size - desc page size in bytes + * @param[in] host_buffer_info - info about host buffer * @param[in] initial_credit_size - initial credit size, if 0 is set the firmware takes its default value. * */ hailo_status HEF_METADATA__add_network_boundary_input_edge_layer( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **edge_layer_current_offset, + vdma::ChannelId channel_id, uint8_t stream_index, - uint8_t vdma_channel_index, uint8_t network_index, const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, - uint16_t desc_page_size, + const CONTROL_PROTOCOL__host_buffer_info_t &host_buffer_info, uint32_t initial_credit_size); /** * build edge layer - vdma intermediate buffer input * * @param[in] context_info - struct holding all the context info - * @param[out] edge_layer_current_offset - pointer to the location of the edge layer struct + * @param[out] edge_layer_current_offset - pointer to the location of the edge layer struct + * @param[in] channel_id - vdma channel id * @param[in] stream_index - stream index - * @param[in] vdma_channel_index - channel index * @param[in] network_index - network index * @param[in] nn_stream_config * @param[in] host_buffer_info - info about host buffer @@ -382,8 +374,8 @@ hailo_status HEF_METADATA__add_network_boundary_input_edge_layer( hailo_status HEF_METADATA__add_inter_context_input_edge_layer( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **edge_layer_current_offset, + vdma::ChannelId channel_id, uint8_t stream_index, - uint8_t vdma_channel_index, uint8_t network_index, const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, const CONTROL_PROTOCOL__host_buffer_info_t &host_buffer_info, @@ -393,33 +385,33 @@ hailo_status HEF_METADATA__add_inter_context_input_edge_layer( * build edge layer - vdma ddr buffer input * * @param[in] context_info - struct holding all the context info - * @param[out] edge_layer_current_offset - pointer to the location of the edge layer struct + * @param[out] edge_layer_current_offset - pointer to the location of the edge layer struct + * @param[in] channel_id - vdma channel id * @param[in] stream_index - stream index - * @param[in] vdma_channel_index - channel index * @param[in] network_index - network index * @param[in] nn_stream_config - * @param[in] host_descriptors_base_address - host descritpors base address - * @param[in] desc_list_depth - descriptor list depth + * @param[in] host_buffer_info - info about host buffer. * @param[in] initial_credit_size - initial credit size, if 0 is set the firmware takes its default value. + * @param[in] connected_d2h_channel_id - vdma channel id of the connected d2h channel. */ hailo_status HEF_METADATA__add_ddr_buffer_input_edge_layer( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **edge_layer_current_offset, + vdma::ChannelId channel_id, uint8_t stream_index, - uint8_t vdma_channel_index, uint8_t network_index, const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, - uint64_t host_descriptors_base_address, - uint8_t desc_list_depth, - uint32_t initial_credit_size); + const CONTROL_PROTOCOL__host_buffer_info_t &host_buffer_info, + uint32_t initial_credit_size, + vdma::ChannelId connected_d2h_channel_id); /** * Build add ddr pair info action * * @param[in] context_info - struct holding all the context info * @param[out] action_data_current_offset - pointer to the action - * @param[in] h2d_vdma_channel_index - DDR pair host to device channel index - * @param[in] d2h_vdma_channel_index - DDR pair device to host channel index + * @param[in] h2d_vdma_channel_id - DDR pair host to device channel ind + * @param[in] d2h_vdma_channel_id - DDR pair device to host channel id * @param[in] descriptors_per_frame - expected total descritors transfered (per one frame) * @param[in] programmed_descriptors_count - total size of the programed descriptors list * @param[in] is_repeated - 'true' if the action is part of a "repeated sequence" (a group of consecutive actions @@ -429,8 +421,8 @@ hailo_status HEF_METADATA__add_ddr_buffer_input_edge_layer( hailo_status HEF_METADATA__add_ddr_pair_info( CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **action_data_current_offset, - const uint8_t h2d_vdma_channel_index, - const uint8_t d2h_vdma_channel_index, + const vdma::ChannelId h2d_vdma_channel_id, + const vdma::ChannelId d2h_vdma_channel_id, const uint32_t descriptors_per_frame, const uint16_t programmed_descriptors_count, bool is_repeated); @@ -463,6 +455,20 @@ hailo_status HEF_METADATA__burst_credits_task_start( uint8_t **action_data_current_offset, bool is_repeated); +/** + * Build edge layer activation actions position marker + * + * @param[in] context_info - struct holding all the context info + * @param[out] action_data_current_offset - pointer to the action + * @param[in] is_repeated - 'true' if the action is part of a "repeated sequence" (a group of consecutive actions + * with the same type) + * + */ +hailo_status HEF_METADATA__edge_layer_activation_actions_position_marker( + CONTROL_PROTOCOL__context_switch_context_info_t *context_info, + uint8_t **action_data_current_offset, + bool is_repeated); + } /* namespace hailort */ #endif /* __CONTEXT_SWITCH__ */ diff --git a/hailort/libhailort/src/context_switch/multi_context/resource_manager.hpp b/hailort/libhailort/src/context_switch/multi_context/resource_manager.hpp index 9ae43b7..a93e936 100644 --- a/hailort/libhailort/src/context_switch/multi_context/resource_manager.hpp +++ b/hailort/libhailort/src/context_switch/multi_context/resource_manager.hpp @@ -10,124 +10,40 @@ * * !-Working with physical device-! * VdmaDevice (either PcieDevice or CoreDevice) - * └── VdmaConfigManager - * └──vector of VdmaConfigNetworkGroup - * └──ResourceManager - * └──reference to physical device + * |-- VdmaConfigManager + * |--vector of VdmaConfigNetworkGroup + * |--ResourceManager + * |--reference to physical device * * !-Working with virtual device-! * VDevice - * └──vector of PcieDevice - * └──vector of VdmaConfigNetworkGroup - * └── vector of ResourceManager - * └──reference to physical device + * |--vector of VdmaDevice (either PcieDevice or CoreDevice) + * |--vector of VdmaConfigNetworkGroup + * |-- vector of ResourceManager + * |--reference to physical device **/ #ifndef _HAILO_CONTEXT_SWITCH_RESOURCE_MANAGER_HPP_ #define _HAILO_CONTEXT_SWITCH_RESOURCE_MANAGER_HPP_ #include "hailo/hailort.h" -#include "intermediate_buffer.hpp" +#include "inter_context_buffer.hpp" +#include "ddr_channels_pair.hpp" #include "config_buffer.hpp" #include "vdma_channel.hpp" #include "control_protocol.hpp" #include "pcie_device.hpp" +#include "channel_allocator.hpp" namespace hailort { -#define MIN_H2D_CHANNEL_INDEX (0) -#define MAX_H2D_CHANNEL_INDEX (15) -#define MIN_D2H_CHANNEL_INDEX (16) -#define MAX_D2H_CHANNEL_INDEX (31) - -#define DDR_NUMBER_OF_ROWS_PER_INTERRUPT (1) -#define DDR_THREAD_DEFAULT_TIMEOUT_MS (20 * 1000) -#define DDR_THREADS_MIN_BUFFERED_ROWS_INITIAL_SCALE (1) - - -class DdrChannelsInfo -{ -public: - uint8_t d2h_channel_index; - uint8_t d2h_stream_index; - uint8_t h2d_channel_index; - uint8_t h2d_stream_index; - uint16_t row_size; - uint32_t min_buffered_rows; - uint32_t desc_list_size_mask; - uint8_t context_index; - uint16_t initial_programed_descs; - uint32_t descriptors_per_frame; - // Ref to ResourcesManager's m_ddr_buffer_channels - VdmaChannel *h2d_ch; - VdmaChannel *d2h_ch; - - // Ref to intermediate buffer; - IntermediateBuffer *intermediate_buffer; -}; -class ChannelInfo -{ -public: - enum class Type : uint8_t - { - NOT_SET = 0, - BOUNDARY = 1, - INTER_CONTEXT = 2, - DDR = 3, - CFG = 4 - }; - - ChannelInfo() : m_info(Type::NOT_SET), m_pcie_stream_index(UINT8_MAX), m_layer_name() {} - - void set_type(Type type) - { - m_info = type; - } - - bool is_type(Type type) const - { - return (m_info == type); - } - - bool is_used() const - { - return (m_info != Type::NOT_SET); - } - - uint8_t get_pcie_stream_index() - { - assert(UINT8_MAX != m_pcie_stream_index); - return m_pcie_stream_index; - } - - void set_pcie_stream_index(uint8_t pcie_channel_index) - { - m_pcie_stream_index = pcie_channel_index; - } - - void set_layer_name(const std::string &name) - { - m_layer_name = name; - } - - const std::string &get_layer_name() - { - return m_layer_name; - } - -private: - Type m_info; - uint8_t m_pcie_stream_index; - std::string m_layer_name; -}; - class ResourcesManager final { public: static Expected create(VdmaDevice &vdma_device, HailoRTDriver &driver, - const ConfigureNetworkParams &config_params, ProtoHEFNetworkGroupPtr network_group_proto, + const ConfigureNetworkParams &config_params, const ProtoHEFNetworkGroup &network_group_proto, std::shared_ptr network_group_metadata, const HefParsingInfo &parsing_info, uint8_t net_group_index); @@ -136,29 +52,31 @@ public: ResourcesManager &operator=(const ResourcesManager &other) = delete; ResourcesManager &operator=(ResourcesManager &&other) = delete; ResourcesManager(ResourcesManager &&other) noexcept : - m_ddr_infos(std::move(other.m_ddr_infos)), m_contexts(std::move(other.m_contexts)), - m_channels_info(std::move(other.m_channels_info)), m_vdma_device(other.m_vdma_device), + m_contexts(std::move(other.m_contexts)), + m_channel_allocator(std::move(other.m_channel_allocator)), + m_vdma_device(other.m_vdma_device), m_driver(other.m_driver), m_config_params(other.m_config_params), m_preliminary_config(std::move(other.m_preliminary_config)), m_dynamic_config(std::move(other.m_dynamic_config)), - m_intermediate_buffers(std::move(other.m_intermediate_buffers)), - m_inter_context_channels(std::move(other.m_inter_context_channels)), - m_config_channels(std::move(other.m_config_channels)), m_ddr_buffer_channels(std::move(other.m_ddr_buffer_channels)), + m_inter_context_buffers(std::move(other.m_inter_context_buffers)), + m_ddr_channels_pairs(std::move(other.m_ddr_channels_pairs)), + m_fw_managed_channels(std::move(other.m_fw_managed_channels)), m_network_group_metadata(std::move(other.m_network_group_metadata)), m_net_group_index(other.m_net_group_index), m_network_index_map(std::move(other.m_network_index_map)), m_latency_meters(std::move(other.m_latency_meters)), m_boundary_channels(std::move(other.m_boundary_channels)) {} - ExpectedRef create_inter_context_buffer(uint32_t transfer_size, uint8_t src_stream_index, + ExpectedRef create_inter_context_buffer(uint32_t transfer_size, uint8_t src_stream_index, uint8_t src_context_index, const std::string &network_name); - ExpectedRef get_intermediate_buffer(const IntermediateBufferKey &key); - Expected> create_boundary_vdma_channel(uint8_t channel_index, uint32_t transfer_size, - const std::string &network_name, const std::string &stream_name, + ExpectedRef get_inter_context_buffer(const IntermediateBufferKey &key); + Expected> create_boundary_vdma_channel(const vdma::ChannelId &channel_id, + uint32_t transfer_size, const std::string &network_name, const std::string &stream_name, VdmaChannel::Direction channel_direction); - ExpectedRef create_ddr_buffer(DdrChannelsInfo &ddr_info, uint8_t context_index); + ExpectedRef create_ddr_channels_pair(const DdrChannelsInfo &ddr_info, uint8_t context_index); + ExpectedRef get_ddr_channels_pair(uint8_t context_index, uint8_t d2h_stream_index); - Expected get_control_network_group_header(bool is_scheduler_used); + Expected get_control_network_group_header(); using context_info_t = CONTROL_PROTOCOL__context_switch_context_info_t; @@ -183,10 +101,7 @@ public: return m_dynamic_config[context_index]; } - std::vector &ddr_infos() - { - return m_ddr_infos; - } + std::vector> get_ddr_channel_pairs_per_context(uint8_t context_index) const; hailo_power_mode_t get_power_mode() { @@ -208,36 +123,28 @@ public: return m_vdma_device; } - Expected get_available_channel_index(std::set& blacklist, ChannelInfo::Type required_type, - VdmaChannel::Direction direction, const std::string &layer_name=""); - - Expected> get_channel_info(uint8_t index) - { - CHECK_AS_EXPECTED(index < m_channels_info.max_size(), HAILO_INVALID_ARGUMENT); - return std::ref(m_channels_info[index]); - } + Expected get_available_channel_id(const LayerIdentifier &layer_identifier, + VdmaChannel::Direction direction, uint8_t engine_index); + hailo_status free_channel_index(const LayerIdentifier &layer_identifier); const char* get_dev_id() const { return m_vdma_device.get_dev_id(); } - LatencyMetersMap &get_latnecy_meters() + LatencyMetersMap &get_latency_meters() { return m_latency_meters; } - Expected get_boundary_channel_index(uint8_t stream_index, - hailo_stream_direction_t direction, const std::string &layer_name); Expected get_default_streams_interface(); Expected read_intermediate_buffer(const IntermediateBufferKey &key); - hailo_status set_number_of_cfg_channels(const uint8_t number_of_cfg_channels); void update_preliminary_config_buffer_info(); void update_dynamic_contexts_buffer_info(); - hailo_status create_internal_vdma_channels(); + hailo_status create_fw_managed_vdma_channels(); hailo_status register_fw_managed_vdma_channels(); hailo_status unregister_fw_managed_vdma_channels(); hailo_status set_inter_context_channels_dynamic_batch_size(uint16_t dynamic_batch_size); @@ -245,31 +152,31 @@ public: void abort_ddr_channels(); void close_ddr_channels(); hailo_status enable_state_machine(uint16_t dynamic_batch_size); - hailo_status reset_state_machine(); + hailo_status reset_state_machine(bool keep_nn_config_during_reset = false); Expected get_network_batch_size(const std::string &network_name) const; Expected> get_boundary_vdma_channel_by_stream_name(const std::string &stream_name); private: - ExpectedRef create_intermediate_buffer(IntermediateBuffer::ChannelType channel_type, - uint32_t transfer_size, uint16_t batch_size, const IntermediateBufferKey &key); void update_config_buffer_info(std::vector &config_buffers, CONTROL_PROTOCOL__context_switch_context_info_t &context); hailo_status fill_infer_features(CONTROL_PROTOCOL__application_header_t &app_header); - hailo_status fill_network_batch_size(CONTROL_PROTOCOL__application_header_t &app_header, bool is_scheduler_used); + hailo_status fill_validation_features(CONTROL_PROTOCOL__application_header_t &app_header); + hailo_status fill_network_batch_size(CONTROL_PROTOCOL__application_header_t &app_header); + + static Expected get_cfg_channel_engine(HailoRTDriver &driver, uint8_t config_stream_index, + const ProtoHEFNetworkGroupMetadata &proto_metadata); - std::vector m_ddr_infos; std::vector m_contexts; - std::array m_channels_info; + ChannelAllocator m_channel_allocator; VdmaDevice &m_vdma_device; HailoRTDriver &m_driver; const ConfigureNetworkParams m_config_params; std::vector m_preliminary_config; // m_dynamic_config[context_index][config_index] std::vector> m_dynamic_config; - std::map m_intermediate_buffers; - std::vector m_inter_context_channels; - std::vector m_config_channels; - std::vector m_ddr_buffer_channels; + std::map m_inter_context_buffers; + std::map m_ddr_channels_pairs; + std::vector m_fw_managed_channels; std::shared_ptr m_network_group_metadata; uint8_t m_net_group_index; const std::vector m_network_index_map; @@ -277,9 +184,11 @@ private: std::map> m_boundary_channels; //map of string name and connected vDMA channel ResourcesManager(VdmaDevice &vdma_device, HailoRTDriver &driver, - const ConfigureNetworkParams config_params, std::vector &&preliminary_config, - std::vector> &&dynamic_config, std::shared_ptr &&network_group_metadata, uint8_t net_group_index, + ChannelAllocator &&channel_allocator, const ConfigureNetworkParams config_params, + std::vector &&preliminary_config, std::vector> &&dynamic_config, + std::shared_ptr &&network_group_metadata, uint8_t net_group_index, const std::vector &&network_index_map, LatencyMetersMap &&latency_meters) : + m_channel_allocator(std::move(channel_allocator)), m_vdma_device(vdma_device), m_driver(driver), m_config_params(config_params), m_preliminary_config(std::move(preliminary_config)), m_dynamic_config(std::move(dynamic_config)), m_network_group_metadata(std::move(network_group_metadata)), m_net_group_index(net_group_index), m_network_index_map(std::move(network_index_map)), diff --git a/hailort/libhailort/src/context_switch/multi_context/vdma_config_activated_network_group.hpp b/hailort/libhailort/src/context_switch/multi_context/vdma_config_activated_network_group.hpp index e1ff082..330d699 100644 --- a/hailort/libhailort/src/context_switch/multi_context/vdma_config_activated_network_group.hpp +++ b/hailort/libhailort/src/context_switch/multi_context/vdma_config_activated_network_group.hpp @@ -48,6 +48,7 @@ public: virtual const std::string &get_network_group_name() const override; virtual Expected get_intermediate_buffer(const IntermediateBufferKey &key) override; + virtual hailo_status set_keep_nn_config_during_reset(const bool keep_nn_config_during_reset) override; private: VdmaConfigActivatedNetworkGroup( @@ -70,13 +71,14 @@ private: std::shared_ptr> desc_list_num_ready); std::string m_network_group_name; - bool m_should_reset_state_machine; + bool m_should_reset_network_group; VdmaConfigActiveAppHolder &m_active_net_group_holder; // One ResourcesManager per connected physical device. Currently only one device is supported. std::vector> m_resources_managers; std::vector m_ddr_send_threads; std::vector m_ddr_recv_threads; AccumulatorPtr m_deactivation_time_accumulator; + bool m_keep_nn_config_during_reset; }; } /* namespace hailort */ diff --git a/hailort/libhailort/src/context_switch/multi_context/vdma_config_manager.hpp b/hailort/libhailort/src/context_switch/multi_context/vdma_config_manager.hpp index 1aa9506..3b61762 100644 --- a/hailort/libhailort/src/context_switch/multi_context/vdma_config_manager.hpp +++ b/hailort/libhailort/src/context_switch/multi_context/vdma_config_manager.hpp @@ -31,25 +31,30 @@ namespace hailort { +#define DISABLE_MULTIPLEXER_ENV_VAR "HAILO_DISABLE_MULTIPLEXER" + +class VDeviceBase; + class VdmaConfigManager : public ConfigManager { public: + // Note: Each manager created on a device clears the network groups configured to that device static Expected create(VdmaDevice &device); - static Expected create(VDevice &vdevice); + static Expected create(VDeviceBase &vdevice); virtual ConfigManagerType get_manager_type(); - virtual Expected add_hef(Hef &hef, - const NetworkGroupsParamsMap &configure_params, bool is_scheduler_used=false); + virtual Expected add_hef(Hef &hef, const NetworkGroupsParamsMap &configure_params); - static hailo_status update_network_batch_size(ConfigureNetworkParams &configure_params); + static hailo_status update_network_batch_size(ConfigureNetworkParams &network_group_config_params); - virtual ~VdmaConfigManager() = default; + virtual ~VdmaConfigManager(); VdmaConfigManager(const VdmaConfigManager &other) noexcept = delete; VdmaConfigManager &operator=(const VdmaConfigManager &other) = delete; VdmaConfigManager &operator=(VdmaConfigManager &&other) = delete; VdmaConfigManager(VdmaConfigManager &&other) noexcept = default; - private: - VdmaConfigManager(std::vector> &&devices, bool is_vdevice, NetworkGroupSchedulerWeakPtr network_group_scheduler); +private: + VdmaConfigManager(std::vector> &&devices, bool is_vdevice, + NetworkGroupSchedulerWeakPtr network_group_scheduler, hailo_status &status); // TODO: (SDK-16665) Dont need is_active flag for dtor? std::vector> m_devices; diff --git a/hailort/libhailort/src/context_switch/multi_context/vdma_config_network_group.hpp b/hailort/libhailort/src/context_switch/multi_context/vdma_config_network_group.hpp index 477b298..eda6d20 100644 --- a/hailort/libhailort/src/context_switch/multi_context/vdma_config_network_group.hpp +++ b/hailort/libhailort/src/context_switch/multi_context/vdma_config_network_group.hpp @@ -22,6 +22,7 @@ #include "context_switch/network_group_internal.hpp" #include "context_switch/multi_context/resource_manager.hpp" #include "network_group_scheduler.hpp" +#include "context_switch/pipeline_multiplexer.hpp" #include #include @@ -33,13 +34,12 @@ namespace hailort #define MAX_CONTEXTS_COUNT (CONTROL_PROTOCOL__MAX_TOTAL_CONTEXTS) - class VdmaConfigNetworkGroup : public ConfiguredNetworkGroupBase { public: static Expected create(VdmaConfigActiveAppHolder &active_net_group_holder, const ConfigureNetworkParams &config_params, - std::vector> resources_managers, + std::vector> resources_managers, const std::string &hef_hash, std::shared_ptr network_group_metadata, NetworkGroupSchedulerWeakPtr network_group_scheduler); std::vector> &get_resources_managers() @@ -47,24 +47,28 @@ public: return m_resources_managers; } - hailo_status create_vdevice_streams_from_config_params(network_group_handle_t network_group_handle); - hailo_status create_output_vdevice_stream_from_config_params( - const hailo_stream_parameters_t &stream_params, const std::string &stream_name, network_group_handle_t network_group_handle); + hailo_status create_vdevice_streams_from_config_params(std::shared_ptr multiplexer, + scheduler_ng_handle_t scheduler_handle); hailo_status create_input_vdevice_stream_from_config_params( - const hailo_stream_parameters_t &stream_params, const std::string &stream_name, network_group_handle_t network_group_handle); + const hailo_stream_parameters_t &stream_params, const std::string &stream_name, + std::shared_ptr multiplexer, scheduler_ng_handle_t scheduler_handle); + hailo_status create_output_vdevice_stream_from_config_params( + const hailo_stream_parameters_t &stream_params, const std::string &stream_name, + std::shared_ptr multiplexer, scheduler_ng_handle_t scheduler_handle); + + hailo_status create_vdevice_streams_from_duplicate(std::shared_ptr other); virtual Expected> activate_impl( const hailo_activate_network_group_params_t &network_group_params, uint16_t dynamic_batch_size) override; virtual Expected get_default_streams_interface() override; - virtual Expected get_boundary_channel_index(uint8_t stream_index, hailo_stream_direction_t direction, - const std::string &layer_name) override; - virtual Expected> get_latnecy_meters() override; + virtual Expected> get_latency_meters() override; virtual Expected> get_boundary_vdma_channel_by_stream_name( const std::string &stream_name) override; - void set_network_group_handle(network_group_handle_t handle); + void set_network_group_handle(scheduler_ng_handle_t handle); + scheduler_ng_handle_t network_group_handle() const; virtual hailo_status set_scheduler_timeout(const std::chrono::milliseconds &timeout, const std::string &network_name) override; virtual hailo_status set_scheduler_threshold(uint32_t threshold, const std::string &network_name) override; @@ -73,20 +77,37 @@ public: VdmaConfigNetworkGroup &operator=(const VdmaConfigNetworkGroup &other) = delete; VdmaConfigNetworkGroup &operator=(VdmaConfigNetworkGroup &&other) = delete; VdmaConfigNetworkGroup(VdmaConfigNetworkGroup &&other) noexcept : ConfiguredNetworkGroupBase(std::move(other)), - m_active_net_group_holder(other.m_active_net_group_holder), - m_resources_managers(std::move(other.m_resources_managers)), m_network_group_scheduler(std::move(other.m_network_group_scheduler)) {} + m_active_net_group_holder(other.m_active_net_group_holder), + m_resources_managers(std::move(other.m_resources_managers)), m_network_group_scheduler(std::move(other.m_network_group_scheduler)), + m_scheduler_handle(std::move(other.m_scheduler_handle)), m_multiplexer_handle(std::move(other.m_multiplexer_handle)), + m_multiplexer(std::move(other.m_multiplexer)), m_hef_hash(std::move(other.m_hef_hash)) + {} + + bool equals(const Hef &hef, const std::string &network_group_name) { + return (network_group_name == name()) && (hef.hash() == m_hef_hash); + } + + uint32_t multiplexer_duplicates_count() + { + assert(m_multiplexer->instances_count() > 0); + return static_cast(m_multiplexer->instances_count() - 1); + } + + virtual Expected> create_output_vstreams(const std::map &outputs_params) override; private: VdmaConfigNetworkGroup(VdmaConfigActiveAppHolder &active_net_group_holder, const ConfigureNetworkParams &config_params, - std::vector> &&resources_managers, + std::vector> &&resources_managers, const std::string &hef_hash, const NetworkGroupMetadata &network_group_metadata, NetworkGroupSchedulerWeakPtr network_group_scheduler, hailo_status &status); VdmaConfigActiveAppHolder &m_active_net_group_holder; std::vector> m_resources_managers; NetworkGroupSchedulerWeakPtr m_network_group_scheduler; - network_group_handle_t m_network_group_handle; - + scheduler_ng_handle_t m_scheduler_handle; + multiplexer_ng_handle_t m_multiplexer_handle; + std::shared_ptr m_multiplexer; + std::string m_hef_hash; }; } /* namespace hailort */ diff --git a/hailort/libhailort/src/context_switch/network_group.cpp b/hailort/libhailort/src/context_switch/network_group.cpp index d364754..6de284d 100644 --- a/hailort/libhailort/src/context_switch/network_group.cpp +++ b/hailort/libhailort/src/context_switch/network_group.cpp @@ -8,6 +8,7 @@ **/ #include "hailo/transform.hpp" +#include "hailo/vstream.hpp" #include "network_group_internal.hpp" #include "hef_internal.hpp" #include "common/utils.hpp" @@ -18,6 +19,7 @@ #include "mipi_stream.hpp" #include "control.hpp" #include "common/runtime_statistics_internal.hpp" +#include "vstream_internal.hpp" namespace hailort { @@ -132,7 +134,7 @@ Expected ConfiguredNetworkGroupBase::get_latency_measu bool clear = ((m_config_params.latency & HAILO_LATENCY_CLEAR_AFTER_GET) == HAILO_LATENCY_CLEAR_AFTER_GET); LatencyMeasurementResult result = {}; - auto latency_meters_exp = get_latnecy_meters(); + auto latency_meters_exp = get_latency_meters(); CHECK_EXPECTED(latency_meters_exp); auto latency_meters = latency_meters_exp.release(); @@ -337,6 +339,11 @@ const std::string &ConfiguredNetworkGroupBase::get_network_group_name() const return m_network_group_metadata.network_group_name(); } +const std::string &ConfiguredNetworkGroupBase::name() const +{ + return m_network_group_metadata.network_group_name(); +} + Expected ConfiguredNetworkGroupBase::get_stream_batch_size(const std::string &stream_name) { auto layer_infos = m_network_group_metadata.get_all_layer_infos(); @@ -355,6 +362,11 @@ Expected ConfiguredNetworkGroupBase::get_stream_batch_size(const std:: return make_unexpected(HAILO_NOT_FOUND); } +bool ConfiguredNetworkGroupBase::is_multi_context() const +{ + return m_network_group_metadata.supported_features().multi_context; +} + const ConfigureNetworkParams ConfiguredNetworkGroupBase::get_config_params() const { return m_config_params; @@ -375,7 +387,7 @@ hailo_status ConfiguredNetworkGroupBase::create_input_stream_from_config_params( { auto batch_size_exp = get_stream_batch_size(stream_name); CHECK_EXPECTED_AS_STATUS(batch_size_exp); - const auto stream_index = edge_layer->index; + const auto stream_index = edge_layer->stream_index; auto vdma_channel_ptr = get_boundary_vdma_channel_by_stream_name(stream_name); CHECK_EXPECTED_AS_STATUS(vdma_channel_ptr, "Failed to get vdma channel for output stream {}", stream_index); @@ -389,7 +401,7 @@ hailo_status ConfiguredNetworkGroupBase::create_input_stream_from_config_params( { auto batch_size_exp = get_stream_batch_size(stream_name); CHECK_EXPECTED_AS_STATUS(batch_size_exp); - const auto stream_index = edge_layer->index; + const auto stream_index = edge_layer->stream_index; auto vdma_channel_ptr = get_boundary_vdma_channel_by_stream_name(stream_name); CHECK_EXPECTED_AS_STATUS(vdma_channel_ptr, "Failed to get vdma channel for output stream {}", stream_index); @@ -440,7 +452,7 @@ hailo_status ConfiguredNetworkGroupBase::create_output_stream_from_config_params { auto batch_size_exp = get_stream_batch_size(stream_name); CHECK_EXPECTED_AS_STATUS(batch_size_exp); - const auto stream_index = edge_layer->index; + const auto stream_index = edge_layer->stream_index; auto vdma_channel_ptr = get_boundary_vdma_channel_by_stream_name(stream_name); CHECK_EXPECTED_AS_STATUS(vdma_channel_ptr, "Failed to get vdma channel for output stream {}", stream_index); @@ -454,7 +466,7 @@ hailo_status ConfiguredNetworkGroupBase::create_output_stream_from_config_params { auto batch_size_exp = get_stream_batch_size(stream_name); CHECK_EXPECTED_AS_STATUS(batch_size_exp); - const auto stream_index = edge_layer->index; + const auto stream_index = edge_layer->stream_index; auto vdma_channel_ptr = get_boundary_vdma_channel_by_stream_name(stream_name); CHECK_EXPECTED_AS_STATUS(vdma_channel_ptr, "Failed to get vdma channel for output stream {}", stream_index); @@ -606,6 +618,8 @@ std::vector> ConfiguredNetworkGroupBase::ge hailo_status ConfiguredNetworkGroupBase::wait_for_activation(const std::chrono::milliseconds &timeout) { + CHECK(!m_is_scheduling, HAILO_INVALID_OPERATION, + "Waiting for network group activation is not allowed when the network group scheduler is active!"); return m_network_group_activated_event->wait(timeout); } @@ -713,4 +727,85 @@ AccumulatorPtr ConfiguredNetworkGroupBase::get_deactivation_time_accumulator() c return m_deactivation_time_accumulator; } +static hailo_vstream_params_t expand_vstream_params_autos(const hailo_stream_info_t &stream_info, + const hailo_vstream_params_t &vstream_params) +{ + auto local_vstream_params = vstream_params; + local_vstream_params.user_buffer_format = HailoRTDefaults::expand_auto_format(vstream_params.user_buffer_format, + stream_info.format); + return local_vstream_params; +} + +static std::map vstream_infos_vector_to_map(std::vector &&vstream_info_vector) +{ + std::map vstream_infos_map; + for (const auto &vstream_info : vstream_info_vector) { + vstream_infos_map.emplace(std::string(vstream_info.name), vstream_info); + } + + return vstream_infos_map; +} + +Expected> ConfiguredNetworkGroupBase::create_input_vstreams(const std::map &inputs_params) +{ + auto input_vstream_infos = get_input_vstream_infos(); + CHECK_EXPECTED(input_vstream_infos); + auto input_vstream_infos_map = vstream_infos_vector_to_map(input_vstream_infos.release()); + + std::vector vstreams; + vstreams.reserve(inputs_params.size()); + for (const auto &name_params_pair : inputs_params) { + auto input_stream_ref = get_input_stream_by_name(name_params_pair.first); + CHECK_EXPECTED(input_stream_ref); + + const auto vstream_info = input_vstream_infos_map.find(name_params_pair.first); + CHECK_AS_EXPECTED(vstream_info != input_vstream_infos_map.end(), HAILO_NOT_FOUND, + "Failed to find vstream info of {}", name_params_pair.first); + + const auto vstream_params = expand_vstream_params_autos(input_stream_ref->get().get_info(), name_params_pair.second); + auto inputs = VStreamsBuilderUtils::create_inputs(input_stream_ref->get(), vstream_info->second, vstream_params); + CHECK_EXPECTED(inputs); + + vstreams.insert(vstreams.end(), std::make_move_iterator(inputs->begin()), std::make_move_iterator(inputs->end())); + } + return vstreams; +} + +Expected> ConfiguredNetworkGroupBase::create_output_vstreams(const std::map &outputs_params) +{ + std::vector vstreams; + vstreams.reserve(outputs_params.size()); + auto output_streams = get_output_streams_from_vstream_names(outputs_params); + CHECK_EXPECTED(output_streams); + + auto output_vstream_infos = get_output_vstream_infos(); + CHECK_EXPECTED(output_vstream_infos); + auto output_vstream_infos_map = vstream_infos_vector_to_map(output_vstream_infos.release()); + + // We iterate through all output streams, and if they are nms, we collect them together by their original stream name. + // We need this step because all nms output streams of the same original stream need to be fused together + std::map> nms_output_streams; + for (auto &stream_params_pair: output_streams.value()) { + auto &out_stream = stream_params_pair.first.get(); + if ((HAILO_FORMAT_ORDER_HAILO_NMS == out_stream.get_info().format.order && out_stream.get_info().nms_info.is_defused) && + (outputs_params.end() != outputs_params.find(out_stream.get_info().nms_info.defuse_info.original_name))) { + auto original_name = stream_params_pair.first.get().get_info().nms_info.defuse_info.original_name; + nms_output_streams.emplace(original_name, std::pair( + OutputStreamRefVector(), outputs_params.at(original_name))); + nms_output_streams[original_name].first.push_back(stream_params_pair.first.get()); + } else { + auto outputs = VStreamsBuilderUtils::create_outputs(stream_params_pair.first.get(), stream_params_pair.second, output_vstream_infos_map); + CHECK_EXPECTED(outputs); + vstreams.insert(vstreams.end(), std::make_move_iterator(outputs->begin()), std::make_move_iterator(outputs->end())); + } + } + for (auto &nms_output_stream_pair : nms_output_streams) { + auto outputs = VStreamsBuilderUtils::create_output_nms(nms_output_stream_pair.second.first, nms_output_stream_pair.second.second, + output_vstream_infos_map); + CHECK_EXPECTED(outputs); + vstreams.insert(vstreams.end(), std::make_move_iterator(outputs->begin()), std::make_move_iterator(outputs->end())); + } + return vstreams; +} + } /* namespace hailort */ diff --git a/hailort/libhailort/src/context_switch/network_group_internal.hpp b/hailort/libhailort/src/context_switch/network_group_internal.hpp index d863990..9e26cab 100644 --- a/hailort/libhailort/src/context_switch/network_group_internal.hpp +++ b/hailort/libhailort/src/context_switch/network_group_internal.hpp @@ -8,21 +8,21 @@ * and ActivatedNetworkGroup interfaces. All internal classes that are relevant should inherit from the * ConfiguredNetworkGroupBase and ActivatedNetworkGroupBase classes. * Hence, the hierarchy is as follows: - * ----------------------------------------------------------------------------- - * | ConfiguredNetworkGroup | (External "interface") - * | ________________|___________________ | - * | / \ | - * | ConfiguredNetworkGroupBase ConfiguredNetworkGroupWrapper | (Base classes) - * | / \ | - * | VdmaConfigNetworkGroup HcpConfigNetworkGroup | (Actual implementations) - * ----------------------------------------------------------------------------- - * | ActivatedNetworkGroup | (External "interface") - * | | | - * | ActivatedNetworkGroupBase | (Base classes) - * | __________________|__________________ | - * | / \ | - * | VdmaConfigActivatedNetworkGroup HcpConfigActivatedNetworkGroup | (Actual implementations) - * ----------------------------------------------------------------------------- + * -------------------------------------------------------------------------------------------------------------- + * | ConfiguredNetworkGroup | (External "interface") + * | ________________________________|________________________________ | + * | / | \ | + * | ConfiguredNetworkGroupBase ConfiguredNetworkGroupWrapper ConfiguredNetworkGroupClient | (Base classes) + * | / \ | + * | VdmaConfigNetworkGroup HcpConfigNetworkGroup | (Actual implementations) + * -------------------------------------------------------------------------------------------------------------| + * | ActivatedNetworkGroup | (External "interface") + * | | | + * | ActivatedNetworkGroupBase | (Base classes) + * | __________________|__________________ | + * | / \ | + * | VdmaConfigActivatedNetworkGroup HcpConfigActivatedNetworkGroup | (Actual implementations) + * -------------------------------------------------------------------------------------------------------------- **/ #ifndef _HAILO_NETWORK_GROUP_INTERNAL_HPP_ @@ -35,6 +35,10 @@ #include "control_protocol.h" #include "vdma_channel.hpp" +#ifdef HAILO_SUPPORT_MULTI_PROCESS +#include "hailort_rpc_client.hpp" +#endif // HAILO_SUPPORT_MULTI_PROCESS + namespace hailort { @@ -84,6 +88,7 @@ public: virtual hailo_status wait_for_activation(const std::chrono::milliseconds &timeout) override; virtual const std::string &get_network_group_name() const override; + virtual const std::string &name() const override; virtual Expected get_input_streams_by_network(const std::string &network_name="") override; virtual Expected get_output_streams_by_network(const std::string &network_name="") override; @@ -118,6 +123,9 @@ public: virtual AccumulatorPtr get_deactivation_time_accumulator() const override; hailo_status create_streams_from_config_params(Device &device); + virtual bool is_multi_context() const override; + virtual const ConfigureNetworkParams get_config_params() const override; + static Expected create_hw_latency_meter(Device &device, const std::vector &layers); @@ -133,7 +141,8 @@ public: Expected get_stream_batch_size(const std::string &stream_name); - const ConfigureNetworkParams get_config_params() const; + virtual Expected> create_input_vstreams(const std::map &inputs_params); + virtual Expected> create_output_vstreams(const std::map &outputs_params); protected: ConfiguredNetworkGroupBase(const ConfigureNetworkParams &config_params, const uint8_t m_net_group_index, @@ -156,9 +165,7 @@ protected: Expected get_layer_info(const std::string &stream_name); - virtual Expected get_boundary_channel_index(uint8_t stream_index, hailo_stream_direction_t direction, - const std::string &layer_name) = 0; - virtual Expected> get_latnecy_meters() = 0; + virtual Expected> get_latency_meters() = 0; virtual Expected> get_boundary_vdma_channel_by_stream_name(const std::string &stream_name) = 0; const ConfigureNetworkParams m_config_params; @@ -179,6 +186,75 @@ private: bool m_is_scheduling; }; +#ifdef HAILO_SUPPORT_MULTI_PROCESS +class ConfiguredNetworkGroupClient : public ConfiguredNetworkGroup +{ +public: + ConfiguredNetworkGroupClient(std::unique_ptr client, uint32_t handle); + + virtual ~ConfiguredNetworkGroupClient(); + ConfiguredNetworkGroupClient(const ConfiguredNetworkGroupClient &other) = delete; + ConfiguredNetworkGroupClient &operator=(const ConfiguredNetworkGroupClient &other) = delete; + ConfiguredNetworkGroupClient &operator=(ConfiguredNetworkGroupClient &&other) = delete; + ConfiguredNetworkGroupClient(ConfiguredNetworkGroupClient &&other) = default; + + virtual const std::string &get_network_group_name() const override; + virtual const std::string &name() const override; + virtual Expected get_default_streams_interface() override; + virtual std::vector> get_input_streams_by_interface(hailo_stream_interface_t stream_interface) override; + virtual std::vector> get_output_streams_by_interface(hailo_stream_interface_t stream_interface) override; + virtual ExpectedRef get_input_stream_by_name(const std::string &name) override; + virtual ExpectedRef get_output_stream_by_name(const std::string &name) override; + virtual Expected get_input_streams_by_network(const std::string &network_name="") override; + virtual Expected get_output_streams_by_network(const std::string &network_name="") override; + virtual InputStreamRefVector get_input_streams() override; + virtual OutputStreamRefVector get_output_streams() override; + virtual Expected get_output_streams_from_vstream_names( + const std::map &outputs_params) override; + + virtual Expected get_latency_measurement(const std::string &network_name="") override; + virtual Expected> activate(const hailo_activate_network_group_params_t &network_group_params) override; + virtual hailo_status wait_for_activation(const std::chrono::milliseconds &timeout) override; + + virtual Expected> make_input_vstream_params( + bool quantized, hailo_format_type_t format_type, uint32_t timeout_ms, uint32_t queue_size, + const std::string &network_name="") override; + virtual Expected> make_output_vstream_params( + bool quantized, hailo_format_type_t format_type, uint32_t timeout_ms, uint32_t queue_size, + const std::string &network_name="") override; + virtual Expected>> make_output_vstream_params_groups( + bool quantized, hailo_format_type_t format_type, uint32_t timeout_ms, uint32_t queue_size) override; + virtual Expected>> get_output_vstream_groups() override; + + virtual Expected> get_all_stream_infos(const std::string &network_name="") const override; + virtual Expected> get_network_infos() const override; + virtual Expected> get_input_vstream_infos(const std::string &network_name="") const override; + virtual Expected> get_output_vstream_infos(const std::string &network_name="") const override; + virtual Expected> get_all_vstream_infos(const std::string &network_name="") const override; + + virtual hailo_status set_scheduler_timeout(const std::chrono::milliseconds &timeout, const std::string &network_name) override; + virtual hailo_status set_scheduler_threshold(uint32_t threshold, const std::string &network_name) override; + + virtual AccumulatorPtr get_activation_time_accumulator() const override; + virtual AccumulatorPtr get_deactivation_time_accumulator() const override; + + virtual bool is_multi_context() const override; + virtual const ConfigureNetworkParams get_config_params() const override; + + virtual Expected> create_input_vstreams(const std::map &inputs_params); + virtual Expected> create_output_vstreams(const std::map &outputs_params); + +protected: + virtual Expected> activate_internal( + const hailo_activate_network_group_params_t &network_group_params, uint16_t dynamic_batch_size) override; + +private: + std::unique_ptr m_client; + uint32_t m_handle; + std::string m_network_group_name; +}; +#endif // HAILO_SUPPORT_MULTI_PROCESS + } /* namespace hailort */ #endif /* _HAILO_NETWORK_GROUP_INTERNAL_HPP_ */ diff --git a/hailort/libhailort/src/context_switch/network_group_wrapper.cpp b/hailort/libhailort/src/context_switch/network_group_wrapper.cpp index 3181e5c..77dbfb9 100644 --- a/hailort/libhailort/src/context_switch/network_group_wrapper.cpp +++ b/hailort/libhailort/src/context_switch/network_group_wrapper.cpp @@ -17,6 +17,11 @@ const std::string &ConfiguredNetworkGroupWrapper::get_network_group_name() const return m_configured_network_group->get_network_group_name(); } +const std::string &ConfiguredNetworkGroupWrapper::name() const +{ + return m_configured_network_group->name(); +} + Expected ConfiguredNetworkGroupWrapper::get_default_streams_interface() { return m_configured_network_group->get_default_streams_interface(); @@ -150,6 +155,15 @@ AccumulatorPtr ConfiguredNetworkGroupWrapper::get_deactivation_time_accumulator( return m_configured_network_group->get_deactivation_time_accumulator(); } +bool ConfiguredNetworkGroupWrapper::is_multi_context() const +{ + return m_configured_network_group->is_multi_context(); +} +const ConfigureNetworkParams ConfiguredNetworkGroupWrapper::get_config_params() const +{ + return m_configured_network_group->get_config_params(); +} + std::shared_ptr ConfiguredNetworkGroupWrapper::get_configured_network() const { return m_configured_network_group; @@ -177,4 +191,14 @@ Expected> ConfiguredNetworkGroupWrapper:: return m_configured_network_group->activate(network_group_params); } +Expected> ConfiguredNetworkGroupWrapper::create_input_vstreams(const std::map &inputs_params) +{ + return m_configured_network_group->create_input_vstreams(inputs_params); +} + +Expected> ConfiguredNetworkGroupWrapper::create_output_vstreams(const std::map &outputs_params) +{ + return m_configured_network_group->create_output_vstreams(outputs_params); +} + } /* namespace hailort */ diff --git a/hailort/libhailort/src/context_switch/network_group_wrapper.hpp b/hailort/libhailort/src/context_switch/network_group_wrapper.hpp index a23f6ac..9c352c8 100644 --- a/hailort/libhailort/src/context_switch/network_group_wrapper.hpp +++ b/hailort/libhailort/src/context_switch/network_group_wrapper.hpp @@ -13,6 +13,7 @@ #include "hailo/hailort.h" #include "hailo/network_group.hpp" +#include "hailo/vstream.hpp" #include "network_group_internal.hpp" namespace hailort @@ -31,6 +32,7 @@ public: Expected clone(); virtual const std::string &get_network_group_name() const override; + virtual const std::string &name() const override; virtual Expected get_default_streams_interface() override; virtual std::vector> get_input_streams_by_interface(hailo_stream_interface_t stream_interface) override; @@ -69,9 +71,14 @@ public: virtual AccumulatorPtr get_activation_time_accumulator() const override; virtual AccumulatorPtr get_deactivation_time_accumulator() const override; + virtual bool is_multi_context() const override; + virtual const ConfigureNetworkParams get_config_params() const override; std::shared_ptr get_configured_network() const; + virtual Expected> create_input_vstreams(const std::map &inputs_params); + virtual Expected> create_output_vstreams(const std::map &outputs_params); + protected: virtual Expected> activate_internal( const hailo_activate_network_group_params_t &network_group_params, uint16_t dynamic_batch_size) override; diff --git a/hailort/libhailort/src/context_switch/pipeline_multiplexer.cpp b/hailort/libhailort/src/context_switch/pipeline_multiplexer.cpp index c39d88c..a2bdda5 100644 --- a/hailort/libhailort/src/context_switch/pipeline_multiplexer.cpp +++ b/hailort/libhailort/src/context_switch/pipeline_multiplexer.cpp @@ -9,66 +9,318 @@ #include "pipeline_multiplexer.hpp" #include "common/utils.hpp" +#include "hailo/hailort_common.hpp" namespace hailort { PipelineMultiplexer::PipelineMultiplexer() : - m_is_enabled(false), - m_mutex(), - m_readers(), - m_write_mutex() + m_next_to_write(0), + m_order_queue(), + m_currently_writing(INVALID_NETWORK_GROUP_HANDLE), + m_written_streams_count(0), + m_read_streams_count(0) {} -hailo_status PipelineMultiplexer::add_reader(EventPtr &reader_event) +hailo_status PipelineMultiplexer::add_network_group_instance(multiplexer_ng_handle_t network_group_handle, ConfiguredNetworkGroup &network_group) { - std::unique_lock lock(m_mutex); + std::unique_lock lock(m_writing_mutex); + std::unique_lock read_lock(m_reading_mutex); + assert(!contains(m_should_ng_stop, network_group_handle)); + + m_should_ng_stop[network_group_handle] = false; + + m_input_streams_count = static_cast(network_group.get_input_streams().size()); + m_output_streams_count = static_cast(network_group.get_output_streams().size()); - auto was_empty = m_readers.empty(); - m_readers.push(reader_event); - if (was_empty) { - auto status = reader_event->signal(); - CHECK_SUCCESS(status); + m_write_barriers[network_group_handle] = make_shared_nothrow(m_input_streams_count); + CHECK(nullptr != m_write_barriers[network_group_handle], HAILO_OUT_OF_HOST_MEMORY); + m_is_waiting_to_write[network_group_handle] = false; + + for (auto &output_stream : network_group.get_output_streams()) { + m_is_stream_reading[network_group_handle][output_stream.get().name()] = false; } return HAILO_SUCCESS; } -hailo_status PipelineMultiplexer::signal_done_reading() +void PipelineMultiplexer::set_output_vstreams_names(multiplexer_ng_handle_t network_group_handle, const std::vector &output_vstreams) { - std::unique_lock lock(m_mutex); + std::unique_lock lock(m_writing_mutex); + for (const auto &output_vstream : output_vstreams) { + m_can_output_vstream_read[network_group_handle][output_vstream.name()] = true; + } + m_can_network_group_read[network_group_handle] = true; +} + +bool PipelineMultiplexer::has_more_than_one_ng_instance() const +{ + return instances_count() > 1; +} + +size_t PipelineMultiplexer::instances_count() const +{ + return m_should_ng_stop.size(); +} - if (m_readers.empty()) { - return HAILO_INTERNAL_FAILURE; +hailo_status PipelineMultiplexer::wait_for_write(multiplexer_ng_handle_t network_group_handle) +{ + std::shared_ptr barrier; + { + std::unique_lock lock(m_writing_mutex); + assert(contains(m_write_barriers, network_group_handle)); + barrier = m_write_barriers[network_group_handle]; } - m_readers.front()->reset(); - m_readers.pop(); + barrier->arrive_and_wait(); + { + std::unique_lock lock(m_writing_mutex); + assert(contains(m_should_ng_stop, network_group_handle)); + assert(contains(m_is_waiting_to_write, network_group_handle)); + + m_is_waiting_to_write[network_group_handle] = true; + m_writing_cv.wait(lock, [this, network_group_handle] { + if (m_should_ng_stop[network_group_handle]) { + return true; + } + + if (m_currently_writing == network_group_handle) { + return true; + } + + if (!can_network_group_read(network_group_handle)) { + return false; + } + + if (INVALID_NETWORK_GROUP_HANDLE == m_currently_writing) { + if ((m_next_to_write != network_group_handle) && m_is_waiting_to_write[m_next_to_write] && can_network_group_read(m_next_to_write)) { + return false; + } - if (!m_readers.empty()) { - m_readers.front()->signal(); + return true; + } + + return false; + }); + m_is_waiting_to_write[network_group_handle] = false; + + if (m_should_ng_stop[network_group_handle]) { + return HAILO_STREAM_INTERNAL_ABORT; + } + + if (INVALID_NETWORK_GROUP_HANDLE == m_currently_writing) { + m_currently_writing = network_group_handle; + { + std::unique_lock reading_lock(m_reading_mutex); + m_order_queue.push(m_currently_writing); + m_next_to_write = m_currently_writing; + } + m_reading_cv.notify_all(); + } } + m_writing_cv.notify_all(); return HAILO_SUCCESS; } -void PipelineMultiplexer::enable() +bool PipelineMultiplexer::can_network_group_read(multiplexer_ng_handle_t network_group_handle) +{ + if (!contains(m_can_network_group_read, network_group_handle)) { + return true; + } + + return m_can_network_group_read[network_group_handle]; +} + +hailo_status PipelineMultiplexer::signal_write_finish() { - m_is_enabled = true; + std::unique_lock lock(m_writing_mutex); + m_written_streams_count++; + if (m_written_streams_count == m_input_streams_count) { + m_written_streams_count = 0; + m_currently_writing = INVALID_NETWORK_GROUP_HANDLE; + + m_next_to_write++; + m_next_to_write %= static_cast(instances_count()); + + lock.unlock(); + m_writing_cv.notify_all(); + } + + return HAILO_SUCCESS; } -bool PipelineMultiplexer::is_enabled() const +hailo_status PipelineMultiplexer::wait_for_read(multiplexer_ng_handle_t network_group_handle, const std::string &stream_name) { - return m_is_enabled; + std::unique_lock lock(m_reading_mutex); + + assert(contains(m_should_ng_stop, network_group_handle)); + assert(contains(m_is_stream_reading, network_group_handle)); + assert(contains(m_is_stream_reading[network_group_handle], stream_name)); + + m_reading_cv.wait(lock, [this, network_group_handle, stream_name] { + if (m_should_ng_stop[network_group_handle]) { + return true; + } + + if (m_order_queue.empty()) { + return false; + } + + if (m_order_queue.front() != network_group_handle) { + return false; + } + + if (m_is_stream_reading[network_group_handle][stream_name]) { + return false; + } + + return true; + }); + if (m_should_ng_stop[network_group_handle]) { + return HAILO_STREAM_INTERNAL_ABORT; + } + + m_is_stream_reading[network_group_handle][stream_name] = true; + + return HAILO_SUCCESS; } -void PipelineMultiplexer::acquire_write_lock() +hailo_status PipelineMultiplexer::signal_read_finish(multiplexer_ng_handle_t network_group_handle) { - m_write_mutex.lock(); + std::unique_lock lock(m_reading_mutex); + assert(contains(m_is_stream_reading, network_group_handle)); + + m_read_streams_count++; + if (m_read_streams_count == m_output_streams_count) { + m_read_streams_count = 0; + m_order_queue.pop(); + + for (auto &name_flag_pair : m_is_stream_reading[network_group_handle]) { + name_flag_pair.second = false; + } + + lock.unlock(); + m_reading_cv.notify_all(); + } + + return HAILO_SUCCESS; } -void PipelineMultiplexer::release_write_lock() +hailo_status PipelineMultiplexer::enable_network_group(multiplexer_ng_handle_t network_group_handle) { - m_write_mutex.unlock(); + { + std::unique_lock write_lock(m_writing_mutex); + std::unique_lock read_lock(m_reading_mutex); + assert(contains(m_should_ng_stop, network_group_handle)); + if (!m_should_ng_stop[network_group_handle]) { + return HAILO_SUCCESS; + } + + m_should_ng_stop[network_group_handle] = false; + } + + m_writing_cv.notify_all(); + m_reading_cv.notify_all(); + + return HAILO_SUCCESS; +} + +hailo_status PipelineMultiplexer::disable_network_group(multiplexer_ng_handle_t network_group_handle) +{ + { + std::unique_lock write_lock(m_writing_mutex); + std::unique_lock read_lock(m_reading_mutex); + assert(contains(m_should_ng_stop, network_group_handle)); + if (m_should_ng_stop[network_group_handle]) { + return HAILO_SUCCESS; + } + + m_should_ng_stop[network_group_handle] = true; + + assert(contains(m_write_barriers, network_group_handle)); + m_write_barriers[network_group_handle]->terminate(); + } + + m_writing_cv.notify_all(); + m_reading_cv.notify_all(); + + return HAILO_SUCCESS; +} + +void PipelineMultiplexer::RunOnceForStream::add_instance() +{ + std::unique_lock lock(m_mutex); + m_was_called[static_cast(m_was_called.size())] = false; +} + +void PipelineMultiplexer::RunOnceForStream::set_callback(std::function callback) +{ + std::unique_lock lock(m_mutex); + m_callback = callback; +} + +hailo_status PipelineMultiplexer::RunOnceForStream::run(multiplexer_ng_handle_t network_group_handle) +{ + std::unique_lock lock(m_mutex); + assert(contains(m_was_called, network_group_handle)); + + m_was_called[network_group_handle] = true; + for (auto &handle_flag_pair : m_was_called) { + if (!handle_flag_pair.second) { + return HAILO_SUCCESS; + } + } + + for (auto &handle_flag_pair : m_was_called) { + handle_flag_pair.second = false; + } + + return m_callback(); +} + +hailo_status PipelineMultiplexer::register_run_once_for_stream(const std::string &stream_name, run_once_for_stream_handle_t handle, + std::function callback) +{ + std::unique_lock lock(m_register_run_once_mutex); + if (!contains(m_run_once_db[stream_name], handle)) { + m_run_once_db[stream_name][handle] = make_shared_nothrow(); + CHECK(nullptr != m_run_once_db[stream_name][handle], HAILO_OUT_OF_HOST_MEMORY); + + m_run_once_db[stream_name][handle]->set_callback(callback); + } + + m_run_once_db[stream_name][handle]->add_instance(); + + return HAILO_SUCCESS; +} + +hailo_status PipelineMultiplexer::run_once_for_stream(const std::string &stream_name, run_once_for_stream_handle_t run_once_handle, + multiplexer_ng_handle_t network_group_handle) +{ + return m_run_once_db[stream_name][run_once_handle]->run(network_group_handle); +} + +void PipelineMultiplexer::set_can_output_vstream_read(multiplexer_ng_handle_t network_group_handle, const std::string &vstream_name, bool can_read) +{ + { + std::unique_lock lock(m_writing_mutex); + assert(contains(m_can_output_vstream_read, network_group_handle)); + assert(contains(m_can_output_vstream_read[network_group_handle], vstream_name)); + assert(contains(m_can_network_group_read, network_group_handle)); + + m_can_output_vstream_read[network_group_handle][vstream_name] = can_read; + + if (can_read != m_can_network_group_read[network_group_handle]) { + m_can_network_group_read[network_group_handle] = true; + for (const auto &name_bool_pair : m_can_output_vstream_read[network_group_handle]) { + if (!name_bool_pair.second) { + m_can_network_group_read[network_group_handle] = false; + break; + } + } + } + } + m_writing_cv.notify_all(); } } /* namespace hailort */ diff --git a/hailort/libhailort/src/context_switch/pipeline_multiplexer.hpp b/hailort/libhailort/src/context_switch/pipeline_multiplexer.hpp index 74a5fb8..8b483e7 100644 --- a/hailort/libhailort/src/context_switch/pipeline_multiplexer.hpp +++ b/hailort/libhailort/src/context_switch/pipeline_multiplexer.hpp @@ -12,6 +12,9 @@ #define _HAILO_PIPELINE_MULTIPLEXER_HPP_ #include "hailo/event.hpp" +#include "hailo/network_group.hpp" +#include "network_group_scheduler.hpp" +#include "common/barrier.hpp" #include #include @@ -19,32 +22,84 @@ namespace hailort { +using multiplexer_ng_handle_t = uint32_t; +using run_once_for_stream_handle_t = uint32_t; + class PipelineMultiplexer { public: + PipelineMultiplexer(); + virtual ~PipelineMultiplexer() = default; PipelineMultiplexer(const PipelineMultiplexer &other) = delete; PipelineMultiplexer &operator=(const PipelineMultiplexer &other) = delete; PipelineMultiplexer &operator=(PipelineMultiplexer &&other) = delete; PipelineMultiplexer(PipelineMultiplexer &&other) = delete; - PipelineMultiplexer(); + hailo_status add_network_group_instance(multiplexer_ng_handle_t network_group_handle, ConfiguredNetworkGroup &network_group); + void set_output_vstreams_names(multiplexer_ng_handle_t network_group_handle, const std::vector &output_vstreams); + bool has_more_than_one_ng_instance() const; + size_t instances_count() const; + hailo_status wait_for_write(multiplexer_ng_handle_t network_group_handle); + hailo_status signal_write_finish(); + hailo_status wait_for_read(multiplexer_ng_handle_t network_group_handle, const std::string &stream_name); + hailo_status signal_read_finish(multiplexer_ng_handle_t network_group_handle); + hailo_status enable_network_group(multiplexer_ng_handle_t network_group_handle); + hailo_status disable_network_group(multiplexer_ng_handle_t network_group_handle); - hailo_status add_reader(EventPtr &reader_event); - hailo_status signal_done_reading(); + hailo_status register_run_once_for_stream(const std::string &stream_name, run_once_for_stream_handle_t handle, std::function callback); + hailo_status run_once_for_stream(const std::string &stream_name, run_once_for_stream_handle_t run_once_handle, + multiplexer_ng_handle_t network_group_handle); - void enable(); - bool is_enabled() const; - - void acquire_write_lock(); - void release_write_lock(); + void set_can_output_vstream_read(multiplexer_ng_handle_t network_group_handle, const std::string &vstream_name, bool can_read); private: - bool m_is_enabled; - std::mutex m_mutex; - std::queue m_readers; + std::unordered_map m_should_ng_stop; + std::unordered_map m_is_waiting_to_write; + + uint32_t m_input_streams_count; + uint32_t m_output_streams_count; + + multiplexer_ng_handle_t m_next_to_write; + std::unordered_map> m_write_barriers; + std::queue m_order_queue; + std::mutex m_writing_mutex; + std::condition_variable m_writing_cv; + multiplexer_ng_handle_t m_currently_writing; + std::atomic_uint32_t m_written_streams_count; + + std::unordered_map> m_is_stream_reading; + std::mutex m_reading_mutex; + std::condition_variable m_reading_cv; + std::atomic_uint32_t m_read_streams_count; + + std::unordered_map> m_can_output_vstream_read; + std::unordered_map m_can_network_group_read; + + bool can_network_group_read(multiplexer_ng_handle_t network_group_handle); + + class RunOnceForStream final + { + public: + RunOnceForStream() {}; + + private: + void add_instance(); + void set_callback(std::function callback); + hailo_status run(multiplexer_ng_handle_t network_group_handle); + + std::unordered_map m_was_called; + std::function m_callback; + std::mutex m_mutex; + + friend class PipelineMultiplexer; + }; - std::mutex m_write_mutex; + // The run once map stores for each stream (by name), a map of RunOnceForStream which the user can register to. + // run_once_for_stream_handle_t is the handle which the user can access to his specific callback (for example, abort stream function). + // This is used for flushing, aborting and clear aborting streams. + std::unordered_map>> m_run_once_db; + std::mutex m_register_run_once_mutex; }; } /* namespace hailort */ diff --git a/hailort/libhailort/src/context_switch/resource_manager.cpp b/hailort/libhailort/src/context_switch/resource_manager.cpp index 141be48..1e4cd53 100644 --- a/hailort/libhailort/src/context_switch/resource_manager.cpp +++ b/hailort/libhailort/src/context_switch/resource_manager.cpp @@ -6,24 +6,24 @@ namespace hailort { -static Expected> build_network_index_map(ProtoHEFNetworkGroupPtr network_group_proto, +static Expected> build_network_index_map(const ProtoHEFNetworkGroup &network_group_proto, const NetworkGroupSupportedFeatures &supported_features) { std::vector network_names_vector; if (supported_features.multi_network_support) { - auto network_count = network_group_proto.get()->networks_names_size(); + auto network_count = network_group_proto.networks_names_size(); CHECK_AS_EXPECTED((network_count > 0), HAILO_INTERNAL_FAILURE, "Hef support multiple networks, but no networks found in the proto"); network_names_vector.reserve(network_count); for (uint8_t network_index = 0; network_index < network_count; network_index++) { - auto partial_network_name = network_group_proto.get()->networks_names(network_index); - auto network_name = HefUtils::get_network_name(*network_group_proto, partial_network_name); + auto partial_network_name = network_group_proto.networks_names(network_index); + auto network_name = HefUtils::get_network_name(network_group_proto, partial_network_name); network_names_vector.push_back(network_name); } } else { /* In case there is no defines networks, add single network with the same name as the network group */ network_names_vector.reserve(1); - auto net_group_name = network_group_proto->network_group_metadata().network_group_name(); + auto net_group_name = network_group_proto.network_group_metadata().network_group_name(); network_names_vector.push_back(HailoRTDefaults::get_network_name(net_group_name)); } @@ -42,7 +42,7 @@ static Expected create_hw_latency_meter(const std::vector create_latency_meters_from_config_params( } Expected ResourcesManager::create(VdmaDevice &vdma_device, HailoRTDriver &driver, - const ConfigureNetworkParams &config_params, ProtoHEFNetworkGroupPtr network_group_proto, + const ConfigureNetworkParams &config_params, const ProtoHEFNetworkGroup &network_group_proto, std::shared_ptr network_group_metadata, const HefParsingInfo &parsing_info, uint8_t net_group_index) { - CHECK_ARG_NOT_NULL_AS_EXPECTED(network_group_proto); + const auto &proto_metadata = network_group_proto.network_group_metadata(); // Backwards compatibility for HEFs without this field - CHECK_AS_EXPECTED(IS_FIT_IN_UINT8(network_group_proto->network_group_metadata().cfg_channels_count()), - HAILO_INTERNAL_FAILURE, "Invalid cfg channels count"); - uint8_t cfg_channels_count = (0 == network_group_proto->network_group_metadata().cfg_channels_count()) ? - 1u : static_cast(network_group_proto->network_group_metadata().cfg_channels_count()); + CHECK_AS_EXPECTED(IS_FIT_IN_UINT8(proto_metadata.cfg_channels_count()), + HAILO_INVALID_HEF, "Invalid cfg channels count"); + uint8_t cfg_channels_count = (0 == proto_metadata.cfg_channels_count()) ? + 1u : static_cast(proto_metadata.cfg_channels_count()); + + // Allocate config channels. In order to use the same channel ids for config channels in all contexts, + // we allocate all of them here, and use in preliminary/dynamic context. + ChannelAllocator allocator(driver.dma_engines_count()); + std::vector cfg_channel_ids; + cfg_channel_ids.reserve(cfg_channels_count); + for (uint8_t cfg_index = 0; cfg_index < cfg_channels_count; cfg_index++) { + const auto cfg_layer_identifier = std::make_tuple(LayerType::CFG, "", cfg_index); + const auto engine_index = get_cfg_channel_engine(driver, cfg_index, proto_metadata); + CHECK_EXPECTED(engine_index); + auto channel_id = allocator.get_available_channel_id(cfg_layer_identifier, VdmaChannel::Direction::H2D, + engine_index.value()); + CHECK_EXPECTED(channel_id); + cfg_channel_ids.push_back(channel_id.release()); + } std::vector preliminary_configs_vector; auto cfg_count_preliminary = parsing_info.cfg_infos_preliminary_config.size(); @@ -100,8 +115,8 @@ Expected ResourcesManager::create(VdmaDevice &vdma_device, Hai preliminary_configs_vector.reserve(cfg_count_preliminary); for (uint8_t cfg_index = MIN_H2D_CHANNEL_INDEX; cfg_index < cfg_count_preliminary; cfg_index++) { CHECK_AS_EXPECTED(contains(parsing_info.cfg_infos_preliminary_config, cfg_index), HAILO_INTERNAL_FAILURE, - "Mismmatch for cfg index {}", cfg_index); - auto buffer_resource = ConfigBuffer::create(driver, cfg_index, + "Mismatch for cfg index {}", cfg_index); + auto buffer_resource = ConfigBuffer::create(driver, cfg_channel_ids[cfg_index], parsing_info.cfg_infos_preliminary_config.at(cfg_index)); CHECK_EXPECTED(buffer_resource); @@ -109,9 +124,9 @@ Expected ResourcesManager::create(VdmaDevice &vdma_device, Hai } std::vector> dynamic_cfg_vectors; - dynamic_cfg_vectors.reserve(network_group_proto->contexts_size()); + dynamic_cfg_vectors.reserve(network_group_proto.contexts_size()); - for (int ctxt_index = 0; ctxt_index < network_group_proto->contexts_size(); ctxt_index++) { + for (int ctxt_index = 0; ctxt_index < network_group_proto.contexts_size(); ctxt_index++) { std::vector dynamic_cfg_vector_per_context; auto cfg_count_ctxt = parsing_info.cfg_infos_per_context[ctxt_index].size(); @@ -122,8 +137,8 @@ Expected ResourcesManager::create(VdmaDevice &vdma_device, Hai dynamic_cfg_vector_per_context.reserve(cfg_count_ctxt); for (uint8_t cfg_index = MIN_H2D_CHANNEL_INDEX; cfg_index < cfg_count_ctxt; cfg_index++) { CHECK_AS_EXPECTED(contains(parsing_info.cfg_infos_per_context[ctxt_index], cfg_index), - HAILO_INTERNAL_FAILURE, "Mismmatch for cfg index {}", cfg_index); - auto buffer_resource = ConfigBuffer::create(driver, cfg_index, + HAILO_INTERNAL_FAILURE, "Mismatch for cfg index {}", cfg_index); + auto buffer_resource = ConfigBuffer::create(driver, cfg_channel_ids[cfg_index], parsing_info.cfg_infos_per_context[ctxt_index].at(cfg_index)); CHECK_EXPECTED(buffer_resource); @@ -137,13 +152,10 @@ Expected ResourcesManager::create(VdmaDevice &vdma_device, Hai auto latency_meters = create_latency_meters_from_config_params(config_params, network_group_metadata); CHECK_EXPECTED(latency_meters); - ResourcesManager resources_manager(vdma_device, driver, config_params, + ResourcesManager resources_manager(vdma_device, driver, std::move(allocator), config_params, std::move(preliminary_configs_vector), std::move(dynamic_cfg_vectors), std::move(network_group_metadata), net_group_index, std::move(network_index_map.release()), latency_meters.release()); - auto status = resources_manager.set_number_of_cfg_channels(cfg_channels_count); - CHECK_SUCCESS_AS_EXPECTED(status); - return resources_manager; } @@ -153,7 +165,21 @@ hailo_status ResourcesManager::fill_infer_features(CONTROL_PROTOCOL__application return HAILO_SUCCESS; } -hailo_status ResourcesManager::fill_network_batch_size(CONTROL_PROTOCOL__application_header_t &app_header, bool is_scheduler_used) + +hailo_status ResourcesManager::fill_validation_features(CONTROL_PROTOCOL__application_header_t &app_header) +{ + static const auto ABBALE_NOT_SUPPORTED = false; + // TODO: fix is_abbale_supported + // auto proto_message = hef.pimpl.proto_message(); + // auto has_included_features = proto_message->has_included_features(); + // if (has_included_features) { + // is_abbale_supported = proto_message->included_features().abbale(); + // } + app_header.validation_features.is_abbale_supported = ABBALE_NOT_SUPPORTED; + return HAILO_SUCCESS; +} + +hailo_status ResourcesManager::fill_network_batch_size(CONTROL_PROTOCOL__application_header_t &app_header) { app_header.networks_count = static_cast(m_config_params.network_params_by_name.size()); for (const auto &network_pair : m_config_params.network_params_by_name) { @@ -162,7 +188,9 @@ hailo_status ResourcesManager::fill_network_batch_size(CONTROL_PROTOCOL__applica for (network_index = 0; network_index < m_network_index_map.size(); network_index++) { auto const network_name_from_map = m_network_index_map[network_index]; if (network_name_from_map == network_name_from_params) { - app_header.batch_size[network_index] = (is_scheduler_used) ? HAILO_DEFAULT_BATCH_SIZE : network_pair.second.batch_size; + auto batch_size = get_network_batch_size(network_name_from_params); + CHECK_EXPECTED_AS_STATUS(batch_size); + app_header.batch_size[network_index] = batch_size.value(); break; } } @@ -175,53 +203,24 @@ hailo_status ResourcesManager::fill_network_batch_size(CONTROL_PROTOCOL__applica return HAILO_SUCCESS; } -hailo_status ResourcesManager::create_internal_vdma_channels() +hailo_status ResourcesManager::create_fw_managed_vdma_channels() { - std::vector intermediate_channels_idx; - std::vector cfg_channels_idx; - std::vector ddr_channels_idx; - - for (uint8_t i = 0; i < m_channels_info.max_size(); ++i) { - if (m_channels_info[i].is_type(ChannelInfo::Type::INTER_CONTEXT)) { - intermediate_channels_idx.push_back(i); - } else if (m_channels_info[i].is_type(ChannelInfo::Type::CFG)) { - cfg_channels_idx.push_back(i); - } else if (m_channels_info[i].is_type(ChannelInfo::Type::DDR)) { - ddr_channels_idx.push_back(i); - } - } - - m_config_channels.reserve(cfg_channels_idx.size()); - m_inter_context_channels.reserve(intermediate_channels_idx.size()); - m_ddr_buffer_channels.reserve(ddr_channels_idx.size()); + auto fw_managed_channel_ids = m_channel_allocator.get_fw_managed_channel_ids(); - for (const auto &ch : cfg_channels_idx) { - auto config_channel = VdmaChannel::create(ch, VdmaChannel::Direction::H2D, m_driver, - m_vdma_device.get_default_desc_page_size()); - CHECK_EXPECTED_AS_STATUS(config_channel); - m_config_channels.emplace_back(config_channel.release()); - } - - for (const auto &ch : intermediate_channels_idx) { - auto direction = (ch < MIN_D2H_CHANNEL_INDEX) ? VdmaChannel::Direction::H2D : VdmaChannel::Direction::D2H; - auto vdma_channel = VdmaChannel::create(ch, direction, m_driver, m_vdma_device.get_default_desc_page_size()); - CHECK_EXPECTED_AS_STATUS(vdma_channel); - m_inter_context_channels.emplace_back(vdma_channel.release()); - } - - for (const auto &ch : ddr_channels_idx) { - auto direction = (ch < MIN_D2H_CHANNEL_INDEX) ? VdmaChannel::Direction::H2D : VdmaChannel::Direction::D2H; + m_fw_managed_channels.reserve(fw_managed_channel_ids.size()); + for (const auto &ch : fw_managed_channel_ids) { + auto direction = (ch.channel_index < MIN_D2H_CHANNEL_INDEX) ? VdmaChannel::Direction::H2D : VdmaChannel::Direction::D2H; auto vdma_channel = VdmaChannel::create(ch, direction, m_driver, m_vdma_device.get_default_desc_page_size()); CHECK_EXPECTED_AS_STATUS(vdma_channel); - m_ddr_buffer_channels.emplace_back(vdma_channel.release()); + m_fw_managed_channels.emplace_back(vdma_channel.release()); } return HAILO_SUCCESS; } Expected> ResourcesManager::create_boundary_vdma_channel( - uint8_t channel_index, uint32_t transfer_size, const std::string &network_name, const std::string &stream_name, - VdmaChannel::Direction channel_direction) + const vdma::ChannelId &channel_id, uint32_t transfer_size, const std::string &network_name, + const std::string &stream_name, VdmaChannel::Direction channel_direction) { auto network_batch_size = get_network_batch_size(network_name); CHECK_EXPECTED(network_batch_size); @@ -237,7 +236,7 @@ Expected> ResourcesManager::create_boundary_vdma_ch auto edge_layer = m_network_group_metadata->get_layer_info_by_stream_name(stream_name); CHECK_EXPECTED(edge_layer); auto latency_meter = (contains(m_latency_meters, edge_layer->network_name)) ? m_latency_meters.at(edge_layer->network_name) : nullptr; - auto stream_index = edge_layer.value().index; + auto stream_index = edge_layer.value().stream_index; /* TODO - HRT-6829- page_size should be calculated inside the vDMA channel class create function */ auto desc_sizes_pair = VdmaDescriptorList::get_desc_buffer_sizes_for_single_transfer(m_driver, @@ -246,7 +245,7 @@ Expected> ResourcesManager::create_boundary_vdma_ch const auto page_size = desc_sizes_pair->first; const auto descs_count = desc_sizes_pair->second; - auto channel = VdmaChannel::create(channel_index, channel_direction, m_driver, page_size, + auto channel = VdmaChannel::create(channel_id, channel_direction, m_driver, page_size, stream_index, latency_meter, network_batch_size.value()); CHECK_EXPECTED(channel); const auto status = channel->allocate_resources(descs_count); @@ -270,129 +269,87 @@ Expected> ResourcesManager::get_boundary_vdma_chann return std::shared_ptr(m_boundary_channels[stream_name]); } -ExpectedRef ResourcesManager::create_inter_context_buffer(uint32_t transfer_size, +ExpectedRef ResourcesManager::create_inter_context_buffer(uint32_t transfer_size, uint8_t src_stream_index, uint8_t src_context_index, const std::string &network_name) { auto network_batch_size_exp = get_network_batch_size(network_name); CHECK_EXPECTED(network_batch_size_exp); auto network_batch_size = network_batch_size_exp.value(); - const auto intermediate_buffer_key = std::make_pair(src_context_index, src_stream_index); - auto intermediate_buffer = create_intermediate_buffer(IntermediateBuffer::ChannelType::INTER_CONTEXT, transfer_size, - network_batch_size, intermediate_buffer_key); - CHECK_EXPECTED(intermediate_buffer); - auto intermediate_buffer_ref = intermediate_buffer.release(); + auto buffer = InterContextBuffer::create(m_driver, transfer_size, network_batch_size); + CHECK_EXPECTED(buffer); - auto status = intermediate_buffer_ref.get().program_inter_context(); - CHECK_SUCCESS_AS_EXPECTED(status); - - return intermediate_buffer_ref; + const auto key = std::make_pair(src_context_index, src_stream_index); + auto emplace_res = m_inter_context_buffers.emplace(key, buffer.release()); + return std::ref(emplace_res.first->second); } -ExpectedRef ResourcesManager::get_intermediate_buffer(const IntermediateBufferKey &key) +ExpectedRef ResourcesManager::get_inter_context_buffer(const IntermediateBufferKey &key) { - auto intermediate_buffer_it = m_intermediate_buffers.find(key); - if (std::end(m_intermediate_buffers) == intermediate_buffer_it) { + auto buffer_it = m_inter_context_buffers.find(key); + if (std::end(m_inter_context_buffers) == buffer_it) { return make_unexpected(HAILO_NOT_FOUND); } - return std::ref(intermediate_buffer_it->second); + return std::ref(buffer_it->second); } -Expected ResourcesManager::get_control_network_group_header(bool is_scheduler_used) +Expected ResourcesManager::get_control_network_group_header() { - CONTROL_PROTOCOL__application_header_t app_header = {}; + CONTROL_PROTOCOL__application_header_t app_header{}; app_header.dynamic_contexts_count = static_cast(m_contexts.size() - 1); - app_header.host_boundary_channels_bitmap = 0; - - /* Bitmask of all boundary and DDR channels*/ - int host_boundary_channels_bitmap_local = 0; - for (size_t i = MIN_H2D_CHANNEL_INDEX; i <= MAX_D2H_CHANNEL_INDEX; i++) { - /* Set boundary channels */ - if (m_channels_info[i].is_type(ChannelInfo::Type::BOUNDARY) && m_channels_info[i].is_used()) { - host_boundary_channels_bitmap_local |= 1 << i; - } - } - app_header.host_boundary_channels_bitmap = static_cast(host_boundary_channels_bitmap_local); + /* Bitmask of all boundary channels (per engine) */ + std::array host_boundary_channels_bitmap{}; - uint8_t cfg_handle_idx = 0; - for (uint8_t ch_idx = MIN_H2D_CHANNEL_INDEX; ch_idx <= MAX_H2D_CHANNEL_INDEX; ch_idx++) { - if ((m_channels_info[ch_idx].is_type(ChannelInfo::Type::CFG)) && - (m_channels_info[ch_idx].is_used())) { - assert(cfg_handle_idx < CONTROL_PROTOCOL__MAX_CFG_CHANNELS); - app_header.cfg_channel_numbers[cfg_handle_idx] = ch_idx; - cfg_handle_idx++; - } + for (const auto &ch : m_channel_allocator.get_boundary_channel_ids()) { + CHECK_AS_EXPECTED(ch.engine_index < host_boundary_channels_bitmap.size(), HAILO_INTERNAL_FAILURE, + "Invalid engine index {}", static_cast(ch.engine_index)); + + host_boundary_channels_bitmap[ch.engine_index] |= (1 << ch.channel_index); } + + std::copy(host_boundary_channels_bitmap.begin(), host_boundary_channels_bitmap.end(), + app_header.host_boundary_channels_bitmap); + app_header.power_mode = static_cast(m_config_params.power_mode); - CHECK_SUCCESS_AS_EXPECTED(fill_infer_features(app_header), "Invalid infer features"); - CHECK_SUCCESS_AS_EXPECTED(fill_network_batch_size(app_header, is_scheduler_used), "Invalid network batch sizes"); + auto status = fill_infer_features(app_header); + CHECK_SUCCESS_AS_EXPECTED(status, "Invalid infer features"); + status = fill_validation_features(app_header); + CHECK_SUCCESS_AS_EXPECTED(status, "Invalid validation features"); + status = fill_network_batch_size(app_header); + CHECK_SUCCESS_AS_EXPECTED(status, "Invalid network batch sizes"); + return app_header; } -Expected ResourcesManager::get_available_channel_index(std::set &blacklist, - ChannelInfo::Type required_type, VdmaChannel::Direction direction, const std::string &layer_name) +std::vector> +ResourcesManager::get_ddr_channel_pairs_per_context(uint8_t context_index) const { - uint8_t min_channel_index = - (direction == VdmaChannel::Direction::H2D) ? MIN_H2D_CHANNEL_INDEX : MIN_D2H_CHANNEL_INDEX; - uint8_t max_channel_index = - (direction == VdmaChannel::Direction::H2D) ? MAX_H2D_CHANNEL_INDEX : MAX_D2H_CHANNEL_INDEX; - - for (uint8_t index = min_channel_index; index <= max_channel_index; ++index) { - // Skip index that are on the blacklist - if (contains(blacklist, index)) { - continue; - } - - // In preliminary_run_asap, channels are reused across contexts (same channels in the preliminary - // context and first dynamic context). - if (get_supported_features().preliminary_run_asap) { - if (m_channels_info[index].is_used() && - (m_channels_info[index].get_layer_name() == layer_name) && - (m_channels_info[index].is_type(required_type))) { - LOGGER__TRACE("Reusing channel {} for layer {} (running in preliminary_run_asap mode)", - index, layer_name); - return index; - } - } - - // Use the empty channel if available - if (!m_channels_info[index].is_used()) { - m_channels_info[index].set_type(required_type); - m_channels_info[index].set_layer_name(layer_name); - return index; - } - - if (((ChannelInfo::Type::BOUNDARY != required_type) && (ChannelInfo::Type::CFG != required_type)) && - ((m_channels_info[index].is_type(ChannelInfo::Type::DDR)) || (m_channels_info[index].is_type(ChannelInfo::Type::INTER_CONTEXT)))) { - m_channels_info[index].set_type(required_type); - m_channels_info[index].set_layer_name(layer_name); - return index; + std::vector> ddr_channels_pairs; + for (auto &ddr_channels_pair : m_ddr_channels_pairs) { + if (ddr_channels_pair.first.first == context_index) { + ddr_channels_pairs.push_back(std::ref(ddr_channels_pair.second)); } } - LOGGER__ERROR("Failed to get available channel_index"); - return make_unexpected(HAILO_INTERNAL_FAILURE); + return ddr_channels_pairs; } -Expected ResourcesManager::get_boundary_channel_index(uint8_t stream_index, - hailo_stream_direction_t direction, const std::string &layer_name) +Expected ResourcesManager::get_available_channel_id(const LayerIdentifier &layer_identifier, + VdmaChannel::Direction direction, uint8_t engine_index) { - uint8_t min_channel_index = - (direction == HAILO_H2D_STREAM) ? MIN_H2D_CHANNEL_INDEX : MIN_D2H_CHANNEL_INDEX; - uint8_t max_channel_index = - (direction == HAILO_H2D_STREAM) ? MAX_H2D_CHANNEL_INDEX : MAX_D2H_CHANNEL_INDEX; - - for (uint8_t channel_index = min_channel_index; channel_index <= max_channel_index; channel_index++) { - auto info = m_channels_info[channel_index]; - if ((info.is_type(ChannelInfo::Type::BOUNDARY) && (stream_index == info.get_pcie_stream_index()) && - layer_name == info.get_layer_name())) { - return channel_index; - } + if (m_driver.dma_type() == HailoRTDriver::DmaType::PCIE) { + // On PCIe we have only 1 engine. To support the same HEF with both PCIe and DRAM, we use default engine here + engine_index = vdma::DEFAULT_ENGINE_INDEX; } - return make_unexpected(HAILO_INVALID_ARGUMENT); + return m_channel_allocator.get_available_channel_id(layer_identifier, direction, engine_index); +} + +hailo_status ResourcesManager::free_channel_index(const LayerIdentifier &layer_identifier) +{ + return m_channel_allocator.free_channel_index(layer_identifier); } void ResourcesManager::update_preliminary_config_buffer_info() @@ -412,36 +369,26 @@ void ResourcesManager::update_dynamic_contexts_buffer_info() } } -ExpectedRef ResourcesManager::create_ddr_buffer(DdrChannelsInfo &ddr_info, uint8_t context_index) +ExpectedRef ResourcesManager::create_ddr_channels_pair(const DdrChannelsInfo &ddr_info, uint8_t context_index) { - const uint32_t number_of_transfers = ddr_info.min_buffered_rows * DDR_THREADS_MIN_BUFFERED_ROWS_INITIAL_SCALE; - CHECK(IS_FIT_IN_UINT16(number_of_transfers), make_unexpected(HAILO_INVALID_ARGUMENT), - "calculated number of transfers for DDR buffer is out of UINT16_T range"); + auto buffer = DdrChannelsPair::create(m_driver, ddr_info); + CHECK_EXPECTED(buffer); - const uint32_t transfer_size = ddr_info.row_size * DDR_NUMBER_OF_ROWS_PER_INTERRUPT; - - auto intermediate_buffer_key = std::make_pair(context_index, ddr_info.d2h_stream_index); - return create_intermediate_buffer(IntermediateBuffer::ChannelType::DDR, transfer_size, static_cast(number_of_transfers), - intermediate_buffer_key); + const auto key = std::make_pair(context_index, ddr_info.d2h_stream_index); + auto emplace_res = m_ddr_channels_pairs.emplace(key, buffer.release()); + return std::ref(emplace_res.first->second); } -hailo_status ResourcesManager::set_number_of_cfg_channels(const uint8_t number_of_cfg_channels) +ExpectedRef ResourcesManager::get_ddr_channels_pair(uint8_t context_index, uint8_t d2h_stream_index) { - CHECK(number_of_cfg_channels <= CONTROL_PROTOCOL__MAX_CFG_CHANNELS, HAILO_INVALID_HEF, "Too many cfg channels"); - size_t channels_count = 0; - for (uint8_t index = MIN_H2D_CHANNEL_INDEX; index <= MAX_H2D_CHANNEL_INDEX; ++index) { - // use the empty channel if avaialble - if (!m_channels_info[index].is_used()) { - m_channels_info[index].set_type(ChannelInfo::Type::CFG); - channels_count++; - } - if (number_of_cfg_channels == channels_count) { - return HAILO_SUCCESS; - } + const auto key = std::make_pair(context_index, d2h_stream_index); + auto ddr_channels_pair = m_ddr_channels_pairs.find(key); + + if (m_ddr_channels_pairs.end() == ddr_channels_pair) { + return make_unexpected(HAILO_NOT_FOUND); } - LOGGER__ERROR("Failed to set cfg channels"); - return HAILO_INTERNAL_FAILURE; + return std::ref(ddr_channels_pair->second); } Expected ResourcesManager::get_default_streams_interface() @@ -453,17 +400,7 @@ hailo_status ResourcesManager::register_fw_managed_vdma_channels() { hailo_status status = HAILO_UNINITIALIZED; - for (auto &ch : m_inter_context_channels) { - status = ch.register_fw_controlled_channel(); - CHECK_SUCCESS(status); - } - - for (auto &ch : m_config_channels) { - status = ch.register_fw_controlled_channel(); - CHECK_SUCCESS(status); - } - - for (auto &ch : m_ddr_buffer_channels) { + for (auto &ch : m_fw_managed_channels) { status = ch.register_fw_controlled_channel(); CHECK_SUCCESS(status); } @@ -476,17 +413,7 @@ hailo_status ResourcesManager::unregister_fw_managed_vdma_channels() hailo_status status = HAILO_UNINITIALIZED; // TODO: Add one icotl to stop all channels at once (HRT-6097) - for (auto &ch : m_inter_context_channels) { - status = ch.unregister_fw_controlled_channel(); - CHECK_SUCCESS(status); - } - - for (auto &ch : m_config_channels) { - status = ch.unregister_fw_controlled_channel(); - CHECK_SUCCESS(status); - } - - for (auto &ch : m_ddr_buffer_channels) { + for (auto &ch : m_fw_managed_channels) { status = ch.unregister_fw_controlled_channel(); CHECK_SUCCESS(status); } @@ -496,8 +423,8 @@ hailo_status ResourcesManager::unregister_fw_managed_vdma_channels() hailo_status ResourcesManager::set_inter_context_channels_dynamic_batch_size(uint16_t dynamic_batch_size) { - for (auto &key_buff_pair : m_intermediate_buffers) { - const auto status = key_buff_pair.second.reprogram_inter_context(dynamic_batch_size); + for (auto &key_buff_pair : m_inter_context_buffers) { + const auto status = key_buff_pair.second.reprogram(dynamic_batch_size); CHECK_SUCCESS(status); } @@ -520,43 +447,40 @@ Expected ResourcesManager::get_network_batch_size(const std::string &n Expected ResourcesManager::read_intermediate_buffer(const IntermediateBufferKey &key) { - auto intermediate_buffer_it = m_intermediate_buffers.find(key); - if (std::end(m_intermediate_buffers) == intermediate_buffer_it) { - LOGGER__ERROR("Failed to find intermediate buffer for src_context {}, src_stream_index {}", key.first, - key.second); - return make_unexpected(HAILO_NOT_FOUND); + auto inter_context_buffer_it = m_inter_context_buffers.find(key); + if (std::end(m_inter_context_buffers) != inter_context_buffer_it) { + return inter_context_buffer_it->second.read(); + } + + auto ddr_channels_pair_it = m_ddr_channels_pairs.find(key); + if (std::end(m_ddr_channels_pairs) != ddr_channels_pair_it) { + return ddr_channels_pair_it->second.read(); } - auto &intermediate_buffer = intermediate_buffer_it->second; - return intermediate_buffer.read(); + LOGGER__ERROR("Failed to find intermediate buffer for src_context {}, src_stream_index {}", key.first, + key.second); + return make_unexpected(HAILO_NOT_FOUND); + } hailo_status ResourcesManager::enable_state_machine(uint16_t dynamic_batch_size) { - if (Device::Type::CORE == m_vdma_device.get_type()) { - // On core device, the nn_manager is not responsible to reset the nn-core so - // we use the SCU control for that. - auto status = m_vdma_device.reset(HAILO_RESET_DEVICE_MODE_NN_CORE); - CHECK_SUCCESS(status); - } - return Control::enable_network_group(m_vdma_device, m_net_group_index, dynamic_batch_size); } -hailo_status ResourcesManager::reset_state_machine() +hailo_status ResourcesManager::reset_state_machine(bool keep_nn_config_during_reset) { - return Control::reset_context_switch_state_machine(m_vdma_device); -} + auto status = Control::reset_context_switch_state_machine(m_vdma_device, keep_nn_config_during_reset); + CHECK_SUCCESS(status); -ExpectedRef ResourcesManager::create_intermediate_buffer( - IntermediateBuffer::ChannelType channel_type, uint32_t transfer_size, uint16_t batch_size, - const IntermediateBufferKey &key) -{ - auto intermediate_buffer = IntermediateBuffer::create(m_driver, channel_type, transfer_size, batch_size); - CHECK_EXPECTED(intermediate_buffer); + if (!keep_nn_config_during_reset && (Device::Type::CORE == m_vdma_device.get_type())) { + // On core device, the nn_manager is not responsible to reset the nn-core so + // we use the SCU control for that. + status = m_vdma_device.reset(HAILO_RESET_DEVICE_MODE_NN_CORE); + CHECK_SUCCESS(status); + } - auto emplace_res = m_intermediate_buffers.emplace(key, intermediate_buffer.release()); - return std::ref(emplace_res.first->second); + return HAILO_SUCCESS; } void ResourcesManager::update_config_buffer_info(std::vector &config_buffers, @@ -567,16 +491,33 @@ void ResourcesManager::update_config_buffer_info(std::vector &conf auto i = 0; for (const auto &config : config_buffers) { - context.config_buffer_infos[i].buffer_type = static_cast( - (config.buffer_type() == vdma::VdmaBuffer::Type::SCATTER_GATHER) ? - CONTROL_PROTOCOL__HOST_BUFFER_TYPE_EXTERNAL_DESC : CONTROL_PROTOCOL__HOST_BUFFER_TYPE_CCB); - context.config_buffer_infos[i].dma_address = config.dma_address(); - context.config_buffer_infos[i].desc_page_size = config.desc_page_size(); - context.config_buffer_infos[i].total_desc_count = config.total_desc_count(); - context.config_buffer_infos[i].bytes_in_pattern = config.acc_desc_count() * config.desc_page_size(); - + context.config_channel_infos[i] = config.get_config_channel_info(); i++; } } +Expected ResourcesManager::get_cfg_channel_engine(HailoRTDriver &driver, uint8_t config_stream_index, + const ProtoHEFNetworkGroupMetadata &proto_metadata) +{ + if (driver.dma_type() == HailoRTDriver::DmaType::PCIE) { + // On PCIe we have only 1 engine. To support the same HEF with both PCIe and DRAM, we use default engine here. + return uint8_t(vdma::DEFAULT_ENGINE_INDEX); + } + + const auto &cfg_channels_config = proto_metadata.cfg_channels_config(); + if (cfg_channels_config.empty()) { + // Old hef, return default value + return uint8_t(vdma::DEFAULT_ENGINE_INDEX); + } + + auto cfg_info = std::find_if(cfg_channels_config.begin(), cfg_channels_config.end(), + [config_stream_index](const auto &cfg_info) + { + return cfg_info.cfg_channel_index() == config_stream_index; + }); + CHECK_AS_EXPECTED(cfg_info != cfg_channels_config.end(), HAILO_INVALID_HEF, "Cfg channel {} not found", config_stream_index); + CHECK_AS_EXPECTED(IS_FIT_IN_UINT8(cfg_info->engine_id()), HAILO_INVALID_HEF, "Invalid dma engine index"); + return static_cast(cfg_info->engine_id()); +} + } /* namespace hailort */ diff --git a/hailort/libhailort/src/context_switch/single_context/hcp_config_activated_network_group.hpp b/hailort/libhailort/src/context_switch/single_context/hcp_config_activated_network_group.hpp index a81871d..4393f3d 100644 --- a/hailort/libhailort/src/context_switch/single_context/hcp_config_activated_network_group.hpp +++ b/hailort/libhailort/src/context_switch/single_context/hcp_config_activated_network_group.hpp @@ -60,6 +60,11 @@ class HcpConfigActivatedNetworkGroup : public ActivatedNetworkGroupBase return make_unexpected(HAILO_INVALID_OPERATION); } + virtual hailo_status set_keep_nn_config_during_reset(const bool /* keep_nn_config_during_reset */) override + { + LOGGER__ERROR("set_keep_nn_config_during_reset() is not supported on single_context network_groups"); + return HAILO_INVALID_OPERATION; + } private: HcpConfigActivatedNetworkGroup(Device &device, HcpConfigActiveAppHolder &active_net_group_holder, diff --git a/hailort/libhailort/src/context_switch/single_context/hcp_config_manager.hpp b/hailort/libhailort/src/context_switch/single_context/hcp_config_manager.hpp index e463edb..8cd0b5d 100644 --- a/hailort/libhailort/src/context_switch/single_context/hcp_config_manager.hpp +++ b/hailort/libhailort/src/context_switch/single_context/hcp_config_manager.hpp @@ -33,8 +33,7 @@ public: virtual ~HcpConfigManager() = default; virtual ConfigManagerType get_manager_type(); - virtual Expected add_hef(Hef &hef, - const NetworkGroupsParamsMap &configure_params, bool is_scheduler_used = false); + virtual Expected add_hef(Hef &hef, const NetworkGroupsParamsMap &configure_params); HcpConfigManager(const HcpConfigManager &other) = delete; HcpConfigManager &operator=(const HcpConfigManager &other) = delete; diff --git a/hailort/libhailort/src/context_switch/single_context/hcp_config_network_group.hpp b/hailort/libhailort/src/context_switch/single_context/hcp_config_network_group.hpp index c346381..2b24ba6 100644 --- a/hailort/libhailort/src/context_switch/single_context/hcp_config_network_group.hpp +++ b/hailort/libhailort/src/context_switch/single_context/hcp_config_network_group.hpp @@ -39,9 +39,7 @@ public: const hailo_activate_network_group_params_t &network_group_params, uint16_t dynamic_batch_size) override; virtual Expected get_default_streams_interface() override; - virtual Expected get_boundary_channel_index(uint8_t stream_index, hailo_stream_direction_t direction, - const std::string &layer_name) override; - virtual Expected> get_latnecy_meters() override; + virtual Expected> get_latency_meters() override; virtual Expected> get_boundary_vdma_channel_by_stream_name( const std::string &stream_name) override; virtual hailo_status set_scheduler_timeout(const std::chrono::milliseconds &timeout, const std::string &network_name) override; diff --git a/hailort/libhailort/src/context_switch/vdma_config_activated_network_group.cpp b/hailort/libhailort/src/context_switch/vdma_config_activated_network_group.cpp index 20dac3c..9344556 100644 --- a/hailort/libhailort/src/context_switch/vdma_config_activated_network_group.cpp +++ b/hailort/libhailort/src/context_switch/vdma_config_activated_network_group.cpp @@ -53,12 +53,13 @@ VdmaConfigActivatedNetworkGroup::VdmaConfigActivatedNetworkGroup( ActivatedNetworkGroupBase(network_group_params, dynamic_batch_size, input_streams, output_streams, std::move(network_group_activated_event), status), m_network_group_name(network_group_name), - m_should_reset_state_machine(true), + m_should_reset_network_group(true), m_active_net_group_holder(active_net_group_holder), m_resources_managers(std::move(resources_managers)), m_ddr_send_threads(), m_ddr_recv_threads(), - m_deactivation_time_accumulator(deactivation_time_accumulator) + m_deactivation_time_accumulator(deactivation_time_accumulator), + m_keep_nn_config_during_reset(false) { // Validate ActivatedNetworkGroup status if (HAILO_SUCCESS != status) { @@ -92,17 +93,18 @@ VdmaConfigActivatedNetworkGroup::VdmaConfigActivatedNetworkGroup( VdmaConfigActivatedNetworkGroup::VdmaConfigActivatedNetworkGroup(VdmaConfigActivatedNetworkGroup &&other) noexcept : ActivatedNetworkGroupBase(std::move(other)), m_network_group_name(std::move(other.m_network_group_name)), - m_should_reset_state_machine(std::exchange(other.m_should_reset_state_machine, false)), + m_should_reset_network_group(std::exchange(other.m_should_reset_network_group, false)), m_active_net_group_holder(other.m_active_net_group_holder), m_resources_managers(std::move(other.m_resources_managers)), m_ddr_send_threads(std::move(other.m_ddr_send_threads)), m_ddr_recv_threads(std::move(other.m_ddr_recv_threads)), - m_deactivation_time_accumulator(std::move(other.m_deactivation_time_accumulator)) + m_deactivation_time_accumulator(std::move(other.m_deactivation_time_accumulator)), + m_keep_nn_config_during_reset(std::move(other.m_keep_nn_config_during_reset)) {} VdmaConfigActivatedNetworkGroup::~VdmaConfigActivatedNetworkGroup() { - if (!m_should_reset_state_machine) { + if (!m_should_reset_network_group) { return; } @@ -113,7 +115,7 @@ VdmaConfigActivatedNetworkGroup::~VdmaConfigActivatedNetworkGroup() deactivate_resources(); for (auto &resources_manager : m_resources_managers) { - status = resources_manager->reset_state_machine(); + status = resources_manager->reset_state_machine(m_keep_nn_config_during_reset); if (HAILO_SUCCESS != status) { LOGGER__ERROR("Failed to reset context switch status"); } @@ -145,4 +147,10 @@ Expected VdmaConfigActivatedNetworkGroup::get_intermediate_buffer(const return m_resources_managers[0]->read_intermediate_buffer(key); } +hailo_status VdmaConfigActivatedNetworkGroup::set_keep_nn_config_during_reset(const bool keep_nn_config_during_reset) +{ + m_keep_nn_config_during_reset = keep_nn_config_during_reset; + return HAILO_SUCCESS; +} + } /* namespace hailort */ diff --git a/hailort/libhailort/src/context_switch/vdma_config_manager.cpp b/hailort/libhailort/src/context_switch/vdma_config_manager.cpp index dd87f5c..c4d3398 100644 --- a/hailort/libhailort/src/context_switch/vdma_config_manager.cpp +++ b/hailort/libhailort/src/context_switch/vdma_config_manager.cpp @@ -24,6 +24,7 @@ #include "control.hpp" #include "hailort_defaults.hpp" #include "vdevice_internal.hpp" +#include "pipeline_multiplexer.hpp" #include "pcie_device.hpp" #include "hlpcie.hpp" @@ -41,57 +42,142 @@ Expected VdmaConfigManager::create(VdmaDevice &device) devices.push_back(device); auto empty_weak_ptr = NetworkGroupSchedulerWeakPtr(); - VdmaConfigManager manager(std::move(devices), is_vdevice, empty_weak_ptr); + hailo_status status = HAILO_UNINITIALIZED; + VdmaConfigManager manager(std::move(devices), is_vdevice, empty_weak_ptr, status); + CHECK_SUCCESS_AS_EXPECTED(status); + return manager; } -Expected VdmaConfigManager::create(VDevice &vdevice) +Expected VdmaConfigManager::create(VDeviceBase &vdevice) { const bool is_vdevice = true; auto devices = vdevice.get_physical_devices(); CHECK_EXPECTED(devices); - // Down casting Device to PcieDevice - std::vector> pcie_devices; + const auto device_type = vdevice.get_device_type(); + CHECK_EXPECTED(device_type); + + // Down casting Device to VdmaDevice + std::vector> vdma_devices; for (auto &dev : devices.release()) { - CHECK_AS_EXPECTED(Device::Type::PCIE == dev.get().get_type(), HAILO_INTERNAL_FAILURE, - "vDevice is supported only with PCIe devices"); - pcie_devices.emplace_back(static_cast(dev.get())); + assert(device_type.value() == dev.get().get_type()); + vdma_devices.emplace_back(static_cast(dev.get())); } - VdmaConfigManager manager(std::move(pcie_devices), is_vdevice, static_cast(vdevice).network_group_scheduler()); + hailo_status status = HAILO_UNINITIALIZED; + VdmaConfigManager manager(std::move(vdma_devices), is_vdevice, vdevice.network_group_scheduler(), status); + CHECK_SUCCESS_AS_EXPECTED(status); + return manager; } -VdmaConfigManager::VdmaConfigManager(std::vector> &&devices, bool is_vdevice, NetworkGroupSchedulerWeakPtr network_group_scheduler) - : m_devices(std::move(devices)), m_net_groups(), m_net_group_wrappers(), m_is_vdevice(is_vdevice), m_network_group_scheduler(network_group_scheduler) {} +VdmaConfigManager::VdmaConfigManager(std::vector> &&devices, + bool is_vdevice, + NetworkGroupSchedulerWeakPtr network_group_scheduler, + hailo_status &status) : + m_devices(std::move(devices)), + m_net_groups(), + m_net_group_wrappers(), + m_is_vdevice(is_vdevice), + m_network_group_scheduler(network_group_scheduler) +{ + for (auto &device : m_devices) { + // TODO: Do we need this control after fixing HRT-7519? + // Reset context_switch state machine - it may have been in an active state if the previous VdmaConfigManager + // wasn't dtor'd (due to SIGKILL for example) + static const auto REMOVE_NN_CONFIG_DURING_RESET = false; + status = Control::reset_context_switch_state_machine(device.get(), REMOVE_NN_CONFIG_DURING_RESET); + if (HAILO_SUCCESS != status) { + return; + } + + // Remove the previously configured network groups + status = Control::clear_configured_apps(device.get()); + if (HAILO_SUCCESS != status) { + return; + } + } + + status = HAILO_SUCCESS; +} + +VdmaConfigManager::~VdmaConfigManager() +{ + for (auto &device : m_devices) { + // Remove the previously configured network groups + const auto status = Control::clear_configured_apps(device.get()); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to clear conigured network groups with status {}", status); + } + + // Note: We don't call Control::reset_context_switch_state_machine in the dtor, since this isn't a resource + // managed by this class. The call to Control::reset_context_switch_state_machine in the ctor is + // present only due to possiblly active fw state machine (leaked if VdmaConfigActivatedNetworkGroup + // wasn't dtor'd, due to SIGKILL for example) + } +} Expected VdmaConfigManager::add_hef(Hef &hef, - const NetworkGroupsParamsMap &configure_params, bool scheduler_is_used) + const NetworkGroupsParamsMap &configure_params) { + /* We assume all devices under VDevice has the same device_arch and partial_clusters_layout_bitmap. + May be changed in SDK-28729 */ + auto device_arch_exp = m_devices[0].get().get_architecture(); + CHECK_EXPECTED(device_arch_exp); + auto device_arch = device_arch_exp.release(); + + auto partial_clusters_layout_bitmap_exp = Control::get_partial_clusters_layout_bitmap(m_devices[0].get()); + CHECK_EXPECTED(partial_clusters_layout_bitmap_exp); + auto partial_clusters_layout_bitmap = partial_clusters_layout_bitmap_exp.release(); + auto &hef_network_groups = hef.pimpl->network_groups(); - auto current_net_group_index = static_cast(m_net_groups.size()); - CHECK(CONTROL_PROTOCOL__MAX_CONTEXT_SWITCH_APPLICATIONS >= m_net_groups.size() + hef_network_groups.size(), - make_unexpected(HAILO_INVALID_OPERATION), - "Can't add network_groups from HEF because of too many network_groups. maximum allowed network_groups are: {}", - CONTROL_PROTOCOL__MAX_CONTEXT_SWITCH_APPLICATIONS); + const auto prev_network_group_count = m_net_groups.size(); + const auto total_network_group_count = prev_network_group_count + hef_network_groups.size(); + CHECK_AS_EXPECTED(CONTROL_PROTOCOL__MAX_CONTEXT_SWITCH_APPLICATIONS >= total_network_group_count, + HAILO_INVALID_OPERATION, + "Can't add {} network groups from HEF. Currently {} network groups are configured; maximum allowed network groups: {}.", + hef_network_groups.size(), prev_network_group_count, CONTROL_PROTOCOL__MAX_CONTEXT_SWITCH_APPLICATIONS); + + bool was_hef_already_configured = false; ConfiguredNetworkGroupVector added_network_groups; added_network_groups.reserve(hef_network_groups.size()); + auto hef_arch = hef.pimpl->get_device_arch(); + + auto current_net_group_index = static_cast(prev_network_group_count); auto configure_params_copy = configure_params; + const ProtoHEFNetworkGroup *network_group_ptr = nullptr; for (const auto &network_group_proto : hef_network_groups) { CHECK_NOT_NULL_AS_EXPECTED(network_group_proto, HAILO_INTERNAL_FAILURE); - auto status = Hef::Impl::validate_net_group_unique_layer_names(network_group_proto); + + if (ProtoHEFHwArch::PROTO__HW_ARCH__HAILO8L == hef_arch) { + // Hailo8 can work with Hailo8L configurations. in that case we choose one of the configurations + for (auto &partial_network_group : network_group_proto->partial_network_groups()) { + if ((partial_clusters_layout_bitmap == partial_network_group.layout().partial_clusters_layout_bitmap()) || + ((HAILO_ARCH_HAILO8 == device_arch))) { + network_group_ptr = &partial_network_group.network_group(); + break; + } + } + CHECK_AS_EXPECTED(nullptr != network_group_ptr, HAILO_INTERNAL_FAILURE, "There is no matching partial_clusters_layout_bitmap configuration in the given HEF"); + } else { + network_group_ptr = network_group_proto.get(); + } + CHECK_NOT_NULL_AS_EXPECTED(network_group_ptr, HAILO_INTERNAL_FAILURE); + std::string network_group_name = network_group_ptr->network_group_metadata().network_group_name(); + + auto status = Hef::Impl::validate_net_group_unique_layer_names(*network_group_ptr); CHECK_SUCCESS_AS_EXPECTED(status); static_assert(HAILO_DEFAULT_BATCH_SIZE <= std::numeric_limits::max(), "Invalid HAILO_DEFAULT_BATCH_SIZE"); - ConfigureNetworkParams config_params = {}; - if (contains(configure_params, network_group_proto->network_group_metadata().network_group_name())) { - config_params = configure_params_copy.at(network_group_proto->network_group_metadata().network_group_name()); - configure_params_copy.erase(network_group_proto->network_group_metadata().network_group_name()); + ConfigureNetworkParams config_params{}; + if (contains(configure_params, network_group_name)) { + config_params = configure_params_copy.at(network_group_name); + configure_params_copy.erase(network_group_name); } else { auto first_streams_interface = m_devices[0].get().get_default_streams_interface(); CHECK_EXPECTED(first_streams_interface); @@ -104,8 +190,7 @@ Expected VdmaConfigManager::add_hef(Hef &hef, "Not all default stream interfaces are the same"); } #endif - auto config_params_exp = hef.create_configure_params(first_streams_interface.value(), - network_group_proto->network_group_metadata().network_group_name()); + auto config_params_exp = hef.create_configure_params(first_streams_interface.value(), network_group_name); CHECK_EXPECTED(config_params_exp); config_params = config_params_exp.release(); } @@ -114,49 +199,95 @@ Expected VdmaConfigManager::add_hef(Hef &hef, status = update_network_batch_size(config_params); CHECK_SUCCESS_AS_EXPECTED(status); - auto network_group_metadata = hef.pimpl->get_network_group_metadata(network_group_proto->network_group_metadata().network_group_name()); + auto network_group_metadata = hef.pimpl->get_network_group_metadata(network_group_name, partial_clusters_layout_bitmap); CHECK_EXPECTED(network_group_metadata); auto network_group_metadata_ptr = make_shared_nothrow(network_group_metadata.release()); CHECK_AS_EXPECTED(nullptr != network_group_metadata_ptr, HAILO_OUT_OF_HOST_MEMORY); - /* build HEF supported features */ + std::shared_ptr identical_network_group = nullptr; std::vector> resources_managers; - for (auto device : m_devices) { - auto resource_manager = Hef::Impl::create_resources_manager(network_group_proto, current_net_group_index, - device.get(), device.get().get_driver(), config_params, network_group_metadata_ptr, hef.pimpl->get_device_arch()); - CHECK_EXPECTED(resource_manager); - resources_managers.push_back(resource_manager.release()); + bool should_create_resources_managers = true; + + auto network_group_scheduler = m_network_group_scheduler.lock(); + + bool should_use_multiplexer = true; + auto disable_multiplexer_env = std::getenv(DISABLE_MULTIPLEXER_ENV_VAR); + if ((nullptr != disable_multiplexer_env) && (strnlen(disable_multiplexer_env, 2) == 1) && (strncmp(disable_multiplexer_env, "1", 1) == 0)) { + should_use_multiplexer = false; + } + + if (m_is_vdevice && network_group_scheduler && should_use_multiplexer) { + for (auto &network_group : m_net_groups) { + if (network_group->equals(hef, network_group_name)) { + identical_network_group = network_group; + LOGGER__INFO("Network group {} was already configured. Using its resources instead of creating new ones...", network_group_name); + break; + } + } + + if (nullptr != identical_network_group) { + should_create_resources_managers = false; + resources_managers = identical_network_group->get_resources_managers(); + was_hef_already_configured = true; + + if (config_params != identical_network_group->get_config_params()) { + LOGGER__WARNING("Configured network group was already configured but has different parameters which will not take effect!"); + } + } + } + + if (should_create_resources_managers) { + /* build HEF supported features */ + for (auto device : m_devices) { + auto resource_manager = Hef::Impl::create_resources_manager(*network_group_ptr, current_net_group_index, + device.get(), device.get().get_driver(), config_params, network_group_metadata_ptr, hef.pimpl->get_device_arch()); + CHECK_EXPECTED(resource_manager); + resources_managers.push_back(resource_manager.release()); + } } auto net_group = VdmaConfigNetworkGroup::create(m_active_net_group_holder, config_params, - resources_managers, network_group_metadata_ptr, m_network_group_scheduler); + resources_managers, hef.hash(), network_group_metadata_ptr, m_network_group_scheduler); current_net_group_index++; auto net_group_ptr = make_shared_nothrow(net_group.release()); CHECK_AS_EXPECTED(nullptr != net_group_ptr, HAILO_OUT_OF_HOST_MEMORY); - m_net_groups.emplace_back(net_group_ptr); // TODO: move this func into VdmaConfigNetworkGroup c'tor if (m_is_vdevice) { - auto network_group_handle = INVALID_NETWORK_GROUP_HANDLE; - auto network_group_scheduler = m_network_group_scheduler.lock(); - if (network_group_scheduler) { - auto network_group_handle_exp = network_group_scheduler->add_network_group(net_group_ptr); - CHECK_EXPECTED(network_group_handle_exp); - network_group_handle = network_group_handle_exp.value(); - net_group_ptr->set_network_group_handle(network_group_handle); - } + if (network_group_scheduler && (nullptr != identical_network_group)) { + status = net_group_ptr->create_vdevice_streams_from_duplicate(identical_network_group); + CHECK_SUCCESS_AS_EXPECTED(status); - status = net_group_ptr->create_vdevice_streams_from_config_params(network_group_handle); - CHECK_SUCCESS_AS_EXPECTED(status); + net_group_ptr->set_network_group_handle(identical_network_group->network_group_handle()); + } else { + auto network_group_handle = INVALID_NETWORK_GROUP_HANDLE; + if (network_group_scheduler) { + auto network_group_handle_exp = network_group_scheduler->add_network_group(net_group_ptr); + CHECK_EXPECTED(network_group_handle_exp); + + network_group_handle = network_group_handle_exp.value(); + net_group_ptr->set_network_group_handle(network_group_handle); + } + + auto multiplexer = make_shared_nothrow(); + CHECK_AS_EXPECTED(nullptr != multiplexer, HAILO_OUT_OF_HOST_MEMORY, "Failed to create PipelineMultiplexer"); + + status = net_group_ptr->create_vdevice_streams_from_config_params(multiplexer, network_group_handle); + CHECK_SUCCESS_AS_EXPECTED(status); + + m_net_groups.emplace_back(net_group_ptr); + } } else { status = net_group_ptr->create_streams_from_config_params(net_group_ptr->get_resources_managers()[0]->get_device()); CHECK_SUCCESS_AS_EXPECTED(status); + + m_net_groups.emplace_back(net_group_ptr); } - + // Check that all boundary streams were created - status = validate_boundary_streams_were_created(hef, network_group_proto->network_group_metadata().network_group_name(), *net_group_ptr); + status = validate_boundary_streams_were_created(hef, network_group_name, *net_group_ptr); CHECK_SUCCESS_AS_EXPECTED(status); auto net_group_wrapper = ConfiguredNetworkGroupWrapper::create(net_group_ptr); @@ -164,8 +295,8 @@ Expected VdmaConfigManager::add_hef(Hef &hef, auto net_group_wrapper_ptr = make_shared_nothrow(net_group_wrapper.release()); CHECK_AS_EXPECTED(nullptr != net_group_wrapper_ptr, HAILO_OUT_OF_HOST_MEMORY); - m_net_group_wrappers.emplace_back(net_group_wrapper_ptr); + m_net_group_wrappers.emplace_back(net_group_wrapper_ptr); added_network_groups.emplace_back(std::static_pointer_cast(net_group_wrapper_ptr)); } std::string unmatched_keys = ""; @@ -176,82 +307,65 @@ Expected VdmaConfigManager::add_hef(Hef &hef, CHECK_AS_EXPECTED(unmatched_keys.size() == 0, HAILO_INVALID_ARGUMENT, "Some network group names in the configuration are not found in the hef file:{}", unmatched_keys); + if (was_hef_already_configured) { + return added_network_groups; + } + for (auto device : m_devices) { - CONTROL_PROTOCOL__context_switch_info_t context_switch_info = {}; - context_switch_info.context_switch_main_header.context_switch_version = CONTROL_PROTOCOL__CONTEXT_SWITCH_VER_V1_0_0; - context_switch_info.context_switch_main_header.application_count = static_cast(m_net_groups.size()); - - auto is_abbale_supported = false; - // TODO: fix is_abbale_supported - // auto proto_message = hef.pimpl.proto_message(); - // auto has_included_features = proto_message->has_included_features(); - // if (has_included_features) { - // is_abbale_supported = proto_message->included_features().abbale(); - // } - - context_switch_info.context_switch_main_header.validation_features.is_abbale_supported = is_abbale_supported; - for (size_t i = 0, contexts = 0; i < m_net_groups.size(); ++i) { - for (auto &resource_manager : m_net_groups[i]->get_resources_managers()) { - if (0 == strcmp(device.get().get_dev_id(), resource_manager->get_dev_id())) { - auto net_group_header_exp = resource_manager->get_control_network_group_header(scheduler_is_used); - CHECK_EXPECTED(net_group_header_exp); - context_switch_info.context_switch_main_header.application_header[i] = net_group_header_exp.value(); - auto net_group_contexts = resource_manager->get_contexts(); - CHECK_AS_EXPECTED((contexts + net_group_contexts.size()) <= CONTROL_PROTOCOL__MAX_TOTAL_CONTEXTS, - HAILO_INVALID_ARGUMENT, - "There are too many contexts in the configured network groups. Max allowed for all network groups together: {}", - CONTROL_PROTOCOL__MAX_TOTAL_CONTEXTS); - std::memcpy(&context_switch_info.context[contexts], net_group_contexts.data(), - net_group_contexts.size() * sizeof(context_switch_info.context[0])); - contexts += net_group_contexts.size(); + // Allocate context_switch_info on the heap (to avoid stack overflows) + auto context_switch_info = make_unique_nothrow(); + CHECK_NOT_NULL_AS_EXPECTED(context_switch_info, HAILO_OUT_OF_HOST_MEMORY); + memset(context_switch_info.get(), 0, sizeof(CONTROL_PROTOCOL__context_switch_info_t)); + + context_switch_info->context_switch_main_header.context_switch_version = CONTROL_PROTOCOL__CONTEXT_SWITCH_VER_V1_0_0; + context_switch_info->context_switch_main_header.application_count = static_cast(added_network_groups.size()); + for (size_t index_in_hef = 0, context_index = 0; index_in_hef < added_network_groups.size(); index_in_hef++) { + for (auto &resource_manager : m_net_groups[prev_network_group_count + index_in_hef]->get_resources_managers()) { + if (std::string(device.get().get_dev_id()) != std::string(resource_manager->get_dev_id())) { + continue; } + auto net_group_header_exp = resource_manager->get_control_network_group_header(); + CHECK_EXPECTED(net_group_header_exp); + context_switch_info->context_switch_main_header.application_header[index_in_hef] = net_group_header_exp.value(); + auto net_group_contexts = resource_manager->get_contexts(); + + CHECK_AS_EXPECTED(ARRAY_ENTRIES(context_switch_info->context) > context_index + net_group_contexts.size(), HAILO_INVALID_OPERATION, + "Can't add {} contexts. Currently {} contexts are configured; maximum allowed contexts: {}.", + net_group_contexts.size(), context_index, ARRAY_ENTRIES(context_switch_info->context)); + std::memcpy(&context_switch_info->context[context_index], net_group_contexts.data(), + net_group_contexts.size() * sizeof(context_switch_info->context[0])); + context_index += net_group_contexts.size(); } } - { - // Add instance that guards scheduler to deactivate network_group temporary - auto scheduler_idle_guard = NetworkGroupScheduler::create_scheduler_idle_guard(); - if (m_is_vdevice) { - auto network_group_scheduler = m_network_group_scheduler.lock(); - if (network_group_scheduler) { - auto status = scheduler_idle_guard->set_scheduler(network_group_scheduler); - CHECK_SUCCESS_AS_EXPECTED(status); - } - } - - // Reset context_switch status - auto status = Control::reset_context_switch_state_machine(device.get()); - CHECK_SUCCESS_AS_EXPECTED(status); - - // Write context_switch info - status = Control::write_context_switch_info(device.get(), &context_switch_info); - CHECK_SUCCESS_AS_EXPECTED(status); - } + // Write context_switch info + const auto status = Control::write_context_switch_info(device.get(), context_switch_info.get()); + CHECK_SUCCESS_AS_EXPECTED(status); } return added_network_groups; } -hailo_status VdmaConfigManager::update_network_batch_size(ConfigureNetworkParams &config_params) +hailo_status VdmaConfigManager::update_network_batch_size(ConfigureNetworkParams &network_group_config_params) { - auto single_network_default_batch = (HAILO_DEFAULT_BATCH_SIZE == config_params.batch_size); + auto single_network_default_batch = (HAILO_DEFAULT_BATCH_SIZE == network_group_config_params.batch_size); auto multi_network_default_batch = true; /* Batch size overide logic - if user modifies network group batch size and not the network batch size, */ - for (auto const &network_params : config_params.network_params_by_name) { + for (auto const &network_params : network_group_config_params.network_params_by_name) { if (HAILO_DEFAULT_BATCH_SIZE != network_params.second.batch_size) { multi_network_default_batch = false; } } CHECK((single_network_default_batch || multi_network_default_batch), HAILO_INVALID_OPERATION, - "User provided non batch size for network group and for network as well. User is adviced to work with network's batch size only"); + "User provided batch size for network group and for network as well. User is adviced to work with network's batch size only"); if (!single_network_default_batch && multi_network_default_batch) { /* In case user works with network group, overide the network batch size.*/ - for (auto &network_params : config_params.network_params_by_name) { - network_params.second.batch_size = config_params.batch_size; + for (auto &network_params : network_group_config_params.network_params_by_name) { + network_params.second.batch_size = network_group_config_params.batch_size; } } diff --git a/hailort/libhailort/src/context_switch/vdma_config_network_group.cpp b/hailort/libhailort/src/context_switch/vdma_config_network_group.cpp index c0187b4..3eac9b9 100644 --- a/hailort/libhailort/src/context_switch/vdma_config_network_group.cpp +++ b/hailort/libhailort/src/context_switch/vdma_config_network_group.cpp @@ -4,19 +4,21 @@ #include "pcie_stream.hpp" #include "mipi_stream.hpp" #include "vdevice_stream.hpp" +#include "vdevice_stream_wrapper.hpp" +#include "vstream_internal.hpp" namespace hailort { Expected VdmaConfigNetworkGroup::create(VdmaConfigActiveAppHolder &active_net_group_holder, const ConfigureNetworkParams &config_params, - std::vector> resources_managers, + std::vector> resources_managers, const std::string &hef_hash, std::shared_ptr network_group_metadata, NetworkGroupSchedulerWeakPtr network_group_scheduler) { auto status = HAILO_UNINITIALIZED; VdmaConfigNetworkGroup object(active_net_group_holder, config_params, - std::move(resources_managers), *network_group_metadata, network_group_scheduler, status); + std::move(resources_managers), hef_hash, *network_group_metadata, network_group_scheduler, status); CHECK_SUCCESS_AS_EXPECTED(status); return object; @@ -24,7 +26,7 @@ Expected VdmaConfigNetworkGroup::create(VdmaConfigActive VdmaConfigNetworkGroup::VdmaConfigNetworkGroup(VdmaConfigActiveAppHolder &active_net_group_holder, const ConfigureNetworkParams &config_params, - std::vector> &&resources_managers, + std::vector> &&resources_managers, const std::string &hef_hash, const NetworkGroupMetadata &network_group_metadata, NetworkGroupSchedulerWeakPtr network_group_scheduler, hailo_status &status) : ConfiguredNetworkGroupBase(config_params, resources_managers[0]->get_network_group_index(), // All ResourceManagers shares the same net_group_index @@ -32,21 +34,24 @@ VdmaConfigNetworkGroup::VdmaConfigNetworkGroup(VdmaConfigActiveAppHolder &active m_active_net_group_holder(active_net_group_holder), m_resources_managers(std::move(resources_managers)), m_network_group_scheduler(network_group_scheduler), - m_network_group_handle(INVALID_NETWORK_GROUP_HANDLE) {} + m_scheduler_handle(INVALID_NETWORK_GROUP_HANDLE), + m_multiplexer_handle(0), + m_hef_hash(hef_hash) +{} Expected> VdmaConfigNetworkGroup::activate_impl( const hailo_activate_network_group_params_t &network_group_params, uint16_t dynamic_batch_size) { auto start_time = std::chrono::steady_clock::now(); auto activated_net_group = VdmaConfigActivatedNetworkGroup::create( - m_active_net_group_holder, get_network_group_name(), m_resources_managers, network_group_params, dynamic_batch_size, + m_active_net_group_holder, name(), m_resources_managers, network_group_params, dynamic_batch_size, m_input_streams, m_output_streams, m_network_group_activated_event, m_deactivation_time_accumulator); const auto elapsed_time_ms = std::chrono::duration( std::chrono::steady_clock::now() - start_time).count(); CHECK_EXPECTED(activated_net_group); LOGGER__INFO("Activating {} took {} milliseconds. Note that the function is asynchronous and" - " thus the network is not fully activated yet.", get_network_group_name(), elapsed_time_ms); + " thus the network is not fully activated yet.", name(), elapsed_time_ms); m_activation_time_accumulator->add_data_point(elapsed_time_ms); std::unique_ptr activated_net_group_ptr = @@ -72,33 +77,28 @@ Expected VdmaConfigNetworkGroup::get_default_streams_i return first_streams_interface; } -Expected VdmaConfigNetworkGroup::get_boundary_channel_index(uint8_t stream_index, - hailo_stream_direction_t direction, const std::string &layer_name) -{ - // All ResourceManagers shares the same metadata and channels info - return m_resources_managers[0]->get_boundary_channel_index(stream_index, direction, layer_name); -} - -hailo_status VdmaConfigNetworkGroup::create_vdevice_streams_from_config_params(network_group_handle_t network_group_handle) +hailo_status VdmaConfigNetworkGroup::create_vdevice_streams_from_config_params(std::shared_ptr multiplexer, scheduler_ng_handle_t scheduler_handle) { // TODO - HRT-6931 - raise error on this case if (((m_config_params.latency & HAILO_LATENCY_MEASURE) == HAILO_LATENCY_MEASURE) && (1 < m_resources_managers.size())) { LOGGER__WARNING("Latency measurement is not supported on more than 1 physical device."); } + m_multiplexer = multiplexer; + for (const auto &stream_parameters_pair : m_config_params.stream_params_by_name) { switch (stream_parameters_pair.second.direction) { case HAILO_H2D_STREAM: { auto status = create_input_vdevice_stream_from_config_params(stream_parameters_pair.second, - stream_parameters_pair.first, network_group_handle); + stream_parameters_pair.first, multiplexer, scheduler_handle); CHECK_SUCCESS(status); } break; case HAILO_D2H_STREAM: { auto status = create_output_vdevice_stream_from_config_params(stream_parameters_pair.second, - stream_parameters_pair.first, network_group_handle); + stream_parameters_pair.first, multiplexer, scheduler_handle); CHECK_SUCCESS(status); } break; @@ -108,57 +108,101 @@ hailo_status VdmaConfigNetworkGroup::create_vdevice_streams_from_config_params(n } } + auto status = m_multiplexer->add_network_group_instance(m_multiplexer_handle, *this); + CHECK_SUCCESS(status); + return HAILO_SUCCESS; } hailo_status VdmaConfigNetworkGroup::create_input_vdevice_stream_from_config_params(const hailo_stream_parameters_t &stream_params, - const std::string &stream_name, network_group_handle_t network_group_handle) + const std::string &stream_name, std::shared_ptr multiplexer, scheduler_ng_handle_t scheduler_handle) { auto edge_layer = get_layer_info(stream_name); CHECK_EXPECTED_AS_STATUS(edge_layer); - CHECK(HAILO_STREAM_INTERFACE_PCIE == stream_params.stream_interface, HAILO_INVALID_OPERATION, - "Only PCIe streams are supported on VDevice usage. {} has {} interface.", stream_name, stream_params.stream_interface); + CHECK(HailoRTCommon::is_vdma_stream_interface(stream_params.stream_interface), HAILO_INVALID_OPERATION, + "Stream {} not supported on VDevice usage. {} has {} interface.", stream_name, stream_params.stream_interface); auto input_stream = VDeviceInputStream::create(m_resources_managers, edge_layer.value(), - stream_name, network_group_handle, m_network_group_activated_event, + stream_name, scheduler_handle, m_network_group_activated_event, m_network_group_scheduler); CHECK_EXPECTED_AS_STATUS(input_stream); - m_input_streams.insert(make_pair(stream_name, input_stream.release())); + auto input_stream_wrapper = VDeviceInputStreamWrapper::create(input_stream.release(), edge_layer->network_name, multiplexer, scheduler_handle); + CHECK_EXPECTED_AS_STATUS(input_stream_wrapper); + m_input_streams.insert(make_pair(stream_name, input_stream_wrapper.release())); return HAILO_SUCCESS; } hailo_status VdmaConfigNetworkGroup::create_output_vdevice_stream_from_config_params(const hailo_stream_parameters_t &stream_params, - const std::string &stream_name, network_group_handle_t network_group_handle) + const std::string &stream_name, std::shared_ptr multiplexer, scheduler_ng_handle_t scheduler_handle) { auto edge_layer = get_layer_info(stream_name); CHECK_EXPECTED_AS_STATUS(edge_layer); - CHECK(HAILO_STREAM_INTERFACE_PCIE == stream_params.stream_interface, HAILO_INVALID_OPERATION, - "Only PCIe streams are supported on VDevice usage. {} has {} interface.", stream_name, stream_params.stream_interface); + CHECK(HailoRTCommon::is_vdma_stream_interface(stream_params.stream_interface), HAILO_INVALID_OPERATION, + "Stream {} not supported on VDevice usage. {} has {} interface.", stream_name, stream_params.stream_interface); auto output_stream = VDeviceOutputStream::create(m_resources_managers, edge_layer.value(), - stream_name, network_group_handle, m_network_group_activated_event, + stream_name, scheduler_handle, m_network_group_activated_event, m_network_group_scheduler); CHECK_EXPECTED_AS_STATUS(output_stream); - m_output_streams.insert(make_pair(stream_name, output_stream.release())); + auto output_stream_wrapper = VDeviceOutputStreamWrapper::create(output_stream.release(), edge_layer->network_name, multiplexer, scheduler_handle); + CHECK_EXPECTED_AS_STATUS(output_stream_wrapper); + m_output_streams.insert(make_pair(stream_name, output_stream_wrapper.release())); + + return HAILO_SUCCESS; +} + +hailo_status VdmaConfigNetworkGroup::create_vdevice_streams_from_duplicate(std::shared_ptr other) +{ + // TODO - HRT-6931 - raise error on this case + if (((m_config_params.latency & HAILO_LATENCY_MEASURE) == HAILO_LATENCY_MEASURE) && (1 < m_resources_managers.size())) { + LOGGER__WARNING("Latency measurement is not supported on more than 1 physical device."); + } + + m_multiplexer = other->m_multiplexer; + m_multiplexer_handle = other->multiplexer_duplicates_count() + 1; + + for (auto &name_stream_pair : other->m_input_streams) { + auto input_stream = static_cast(name_stream_pair.second.get()); + auto copy = input_stream->clone(m_multiplexer_handle); + CHECK_EXPECTED_AS_STATUS(copy); + + m_input_streams.insert(make_pair(name_stream_pair.first, copy.release())); + } + + for (auto &name_stream_pair : other->m_output_streams) { + auto output_stream = static_cast(name_stream_pair.second.get()); + auto copy = output_stream->clone(m_multiplexer_handle); + CHECK_EXPECTED_AS_STATUS(copy); + + m_output_streams.insert(make_pair(name_stream_pair.first, copy.release())); + } + + auto status = other->m_multiplexer->add_network_group_instance(m_multiplexer_handle, *this); + CHECK_SUCCESS(status); return HAILO_SUCCESS; } -void VdmaConfigNetworkGroup::set_network_group_handle(network_group_handle_t handle) +void VdmaConfigNetworkGroup::set_network_group_handle(scheduler_ng_handle_t handle) +{ + m_scheduler_handle = handle; +} + +scheduler_ng_handle_t VdmaConfigNetworkGroup::network_group_handle() const { - m_network_group_handle = handle; + return m_scheduler_handle; } hailo_status VdmaConfigNetworkGroup::set_scheduler_timeout(const std::chrono::milliseconds &timeout, const std::string &network_name) { auto network_group_scheduler = m_network_group_scheduler.lock(); CHECK(network_group_scheduler, HAILO_INVALID_OPERATION, - "Cannot set scheduler timeout for network group {}, as it is configured on a vdevice which does not have scheduling enabled", get_network_group_name()); - if (network_name != HailoRTDefaults::get_network_name(get_network_group_name())) { + "Cannot set scheduler timeout for network group {}, as it is configured on a vdevice which does not have scheduling enabled", name()); + if (network_name != HailoRTDefaults::get_network_name(name())) { CHECK(network_name.empty(), HAILO_NOT_IMPLEMENTED, "Setting scheduler timeout for a specific network is currently not supported"); } - auto status = network_group_scheduler->set_timeout(m_network_group_handle, timeout, network_name); + auto status = network_group_scheduler->set_timeout(m_scheduler_handle, timeout, network_name); CHECK_SUCCESS(status); return HAILO_SUCCESS; } @@ -167,18 +211,18 @@ hailo_status VdmaConfigNetworkGroup::set_scheduler_threshold(uint32_t threshold, { auto network_group_scheduler = m_network_group_scheduler.lock(); CHECK(network_group_scheduler, HAILO_INVALID_OPERATION, - "Cannot set scheduler threshold for network group {}, as it is configured on a vdevice which does not have scheduling enabled", get_network_group_name()); - if (network_name != HailoRTDefaults::get_network_name(get_network_group_name())) { + "Cannot set scheduler threshold for network group {}, as it is configured on a vdevice which does not have scheduling enabled", name()); + if (network_name != HailoRTDefaults::get_network_name(name())) { CHECK(network_name.empty(), HAILO_NOT_IMPLEMENTED, "Setting scheduler threshold for a specific network is currently not supported"); } - auto status = network_group_scheduler->set_threshold(m_network_group_handle, threshold, network_name); + auto status = network_group_scheduler->set_threshold(m_scheduler_handle, threshold, network_name); CHECK_SUCCESS(status); return HAILO_SUCCESS; } -Expected> VdmaConfigNetworkGroup::get_latnecy_meters() +Expected> VdmaConfigNetworkGroup::get_latency_meters() { - auto latency_meters = m_resources_managers[0]->get_latnecy_meters(); + auto latency_meters = m_resources_managers[0]->get_latency_meters(); return make_shared_nothrow(latency_meters); } @@ -192,4 +236,27 @@ Expected> VdmaConfigNetworkGroup::get_boundary_vdma return m_resources_managers[0]->get_boundary_vdma_channel_by_stream_name(stream_name); } +Expected> VdmaConfigNetworkGroup::create_output_vstreams(const std::map &outputs_params) +{ + auto expected = ConfiguredNetworkGroupBase::create_output_vstreams(outputs_params); + CHECK_EXPECTED(expected); + + if (nullptr == m_multiplexer) { + return expected.release(); + } + + m_multiplexer->set_output_vstreams_names(m_multiplexer_handle, expected.value()); + + for (auto &vstream : expected.value()) { + static_cast(*vstream.m_vstream).set_on_vstream_cant_read_callback([this, name = vstream.name()] () { + m_multiplexer->set_can_output_vstream_read(m_multiplexer_handle, name, false); + }); + static_cast(*vstream.m_vstream).set_on_vstream_can_read_callback([this, name = vstream.name()] () { + m_multiplexer->set_can_output_vstream_read(m_multiplexer_handle, name, true); + }); + } + + return expected.release(); +} + } /* namespace hailort */ diff --git a/hailort/libhailort/src/control.cpp b/hailort/libhailort/src/control.cpp index b20b4cc..7bb2105 100644 --- a/hailort/libhailort/src/control.cpp +++ b/hailort/libhailort/src/control.cpp @@ -17,6 +17,7 @@ #include "d2h_events.h" #include "hw_consts.hpp" #include +#include "hef_internal.hpp" namespace hailort { @@ -76,9 +77,7 @@ Expected control__parse_identify_results(CONTROL_PROTOC /* Clear debug/release bit */ board_info.fw_version.revision = GET_REVISION_NUMBER_VALUE(board_info.fw_version.revision); - // TODO: Fix static cast, we are assuming they are the same (HRT-3177) - board_info.device_architecture = static_cast( - BYTE_ORDER__ntohl(identify_response->device_architecture)); + board_info.device_architecture = static_cast(BYTE_ORDER__ntohl(identify_response->device_architecture)); /* Write identify results to log */ LOGGER__INFO("firmware_version is: {}.{}.{}", @@ -94,14 +93,12 @@ Expected control__parse_identify_results(CONTROL_PROTOC } Expected control__parse_get_extended_device_information_results - (CONTROL_PROTOCOL__get_extended_device_information_response_t *get_extended_device_information_response) + (CONTROL_PROTOCOL__get_extended_device_information_response_t &get_extended_device_information_response) { uint8_t local_supported_features; hailo_extended_device_information_t device_info; - CHECK_AS_EXPECTED(nullptr != get_extended_device_information_response, HAILO_INVALID_ARGUMENT); - - local_supported_features = (uint8_t)BYTE_ORDER__ntohl(get_extended_device_information_response->supported_features); + local_supported_features = (uint8_t)BYTE_ORDER__ntohl(get_extended_device_information_response.supported_features); device_info.supported_features.ethernet = (local_supported_features & (1 << CONTROL_PROTOCOL__SUPPORTED_FEATURES_ETHERNET_BIT_OFFSET)) != 0; @@ -113,22 +110,22 @@ Expected control__parse_get_extended_device (1 << CONTROL_PROTOCOL__SUPPORTED_FEATURES_CURRENT_MONITORING_BIT_OFFSET)) != 0; device_info.supported_features.mdio = (local_supported_features & (1 << CONTROL_PROTOCOL__SUPPORTED_FEATURES_MDIO_BIT_OFFSET)) != 0; - device_info.neural_network_core_clock_rate = BYTE_ORDER__ntohl(get_extended_device_information_response->neural_network_core_clock_rate); + device_info.neural_network_core_clock_rate = BYTE_ORDER__ntohl(get_extended_device_information_response.neural_network_core_clock_rate); LOGGER__DEBUG("Max Neural Network Core Clock Rate: {}", device_info.neural_network_core_clock_rate); device_info.boot_source = static_cast( - BYTE_ORDER__ntohl(get_extended_device_information_response->boot_source)); + BYTE_ORDER__ntohl(get_extended_device_information_response.boot_source)); (void)memcpy(device_info.soc_id, - get_extended_device_information_response->soc_id, - BYTE_ORDER__ntohl(get_extended_device_information_response->soc_id_length)); + get_extended_device_information_response.soc_id, + BYTE_ORDER__ntohl(get_extended_device_information_response.soc_id_length)); - device_info.lcs = get_extended_device_information_response->lcs; + device_info.lcs = get_extended_device_information_response.lcs; - memcpy(&device_info.unit_level_tracking_id[0], &get_extended_device_information_response->fuse_info, sizeof(device_info.unit_level_tracking_id)); - memcpy(&device_info.eth_mac_address[0], &get_extended_device_information_response->eth_mac_address[0], BYTE_ORDER__ntohl(get_extended_device_information_response->eth_mac_length)); - memcpy(&device_info.soc_pm_values, &get_extended_device_information_response->pd_info, sizeof(device_info.soc_pm_values)); + memcpy(&device_info.unit_level_tracking_id[0], &get_extended_device_information_response.fuse_info, sizeof(device_info.unit_level_tracking_id)); + memcpy(&device_info.eth_mac_address[0], &get_extended_device_information_response.eth_mac_address[0], BYTE_ORDER__ntohl(get_extended_device_information_response.eth_mac_length)); + memcpy(&device_info.soc_pm_values, &get_extended_device_information_response.pd_info, sizeof(device_info.soc_pm_values)); return device_info; } @@ -144,7 +141,7 @@ Expected control__parse_get_health_information_results health_info.current_overcurrent_zone = get_health_information_response->current_overcurrent_zone; // Re-convertion to floats after health_info.red_overcurrent_threshold = float32_t(BYTE_ORDER__ntohl(get_health_information_response->red_overcurrent_threshold)); - health_info.orange_overcurrent_threshold = float32_t(BYTE_ORDER__ntohl(get_health_information_response->orange_overcurrent_threshold)); + health_info.overcurrent_throttling_active = get_health_information_response->overcurrent_throttling_active; health_info.temperature_throttling_active = get_health_information_response->temperature_throttling_active; health_info.current_temperature_zone = get_health_information_response->current_temperature_zone; health_info.current_temperature_throttling_level = get_health_information_response->current_temperature_throttling_level; @@ -154,6 +151,8 @@ Expected control__parse_get_health_information_results health_info.orange_hysteresis_temperature_threshold = BYTE_ORDER__ntohl(get_health_information_response->orange_hysteresis_temperature_threshold); health_info.red_temperature_threshold = BYTE_ORDER__ntohl(get_health_information_response->red_temperature_threshold); health_info.red_hysteresis_temperature_threshold = BYTE_ORDER__ntohl(get_health_information_response->red_hysteresis_temperature_threshold); + health_info.requested_overcurrent_clock_freq = BYTE_ORDER__ntohl(get_health_information_response->requested_overcurrent_clock_freq); + health_info.requested_temperature_clock_freq = BYTE_ORDER__ntohl(get_health_information_response->requested_temperature_clock_freq); return health_info; } @@ -2402,14 +2401,14 @@ hailo_status Control::context_switch_set_context_info(Device &device, context_info_single_control.is_first_control_per_context = is_first_control_per_context; context_info_single_control.is_last_control_per_context = is_last_control_per_context; - static_assert(sizeof(context_info_single_control.config_buffer_infos) == sizeof(context_info->config_buffer_infos), - "mismatch in sizes of config_buffer_infos"); + static_assert(sizeof(context_info_single_control.config_channel_infos) == sizeof(context_info->config_channel_infos), + "mismatch in sizes of config_channel_infos"); static_assert(sizeof(context_info_single_control.context_stream_remap_data) == sizeof(context_info->context_stream_remap_data), "mismatch in sizes of context_stream_remap_data"); context_info_single_control.cfg_channels_count = context_info->cfg_channels_count; - memcpy(context_info_single_control.config_buffer_infos, - context_info->config_buffer_infos, - sizeof(context_info_single_control.config_buffer_infos)); + memcpy(context_info_single_control.config_channel_infos, + context_info->config_channel_infos, + sizeof(context_info_single_control.config_channel_infos)); memcpy(&(context_info_single_control.context_stream_remap_data), &(context_info->context_stream_remap_data), @@ -2723,7 +2722,7 @@ hailo_status Control::download_context_action_list(Device &device, uint8_t conte hailo_status Control::change_context_switch_status(Device &device, CONTROL_PROTOCOL__CONTEXT_SWITCH_STATUS_t state_machine_status, - uint8_t network_group_index, uint16_t dynamic_batch_size) + uint8_t network_group_index, uint16_t dynamic_batch_size, bool keep_nn_config_during_reset) { hailo_status status = HAILO_UNINITIALIZED; HAILO_COMMON_STATUS_t common_status = HAILO_COMMON_STATUS__UNINITIALIZED; @@ -2735,7 +2734,8 @@ hailo_status Control::change_context_switch_status(Device &device, CONTROL_PROTOCOL__payload_t *payload = NULL; common_status = CONTROL_PROTOCOL__pack_change_context_switch_status_request(&request, &request_size, - device.get_control_sequence(), state_machine_status, network_group_index, dynamic_batch_size); + device.get_control_sequence(), state_machine_status, network_group_index, dynamic_batch_size, + keep_nn_config_during_reset); status = (HAILO_COMMON_STATUS__SUCCESS == common_status) ? HAILO_SUCCESS : HAILO_INTERNAL_FAILURE; if (HAILO_SUCCESS != status) { goto exit; @@ -2760,16 +2760,17 @@ exit: hailo_status Control::enable_network_group(Device &device, uint8_t network_group_index, uint16_t dynamic_batch_size) { + static const auto REMOVE_NN_CONFIG_DURING_RESET = false; return Control::change_context_switch_status(device, CONTROL_PROTOCOL__CONTEXT_SWITCH_STATUS_ENABLED, - network_group_index, dynamic_batch_size); + network_group_index, dynamic_batch_size, REMOVE_NN_CONFIG_DURING_RESET); } -hailo_status Control::reset_context_switch_state_machine(Device &device) +hailo_status Control::reset_context_switch_state_machine(Device &device, bool keep_nn_config_during_reset) { static const auto IGNORE_NETWORK_GROUP_INDEX = 0; static const auto IGNORE_DYNAMIC_BATCH_SIZE = 0; return Control::change_context_switch_status(device, CONTROL_PROTOCOL__CONTEXT_SWITCH_STATUS_RESET, - IGNORE_NETWORK_GROUP_INDEX, IGNORE_DYNAMIC_BATCH_SIZE); + IGNORE_NETWORK_GROUP_INDEX, IGNORE_DYNAMIC_BATCH_SIZE, keep_nn_config_during_reset); } hailo_status Control::wd_enable(Device &device, uint8_t cpu_id, bool should_enable) @@ -3003,23 +3004,26 @@ exit: return status; } -hailo_status Control::switch_network_group(Device &device, uint8_t network_group_index, uint16_t dynamic_batch_size) + +hailo_status Control::clear_configured_apps(Device &device) { hailo_status status = HAILO_UNINITIALIZED; HAILO_COMMON_STATUS_t common_status = HAILO_COMMON_STATUS__UNINITIALIZED; CONTROL_PROTOCOL__request_t request = {}; size_t request_size = 0; uint8_t response_buffer[RESPONSE_MAX_BUFFER_SIZE] = {}; - size_t response_size = sizeof(response_buffer); + size_t response_size = RESPONSE_MAX_BUFFER_SIZE; CONTROL_PROTOCOL__response_header_t *header = NULL; CONTROL_PROTOCOL__payload_t *payload = NULL; - LOGGER__DEBUG("Set network_group_index {}", network_group_index); + LOGGER__DEBUG("Sending clear_configured_apps"); - common_status = CONTROL_PROTOCOL__pack_switch_application_request(&request, &request_size, device.get_control_sequence(), - network_group_index, dynamic_batch_size); + common_status = CONTROL_PROTOCOL__pack_context_switch_clear_configured_apps_request(&request, &request_size, + device.get_control_sequence()); status = (HAILO_COMMON_STATUS__SUCCESS == common_status) ? HAILO_SUCCESS : HAILO_INTERNAL_FAILURE; if (HAILO_SUCCESS != status) { + LOGGER__ERROR("failed CONTROL_PROTOCOL__pack_context_switch_clear_configured_apps_request with status {:#X}", + common_status); goto exit; } @@ -3029,9 +3033,9 @@ hailo_status Control::switch_network_group(Device &device, uint8_t network_group } /* Parse response */ - status = parse_and_validate_response(response_buffer, (uint32_t)(response_size), &header, &payload, - &request); + status = parse_and_validate_response(response_buffer, (uint32_t)(response_size), &header, &payload, &request); if (HAILO_SUCCESS != status) { + LOGGER__ERROR("failed clear_configured_apps control with status {}", status); goto exit; } @@ -3114,7 +3118,7 @@ exit: return status; } -Expected Control::get_extended_device_information(Device &device) +Expected Control::get_extended_device_info_response(Device &device) { hailo_status status = HAILO_UNINITIALIZED; HAILO_COMMON_STATUS_t common_status = HAILO_COMMON_STATUS__UNINITIALIZED; @@ -3124,7 +3128,6 @@ Expected Control::get_extended_device_infor size_t response_size = RESPONSE_MAX_BUFFER_SIZE; CONTROL_PROTOCOL__response_header_t *header = NULL; CONTROL_PROTOCOL__payload_t *payload = NULL; - CONTROL_PROTOCOL__get_extended_device_information_response_t *get_extended_device_information_response = NULL; /* Validate arguments */ @@ -3139,9 +3142,27 @@ Expected Control::get_extended_device_infor status = parse_and_validate_response(response_buffer, (uint32_t)(response_size), &header, &payload, &request); CHECK_SUCCESS_AS_EXPECTED(status); - get_extended_device_information_response = (CONTROL_PROTOCOL__get_extended_device_information_response_t *)(payload->parameters); + return std::move(*(CONTROL_PROTOCOL__get_extended_device_information_response_t *)(payload->parameters)); +} - return control__parse_get_extended_device_information_results(get_extended_device_information_response); +Expected Control::get_partial_clusters_layout_bitmap(Device &device) +{ + auto device_arch_exp = device.get_architecture(); + CHECK_EXPECTED(device_arch_exp); + if (HAILO_ARCH_HAILO8L != device_arch_exp.value()) { + // Partial clusters layout is only relevant in HAILO_ARCH_HAILO8L arch + return Expected(PARTIAL_CLUSTERS_LAYOUT_IGNORE); + } + auto extended_device_info_response = get_extended_device_info_response(device); + CHECK_EXPECTED(extended_device_info_response); + return BYTE_ORDER__ntohl(extended_device_info_response->partial_clusters_layout_bitmap); +} + +Expected Control::get_extended_device_information(Device &device) +{ + auto extended_device_info_response = get_extended_device_info_response(device); + CHECK_EXPECTED(extended_device_info_response); + return control__parse_get_extended_device_information_results(extended_device_info_response.value()); } Expected Control::get_health_information(Device &device) diff --git a/hailort/libhailort/src/control.hpp b/hailort/libhailort/src/control.hpp index 57f303c..3e7e149 100644 --- a/hailort/libhailort/src/control.hpp +++ b/hailort/libhailort/src/control.hpp @@ -298,11 +298,13 @@ public: * reset context switch state machine * * @param[in] device - The Hailo device. - * @param[in] network_group_index - network_group index + * @param[in] keep_nn_config_during_reset - + * Use if in the reset flow, user wise to remain in the same network group. + * this reset flow keep most of the configuration on the network group for faster batch switching. * * @return Upon success, returns @a HAILO_SUCCESS. Otherwise, returns an @a static hailo_status error. */ - static hailo_status reset_context_switch_state_machine(Device &device); + static hailo_status reset_context_switch_state_machine(Device &device, bool keep_nn_config_during_reset); /** * set dataflow interrupt by control * @@ -320,8 +322,8 @@ public: * set d2h manager a new host configuration by control * * @param[in] device - The Hailo device. - * @param[in] host_port - host port in case connection_type is Ethernet  , otherwise neglected. - * @param[in] host_ip_address - host ip in case connection_type is Ethernet , otherwise neglected, + * @param[in] host_port - host port in case connection_type is Ethernet, otherwise neglected. + * @param[in] host_ip_address - host ip in case connection_type is Ethernet, otherwise neglected, * 0 means auto detect IP address from control. * * @return Upon success, returns @a HAILO_SUCCESS. Otherwise, returns an @a static hailo_status error. @@ -329,18 +331,6 @@ public: static hailo_status d2h_notification_manager_set_host_info(Device &device, uint16_t host_port, uint32_t host_ip_address); static hailo_status d2h_notification_manager_send_host_info_notification(Device &device, uint8_t notification_priority); - /** - * set the user desired network_group (must be sent after sending the meta data header) - * - * @param[in] device - The Hailo device. - * @param[in] network_group_index - network_group index - * @param[in] dynamic_batch_size - dynamic_batch size (or CONTROL_PROTOCOL__IGNORE_DYNAMIC_BATCH_SIZE if it's - * to be ignored) - * - * @return Upon success, returns @a HAILO_SUCCESS. Otherwise, returns an @a static hailo_status error. - */ - static hailo_status switch_network_group(Device &device, uint8_t network_group_index, uint16_t dynamic_batch_size); - /** * Enable/disable halt transmition following Rx pause frame * @@ -360,6 +350,7 @@ public: static hailo_status wd_enable(Device &device, uint8_t cpu_id, bool should_enable); static hailo_status wd_config(Device &device, uint8_t cpu_id, uint32_t wd_cycles, CONTROL_PROTOCOL__WATCHDOG_MODE_t wd_mode); static hailo_status previous_system_state(Device &device, uint8_t cpu_id, CONTROL_PROTOCOL__system_state_t *system_state); + static hailo_status clear_configured_apps(Device &device); static hailo_status get_chip_temperature(Device &device, hailo_chip_temperature_info_t *temp_info); static hailo_status enable_debugging(Device &device, bool is_rma); @@ -392,6 +383,8 @@ public: CONTROL_PROTOCOL__averaging_factor_t averaging_factor, CONTROL_PROTOCOL__sampling_period_t sampling_period); static hailo_status stop_power_measurement(Device &device); + static Expected get_partial_clusters_layout_bitmap(Device &device); + private: static hailo_status write_memory_chunk(Device &device, uint32_t address, const uint8_t *data, uint32_t chunk_size); static hailo_status read_memory_chunk(Device &device, uint32_t address, uint8_t *data, uint32_t chunk_size); @@ -405,7 +398,8 @@ private: CONTROL_PROTOCOL__context_switch_context_info_single_control_t *context_info); static hailo_status change_context_switch_status(Device &device, CONTROL_PROTOCOL__CONTEXT_SWITCH_STATUS_t state_machine_status, - uint8_t network_group_index, uint16_t dynamic_batch_size); + uint8_t network_group_index, uint16_t dynamic_batch_size, bool keep_nn_config_during_reset); + static Expected get_extended_device_info_response(Device &device); }; } /* namespace hailort */ diff --git a/hailort/libhailort/src/control_protocol.cpp b/hailort/libhailort/src/control_protocol.cpp index ef17df8..ee0c668 100644 --- a/hailort/libhailort/src/control_protocol.cpp +++ b/hailort/libhailort/src/control_protocol.cpp @@ -1635,7 +1635,7 @@ HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_context_switch_set_main_header_requ /* Header */ local_request_size = CONTROL_PROTOCOL__REQUEST_BASE_SIZE + sizeof(CONTROL_PROTOCOL__context_switch_set_main_header_request_t); - control_protocol__pack_request_header(request, sequence, HAILO_CONTROL_OPCODE_CONTEXT_SWITCH_SET_MAIN_HEADER, 4); + control_protocol__pack_request_header(request, sequence, HAILO_CONTROL_OPCODE_CONTEXT_SWITCH_SET_MAIN_HEADER, 3); /* context_switch_version */ request->parameters.context_switch_set_main_header_request.context_switch_version_length = @@ -1643,11 +1643,6 @@ HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_context_switch_set_main_header_requ request->parameters.context_switch_set_main_header_request.context_switch_version = context_switch_header->context_switch_version; - /* validation_features */ - request->parameters.context_switch_set_main_header_request.validation_features_length = - BYTE_ORDER__htonl(sizeof(request->parameters.context_switch_set_main_header_request.validation_features)); - request->parameters.context_switch_set_main_header_request.validation_features = context_switch_header->validation_features; - /* application_count */ request->parameters.context_switch_set_main_header_request.application_count_length = BYTE_ORDER__htonl(sizeof(request->parameters.context_switch_set_main_header_request.application_count)); @@ -1702,11 +1697,11 @@ HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_context_switch_set_context_info_req request->parameters.context_switch_set_context_info_request.cfg_channels_count = context_info->cfg_channels_count; /* context cfg buffer info */ - request->parameters.context_switch_set_context_info_request.config_buffer_infos_length = - BYTE_ORDER__htonl(sizeof(request->parameters.context_switch_set_context_info_request.config_buffer_infos)); - memcpy(&(request->parameters.context_switch_set_context_info_request.config_buffer_infos), - &(context_info->config_buffer_infos), - sizeof(request->parameters.context_switch_set_context_info_request.config_buffer_infos)); + request->parameters.context_switch_set_context_info_request.config_channel_infos_length = + BYTE_ORDER__htonl(sizeof(request->parameters.context_switch_set_context_info_request.config_channel_infos)); + memcpy(&(request->parameters.context_switch_set_context_info_request.config_channel_infos), + &(context_info->config_channel_infos), + sizeof(request->parameters.context_switch_set_context_info_request.config_channel_infos)); /* context unused stream index */ request->parameters.context_switch_set_context_info_request.context_stream_remap_data_length = @@ -1833,7 +1828,7 @@ exit: HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_change_context_switch_status_request( CONTROL_PROTOCOL__request_t *request, size_t *request_size, uint32_t sequence, CONTROL_PROTOCOL__CONTEXT_SWITCH_STATUS_t state_machine_status, uint8_t application_index, - uint16_t dynamic_batch_size) + uint16_t dynamic_batch_size, bool keep_nn_config_during_reset) { HAILO_COMMON_STATUS_t status = HAILO_COMMON_STATUS__UNINITIALIZED; size_t local_request_size = 0; @@ -1846,7 +1841,7 @@ HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_change_context_switch_status_reques /* Header */ local_request_size = CONTROL_PROTOCOL__REQUEST_BASE_SIZE + sizeof(CONTROL_PROTOCOL__change_context_switch_status_request_t); - control_protocol__pack_request_header(request, sequence, HAILO_CONTROL_OPCODE_CHANGE_CONTEXT_SWITCH_STATUS, 3); + control_protocol__pack_request_header(request, sequence, HAILO_CONTROL_OPCODE_CHANGE_CONTEXT_SWITCH_STATUS, 4); /* state_machine_status */ request->parameters.change_context_switch_status_request.state_machine_status_length = @@ -1865,6 +1860,11 @@ HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_change_context_switch_status_reques BYTE_ORDER__htonl(sizeof(request->parameters.change_context_switch_status_request.dynamic_batch_size)); request->parameters.change_context_switch_status_request.dynamic_batch_size = dynamic_batch_size; + /* dynamic_batch_size */ + request->parameters.change_context_switch_status_request.keep_nn_config_during_reset_length = + BYTE_ORDER__htonl(sizeof(request->parameters.change_context_switch_status_request.keep_nn_config_during_reset)); + request->parameters.change_context_switch_status_request.keep_nn_config_during_reset = keep_nn_config_during_reset; + *request_size = local_request_size; status = HAILO_COMMON_STATUS__SUCCESS; exit: @@ -1950,6 +1950,28 @@ exit: return status; } +HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_context_switch_clear_configured_apps_request( + CONTROL_PROTOCOL__request_t *request, + size_t *request_size, + uint32_t sequence) +{ + HAILO_COMMON_STATUS_t status = HAILO_COMMON_STATUS__UNINITIALIZED; + + if ((NULL == request) || (NULL == request_size)) { + status = HAILO_STATUS__CONTROL_PROTOCOL__NULL_ARGUMENT_PASSED; + goto exit; + } + + *request_size = CONTROL_PROTOCOL__REQUEST_BASE_SIZE; + control_protocol__pack_empty_request(request, request_size, sequence, + HAILO_CONTROL_OPCODE_CONTEXT_SWITCH_CLEAR_CONFIGURED_APPS); + + status = HAILO_COMMON_STATUS__SUCCESS; + +exit: + return status; +} + HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_previous_system_state( CONTROL_PROTOCOL__request_t *request, size_t *request_size, @@ -2089,37 +2111,6 @@ HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_d2h_event_manager_send_host_info_ev request->parameters.d2h_event_manager_send_host_info_event_request.priority = event_priority; - *request_size = local_request_size; - status = HAILO_COMMON_STATUS__SUCCESS; -exit: - return status; -} -HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_switch_application_request( - CONTROL_PROTOCOL__request_t *request, size_t *request_size, uint32_t sequence, - uint8_t application_index, uint16_t dynamic_batch_size) -{ - HAILO_COMMON_STATUS_t status = HAILO_COMMON_STATUS__UNINITIALIZED; - size_t local_request_size = 0; - - if ((NULL == request) || (NULL == request_size)) { - status = HAILO_STATUS__CONTROL_PROTOCOL__NULL_ARGUMENT_PASSED; - goto exit; - } - - /* Header */ - local_request_size = CONTROL_PROTOCOL__REQUEST_BASE_SIZE + sizeof(CONTROL_PROTOCOL__switch_application_request_t); - control_protocol__pack_request_header(request, sequence, HAILO_CONTROL_OPCODE_SWITCH_APPLICATION, 1); - - /* application_index */ - request->parameters.switch_application_request.application_index_length = - BYTE_ORDER__htonl(sizeof(request->parameters.switch_application_request.application_index)); - request->parameters.switch_application_request.application_index = application_index; - - /* dynamic_batch_size */ - request->parameters.switch_application_request.dynamic_batch_size_length = - BYTE_ORDER__htonl(sizeof(request->parameters.switch_application_request.dynamic_batch_size)); - request->parameters.switch_application_request.dynamic_batch_size = dynamic_batch_size; - *request_size = local_request_size; status = HAILO_COMMON_STATUS__SUCCESS; exit: diff --git a/hailort/libhailort/src/control_protocol.hpp b/hailort/libhailort/src/control_protocol.hpp index 0337f7b..26e18d0 100644 --- a/hailort/libhailort/src/control_protocol.hpp +++ b/hailort/libhailort/src/control_protocol.hpp @@ -47,7 +47,7 @@ typedef struct { typedef struct { uint8_t cfg_channels_count; - CONTROL_PROTOCOL__host_buffer_info_t config_buffer_infos[CONTROL_PROTOCOL__MAX_CFG_CHANNELS]; + CONTROL_PROTOCOL__config_channel_info_t config_channel_infos[CONTROL_PROTOCOL__MAX_CFG_CHANNELS]; uint32_t context_network_data_length; CONTROL_PROTOCOL__stream_remap_data_t context_stream_remap_data; uint8_t context_network_data[CONTROL_PROTOCOL__CONTEXT_NETWORK_DATA_MAX_SIZE]; @@ -131,7 +131,7 @@ HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_download_context_action_list_reques HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_change_context_switch_status_request( CONTROL_PROTOCOL__request_t *request, size_t *request_size, uint32_t sequence, CONTROL_PROTOCOL__CONTEXT_SWITCH_STATUS_t state_machine_status, uint8_t application_index, - uint16_t dynamic_batch_size); + uint16_t dynamic_batch_size, bool keep_nn_config_during_reset); HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_wd_enable( CONTROL_PROTOCOL__request_t *request, size_t *request_size, @@ -145,6 +145,8 @@ HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_wd_config( uint8_t cpu_id, uint32_t wd_cycles, CONTROL_PROTOCOL__WATCHDOG_MODE_t wd_mode); +HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_context_switch_clear_configured_apps_request( + CONTROL_PROTOCOL__request_t *request, size_t *request_size, uint32_t sequence); HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_previous_system_state(CONTROL_PROTOCOL__request_t *request, size_t *request_size, uint32_t sequence, uint8_t cpu_id); HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_set_dataflow_interrupt_request( CONTROL_PROTOCOL__request_t *request, size_t *request_size, uint32_t sequence, @@ -153,8 +155,6 @@ HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_d2h_event_manager_set_host_info_req uint8_t connection_type, uint16_t host_port, uint32_t host_ip_address); HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_d2h_event_manager_send_host_info_event_request( CONTROL_PROTOCOL__request_t *request, size_t *request_size, uint32_t sequence, uint8_t event_priority); -HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_switch_application_request(CONTROL_PROTOCOL__request_t *request, size_t *request_size, uint32_t sequence, - uint8_t application_index, uint16_t dynamic_batch_size); HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_get_chip_temperature_request(CONTROL_PROTOCOL__request_t *request, size_t *request_size, uint32_t sequence); HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_read_board_config(CONTROL_PROTOCOL__request_t *request, size_t *request_size, uint32_t sequence, uint32_t address, uint32_t data_length); HAILO_COMMON_STATUS_t CONTROL_PROTOCOL__pack_write_board_config_request(CONTROL_PROTOCOL__request_t *request, size_t *request_size, uint32_t sequence, uint32_t address, const uint8_t *data, uint32_t data_length); diff --git a/hailort/libhailort/src/core_device.cpp b/hailort/libhailort/src/core_device.cpp index 867bb30..2a15606 100644 --- a/hailort/libhailort/src/core_device.cpp +++ b/hailort/libhailort/src/core_device.cpp @@ -39,7 +39,8 @@ Expected> CoreDevice::create() CoreDevice::CoreDevice(HailoRTDriver &&driver, hailo_status &status) : - VdmaDevice::VdmaDevice(std::move(driver), Device::Type::CORE) + VdmaDevice::VdmaDevice(std::move(driver), Device::Type::CORE), + m_context_switch_manager(nullptr) { status = update_fw_state(); if (HAILO_SUCCESS != status) { @@ -59,6 +60,7 @@ Expected CoreDevice::get_architecture() const { hailo_status CoreDevice::fw_interact_impl(uint8_t *request_buffer, size_t request_size, uint8_t *response_buffer, size_t *response_size, hailo_cpu_id_t cpu_id) { + // TODO: HRT-7535 uint8_t request_md5[PCIE_EXPECTED_MD5_LENGTH]; MD5_CTX ctx; @@ -98,6 +100,22 @@ hailo_status CoreDevice::reset_impl(CONTROL_PROTOCOL__reset_type_t reset_type) return HAILO_NOT_IMPLEMENTED; } +ExpectedRef CoreDevice::get_config_manager() +{ + auto status = mark_as_used(); + CHECK_SUCCESS_AS_EXPECTED(status); + + if (!m_context_switch_manager) { + auto local_context_switch_manager = VdmaConfigManager::create(*this); + CHECK_EXPECTED(local_context_switch_manager); + + m_context_switch_manager = make_unique_nothrow(local_context_switch_manager.release()); + CHECK_AS_EXPECTED(nullptr != m_context_switch_manager, HAILO_OUT_OF_HOST_MEMORY); + } + + return std::ref(*m_context_switch_manager); +} + Expected CoreDevice::read_log(MemoryView &buffer, hailo_cpu_id_t cpu_id) { if (hailo_cpu_id_t::HAILO_CPU_ID_0 == cpu_id) { diff --git a/hailort/libhailort/src/core_device.hpp b/hailort/libhailort/src/core_device.hpp index 1c19e8f..f7e97fd 100644 --- a/hailort/libhailort/src/core_device.hpp +++ b/hailort/libhailort/src/core_device.hpp @@ -27,7 +27,7 @@ public: static Expected> create(); virtual Expected get_architecture() const override; - virtual const char* get_dev_id() const override {return "Core";} + virtual const char* get_dev_id() const override {return DEVICE_ID;} Expected read_log(MemoryView &buffer, hailo_cpu_id_t cpu_id); virtual bool is_stream_interface_supported(const hailo_stream_interface_t &stream_interface) const override @@ -45,13 +45,23 @@ public: } } + static constexpr const char *DEVICE_ID = "[core]"; + protected: virtual hailo_status fw_interact_impl(uint8_t *request_buffer, size_t request_size, uint8_t *response_buffer, size_t *response_size, hailo_cpu_id_t cpu_id) override; virtual hailo_status reset_impl(CONTROL_PROTOCOL__reset_type_t reset_type) override; + virtual ExpectedRef get_config_manager() override; private: CoreDevice(HailoRTDriver &&driver, hailo_status &status); + + // TODO: (HRT-7535) This member needs to be held in the object that impls fw_interact_impl func, + // because VdmaConfigManager calls a control (which in turn calls fw_interact_impl). + // (otherwise we'll get a "pure virtual method called" runtime error in the Device's dtor) + // Once we merge CoreDevice::fw_interact_impl and PcieDevice::fw_interact_impl we can + // move the m_context_switch_manager member and get_config_manager() func to VdmaDevice. + std::unique_ptr m_context_switch_manager; }; diff --git a/hailort/libhailort/src/d2h_events_parser.cpp b/hailort/libhailort/src/d2h_events_parser.cpp index ab60f20..412e928 100644 --- a/hailort/libhailort/src/d2h_events_parser.cpp +++ b/hailort/libhailort/src/d2h_events_parser.cpp @@ -220,27 +220,24 @@ static HAILO_COMMON_STATUS_t D2H_EVENTS__parse_health_monitor_overcurrent_alert_ goto l_exit; } - switch (d2h_notification_message->message_parameters.health_monitor_overcurrent_alert_event.overcurrent_zone) { - case HAILO_OVERCURRENT_PROTECTION_OVERCURRENT_ZONE__NONE: - LOGGER__INFO("Got health monitor notification - overcurrent alert state cleared."); - break; - - case HAILO_OVERCURRENT_PROTECTION_OVERCURRENT_ZONE__ORANGE: - LOGGER__WARNING("Got health monitor notification - overcurrent alert state exceeded orange threshold ({} mA), and sampled current during alert ({} mA)", - d2h_notification_message->message_parameters.health_monitor_overcurrent_alert_event.exceeded_alert_threshold, - d2h_notification_message->message_parameters.health_monitor_overcurrent_alert_event.sampled_current_during_alert); - break; - - case HAILO_OVERCURRENT_PROTECTION_OVERCURRENT_ZONE__RED: - LOGGER__CRITICAL("Got health monitor notification - overcurrent alert state exceeded red threshold ({} mA), and sampled current during alert ({} mA)", - d2h_notification_message->message_parameters.health_monitor_overcurrent_alert_event.exceeded_alert_threshold, - d2h_notification_message->message_parameters.health_monitor_overcurrent_alert_event.sampled_current_during_alert); - break; - - default: - LOGGER__ERROR("Got invalid health monitor notification - overcurrent alert state could not be parsed."); - status = HAILO_STATUS__D2H_EVENTS__INVALID_ARGUMENT; - goto l_exit; + if (d2h_notification_message->message_parameters.health_monitor_overcurrent_alert_event.is_last_overcurrent_violation_reached) { + LOGGER__WARNING("Got health monitor notification - last overcurrent violation allow alert state. The exceeded alert threshold is {} mA", + d2h_notification_message->message_parameters.health_monitor_overcurrent_alert_event.exceeded_alert_threshold); + } else { + switch (d2h_notification_message->message_parameters.health_monitor_overcurrent_alert_event.overcurrent_zone) { + case HAILO_OVERCURRENT_PROTECTION_OVERCURRENT_ZONE__GREEN: + LOGGER__INFO("Got health monitor notification - overcurrent reached green zone. clk frequency decrease process was stopped. The exceeded alert threshold is {} mA", + d2h_notification_message->message_parameters.health_monitor_overcurrent_alert_event.exceeded_alert_threshold); + break; + case HAILO_OVERCURRENT_PROTECTION_OVERCURRENT_ZONE__RED: + LOGGER__CRITICAL("Got health monitor notification - overcurrent reached red zone. clk frequency decrease process was started. The exceeded alert threshold is {} mA", + d2h_notification_message->message_parameters.health_monitor_overcurrent_alert_event.exceeded_alert_threshold); + break; + default: + LOGGER__ERROR("Got invalid health monitor notification - overcurrent alert state could not be parsed."); + status = HAILO_STATUS__D2H_EVENTS__INVALID_ARGUMENT; + goto l_exit; + } } status = HAILO_COMMON_STATUS__SUCCESS; diff --git a/hailort/libhailort/src/ddr_channels_pair.cpp b/hailort/libhailort/src/ddr_channels_pair.cpp new file mode 100644 index 0000000..82edbd6 --- /dev/null +++ b/hailort/libhailort/src/ddr_channels_pair.cpp @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) +**/ +/** + * @file ddr_channels_pair.cpp + **/ + +#include "ddr_channels_pair.hpp" +#include "vdma/continuous_buffer.hpp" +#include "vdma/sg_buffer.hpp" +#include "common/utils.hpp" + +namespace hailort +{ + + +Expected DdrChannelsPair::create(HailoRTDriver &driver, const DdrChannelsInfo &ddr_channels_info) +{ + auto buffer_exp = should_use_ccb(driver) ? + create_ccb_buffer(driver, ddr_channels_info.row_size, ddr_channels_info.min_buffered_rows) : + create_sg_buffer(driver, ddr_channels_info.row_size, ddr_channels_info.min_buffered_rows); + CHECK_EXPECTED(buffer_exp); + auto buffer_ptr = buffer_exp.release(); + + CHECK_AS_EXPECTED(0 == (ddr_channels_info.row_size % buffer_ptr->desc_page_size()), HAILO_INTERNAL_FAILURE, + "DDR channel buffer row size must be a multiple of descriptor page size"); + + const auto interrupts_domain = VdmaInterruptsDomain::NONE; + const auto total_size = buffer_ptr->descs_count() * buffer_ptr->desc_page_size(); + auto desc_count_local = buffer_ptr->program_descriptors(total_size, interrupts_domain, interrupts_domain, 0, true); + CHECK_EXPECTED(desc_count_local); + + return DdrChannelsPair(std::move(buffer_ptr), ddr_channels_info); +} + +uint16_t DdrChannelsPair::descs_count() const +{ + assert(IS_FIT_IN_UINT16(m_buffer->descs_count())); + return static_cast(m_buffer->descs_count()); +} + +uint32_t DdrChannelsPair::descriptors_per_frame() const +{ + return (m_info.row_size / m_buffer->desc_page_size()) * m_info.total_buffers_per_frame; +} + +Expected DdrChannelsPair::read() +{ + const auto size = m_buffer->size(); + auto res = Buffer::create(size); + CHECK_EXPECTED(res); + + auto status = m_buffer->read(res->data(), size, 0); + CHECK_SUCCESS_AS_EXPECTED(status); + + return res.release(); +} + +const DdrChannelsInfo& DdrChannelsPair::info() const +{ + return m_info; +} + + +bool DdrChannelsPair::need_manual_credit_management() const +{ + // On scatter gather manual credit management is needed + return m_buffer->type() == vdma::VdmaBuffer::Type::SCATTER_GATHER; +} + +CONTROL_PROTOCOL__host_buffer_info_t DdrChannelsPair::get_host_buffer_info() const +{ + return m_buffer->get_host_buffer_info(m_info.row_size); +} + +Expected> DdrChannelsPair::create_sg_buffer(HailoRTDriver &driver, + uint32_t row_size, uint16_t buffered_rows) +{ + auto desc_sizes_pair = VdmaDescriptorList::get_desc_buffer_sizes_for_single_transfer(driver, + buffered_rows, buffered_rows, row_size); + CHECK_EXPECTED(desc_sizes_pair); + auto desc_page_size = desc_sizes_pair->first; + auto descs_count = desc_sizes_pair->second; + + auto buffer = vdma::SgBuffer::create(driver, descs_count, desc_page_size, + HailoRTDriver::DmaDirection::BOTH); + CHECK_EXPECTED(buffer); + + auto buffer_ptr = make_unique_nothrow(buffer.release()); + CHECK_NOT_NULL_AS_EXPECTED(buffer_ptr, HAILO_OUT_OF_HOST_MEMORY); + + return std::unique_ptr(std::move(buffer_ptr)); +} + +DdrChannelsPair::DdrChannelsPair(std::unique_ptr &&buffer, const DdrChannelsInfo &ddr_channels_info) : + m_buffer(std::move(buffer)), + m_info(ddr_channels_info) +{} + +Expected> DdrChannelsPair::create_ccb_buffer(HailoRTDriver &driver, + uint32_t row_size, uint16_t buffered_rows) +{ + // The first 12 channels in D2H CCB ("regular channels") requires that the amount of descriptors will be a power + // of 2. Altough the 4 last channels ("enhanced channels") don't have this requirements, we keep the code the same. + auto buffer_size = vdma::ContinuousBuffer::get_buffer_size_desc_power2(row_size * buffered_rows); + auto buffer = vdma::ContinuousBuffer::create(buffer_size, driver); + CHECK_EXPECTED(buffer); + + auto buffer_ptr = make_unique_nothrow(buffer.release()); + CHECK_NOT_NULL_AS_EXPECTED(buffer_ptr, HAILO_OUT_OF_HOST_MEMORY); + + return std::unique_ptr(std::move(buffer_ptr)); +} + +bool DdrChannelsPair::should_use_ccb(HailoRTDriver &driver) +{ + switch (driver.dma_type()) { + case HailoRTDriver::DmaType::PCIE: + return false; + case HailoRTDriver::DmaType::DRAM: + return true; + } + + + // Shouldn't reach here + assert(false); + return false; +} + +} /* namespace hailort */ diff --git a/hailort/libhailort/src/ddr_channels_pair.hpp b/hailort/libhailort/src/ddr_channels_pair.hpp new file mode 100644 index 0000000..ec87533 --- /dev/null +++ b/hailort/libhailort/src/ddr_channels_pair.hpp @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) +**/ +/** + * @file ddr_channels_pair.hpp + * @brief DDR channel pairs are pair of vdma channels used in the same context for skip-connection. + **/ + +#ifndef _HAILO_DDR_CHANNELS_PAIR_HPP_ +#define _HAILO_DDR_CHANNELS_PAIR_HPP_ + +#include "hailo/hailort.h" +#include "hailo/buffer.hpp" +#include "vdma/vdma_buffer.hpp" + +namespace hailort +{ + +struct DdrChannelsInfo +{ + vdma::ChannelId d2h_channel_id; + uint8_t d2h_stream_index; + vdma::ChannelId h2d_channel_id; + uint8_t h2d_stream_index; + uint16_t row_size; + uint16_t min_buffered_rows; + // total_buffers_per_frame not same as core_buffer_per frame. + //(In DDR core buffer per frame is 1). Used to calc total host descriptors_per_frame. + uint16_t total_buffers_per_frame; +}; + +class DdrChannelsPair final +{ +public: + static Expected create(HailoRTDriver &driver, const DdrChannelsInfo &ddr_channels_info); + + uint16_t descs_count() const; + uint32_t descriptors_per_frame() const; + Expected read(); + const DdrChannelsInfo & info() const; + + // Checks if the credits are automaticaly going from d2h channel to its h2d channel, or it needs to be done manually + // (Using a fw task). + bool need_manual_credit_management() const; + + CONTROL_PROTOCOL__host_buffer_info_t get_host_buffer_info() const; + +private: + DdrChannelsPair(std::unique_ptr &&buffer, const DdrChannelsInfo &ddr_channels_info); + + static Expected> create_sg_buffer(HailoRTDriver &driver, + uint32_t row_size, uint16_t buffered_rows); + static Expected> create_ccb_buffer(HailoRTDriver &driver, + uint32_t row_size, uint16_t buffered_rows); + + static bool should_use_ccb(HailoRTDriver &driver); + + std::unique_ptr m_buffer; + DdrChannelsInfo m_info; +}; + +} /* namespace hailort */ + +#endif /* _HAILO_DDR_CHANNELS_PAIR_HPP_ */ diff --git a/hailort/libhailort/src/device.cpp b/hailort/libhailort/src/device.cpp index 7c70aed..530e59e 100644 --- a/hailort/libhailort/src/device.cpp +++ b/hailort/libhailort/src/device.cpp @@ -48,6 +48,29 @@ Device::Device(Type type) : #endif } +Expected> Device::scan() +{ + // TODO: HRT-7530 support both CORE and PCIE + if (CoreDevice::is_loaded()) { + return std::vector{CoreDevice::DEVICE_ID}; + } + else { + auto pcie_device_infos = PcieDevice::scan(); + CHECK_EXPECTED(pcie_device_infos); + + std::vector results; + results.reserve(pcie_device_infos->size()); + + for (const auto pcie_device_info : pcie_device_infos.release()) { + auto device_id = pcie_device_info_to_string(pcie_device_info); + CHECK_EXPECTED(device_id); + results.emplace_back(device_id.release()); + } + + return results; + } +} + Expected> Device::scan_pcie() { return PcieDevice::scan(); @@ -65,6 +88,34 @@ Expected> Device::scan_eth_by_host_address( return EthernetDevice::scan_by_host_address(host_address, timeout); } +Expected> Device::create() +{ + auto device_ids = scan(); + CHECK_EXPECTED(device_ids, "Failed scan devices"); + CHECK_AS_EXPECTED(device_ids->size() == 1, HAILO_INVALID_OPERATION, + "Expected only 1 device on the system (found {}). Pass device_id to create a specific device", device_ids->size()); + + return Device::create(device_ids->at(0)); +} + +Expected> Device::create(const std::string &device_id) +{ + const bool DONT_LOG_ON_FAILURE = false; + if (CoreDevice::DEVICE_ID == device_id) { + return create_core(); + } + else if (auto pcie_info = PcieDevice::parse_pcie_device_info(device_id, DONT_LOG_ON_FAILURE)) { + return create_pcie(pcie_info.release()); + } + else if (auto eth_info = EthernetDevice::parse_eth_device_info(device_id, DONT_LOG_ON_FAILURE)) { + return create_eth(eth_info.release()); + } + else { + LOGGER__ERROR("Invalid device id {}", device_id); + return make_unexpected(HAILO_INVALID_ARGUMENT); + } +} + Expected> Device::create_pcie() { auto pcie_device = PcieDevice::create(); @@ -103,7 +154,8 @@ Expected> Device::create_eth(const std::string &ip_addr) Expected Device::parse_pcie_device_info(const std::string &device_info_str) { - return PcieDevice::parse_pcie_device_info(device_info_str); + const bool LOG_ON_FAILURE = true; + return PcieDevice::parse_pcie_device_info(device_info_str, LOG_ON_FAILURE); } Expected Device::pcie_device_info_to_string(const hailo_pcie_device_info_t &device_info) @@ -111,18 +163,22 @@ Expected Device::pcie_device_info_to_string(const hailo_pcie_device return PcieDevice::pcie_device_info_to_string(device_info); } -bool Device::is_core_driver_loaded() +Expected Device::get_device_type(const std::string &device_id) { - return CoreDevice::is_loaded(); -} - -Expected> Device::create_core_device() -{ - auto core_device = CoreDevice::create(); - CHECK_EXPECTED(core_device); - // Upcasting to Device unique_ptr (from CoreDevice unique_ptr) - auto device = std::unique_ptr(core_device.release()); - return device; + const bool DONT_LOG_ON_FAILURE = false; + if (CoreDevice::DEVICE_ID == device_id) { + return Type::CORE; + } + else if (auto pcie_info = PcieDevice::parse_pcie_device_info(device_id, DONT_LOG_ON_FAILURE)) { + return Type::PCIE; + } + else if (auto eth_info = EthernetDevice::parse_eth_device_info(device_id, DONT_LOG_ON_FAILURE)) { + return Type::ETH; + } + else { + LOGGER__ERROR("Invalid device id {}", device_id); + return make_unexpected(HAILO_INVALID_ARGUMENT); + } } uint32_t Device::get_control_sequence() @@ -469,4 +525,13 @@ hailo_status Device::set_context_action_list_timestamp_batch(uint16_t batch_inde return Control::config_context_switch_timestamp(*this, batch_index, ENABLE_USER_CONFIG); } +Expected> Device::create_core() +{ + auto core_device = CoreDevice::create(); + CHECK_EXPECTED(core_device); + // Upcasting to Device unique_ptr (from CoreDevice unique_ptr) + auto device = std::unique_ptr(core_device.release()); + return device; +} + } /* namespace hailort */ diff --git a/hailort/libhailort/src/device_internal.cpp b/hailort/libhailort/src/device_internal.cpp index 4a6d87f..1b0a3a4 100644 --- a/hailort/libhailort/src/device_internal.cpp +++ b/hailort/libhailort/src/device_internal.cpp @@ -160,7 +160,7 @@ Expected DeviceBase::get_fw_type() const auto architecture = get_architecture(); CHECK_EXPECTED(architecture); - if (architecture.value() == HAILO_ARCH_HAILO8_B0) { + if ((architecture.value() == HAILO_ARCH_HAILO8) || (architecture.value() == HAILO_ARCH_HAILO8L)) { firmware_type = FIRMWARE_TYPE_HAILO8; } else if (architecture.value() == HAILO_ARCH_MERCURY_CA || architecture.value() == HAILO_ARCH_MERCURY_VPU) { @@ -595,7 +595,7 @@ hailo_status DeviceBase::check_hef_is_compatible(Hef &hef) } // TODO: MSW-227 check clock rate for mercury as well. - if (HAILO_ARCH_HAILO8_B0 == device_arch.value()) { + if ((HAILO_ARCH_HAILO8 == device_arch.value()) || (HAILO_ARCH_HAILO8L == device_arch.value())) { auto extended_device_info_expected = Control::get_extended_device_information(*this); CHECK_EXPECTED_AS_STATUS(extended_device_info_expected, "Can't get device extended info"); hailo_extended_device_information_t extended_device_information = extended_device_info_expected.release(); @@ -603,6 +603,13 @@ hailo_status DeviceBase::check_hef_is_compatible(Hef &hef) hef.pimpl->get_device_arch()); } + if ((ProtoHEFHwArch::PROTO__HW_ARCH__HAILO8L == hef.pimpl->get_device_arch()) && (HAILO_ARCH_HAILO8 == device_arch.value())) { + LOGGER__WARNING( + "HEF was compiled for Hailo8L device, while the device itself is Hailo8. " \ + "This will result in lower performance."); + } + + return HAILO_SUCCESS; } @@ -689,8 +696,10 @@ hailo_status DeviceBase::validate_fw_version_for_platform(const hailo_device_ide bool DeviceBase::is_hef_compatible(hailo_device_architecture_t device_arch, ProtoHEFHwArch hef_arch) { switch (device_arch) { - case HAILO_ARCH_HAILO8_B0: - return (hef_arch == PROTO__HW_ARCH__HAILO8P) || (hef_arch == PROTO__HW_ARCH__HAILO8R); + case HAILO_ARCH_HAILO8: + return (hef_arch == PROTO__HW_ARCH__HAILO8P) || (hef_arch == PROTO__HW_ARCH__HAILO8R) || (hef_arch == PROTO__HW_ARCH__HAILO8L); + case HAILO_ARCH_HAILO8L: + return (hef_arch == PROTO__HW_ARCH__HAILO8L); case HAILO_ARCH_MERCURY_CA: case HAILO_ARCH_MERCURY_VPU: return (hef_arch == PROTO__HW_ARCH__MERCURY) || (hef_arch == PROTO__HW_ARCH__GINGER) || @@ -702,7 +711,7 @@ bool DeviceBase::is_hef_compatible(hailo_device_architecture_t device_arch, Prot void DeviceBase::check_clock_rate_for_hailo8(uint32_t clock_rate, ProtoHEFHwArch hef_hw_arch) { - uint32_t expected_clock_rate = (hef_hw_arch == ProtoHEFHwArch::PROTO__HW_ARCH__HAILO8P) ? HAILO8_CLOCK_RATE : HAILO8R_CLOCK_RATE; + uint32_t expected_clock_rate = (hef_hw_arch == ProtoHEFHwArch::PROTO__HW_ARCH__HAILO8R) ? HAILO8R_CLOCK_RATE : HAILO8_CLOCK_RATE; if (expected_clock_rate != clock_rate) { LOGGER__WARNING( "HEF was compiled assuming clock rate of {} MHz, while the device clock rate is {} MHz. " \ diff --git a/hailort/libhailort/src/device_internal.hpp b/hailort/libhailort/src/device_internal.hpp index 344f8eb..c0b0668 100644 --- a/hailort/libhailort/src/device_internal.hpp +++ b/hailort/libhailort/src/device_internal.hpp @@ -9,11 +9,11 @@ * Hence, the hierarchy is as follows: * * Device (External "interface") - * └── BaseDevice (Base classes) - * ├── VdmaDevice - * │ ├── PcieDevice - * │ └── CoreDevice - * └── EthernetDevice + * |-- BaseDevice (Base classes) + * |-- VdmaDevice + * | |-- PcieDevice + * | |-- CoreDevice + * |-- EthernetDevice **/ #ifndef _HAILO_DEVICE_INTERNAL_HPP_ @@ -27,6 +27,7 @@ #include "firmware_header_utils.h" #include "control_protocol.h" #include "context_switch/config_manager.hpp" +#include "hef_internal.hpp" #include @@ -108,6 +109,8 @@ protected: std::shared_ptr m_notif_fetch_thread_params; private: + friend class VDeviceBase; + static hailo_status fw_notification_id_to_hailo(D2H_EVENT_ID_t fw_notification_id, hailo_notification_id_t* hailo_notification_id); static hailo_status validate_binary_version_for_platform(firmware_version_t *new_binary_version, diff --git a/hailort/libhailort/src/eth_device.cpp b/hailort/libhailort/src/eth_device.cpp index 441d066..66a74b0 100644 --- a/hailort/libhailort/src/eth_device.cpp +++ b/hailort/libhailort/src/eth_device.cpp @@ -100,25 +100,10 @@ Expected> EthernetDevice::create(const hailo_eth Expected> EthernetDevice::create(const std::string &ip_addr) { - auto status = HAILO_UNINITIALIZED; - hailo_eth_device_info_t device_info{}; - - device_info.host_address.sin_family = AF_INET; - device_info.host_address.sin_port = HAILO_ETH_PORT_ANY; - - status = Socket::pton(AF_INET, HAILO_ETH_ADDRESS_ANY, &(device_info.host_address.sin_addr)); - CHECK_SUCCESS_AS_EXPECTED(status); - - device_info.device_address.sin_family = AF_INET; - device_info.device_address.sin_port = HAILO_DEFAULT_ETH_CONTROL_PORT; - status = Socket::pton(AF_INET, ip_addr.c_str(), &(device_info.device_address.sin_addr)); - CHECK_SUCCESS_AS_EXPECTED(status); - - device_info.timeout_millis = HAILO_DEFAULT_ETH_SCAN_TIMEOUT_MS; - device_info.max_number_of_attempts = HAILO_DEFAULT_ETH_MAX_NUMBER_OF_RETRIES; - device_info.max_payload_size = HAILO_DEFAULT_ETH_MAX_PAYLOAD_SIZE; - - return create(device_info); + const bool LOG_ON_FAILURE = true; + auto device_info = parse_eth_device_info(ip_addr, LOG_ON_FAILURE); + CHECK_EXPECTED(device_info, "Failed to parse ip address {}", ip_addr); + return create(device_info.release()); } EthernetDevice::EthernetDevice(const hailo_eth_device_info_t &device_info, Udp &&control_udp, hailo_status &status) : @@ -238,7 +223,7 @@ hailo_status get_udp_broadcast_params(const char *host_address, struct in_addr & assert(nullptr != host_address); auto status = Socket::pton(AF_INET, host_address, &interface_ip_address); - CHECK_SUCCESS(status); + CHECK_SUCCESS(status, "Invalid host ip address {}", host_address); status = Socket::pton(AF_INET, ETH_BROADCAST_IP, &broadcast_ip_address); CHECK_SUCCESS(status); @@ -278,6 +263,34 @@ Expected> EthernetDevice::scan_by_host_addr return eth_device__receive_responses(*udp_broadcast); } +Expected EthernetDevice::parse_eth_device_info(const std::string &ip_addr, + bool log_on_failure) +{ + hailo_eth_device_info_t device_info{}; + + device_info.host_address.sin_family = AF_INET; + device_info.host_address.sin_port = HAILO_ETH_PORT_ANY; + + auto status = Socket::pton(AF_INET, HAILO_ETH_ADDRESS_ANY, &(device_info.host_address.sin_addr)); + CHECK_SUCCESS_AS_EXPECTED(status); + + device_info.device_address.sin_family = AF_INET; + device_info.device_address.sin_port = HAILO_DEFAULT_ETH_CONTROL_PORT; + status = Socket::pton(AF_INET, ip_addr.c_str(), &(device_info.device_address.sin_addr)); + if (status != HAILO_SUCCESS) { + if (log_on_failure) { + LOGGER__ERROR("Invalid ip address {}", ip_addr); + } + return make_unexpected(status); + } + + device_info.timeout_millis = HAILO_DEFAULT_ETH_SCAN_TIMEOUT_MS; + device_info.max_number_of_attempts = HAILO_DEFAULT_ETH_MAX_NUMBER_OF_RETRIES; + device_info.max_payload_size = HAILO_DEFAULT_ETH_MAX_PAYLOAD_SIZE; + + return device_info; +} + void EthernetDevice::increment_control_sequence() { m_control_sequence = (m_control_sequence + 1) % CONTROL__MAX_SEQUENCE; diff --git a/hailort/libhailort/src/eth_device.hpp b/hailort/libhailort/src/eth_device.hpp index 2801e70..662b7f0 100644 --- a/hailort/libhailort/src/eth_device.hpp +++ b/hailort/libhailort/src/eth_device.hpp @@ -50,6 +50,7 @@ public: std::chrono::milliseconds timeout); static Expected> scan_by_host_address(const std::string &host_address, std::chrono::milliseconds timeout); + static Expected parse_eth_device_info(const std::string &ip_addr, bool log_on_failure); static Expected> create(const hailo_eth_device_info_t &device_info); static Expected> create(const std::string &ip_addr); diff --git a/hailort/libhailort/src/eth_stream.cpp b/hailort/libhailort/src/eth_stream.cpp index 8b7bd10..30b0e53 100644 --- a/hailort/libhailort/src/eth_stream.cpp +++ b/hailort/libhailort/src/eth_stream.cpp @@ -370,7 +370,7 @@ Expected> EthernetInputStream::create(Devic auto eth_device = reinterpret_cast(&device); std::unique_ptr local_stream; - auto stream_index = edge_layer.index; + auto stream_index = edge_layer.stream_index; auto udp = eth_stream__create_udp(eth_device, params.host_address, stream_index, params.device_port, true); CHECK_EXPECTED(udp); @@ -398,7 +398,12 @@ Expected> EthernetInputStream::create(Devic auto device_architecture = eth_device->get_architecture(); CHECK_EXPECTED(device_architecture); - local_stream->configuration.use_dataflow_padding = (HAILO_ARCH_HAILO8_B0 == device_architecture.value()); + if ((HAILO_ARCH_HAILO8 == device_architecture.value()) || (HAILO_ARCH_HAILO8L == device_architecture.value())) { + local_stream->configuration.use_dataflow_padding = true; + } + else { + local_stream->configuration.use_dataflow_padding = false; + } local_stream->set_max_payload_size(params.max_payload_size); @@ -700,7 +705,7 @@ Expected> EthernetOutputStream::create(Dev // TODO: try to avoid cast auto eth_device = reinterpret_cast(&device); - const auto stream_index = edge_layer.index; + const auto stream_index = edge_layer.stream_index; auto udp = eth_stream__create_udp(eth_device, params.host_address, stream_index, params.device_port, false); CHECK_EXPECTED(udp); local_stream = std::unique_ptr(new (std::nothrow) EthernetOutputStream(device, diff --git a/hailort/libhailort/src/hailort.cpp b/hailort/libhailort/src/hailort.cpp index 26aff59..ed3ce33 100644 --- a/hailort/libhailort/src/hailort.cpp +++ b/hailort/libhailort/src/hailort.cpp @@ -29,6 +29,8 @@ #include "sensor_config_utils.hpp" #include "common/compiler_extensions_compat.hpp" #include "hailort_logger.hpp" +#include "shared_resource_manager.hpp" +#include "vdevice_internal.hpp" #include @@ -38,6 +40,7 @@ COMPAT__INITIALIZER(hailort__initialize_logger) { // Init logger singleton if compiling only HailoRT (void) HailoRTLogger::get_instance(); + (void) SharedResourceManager::get_instance(); } hailo_status hailo_get_library_version(hailo_version_t *version) @@ -118,6 +121,44 @@ hailo_status hailo_get_extended_device_information(hailo_device device, hailo_ex return HAILO_SUCCESS; } +hailo_status hailo_scan_devices(hailo_scan_devices_params_t *params, hailo_device_id_t *device_ids, + size_t *device_ids_length) +{ + CHECK_ARG_NOT_NULL(device_ids); + CHECK_ARG_NOT_NULL(device_ids_length); + CHECK(params == nullptr, HAILO_INVALID_ARGUMENT, "Passing scan params is not allowed"); + + auto device_ids_vector = Device::scan(); + CHECK_EXPECTED_AS_STATUS(device_ids_vector); + + if (device_ids_vector->size() > *device_ids_length) { + LOGGER__ERROR("Too many devices detected. devices count: {}, scan_results_length: {}", + device_ids_vector->size(), *device_ids_length); + *device_ids_length = device_ids_vector->size(); + return HAILO_INSUFFICIENT_BUFFER; + } + *device_ids_length = device_ids_vector->size(); + + for (size_t i = 0; i < device_ids_vector->size(); i++) { + auto device_id = HailoRTCommon::to_device_id(device_ids_vector.value()[i]); + CHECK_EXPECTED_AS_STATUS(device_id); + device_ids[i] = device_id.release(); + } + + return HAILO_SUCCESS; +} + +hailo_status hailo_create_device_by_id(const hailo_device_id_t *device_id, hailo_device *device_out) +{ + CHECK_ARG_NOT_NULL(device_out); + + auto device = (device_id == nullptr) ? Device::create() : Device::create(device_id->id); + CHECK_EXPECTED_AS_STATUS(device, "Failed creating pcie device"); + *device_out = reinterpret_cast(device.release().release()); + + return HAILO_SUCCESS; +} + // TODO: Fill pcie_device_infos_length items into pcie_device_infos, // even if 'scan_results->size() > pcie_device_infos_length' (HRT-3163) hailo_status hailo_scan_pcie_devices( @@ -143,7 +184,7 @@ hailo_status hailo_parse_pcie_device_info(const char *device_info_str, CHECK_ARG_NOT_NULL(device_info_str); CHECK_ARG_NOT_NULL(device_info); - auto local_device_info = PcieDevice::parse_pcie_device_info(std::string(device_info_str)); + auto local_device_info = Device::parse_pcie_device_info(std::string(device_info_str)); CHECK_EXPECTED_AS_STATUS(local_device_info); *device_info = local_device_info.value(); @@ -168,6 +209,31 @@ hailo_status hailo_release_device(hailo_device device_ptr) return HAILO_SUCCESS; } +hailo_status hailo_device_get_type_by_device_id(const hailo_device_id_t *device_id, + hailo_device_type_t *device_type) +{ + CHECK_ARG_NOT_NULL(device_id); + const auto local_device_type = Device::get_device_type(device_id->id); + CHECK_EXPECTED_AS_STATUS(local_device_type); + + switch (local_device_type.value()) { + case Device::Type::PCIE: + *device_type = HAILO_DEVICE_TYPE_PCIE; + break; + case Device::Type::ETH: + *device_type = HAILO_DEVICE_TYPE_ETH; + break; + case Device::Type::CORE: + *device_type = HAILO_DEVICE_TYPE_CORE; + break; + default: + LOGGER__ERROR("Internal failure, invalid device type returned"); + return HAILO_INTERNAL_FAILURE; + } + + return HAILO_SUCCESS; +} + hailo_status hailo_set_fw_logger(hailo_device device, hailo_fw_logger_level_t level, uint32_t interface_mask) { CHECK_ARG_NOT_NULL(device); @@ -241,11 +307,11 @@ hailo_status hailo_get_device_id(hailo_device device, hailo_device_id_t *id) { CHECK_ARG_NOT_NULL(device); CHECK_ARG_NOT_NULL(id); - auto device_id = (reinterpret_cast(device))->get_dev_id(); - CHECK(strnlen(device_id, HAILO_MAX_DEVICE_ID_LENGTH) < HAILO_MAX_DEVICE_ID_LENGTH, HAILO_INTERNAL_FAILURE, - "Device '{}' has a too long name (max is HAILO_MAX_DEVICE_ID_LENGTH)", device_id); - strncpy(id->id, device_id, HAILO_MAX_DEVICE_ID_LENGTH - 1); - id->id[HAILO_MAX_DEVICE_ID_LENGTH - 1] = 0; + + auto id_expected = HailoRTCommon::to_device_id(reinterpret_cast(device)->get_dev_id()); + CHECK_EXPECTED_AS_STATUS(id_expected); + *id = id_expected.release(); + return HAILO_SUCCESS; } @@ -1806,11 +1872,14 @@ hailo_status hailo_get_physical_devices(hailo_vdevice vdevice, hailo_device *dev hailo_status hailo_get_physical_devices_infos(hailo_vdevice vdevice, hailo_pcie_device_info_t *devices_infos, size_t *number_of_devices) { + CHECK_ARG_NOT_NULL(vdevice); CHECK_ARG_NOT_NULL(devices_infos); CHECK_ARG_NOT_NULL(number_of_devices); +IGNORE_DEPRECATION_WARNINGS_BEGIN auto phys_devices_infos = (reinterpret_cast(vdevice))->get_physical_devices_infos(); CHECK_EXPECTED_AS_STATUS(phys_devices_infos); +IGNORE_DEPRECATION_WARNINGS_END if (*number_of_devices < phys_devices_infos->size()) { LOGGER__ERROR("Can't return all physical devices infos. There are {} physical devices infos under the vdevice, but output array is of size {}", @@ -1827,6 +1896,33 @@ hailo_status hailo_get_physical_devices_infos(hailo_vdevice vdevice, hailo_pcie_ return HAILO_SUCCESS; } +hailo_status hailo_vdevice_get_physical_devices_ids(hailo_vdevice vdevice, hailo_device_id_t *devices_ids, + size_t *number_of_devices) +{ + CHECK_ARG_NOT_NULL(vdevice); + CHECK_ARG_NOT_NULL(devices_ids); + CHECK_ARG_NOT_NULL(number_of_devices); + + const auto phys_devices_ids = (reinterpret_cast(vdevice))->get_physical_devices_ids(); + CHECK_EXPECTED_AS_STATUS(phys_devices_ids); + + if (*number_of_devices < phys_devices_ids->size()) { + LOGGER__ERROR("Can't return all physical devices ids. There are {} physical devices ids under the vdevice, but output array is of size {}", + phys_devices_ids->size(), *number_of_devices); + *number_of_devices = phys_devices_ids->size(); + return HAILO_INSUFFICIENT_BUFFER; + } + *number_of_devices = phys_devices_ids->size(); + + for (size_t i = 0; i < phys_devices_ids->size(); i++) { + auto id_expected = HailoRTCommon::to_device_id(phys_devices_ids.value()[i]); + CHECK_EXPECTED_AS_STATUS(id_expected); + devices_ids[i] = id_expected.release(); + } + + return HAILO_SUCCESS; +} + hailo_status hailo_release_vdevice(hailo_vdevice vdevice_ptr) { CHECK_ARG_NOT_NULL(vdevice_ptr); diff --git a/hailort/libhailort/src/hailort_common.cpp b/hailort/libhailort/src/hailort_common.cpp index f6bf80d..7f59c47 100644 --- a/hailort/libhailort/src/hailort_common.cpp +++ b/hailort/libhailort/src/hailort_common.cpp @@ -8,6 +8,7 @@ **/ #include "hailo/hailort_common.hpp" +#include "common/utils.hpp" namespace hailort { @@ -19,4 +20,29 @@ const size_t HailoRTCommon::HW_DATA_ALIGNMENT; const uint64_t HailoRTCommon::NMS_DELIMITER; const uint64_t HailoRTCommon::NMS_DUMMY_DELIMITER; +Expected HailoRTCommon::to_device_id(const std::string &device_id) +{ + hailo_device_id_t id = {}; + static constexpr size_t id_size = ARRAY_ENTRIES(id.id); + + CHECK_AS_EXPECTED(device_id.size() < id_size, HAILO_INTERNAL_FAILURE, + "Device '{}' has a too long id (max is {})", device_id, id_size); + + strncpy(id.id, device_id.c_str(), id_size - 1); + id.id[id_size - 1] = 0; + return id; +} + +Expected> HailoRTCommon::to_device_ids_vector(const std::vector &device_ids_str) +{ + std::vector device_ids_vector; + device_ids_vector.reserve(device_ids_str.size()); + for (const auto &device_id_str : device_ids_str) { + auto device_id_struct = to_device_id(device_id_str); + CHECK_EXPECTED(device_id_struct); + device_ids_vector.push_back(device_id_struct.release()); + } + return device_ids_vector; +} + } /* namespace hailort */ diff --git a/hailort/libhailort/src/hailort_defaults.hpp b/hailort/libhailort/src/hailort_defaults.hpp index ad428b7..0787fad 100644 --- a/hailort/libhailort/src/hailort_defaults.hpp +++ b/hailort/libhailort/src/hailort_defaults.hpp @@ -12,7 +12,9 @@ #include #include "hailo/expected.hpp" #include "hailo/network_group.hpp" +#include "hailo/hef.hpp" #include "common/logger_macros.hpp" +#include "common/utils.hpp" namespace hailort { @@ -31,7 +33,11 @@ constexpr hailo_format_order_t DEFAULT_FORMAT_ORDER_MAP[] = { HAILO_FORMAT_ORDER_HAILO_NMS, // HAILO_FORMAT_ORDER_HAILO_NMS, HAILO_FORMAT_ORDER_NHWC, // HAILO_FORMAT_ORDER_RGB888, HAILO_FORMAT_ORDER_NCHW, // HAILO_FORMAT_ORDER_NCHW, - HAILO_FORMAT_ORDER_YUY2 // HAILO_FORMAT_ORDER_YUY2 + HAILO_FORMAT_ORDER_YUY2, // HAILO_FORMAT_ORDER_YUY2, + HAILO_FORMAT_ORDER_MAX_ENUM, // Not used in device side - HAILO_FORMAT_ORDER_NV12, + HAILO_FORMAT_ORDER_MAX_ENUM, // Not used in device side - HAILO_FORMAT_ORDER_NV21, + HAILO_FORMAT_ORDER_NV12, // HAILO_FORMAT_ORDER_HAILO_YYUV, + HAILO_FORMAT_ORDER_NV21 // HAILO_FORMAT_ORDER_HAILO_YYVU }; constexpr hailo_format_order_t DEFAULT_FORMAT_ARGMAX_ORDER_MAP[] = { @@ -48,7 +54,11 @@ constexpr hailo_format_order_t DEFAULT_FORMAT_ARGMAX_ORDER_MAP[] = { HAILO_FORMAT_ORDER_HAILO_NMS, // HAILO_FORMAT_ORDER_HAILO_NMS, HAILO_FORMAT_ORDER_NHW, // HAILO_FORMAT_ORDER_RGB888, HAILO_FORMAT_ORDER_NHW, // HAILO_FORMAT_ORDER_NCHW, - HAILO_FORMAT_ORDER_YUY2 // HAILO_FORMAT_ORDER_YUY2 + HAILO_FORMAT_ORDER_YUY2, // HAILO_FORMAT_ORDER_YUY2, + HAILO_FORMAT_ORDER_MAX_ENUM, // Not used in device side - HAILO_FORMAT_ORDER_NV12, + HAILO_FORMAT_ORDER_MAX_ENUM, // Not used in device side - HAILO_FORMAT_ORDER_NV21, + HAILO_FORMAT_ORDER_NV12, // HAILO_FORMAT_ORDER_HAILO_YYUV, + HAILO_FORMAT_ORDER_NV21 // HAILO_FORMAT_ORDER_HAILO_YYVU }; @@ -94,6 +104,12 @@ public: case 11: return std::move(HAILO_FORMAT_ORDER_YUY2); break; + case 14: + return std::move(HAILO_FORMAT_ORDER_HAILO_YYUV); + break; + case 15: + return std::move(HAILO_FORMAT_ORDER_HAILO_YYVU); + break; default: LOGGER__ERROR("Invalid allocator_format_order ({})", allocator_format_order); return make_unexpected(HAILO_INTERNAL_FAILURE); @@ -344,7 +360,14 @@ public: { hailo_vdevice_params_t params = {}; params.device_count = HAILO_DEFAULT_DEVICE_COUNT; + +IGNORE_DEPRECATION_WARNINGS_BEGIN params.device_infos = nullptr; +IGNORE_DEPRECATION_WARNINGS_END + + params.device_ids = nullptr; + params.group_id = HAILO_DEFAULT_VDEVICE_GROUP_ID; + params.multi_process_service = false; return params; } }; diff --git a/hailort/libhailort/src/hailort_logger.cpp b/hailort/libhailort/src/hailort_logger.cpp index 6a5d59c..6bd7268 100644 --- a/hailort/libhailort/src/hailort_logger.cpp +++ b/hailort/libhailort/src/hailort_logger.cpp @@ -34,8 +34,12 @@ namespace hailort #define HAILORT_FILE_LOGGER_PATTERN "[%Y-%m-%d %X.%e] [%n] [%l] [%s:%#] [%!] %v" //File logger will print: [timestamp] [hailort] [log level] [source file:line number] [function name] msg #define HAILORT_ANDROID_LOGGER_PATTERN "%v" // Android logger will print only message (additional info are built-in) +#define HAILORT_LOGGER_PATH "HAILORT_LOGGER_PATH" + #ifdef _WIN32 -bool is_curr_dir_accesible() +#define PATH_SEPARATOR "\\" + +bool is_dir_accesible(const std::string &dir) { // The code is based on examples from: https://cpp.hotexamples.com/examples/-/-/AccessCheck/cpp-accesscheck-function-examples.html bool return_val = false; @@ -55,7 +59,7 @@ bool is_curr_dir_accesible() BOOL access_status = FALSE; // Retrieves a copy of the security descriptor for current dir. - DWORD result = GetNamedSecurityInfo(".", SE_FILE_OBJECT, security_Info, NULL, NULL, NULL, NULL, &security_desc); + DWORD result = GetNamedSecurityInfo(dir.c_str(), SE_FILE_OBJECT, security_Info, NULL, NULL, NULL, NULL, &security_desc); if (result != ERROR_SUCCESS) { std::cerr << "Failed to get security information for local directory with error = " << result << std::endl; return_val = false; @@ -102,9 +106,11 @@ l_exit: return return_val; } #else -bool is_curr_dir_accesible() +#define PATH_SEPARATOR "/" + +bool is_dir_accesible(const std::string &dir) { - auto ret = access(".", W_OK); + auto ret = access(dir.c_str(), W_OK); if (ret == 0) { return true; } @@ -120,12 +126,14 @@ bool is_curr_dir_accesible() std::shared_ptr HailoRTLogger::create_file_sink() { - // TODO: HRT-4937 - Consider creating the hailort.log file in predetermined directory or as a part of SYSLOG - if (is_curr_dir_accesible()) { - return make_shared_nothrow(HAILORT_LOGGER_FILENAME, MAX_LOG_FILE_SIZE, + auto dir_env_var = std::getenv(HAILORT_LOGGER_PATH); + std::string dir = ((nullptr == dir_env_var) || (0 == std::strlen(dir_env_var))) ? "." : dir_env_var; // defaulted to current directory + if (dir == "NONE") { + return make_shared_nothrow(); + } else if (is_dir_accesible(dir)) { + return make_shared_nothrow((dir + PATH_SEPARATOR + HAILORT_LOGGER_FILENAME), MAX_LOG_FILE_SIZE, HAILORT_MAX_NUMBER_OF_LOG_FILES); - } - else { + } else { std::cerr << "HailoRT warning: Cannot create HailoRT log file! Please check the current directory's write permissions." << std::endl; // Create null sink instead (Will throw away its log) return make_shared_nothrow(); diff --git a/hailort/libhailort/src/hailort_rpc_client.cpp b/hailort/libhailort/src/hailort_rpc_client.cpp new file mode 100644 index 0000000..4dbd731 --- /dev/null +++ b/hailort/libhailort/src/hailort_rpc_client.cpp @@ -0,0 +1,801 @@ +/** + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + **/ +/** + * @file hailort_rpc_client.cpp + * @brief Implementation of the hailort rpc client + **/ + +#include "hailort_rpc_client.hpp" +#include "common/utils.hpp" +#include "hef_internal.hpp" + +#include + + +namespace hailort +{ + +hailo_status HailoRtRpcClient::client_keep_alive(uint32_t process_id) +{ + keepalive_Request request; + request.set_process_id(process_id); + empty reply; + grpc::ClientContext context; + grpc::Status status = m_stub->client_keep_alive(&context, request, &reply); + CHECK_GRPC_STATUS(status); + return HAILO_SUCCESS; +} + +Expected HailoRtRpcClient::get_service_version() +{ + get_service_version_Request request; + get_service_version_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->get_service_version(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + auto version_proto = reply.hailo_version(); + hailo_version_t service_version = {version_proto.major_version(), version_proto.minor_version(), version_proto.revision_version()}; + return service_version; +} + +Expected HailoRtRpcClient::VDevice_create(const hailo_vdevice_params_t ¶ms, uint32_t pid) { + VDevice_create_Request request; + request.set_pid(pid); + auto proto_vdevice_params = request.mutable_hailo_vdevice_params(); + proto_vdevice_params->set_device_count(params.device_count); + auto ids = proto_vdevice_params->mutable_device_ids(); + if (params.device_ids != nullptr) { + for (size_t i = 0; i < params.device_count; ++i) { + ids->Add(std::string(params.device_ids[i].id)); + } + } + proto_vdevice_params->set_scheduling_algorithm(params.scheduling_algorithm); + proto_vdevice_params->set_group_id(params.group_id == nullptr ? "" : std::string(params.group_id)); + + VDevice_create_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->VDevice_create(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + return reply.handle(); +} + +hailo_status HailoRtRpcClient::VDevice_release(uint32_t handle) +{ + Release_Request request; + request.set_handle(handle); + + Release_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->VDevice_release(&context, request, &reply); + CHECK_GRPC_STATUS(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS(static_cast(reply.status())); + return HAILO_SUCCESS; +} + +Expected> HailoRtRpcClient::InputVStreams_create(uint32_t net_group_handle, + const std::map &inputs_params, uint32_t pid) +{ + VStream_create_Request request; + request.set_net_group(net_group_handle); + request.set_pid(pid); + auto proto_vstreams_params = request.mutable_vstreams_params(); + for (const auto &name_params_pair : inputs_params) { + NamedVStreamParams proto_name_param_pair; + auto vstream_params = name_params_pair.second; + + proto_name_param_pair.set_name(name_params_pair.first); + auto proto_vstream_param = proto_name_param_pair.mutable_params(); + + auto proto_user_buffer_format = proto_vstream_param->mutable_user_buffer_format(); + auto user_buffer_format = vstream_params.user_buffer_format; + proto_user_buffer_format->set_type(user_buffer_format.type); + proto_user_buffer_format->set_order(user_buffer_format.order); + proto_user_buffer_format->set_flags(user_buffer_format.flags); + + proto_vstream_param->set_timeout_ms(vstream_params.timeout_ms); + proto_vstream_param->set_queue_size(vstream_params.queue_size); + + proto_vstream_param->set_vstream_stats_flags(vstream_params.vstream_stats_flags); + proto_vstream_param->set_pipeline_elements_stats_flags(vstream_params.vstream_stats_flags); + + proto_vstreams_params->Add(std::move(proto_name_param_pair)); + } + + VStreams_create_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->InputVStreams_create(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + std::vector input_vstreams_handles; + input_vstreams_handles.reserve(reply.handles_size()); + for (auto &handle : *reply.mutable_handles()) { + input_vstreams_handles.push_back(handle); + } + return input_vstreams_handles; +} + +hailo_status HailoRtRpcClient::InputVStream_release(uint32_t handle) +{ + Release_Request request; + request.set_handle(handle); + + Release_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->InputVStream_release(&context, request, &reply); + CHECK_GRPC_STATUS(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS(static_cast(reply.status())); + return HAILO_SUCCESS; +} + +Expected> HailoRtRpcClient::OutputVStreams_create(uint32_t net_group_handle, + const std::map &output_params, uint32_t pid) +{ + VStream_create_Request request; + request.set_net_group(net_group_handle); + request.set_pid(pid); + auto proto_vstreams_params = request.mutable_vstreams_params(); + for (const auto &name_params_pair : output_params) { + NamedVStreamParams proto_name_param_pair; + auto vstream_params = name_params_pair.second; + + proto_name_param_pair.set_name(name_params_pair.first); + auto proto_vstream_param = proto_name_param_pair.mutable_params(); + + auto proto_user_buffer_format = proto_vstream_param->mutable_user_buffer_format(); + auto user_buffer_format = vstream_params.user_buffer_format; + proto_user_buffer_format->set_type(user_buffer_format.type); + proto_user_buffer_format->set_order(user_buffer_format.order); + proto_user_buffer_format->set_flags(user_buffer_format.flags); + + proto_vstream_param->set_timeout_ms(vstream_params.timeout_ms); + proto_vstream_param->set_queue_size(vstream_params.queue_size); + + proto_vstream_param->set_vstream_stats_flags(vstream_params.vstream_stats_flags); + proto_vstream_param->set_pipeline_elements_stats_flags(vstream_params.vstream_stats_flags); + + proto_vstreams_params->Add(std::move(proto_name_param_pair)); + } + + VStreams_create_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->OutputVStreams_create(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + std::vector output_vstreams_handles; + output_vstreams_handles.reserve(reply.handles_size()); + for (auto &handle : *reply.mutable_handles()) { + output_vstreams_handles.push_back(handle); + } + return output_vstreams_handles; +} + +hailo_status HailoRtRpcClient::OutputVStream_release(uint32_t handle) +{ + Release_Request request; + request.set_handle(handle); + + Release_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->OutputVStream_release(&context, request, &reply); + CHECK_GRPC_STATUS(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS(static_cast(reply.status())); + return HAILO_SUCCESS; +} + +Expected> HailoRtRpcClient::VDevice_configure(uint32_t vdevice_handle, const Hef &hef, + uint32_t pid, const NetworkGroupsParamsMap &configure_params) +{ + VDevice_configure_Request request; + request.set_handle(vdevice_handle); + request.set_pid(pid); + auto hef_memview = hef.pimpl->get_hef_memview(); + request.set_hef(hef_memview.data(), hef_memview.size()); + + // Serialize NetworkGroupsParamsMap + for (const auto &name_params_pair : configure_params) { + auto proto_net_params = request.add_configure_params_map(); + proto_net_params->set_name(name_params_pair.first); + + auto net_configure_params = name_params_pair.second; + auto proto_network_configure_params = proto_net_params->mutable_params(); + proto_network_configure_params->set_batch_size(net_configure_params.batch_size); + proto_network_configure_params->set_power_mode(net_configure_params.power_mode); + proto_network_configure_params->set_latency(net_configure_params.latency); + + // Init stream params map + for (const auto &name_stream_params_pair : net_configure_params.stream_params_by_name) { + auto proto_name_streams_params = proto_network_configure_params->add_stream_params_map(); + proto_name_streams_params->set_name(name_stream_params_pair.first); + + auto proto_stream_params = proto_name_streams_params->mutable_params(); + auto stream_params = name_stream_params_pair.second; + proto_stream_params->set_stream_interface(stream_params.stream_interface); + proto_stream_params->set_direction(stream_params.direction); + } + + // Init network params map + for (const auto &name_network_params_pair : net_configure_params.network_params_by_name) { + auto proto_name_network_params = proto_network_configure_params->add_network_params_map(); + proto_name_network_params->set_name(name_network_params_pair.first); + + auto proto_network_params = proto_name_network_params->mutable_params(); + auto network_params = name_network_params_pair.second; + proto_network_params->set_batch_size(network_params.batch_size); + } + } + + VDevice_configure_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->VDevice_configure(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + + std::vector networks_handles(reply.networks_handles().begin(), reply.networks_handles().end()); + return networks_handles; +} + +Expected> HailoRtRpcClient::VDevice_get_physical_devices_ids(uint32_t handle) +{ + VDevice_get_physical_devices_ids_Request request; + request.set_handle(handle); + + VDevice_get_physical_devices_ids_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->VDevice_get_physical_devices_ids(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + std::vector result; + for (auto &device_id_proto : reply.devices_ids()) { + result.push_back(device_id_proto); + } + return result; +} + +hailo_status HailoRtRpcClient::ConfiguredNetworkGroup_release(uint32_t handle) +{ + Release_Request request; + request.set_handle(handle); + + Release_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->ConfiguredNetworkGroup_release(&context, request, &reply); + CHECK_GRPC_STATUS(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS(static_cast(reply.status())); + return HAILO_SUCCESS; +} + +Expected> HailoRtRpcClient::ConfiguredNetworkGroup_make_input_vstream_params( + uint32_t handle, bool quantized, hailo_format_type_t format_type, uint32_t timeout_ms, uint32_t queue_size, + const std::string &network_name) +{ + ConfiguredNetworkGroup_make_input_vstream_params_Request request; + request.set_handle(handle); + request.set_quantized(quantized); + request.set_format_type(format_type); + request.set_timeout_ms(timeout_ms); + request.set_queue_size(queue_size); + request.set_network_name(network_name); + + ConfiguredNetworkGroup_make_input_vstream_params_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->ConfiguredNetworkGroup_make_input_vstream_params(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + std::map result; + for (int i = 0; i < reply.vstream_params_map_size(); ++i) { + auto name = reply.vstream_params_map(i).name(); + auto proto_params = reply.vstream_params_map(i).params(); + auto proto_user_buffer_format = proto_params.user_buffer_format(); + hailo_format_t user_buffer_format = { + .type = static_cast(proto_user_buffer_format.type()), + .order = static_cast(proto_user_buffer_format.order()), + .flags = static_cast(proto_user_buffer_format.flags()) + }; + hailo_vstream_params_t params = { + .user_buffer_format = user_buffer_format, + .timeout_ms = proto_params.timeout_ms(), + .queue_size = proto_params.queue_size(), + .vstream_stats_flags = static_cast(proto_params.vstream_stats_flags()), + .pipeline_elements_stats_flags = static_cast(proto_params.pipeline_elements_stats_flags()) + }; + result.insert({name, params}); + } + return result; +} + +Expected> HailoRtRpcClient::ConfiguredNetworkGroup_make_output_vstream_params( + uint32_t handle, bool quantized, hailo_format_type_t format_type, uint32_t timeout_ms, uint32_t queue_size, + const std::string &network_name) +{ + ConfiguredNetworkGroup_make_output_vstream_params_Request request; + request.set_handle(handle); + request.set_quantized(quantized); + request.set_format_type(format_type); + request.set_timeout_ms(timeout_ms); + request.set_queue_size(queue_size); + request.set_network_name(network_name); + + ConfiguredNetworkGroup_make_output_vstream_params_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->ConfiguredNetworkGroup_make_output_vstream_params(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + std::map result; + for (int i = 0; i < reply.vstream_params_map_size(); ++i) { + auto name = reply.vstream_params_map(i).name(); + auto proto_params = reply.vstream_params_map(i).params(); + auto proto_user_buffer_format = proto_params.user_buffer_format(); + hailo_format_t user_buffer_format = { + .type = static_cast(proto_user_buffer_format.type()), + .order = static_cast(proto_user_buffer_format.order()), + .flags = static_cast(proto_user_buffer_format.flags()) + }; + hailo_vstream_params_t params = { + .user_buffer_format = user_buffer_format, + .timeout_ms = proto_params.timeout_ms(), + .queue_size = proto_params.queue_size(), + .vstream_stats_flags = static_cast(proto_params.vstream_stats_flags()), + .pipeline_elements_stats_flags = static_cast(proto_params.pipeline_elements_stats_flags()) + }; + result.insert({name, params}); + } + return result; +} + +Expected HailoRtRpcClient::ConfiguredNetworkGroup_get_network_group_name(uint32_t handle) +{ + return ConfiguredNetworkGroup_get_name(handle); +} + +Expected HailoRtRpcClient::ConfiguredNetworkGroup_get_name(uint32_t handle) +{ + ConfiguredNetworkGroup_get_name_Request request; + request.set_handle(handle); + + ConfiguredNetworkGroup_get_name_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->ConfiguredNetworkGroup_get_name(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + auto network_group_name = reply.network_group_name(); + return network_group_name; +} + +Expected> HailoRtRpcClient::ConfiguredNetworkGroup_get_network_infos(uint32_t handle) +{ + ConfiguredNetworkGroup_get_network_infos_Request request; + request.set_handle(handle); + + ConfiguredNetworkGroup_get_network_infos_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->ConfiguredNetworkGroup_get_network_infos(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + auto network_infos_proto = reply.network_infos(); + std::vector network_infos; + network_infos.reserve(network_infos_proto.size()); + for (auto& info_proto : network_infos_proto) { + hailo_network_info_t info; + strcpy(info.name, info_proto.c_str()); + network_infos.push_back(info); + } + return network_infos; +} + +Expected> HailoRtRpcClient::ConfiguredNetworkGroup_get_all_stream_infos(uint32_t handle, + const std::string &network_name) +{ + ConfiguredNetworkGroup_get_all_stream_infos_Request request; + request.set_handle(handle); + request.set_network_name(network_name); + + ConfiguredNetworkGroup_get_all_stream_infos_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->ConfiguredNetworkGroup_get_all_stream_infos(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + std::vector result; + result.reserve(reply.stream_infos().size()); + for (auto proto_stream_info : reply.stream_infos()) { + hailo_3d_image_shape_t shape{ + .height = proto_stream_info.stream_shape().shape().height(), + .width = proto_stream_info.stream_shape().shape().width(), + .features = proto_stream_info.stream_shape().shape().features(), + }; + hailo_3d_image_shape_t hw_shape{ + .height = proto_stream_info.stream_shape().hw_shape().height(), + .width = proto_stream_info.stream_shape().hw_shape().width(), + .features = proto_stream_info.stream_shape().hw_shape().features(), + }; + hailo_nms_defuse_info_t nms_defuse_info{ + .class_group_index = proto_stream_info.nms_info().defuse_info().class_group_index(), + .original_name = {0} + }; + strcpy(nms_defuse_info.original_name, proto_stream_info.nms_info().defuse_info().original_name().c_str()); + hailo_nms_info_t nms_info{ + .number_of_classes = proto_stream_info.nms_info().number_of_classes(), + .max_bboxes_per_class = proto_stream_info.nms_info().max_bboxes_per_class(), + .bbox_size = proto_stream_info.nms_info().bbox_size(), + .chunks_per_frame = proto_stream_info.nms_info().chunks_per_frame(), + .is_defused = proto_stream_info.nms_info().is_defused(), + .defuse_info = nms_defuse_info, + }; + hailo_format_t format{ + .type = static_cast(proto_stream_info.format().type()), + .order = static_cast(proto_stream_info.format().order()), + .flags = static_cast(proto_stream_info.format().flags()) + }; + hailo_quant_info_t quant_info{ + .qp_zp = proto_stream_info.quant_info().qp_zp(), + .qp_scale = proto_stream_info.quant_info().qp_scale(), + .limvals_min = proto_stream_info.quant_info().limvals_min(), + .limvals_max = proto_stream_info.quant_info().limvals_max() + }; + hailo_stream_info_t stream_info; + if (format.order == HAILO_FORMAT_ORDER_HAILO_NMS) { + stream_info.nms_info = nms_info; + } else { + stream_info.shape = shape; + stream_info.hw_shape = hw_shape; + } + stream_info.hw_data_bytes = proto_stream_info.hw_data_bytes(); + stream_info.hw_frame_size = proto_stream_info.hw_frame_size(); + stream_info.format = format; + stream_info.direction = static_cast(proto_stream_info.direction()); + stream_info.index = static_cast(proto_stream_info.index()); + strcpy(stream_info.name, proto_stream_info.name().c_str()); + stream_info.quant_info = quant_info; + stream_info.is_mux = proto_stream_info.is_mux(); + result.push_back(stream_info); + } + return result; +} + +Expected HailoRtRpcClient::ConfiguredNetworkGroup_get_default_stream_interface(uint32_t handle) +{ + ConfiguredNetworkGroup_get_default_stream_interface_Request request; + request.set_handle(handle); + + ConfiguredNetworkGroup_get_default_stream_interface_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->ConfiguredNetworkGroup_get_default_stream_interface(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + auto stream_interface = static_cast(reply.stream_interface()); + return stream_interface; +} + +Expected>> HailoRtRpcClient::ConfiguredNetworkGroup_get_output_vstream_groups(uint32_t handle) +{ + ConfiguredNetworkGroup_get_output_vstream_groups_Request request; + request.set_handle(handle); + + ConfiguredNetworkGroup_get_output_vstream_groups_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->ConfiguredNetworkGroup_get_output_vstream_groups(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + auto vstream_groups_proto = reply.output_vstream_groups(); + std::vector> result; + result.reserve(vstream_groups_proto.size()); + for (auto& vstream_group_proto : vstream_groups_proto) { + std::vector group; + group.reserve(vstream_group_proto.vstream_group().size()); + for (auto& name : vstream_group_proto.vstream_group()) { + group.push_back(name); + } + result.push_back(group); + } + return result; +} + +Expected> deserialize_vstream_infos(const ConfiguredNetworkGroup_get_vstream_infos_Reply &reply) +{ + std::vector result; + result.reserve(reply.vstream_infos().size()); + for (auto& info_proto : reply.vstream_infos()) { + hailo_vstream_info_t info; + strcpy(info.name, info_proto.name().c_str()); + strcpy(info.network_name, info_proto.network_name().c_str()); + info.direction = static_cast(info_proto.direction()); + hailo_format_t format = { + .type = static_cast(info_proto.format().type()), + .order = static_cast(info_proto.format().order()), + .flags = static_cast(info_proto.format().flags()) + }; + info.format = format; + if (format.order == HAILO_FORMAT_ORDER_HAILO_NMS) { + hailo_nms_shape_t nms_shape = { + .number_of_classes = info_proto.nms_shape().number_of_classes(), + .max_bboxes_per_class = info_proto.nms_shape().max_bbox_per_class() + }; + info.nms_shape = nms_shape; + } else { + hailo_3d_image_shape_t shape = { + .height = info_proto.shape().height(), + .width = info_proto.shape().width(), + .features = info_proto.shape().features() + }; + info.shape = shape; + } + hailo_quant_info_t quant_info = { + .qp_zp = info_proto.quant_info().qp_zp(), + .qp_scale = info_proto.quant_info().qp_scale(), + .limvals_min = info_proto.quant_info().limvals_min(), + .limvals_max = info_proto.quant_info().limvals_max() + }; + info.quant_info = quant_info; + result.push_back(info); + } + return result; +} + +Expected> HailoRtRpcClient::ConfiguredNetworkGroup_get_input_vstream_infos(uint32_t handle, + std::string network_name) +{ + ConfiguredNetworkGroup_get_vstream_infos_Request request; + request.set_handle(handle); + request.set_network_name(network_name); + + ConfiguredNetworkGroup_get_vstream_infos_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->ConfiguredNetworkGroup_get_input_vstream_infos(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + return deserialize_vstream_infos(reply); +} + +Expected> HailoRtRpcClient::ConfiguredNetworkGroup_get_output_vstream_infos(uint32_t handle, + std::string network_name) +{ + ConfiguredNetworkGroup_get_vstream_infos_Request request; + request.set_handle(handle); + request.set_network_name(network_name); + + ConfiguredNetworkGroup_get_vstream_infos_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->ConfiguredNetworkGroup_get_output_vstream_infos(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + return deserialize_vstream_infos(reply); +} + +Expected> HailoRtRpcClient::ConfiguredNetworkGroup_get_all_vstream_infos(uint32_t handle, + std::string network_name) +{ + ConfiguredNetworkGroup_get_vstream_infos_Request request; + request.set_handle(handle); + request.set_network_name(network_name); + + ConfiguredNetworkGroup_get_vstream_infos_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->ConfiguredNetworkGroup_get_all_vstream_infos(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + return deserialize_vstream_infos(reply); +} + +hailo_status HailoRtRpcClient::ConfiguredNetworkGroup_set_scheduler_timeout(uint32_t handle, + const std::chrono::milliseconds &timeout, const std::string &network_name) +{ + ConfiguredNetworkGroup_set_scheduler_timeout_Request request; + request.set_handle(handle); + request.set_timeout_ms(static_cast(timeout.count())); + request.set_network_name(network_name); + + ConfiguredNetworkGroup_set_scheduler_timeout_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->ConfiguredNetworkGroup_set_scheduler_timeout(&context, request, &reply); + CHECK_GRPC_STATUS(status); + assert(reply.status() < HAILO_STATUS_COUNT); + return static_cast(reply.status()); +} + +hailo_status HailoRtRpcClient::ConfiguredNetworkGroup_set_scheduler_threshold(uint32_t handle, uint32_t threshold, + const std::string &network_name) +{ + ConfiguredNetworkGroup_set_scheduler_threshold_Request request; + request.set_handle(handle); + request.set_threshold(threshold); + request.set_network_name(network_name); + + ConfiguredNetworkGroup_set_scheduler_threshold_Reply reply; + grpc::ClientContext context; + grpc::Status status = m_stub->ConfiguredNetworkGroup_set_scheduler_threshold(&context, request, &reply); + CHECK_GRPC_STATUS(status); + assert(reply.status() < HAILO_STATUS_COUNT); + return static_cast(reply.status()); +} + +Expected HailoRtRpcClient::ConfiguredNetworkGroup_get_latency_measurement(uint32_t handle, + const std::string &network_name) +{ + ConfiguredNetworkGroup_get_latency_measurement_Request request; + ConfiguredNetworkGroup_get_latency_measurement_Reply reply; + request.set_handle(handle); + request.set_network_name(network_name); + grpc::ClientContext context; + grpc::Status status = m_stub->ConfiguredNetworkGroup_get_latency_measurement(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + LatencyMeasurementResult result{ + .avg_hw_latency = std::chrono::nanoseconds(reply.avg_hw_latency()) + }; + return result; +} + +hailo_status HailoRtRpcClient::InputVStream_write(uint32_t handle, const MemoryView &buffer) +{ + InputVStream_write_Request request; + request.set_handle(handle); + request.set_data(buffer.data(), buffer.size()); + grpc::ClientContext context; + InputVStream_write_Reply reply; + grpc::Status status = m_stub->InputVStream_write(&context, request, &reply); + CHECK_GRPC_STATUS(status); + assert(reply.status() < HAILO_STATUS_COUNT); + if (reply.status() == HAILO_STREAM_INTERNAL_ABORT) { + return static_cast(reply.status()); + } + CHECK_SUCCESS(static_cast(reply.status())); + return HAILO_SUCCESS; +} + +hailo_status HailoRtRpcClient::OutputVStream_read(uint32_t handle, MemoryView buffer) +{ + OutputVStream_read_Request request; + request.set_handle(handle); + request.set_size(static_cast(buffer.size())); + grpc::ClientContext context; + OutputVStream_read_Reply reply; + grpc::Status status = m_stub->OutputVStream_read(&context, request, &reply); + CHECK_GRPC_STATUS(status); + assert(reply.status() < HAILO_STATUS_COUNT); + if (reply.status() == HAILO_STREAM_INTERNAL_ABORT) { + return static_cast(reply.status()); + } + CHECK_SUCCESS(static_cast(reply.status())); + memcpy(buffer.data(), reply.data().data(), buffer.size()); + return HAILO_SUCCESS; +} + +Expected HailoRtRpcClient::InputVStream_get_frame_size(uint32_t handle) +{ + VStream_get_frame_size_Request request; + request.set_handle(handle); + grpc::ClientContext context; + VStream_get_frame_size_Reply reply; + grpc::Status status = m_stub->InputVStream_get_frame_size(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + return reply.frame_size(); +} + +Expected HailoRtRpcClient::OutputVStream_get_frame_size(uint32_t handle) +{ + VStream_get_frame_size_Request request; + request.set_handle(handle); + grpc::ClientContext context; + VStream_get_frame_size_Reply reply; + grpc::Status status = m_stub->OutputVStream_get_frame_size(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + return reply.frame_size(); +} + +hailo_status HailoRtRpcClient::InputVStream_flush(uint32_t handle) +{ + InputVStream_flush_Request request; + request.set_handle(handle); + grpc::ClientContext context; + InputVStream_flush_Reply reply; + grpc::Status status = m_stub->InputVStream_flush(&context, request, &reply); + CHECK_GRPC_STATUS(status); + assert(reply.status() < HAILO_STATUS_COUNT); + return static_cast(reply.status()); +} + +Expected HailoRtRpcClient::InputVStream_name(uint32_t handle) +{ + VStream_name_Request request; + request.set_handle(handle); + grpc::ClientContext context; + VStream_name_Reply reply; + grpc::Status status = m_stub->InputVStream_name(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + auto name = reply.name(); + return name; +} + +Expected HailoRtRpcClient::OutputVStream_name(uint32_t handle) +{ + VStream_name_Request request; + request.set_handle(handle); + grpc::ClientContext context; + VStream_name_Reply reply; + grpc::Status status = m_stub->OutputVStream_name(&context, request, &reply); + CHECK_GRPC_STATUS_AS_EXPECTED(status); + assert(reply.status() < HAILO_STATUS_COUNT); + CHECK_SUCCESS_AS_EXPECTED(static_cast(reply.status())); + auto name = reply.name(); + return name; +} + +hailo_status HailoRtRpcClient::InputVStream_abort(uint32_t handle) +{ + VStream_abort_Request request; + request.set_handle(handle); + grpc::ClientContext context; + VStream_abort_Reply reply; + grpc::Status status = m_stub->InputVStream_abort(&context, request, &reply); + CHECK(status.ok(), HAILO_RPC_FAILED, "InputVStream_abort: RPC failed"); + assert(reply.status() < HAILO_STATUS_COUNT); + return static_cast(reply.status()); +} + +hailo_status HailoRtRpcClient::OutputVStream_abort(uint32_t handle) +{ + VStream_abort_Request request; + request.set_handle(handle); + grpc::ClientContext context; + VStream_abort_Reply reply; + grpc::Status status = m_stub->OutputVStream_abort(&context, request, &reply); + CHECK(status.ok(), HAILO_RPC_FAILED, "OutputVStream_abort: RPC failed"); + assert(reply.status() < HAILO_STATUS_COUNT); + return static_cast(reply.status()); +} + +hailo_status HailoRtRpcClient::InputVStream_resume(uint32_t handle) +{ + VStream_resume_Request request; + request.set_handle(handle); + grpc::ClientContext context; + VStream_resume_Reply reply; + grpc::Status status = m_stub->InputVStream_resume(&context, request, &reply); + CHECK(status.ok(), HAILO_RPC_FAILED, "InputVStream_resume: RPC failed"); + assert(reply.status() < HAILO_STATUS_COUNT); + return static_cast(reply.status()); +} + +hailo_status HailoRtRpcClient::OutputVStream_resume(uint32_t handle) +{ + VStream_resume_Request request; + request.set_handle(handle); + grpc::ClientContext context; + VStream_resume_Reply reply; + grpc::Status status = m_stub->OutputVStream_resume(&context, request, &reply); + CHECK(status.ok(), HAILO_RPC_FAILED, "OutputVStream_resume: RPC failed"); + assert(reply.status() < HAILO_STATUS_COUNT); + return static_cast(reply.status()); +} + +} \ No newline at end of file diff --git a/hailort/libhailort/src/hailort_rpc_client.hpp b/hailort/libhailort/src/hailort_rpc_client.hpp new file mode 100644 index 0000000..638309c --- /dev/null +++ b/hailort/libhailort/src/hailort_rpc_client.hpp @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + **/ +/** + * @file hailort_rpc_client.hpp + * @brief TODO + **/ + +#ifndef HAILO_HAILORT_RPC_CLIENT_HPP_ +#define HAILO_HAILORT_RPC_CLIENT_HPP_ + +#include "hailo/expected.hpp" +#include "hailo/hailort.hpp" +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4244 4267 4127) +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif +#include +#include "hailort_rpc.grpc.pb.h" +#if defined(_MSC_VER) +#pragma warning( pop ) +#else +#pragma GCC diagnostic pop +#endif +#include + +namespace hailort +{ + +class HailoRtRpcClient final { +public: + HailoRtRpcClient(std::shared_ptr channel) + : m_stub(HailoRtRpc::NewStub(channel)) {} + + hailo_status client_keep_alive(uint32_t process_id); + Expected get_service_version(); + + Expected VDevice_create(const hailo_vdevice_params_t ¶ms, uint32_t pid); + hailo_status VDevice_release(uint32_t handle); + Expected> VDevice_get_physical_devices_ids(uint32_t handle); + Expected> VDevice_configure(uint32_t vdevice_handle, const Hef &hef, uint32_t pid, const NetworkGroupsParamsMap &configure_params={}); + hailo_status ConfiguredNetworkGroup_release(uint32_t handle); + Expected> ConfiguredNetworkGroup_make_input_vstream_params(uint32_t handle, + bool quantized, hailo_format_type_t format_type, uint32_t timeout_ms, uint32_t queue_size, + const std::string &network_name); + Expected> ConfiguredNetworkGroup_make_output_vstream_params(uint32_t handle, + bool quantized, hailo_format_type_t format_type, uint32_t timeout_ms, uint32_t queue_size, + const std::string &network_name); + Expected ConfiguredNetworkGroup_get_network_group_name(uint32_t handle); + Expected ConfiguredNetworkGroup_get_name(uint32_t handle); + Expected> ConfiguredNetworkGroup_get_network_infos(uint32_t handle); + Expected> ConfiguredNetworkGroup_get_all_stream_infos(uint32_t handle, const std::string &network_name); + Expected ConfiguredNetworkGroup_get_default_stream_interface(uint32_t handle); + Expected>> ConfiguredNetworkGroup_get_output_vstream_groups(uint32_t handle); + Expected> ConfiguredNetworkGroup_get_input_vstream_infos(uint32_t handle, std::string network_name); + Expected> ConfiguredNetworkGroup_get_output_vstream_infos(uint32_t handle, std::string network_name); + Expected> ConfiguredNetworkGroup_get_all_vstream_infos(uint32_t handle, std::string network_name); + hailo_status ConfiguredNetworkGroup_set_scheduler_timeout(uint32_t handle, const std::chrono::milliseconds &timeout, + const std::string &network_name); + hailo_status ConfiguredNetworkGroup_set_scheduler_threshold(uint32_t handle, uint32_t threshold, const std::string &network_name); + Expected ConfiguredNetworkGroup_get_latency_measurement(uint32_t handle, const std::string &network_name); + + Expected> InputVStreams_create(uint32_t net_group_handle, + const std::map &inputs_params, uint32_t pid); + hailo_status InputVStream_release(uint32_t handle); + Expected> OutputVStreams_create(uint32_t net_group_handle, + const std::map &output_params, uint32_t pid); + hailo_status OutputVStream_release(uint32_t handle); + hailo_status InputVStream_write(uint32_t handle, const MemoryView &buffer); + hailo_status OutputVStream_read(uint32_t handle, MemoryView buffer); + Expected InputVStream_get_frame_size(uint32_t handle); + Expected OutputVStream_get_frame_size(uint32_t handle); + + hailo_status InputVStream_flush(uint32_t handle); + + Expected InputVStream_name(uint32_t handle); + Expected OutputVStream_name(uint32_t handle); + + hailo_status InputVStream_abort(uint32_t handle); + hailo_status OutputVStream_abort(uint32_t handle); + + hailo_status InputVStream_resume(uint32_t handle); + hailo_status OutputVStream_resume(uint32_t handle); + +private: + std::unique_ptr m_stub; +}; + +} + +#endif // HAILO_HAILORT_CLIENT_RPC_HPP_ \ No newline at end of file diff --git a/hailort/libhailort/src/hef.cpp b/hailort/libhailort/src/hef.cpp index 3c5f624..b2b2657 100644 --- a/hailort/libhailort/src/hef.cpp +++ b/hailort/libhailort/src/hef.cpp @@ -17,6 +17,7 @@ #include "common/utils.hpp" #include "hailo/hailort_common.hpp" #include "hailort_defaults.hpp" +#include "common/string_utils.hpp" #include "hlpcie.hpp" #include "pcie_device.hpp" @@ -65,17 +66,29 @@ typedef struct { } CcwHeader; #pragma pack(pop) -typedef struct { - std::set D2H_channels_in_use; - std::set H2D_channels_in_use; -} ContextSwitchChannelsParsingInfo; +bool ConfigureNetworkParams::operator==(const ConfigureNetworkParams &other) +{ + for (auto &name_param_pair : network_params_by_name) { + if ((other.network_params_by_name.find(name_param_pair.first) == other.network_params_by_name.end()) || + (name_param_pair.second.batch_size != other.network_params_by_name.at(name_param_pair.first).batch_size) ) { + return false; + } + } + return (batch_size == other.batch_size) && (power_mode == other.power_mode) && (latency == other.latency); +} + +bool ConfigureNetworkParams::operator!=(const ConfigureNetworkParams &other) +{ + return !(*this == other); +} + // Note: Can't add the definition in the header. This will lead to the following error: -// /usr/include/c++/7/bits/unique_ptr.h: In instantiation of ‘void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = Hef::Impl]’: -// /usr/include/c++/7/bits/unique_ptr.h:263:17: required from ‘std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = Hef::Impl; _Dp = std::default_delete]’ -// /local/users/projects/platform-sw/hailort/libhailort/src/../include/hailo/hef.hpp:61:7: required from ‘Expected::~Expected() [with T = Hef]’ +// /usr/include/c++/7/bits/unique_ptr.h: In instantiation of 'void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = Hef::Impl]': +// /usr/include/c++/7/bits/unique_ptr.h:263:17: required from 'std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = Hef::Impl; _Dp = std::default_delete]' +// /local/users/projects/platform-sw/hailort/libhailort/src/../include/hailo/hef.hpp:61:7: required from 'Expected::~Expected() [with T = Hef]' // /local/users/projects/platform-sw/hailort/hailortcli/run_command.cpp:705:51: required from here -// /usr/include/c++/7/bits/unique_ptr.h:76:22: error: invalid application of ‘sizeof’ to incomplete type ‘Hef::Impl’ +// /usr/include/c++/7/bits/unique_ptr.h:76:22: error: invalid application of 'sizeof' to incomplete type 'Hef::Impl' // static_assert(sizeof(_Tp)>0, Hef::~Hef() = default; Hef::Hef(Hef &&) = default; @@ -317,8 +330,19 @@ hailo_status Hef::Impl::validate_hef_extensions() return HAILO_SUCCESS; } +void Hef::Impl::init_md5(MD5_SUM_t &calculated_md5) +{ + memcpy(m_md5, calculated_md5, sizeof(m_md5)); +} + hailo_status Hef::Impl::parse_hef_file(const std::string &hef_path) { +#ifdef HAILO_SUPPORT_MULTI_PROCESS + auto hef_buffer = read_binary_file(hef_path); + CHECK_EXPECTED_AS_STATUS(hef_buffer); + m_hef_buffer = hef_buffer.release(); +#endif // HAILO_SUPPORT_MULTI_PROCESS + auto hef_file = std::ifstream(hef_path, std::ios::in | std::ios::binary); CHECK(hef_file.is_open(), HAILO_OPEN_FILE_FAILURE, "Failed to open HEF file \"{}\". errno: {}", hef_path, errno); @@ -336,6 +360,8 @@ hailo_status Hef::Impl::parse_hef_file(const std::string &hef_path) status = validate_hef_header(header, calculated_md5, proto_size.value()); CHECK_SUCCESS(status); + init_md5(calculated_md5); + ProtoHEFHef hef_message; auto rb = hef_message.ParseFromIstream(&hef_file); CHECK(rb, HAILO_INVALID_HEF, "Failed parsing HEF file"); @@ -354,6 +380,12 @@ hailo_status Hef::Impl::parse_hef_file(const std::string &hef_path) hailo_status Hef::Impl::parse_hef_memview(const MemoryView &hef_memview) { +#ifdef HAILO_SUPPORT_MULTI_PROCESS + auto hef_buffer = Buffer::create(hef_memview.data(), hef_memview.size()); + CHECK_EXPECTED_AS_STATUS(hef_buffer); + m_hef_buffer = hef_buffer.release(); +#endif // HAILO_SUPPORT_MULTI_PROCESS + CHECK(hef_memview.size() >= sizeof(hef__header_t), HAILO_INVALID_HEF, "Invalid HEF header"); const hef__header_t &header = reinterpret_cast(*hef_memview.data()); @@ -369,6 +401,8 @@ hailo_status Hef::Impl::parse_hef_memview(const MemoryView &hef_memview) auto status = validate_hef_header(header, calculated_md5, proto_size); CHECK_SUCCESS(status); + init_md5(calculated_md5); + ProtoHEFHef hef_message; auto rb = hef_message.ParseFromArray(proto_buffer, static_cast(proto_size)); CHECK(rb, HAILO_INVALID_HEF, "Failed parsing HEF buffer"); @@ -391,37 +425,67 @@ hailo_status Hef::Impl::fill_networks_metadata() auto supported_features = get_supported_features(m_header, m_hef_extensions, m_included_features, m_hef_optional_extensions); - for (auto &network_group : m_groups) { - - CHECK(!contains(m_network_group_metadata, - network_group->network_group_metadata().network_group_name()), - HAILO_INVALID_OPERATION, "Network group with the name {} is already configured on the device", - network_group->network_group_metadata().network_group_name()); - - auto layer_infos = HefUtils::get_all_layers_info(*network_group, supported_features); - CHECK_EXPECTED_AS_STATUS(layer_infos); - - auto sorted_output_names = HefUtils::get_sorted_output_names(*network_group); - CHECK_EXPECTED_AS_STATUS(sorted_output_names); + NetworkGroupMetadataPerArch metadata; + uint32_t partial_clusters_layout_bitmap = 0; + std::string network_group_name; - std::vector sorted_network_names; - if (supported_features.multi_network_support) { - sorted_network_names.reserve(network_group->networks_names().size()); - for (auto &partial_network_name : network_group->networks_names()) { - auto network_name = HefUtils::get_network_name(*network_group, partial_network_name); - sorted_network_names.push_back(network_name); + for (auto &network_group : m_groups) { + if (ProtoHEFHwArch::PROTO__HW_ARCH__HAILO8L == get_device_arch()) { + for (auto &partial_network_group : network_group->partial_network_groups()) { + partial_clusters_layout_bitmap = partial_network_group.layout().partial_clusters_layout_bitmap(); + network_group_name = partial_network_group.network_group().network_group_metadata().network_group_name(); + auto metadata_per_arch = create_metadata_per_arch(partial_network_group.network_group(), supported_features); + CHECK_EXPECTED_AS_STATUS(metadata_per_arch); + metadata.add_metadata(metadata_per_arch.release(), partial_clusters_layout_bitmap); } } else { - sorted_network_names.push_back(HailoRTDefaults::get_network_name(network_group->network_group_metadata().network_group_name())); + partial_clusters_layout_bitmap = PARTIAL_CLUSTERS_LAYOUT_IGNORE; + network_group_name = network_group->network_group_metadata().network_group_name(); + auto metadata_per_arch = create_metadata_per_arch(*network_group, supported_features); + CHECK_EXPECTED_AS_STATUS(metadata_per_arch); + metadata.add_metadata(metadata_per_arch.release(), partial_clusters_layout_bitmap); } + CHECK(!contains(m_network_group_metadata_per_arch, network_group_name), + HAILO_INVALID_OPERATION, "Network group with the name {} is already configured on the device", network_group_name); + m_network_group_metadata_per_arch.emplace(network_group_name, metadata); + } + return HAILO_SUCCESS; +} + +Expected Hef::Impl::create_metadata_per_arch(const ProtoHEFNetworkGroup &network_group, NetworkGroupSupportedFeatures &supported_features) +{ + std::vector> boundary_input_layers; + std::vector> boundary_output_layers; + std::vector> inter_context_input_layers; + std::vector> inter_context_output_layers; + std::vector> ddr_input_layers; + std::vector> ddr_output_layers; + auto status = HefUtils::get_all_layers_info(network_group, supported_features, + boundary_input_layers, boundary_output_layers, + inter_context_input_layers, inter_context_output_layers, + ddr_input_layers, ddr_output_layers); + CHECK_SUCCESS_AS_EXPECTED(status); - NetworkGroupMetadata metadata(network_group->network_group_metadata().network_group_name(), - layer_infos.release(), sorted_output_names.release(), supported_features, sorted_network_names); - m_network_group_metadata.emplace( - network_group->network_group_metadata().network_group_name(), metadata); + auto sorted_output_names = HefUtils::get_sorted_output_names(network_group); + CHECK_EXPECTED(sorted_output_names); + + std::vector sorted_network_names; + if (supported_features.multi_network_support) { + sorted_network_names.reserve(network_group.networks_names().size()); + for (auto &partial_network_name : network_group.networks_names()) { + auto network_name = HefUtils::get_network_name(network_group, partial_network_name); + sorted_network_names.push_back(network_name); + } + } else { + sorted_network_names.push_back(HailoRTDefaults::get_network_name(network_group.network_group_metadata().network_group_name())); } - return HAILO_SUCCESS; + NetworkGroupMetadata metadata_per_arch(network_group.network_group_metadata().network_group_name(), + std::move(boundary_input_layers), std::move(boundary_output_layers), + std::move(inter_context_input_layers), std::move(inter_context_output_layers), + std::move(ddr_input_layers), std::move(ddr_output_layers), + sorted_output_names.release(), supported_features, sorted_network_names); + return metadata_per_arch; } hailo_status Hef::Impl::transfer_protobuf_field_ownership(ProtoHEFHef &hef_message) @@ -451,6 +515,13 @@ hailo_status Hef::Impl::transfer_protobuf_field_ownership(ProtoHEFHef &hef_messa return HAILO_SUCCESS; } +#ifdef HAILO_SUPPORT_MULTI_PROCESS +const MemoryView Hef::Impl::get_hef_memview() +{ + return MemoryView(m_hef_buffer); +} +#endif // HAILO_SUPPORT_MULTI_PROCESS + Hef::Impl::Impl(const std::string &hef_path, hailo_status &status) { status = HAILO_UNINITIALIZED; @@ -497,7 +568,7 @@ NetworkGroupSupportedFeatures Hef::Impl::get_supported_features(const ProtoHEFHe header, hef_optional_extensions); supported_features.multi_context = check_hef_extension(ProtoHEFExtensionType::IS_MULTI_CONTEXTS, header, hef_extensions, included_features); - supported_features.preliminary_run_asap = check_hef_extension(ProtoHEFExtensionType::PRELIMINARY_RUN_ASAP, + supported_features.preliminary_run_asap = check_hef_extension(ProtoHEFExtensionType::KO_RUN_ASAP, header, hef_extensions, included_features); return supported_features; @@ -553,7 +624,7 @@ Expected HefConfigurator::parse_nn_stream_ /* For DDR buffering - core buffers is depended on the amount of buffers per PCIe interrupt. No HW padding required */ if (is_ddr) { - stream_config.core_buffers_per_frame = DDR_NUMBER_OF_ROWS_PER_INTERRUPT; + stream_config.core_buffers_per_frame = 1; stream_config.feature_padding_payload = 0; stream_config.periph_bytes_per_buffer = stream_config.core_bytes_per_buffer; } else { @@ -593,13 +664,14 @@ Expected HefConfigurator::parse_nn_stream_ Expected HefConfigurator::parse_nn_stream_config(const LayerInfo &edge_layer, bool hw_padding_supported) { + // TODO HRT-7177 - pass interface to layer info instead of re-calculated Layer info from stream_internal.hpp + // After passing stream interface, there is no need for this function. Just use CONTROL_PROTOCOL__nn_stream_config_t from layer info. auto is_ddr = false; // This function is called only on boundary layers, so no DDR return parse_nn_stream_config(edge_layer.format.order, edge_layer.hw_shape.width, edge_layer.hw_shape.features, - edge_layer.hw_data_bytes, edge_layer.core_buffers_per_frame, edge_layer.core_bytes_per_buffer, hw_padding_supported, - is_ddr); + edge_layer.hw_data_bytes, edge_layer.nn_stream_config.core_buffers_per_frame, + edge_layer.nn_stream_config.core_bytes_per_buffer, hw_padding_supported, is_ddr); } - bool HefConfigurator::is_hw_padding_supported(bool is_boundary, bool is_mux, hailo_format_order_t format_order, uint16_t core_buffers_per_frame, uint32_t height, uint32_t width, uint32_t features, uint32_t hw_data_bytes) { @@ -648,8 +720,9 @@ bool HefConfigurator::is_hw_padding_supported(const LayerInfo &layer_info) } auto is_boundary = true; // This function is called only on boundary layers - return is_hw_padding_supported(is_boundary, layer_info.is_mux, layer_info.format.order,layer_info.core_buffers_per_frame, - height, width, layer_info.shape.features, layer_info.hw_data_bytes); + return is_hw_padding_supported(is_boundary, layer_info.is_mux, layer_info.format.order, + layer_info.nn_stream_config.core_buffers_per_frame, height, width, + layer_info.shape.features, layer_info.hw_data_bytes); } bool HefConfigurator::is_hw_padding_supported(const ProtoHEFEdgeLayer &edge_layer) @@ -778,7 +851,7 @@ bool Hef::Impl::check_hef_extension(const ProtoHEFExtensionType &extension, cons case ProtoHEFExtensionType::DDR: return included_features.ddr(); case ProtoHEFExtensionType::IS_MULTI_CONTEXTS: - return (included_features.number_of_contexts() > 0); + return included_features.is_multi_context(); case ProtoHEFExtensionType::COMPRESSED_PARAMS: return included_features.compressed_params(); case ProtoHEFExtensionType::TRANSPOSE_COMPONENT: @@ -808,21 +881,28 @@ Expected> Hef::Impl::get_network_group_and_n std::string network_group_name; if (name.empty()) { // Name is not given - addressing all networks in the first network_group - network_group_name = m_groups[0]->network_group_metadata().network_group_name(); + network_group_name = (ProtoHEFHwArch::PROTO__HW_ARCH__HAILO8L == get_device_arch()) ? + m_groups[0]->partial_network_groups(0).network_group().network_group_metadata().network_group_name() + : m_groups[0]->network_group_metadata().network_group_name(); LOGGER__INFO("No name was given. Addressing all networks of default network_group: {}", network_group_name); auto network_name = HailoRTDefaults::get_network_name(network_group_name); return std::make_pair(network_group_name, network_name); } else { + const ProtoHEFNetworkGroup *network_group_ptr = nullptr; for (const auto &network_group : m_groups) { - network_group_name = network_group->network_group_metadata().network_group_name(); + network_group_ptr = (ProtoHEFHwArch::PROTO__HW_ARCH__HAILO8L == get_device_arch()) ? + &network_group->partial_network_groups(0).network_group() + : network_group.get(); + network_group_name = network_group_ptr->network_group_metadata().network_group_name(); + // Look for network_group with the given name if (name == network_group_name) { auto network_name = HailoRTDefaults::get_network_name(network_group_name); return std::make_pair(network_group_name, network_name); } // Look for network with the given name - for (const auto &partial_network_name : network_group->networks_names()) { + for (const auto &partial_network_name : network_group_ptr->networks_names()) { auto full_network_name = HefUtils::get_network_name(network_group_name, partial_network_name); if (name == full_network_name) { return std::make_pair(network_group_name, full_network_name); @@ -842,14 +922,29 @@ Expected> Hef::Impl::get_network_group_and_n Expected Hef::Impl::get_net_group_by_name(const std::string &net_group_name) { + const ProtoHEFNetworkGroup *network_group_ptr = nullptr; if ("" == net_group_name) { + network_group_ptr = (ProtoHEFHwArch::PROTO__HW_ARCH__HAILO8L == get_device_arch()) ? + &m_groups[0]->partial_network_groups(0).network_group() : m_groups[0].get(); + LOGGER__INFO("No network_group name was given. Addressing default network_group: {}", - m_groups[0]->network_group_metadata().network_group_name()); - return Expected(m_groups[0]); + network_group_ptr->network_group_metadata().network_group_name()); + + ProtoHEFNetworkGroupPtr network_group_shared_ptr = make_shared_nothrow(*network_group_ptr); + CHECK_NOT_NULL_AS_EXPECTED(network_group_shared_ptr, HAILO_OUT_OF_HOST_MEMORY); + return Expected(network_group_shared_ptr); } for (auto const &net_group : m_groups) { CHECK_AS_EXPECTED(nullptr != net_group, HAILO_INTERNAL_FAILURE, "null netwrok group"); - if (net_group_name == net_group->network_group_metadata().network_group_name()) { + if (ProtoHEFHwArch::PROTO__HW_ARCH__HAILO8L == get_device_arch()) { + if (net_group_name == + net_group->partial_network_groups(0).network_group().network_group_metadata().network_group_name()) { + ProtoHEFNetworkGroupPtr network_group_shared_ptr = + make_shared_nothrow(m_groups[0]->partial_network_groups(0).network_group()); + CHECK_NOT_NULL_AS_EXPECTED(network_group_shared_ptr, HAILO_OUT_OF_HOST_MEMORY); + return Expected(network_group_shared_ptr); + } + } else if (net_group_name == net_group->network_group_metadata().network_group_name()) { return Expected(net_group); } } @@ -879,7 +974,8 @@ Expected Hef::Impl::get_number_of_output_streams(const std::string &net_ hailo_status HefUtils::fill_layer_info_with_base_info(const ProtoHEFEdgeLayerBase &base_info, const ProtoHEFEdgeConnectionType &edge_connection_type, const ProtoHEFNetworkGroupMetadata &network_group_proto, - bool hw_padding_supported, bool transposed, LayerInfo &layer_info) + bool hw_padding_supported, bool transposed, const uint8_t context_index, const uint8_t network_index, + LayerInfo &layer_info) { auto format_order_exp = HailoRTDefaults::get_device_format_order(base_info.format()); CHECK_EXPECTED_AS_STATUS(format_order_exp); @@ -931,14 +1027,16 @@ hailo_status HefUtils::fill_layer_info_with_base_info(const ProtoHEFEdgeLayerBas auto nn_stream_config = HefConfigurator::parse_nn_stream_config(base_info, hw_padding_supported, edge_connection_type); CHECK_EXPECTED_AS_STATUS(nn_stream_config, "Failed parse nn stream config"); - layer_info.core_bytes_per_buffer = nn_stream_config->core_bytes_per_buffer; - layer_info.core_buffers_per_frame = nn_stream_config->core_buffers_per_frame; + layer_info.nn_stream_config = nn_stream_config.release(); + layer_info.network_index = network_index; + layer_info.context_index = context_index; - if (!IS_FIT_IN_UINT8(base_info.sys_index())) { - LOGGER__ERROR("Failed to parse HEF. Invalid sys_index {}.", base_info.sys_index()); - return HAILO_INVALID_HEF; - } - layer_info.index = static_cast(base_info.sys_index()); + CHECK(IS_FIT_IN_UINT8(base_info.sys_index()), HAILO_INVALID_HEF, + "Failed to parse HEF. Invalid sys_index: {}.", base_info.sys_index()); + layer_info.stream_index = static_cast(base_info.sys_index()); + CHECK(IS_FIT_IN_UINT8(base_info.engine_id()), HAILO_INVALID_HEF, + "Failed to parse HEF. Invalid engine_id: {}.", base_info.engine_id()); + layer_info.dma_engine_index = static_cast(base_info.engine_id()); if (HAILO_FORMAT_ORDER_HAILO_NMS == layer_info.format.order) { auto expected_nms_info = parse_proto_nms_info(base_info.additional_info().nms_info()); @@ -946,16 +1044,19 @@ hailo_status HefUtils::fill_layer_info_with_base_info(const ProtoHEFEdgeLayerBas layer_info.nms_info = expected_nms_info.release(); } + layer_info.max_shmifo_size = base_info.max_shmifo_size(); + return HAILO_SUCCESS; } hailo_status HefUtils::fill_layer_info(const ProtoHEFEdgeLayerInfo &info, const ProtoHEFEdgeConnectionType &edge_connection_type, const ProtoHEFNetworkGroup &net_group, hailo_stream_direction_t direction, - bool hw_padding_supported, const std::string &partial_network_name, LayerInfo &layer_info) + bool hw_padding_supported, const uint8_t context_index, const std::string &partial_network_name, + uint8_t network_index, LayerInfo &layer_info) { auto status = fill_layer_info_with_base_info(info.edge_layer_base(), edge_connection_type, net_group.network_group_metadata(), - hw_padding_supported, info.transposed(), layer_info); + hw_padding_supported, info.transposed(), context_index, network_index, layer_info); CHECK_SUCCESS(status); if (HAILO_MAX_STREAM_NAME_SIZE < (info.name().length() + 1)) { @@ -1047,11 +1148,12 @@ hailo_status HefUtils::fill_fused_nms_info(const ProtoHEFEdgeLayerFused &info, L hailo_status HefUtils::fill_mux_info(const ProtoHEFEdgeLayerMux &info, const ProtoHEFEdgeConnectionType &edge_connection_type, const ProtoHEFNetworkGroup &net_group, hailo_stream_direction_t direction, - bool hw_padding_supported, const std::string &partial_network_name, LayerInfo &layer_info) + bool hw_padding_supported, const uint8_t context_index, const std::string &partial_network_name, + uint8_t network_index, LayerInfo &layer_info) { const bool transposed = false; auto status = fill_layer_info_with_base_info(info.edge_layer_base(), edge_connection_type, net_group.network_group_metadata(), - hw_padding_supported, transposed, layer_info); + hw_padding_supported, transposed, context_index, network_index, layer_info); CHECK_SUCCESS(status); if (HAILO_MAX_STREAM_NAME_SIZE < (info.name().length() + 1)) { @@ -1082,7 +1184,7 @@ hailo_status HefUtils::fill_mux_info(const ProtoHEFEdgeLayerMux &info, switch (info.predecessors(i).edge_case()) { case ProtoHefEdge::kLayerInfo: status = fill_layer_info(info.predecessors(i).layer_info(), edge_connection_type, net_group, - direction, hw_padding_supported, partial_network_name, temp_layer); + direction, hw_padding_supported, context_index, partial_network_name, network_index, temp_layer); if (HAILO_SUCCESS != status) { return status; } @@ -1090,7 +1192,7 @@ hailo_status HefUtils::fill_mux_info(const ProtoHEFEdgeLayerMux &info, break; case ProtoHefEdge::kLayerMux: status = fill_mux_info(info.predecessors(i).layer_mux(), edge_connection_type, net_group, - direction, hw_padding_supported, partial_network_name, temp_layer); + direction, hw_padding_supported, context_index, partial_network_name, network_index, temp_layer); if (HAILO_SUCCESS != status) { return status; } @@ -1106,31 +1208,160 @@ hailo_status HefUtils::fill_mux_info(const ProtoHEFEdgeLayerMux &info, return HAILO_SUCCESS; } -Expected> HefUtils::get_all_layers_info(const ProtoHEFNetworkGroup &network_group_proto, - const NetworkGroupSupportedFeatures &supported_features) +hailo_status HefUtils::fill_boundary_layers_info( + const ProtoHEFNetworkGroup &network_group_proto, + const uint8_t context_index, + const ProtoHEFEdgeLayer &layer, + const NetworkGroupSupportedFeatures &supported_features, + std::vector &layer_name, + std::vector &context_boundary_input_layers, + std::vector &context_boundary_output_layers) { - std::vector layers_info; + auto layer_info = get_boundary_layer_info(network_group_proto, context_index, layer, supported_features); + CHECK_EXPECTED_AS_STATUS(layer_info); + + // Validate unique layer names + for (const auto &parsed_layer_name : layer_name) { + CHECK((parsed_layer_name != layer_info->name), HAILO_INVALID_HEF, + "Layer name should be unique. name '{}' appears more than once", layer_info->name); + } + + // Temp vector for validation + layer_name.emplace_back(layer_info.value().name); + + if (HAILO_H2D_STREAM == layer_info->direction) { + context_boundary_input_layers.emplace_back(layer_info.release()); + } else { + context_boundary_output_layers.emplace_back(layer_info.release()); + } + + return HAILO_SUCCESS; +} + +hailo_status HefUtils::fill_inter_context_layers_info( + const ProtoHEFNetworkGroup &network_group_proto, + const uint8_t context_index, + const ProtoHEFEdgeLayer &layer, + const NetworkGroupSupportedFeatures &supported_features, + std::vector &context_inter_context_input_layers, + std::vector &context_inter_context_output_layers) +{ + auto layer_info = get_inter_context_layer_info(network_group_proto, context_index, layer, supported_features); + CHECK_EXPECTED_AS_STATUS(layer_info); + + if (HAILO_H2D_STREAM == layer_info->direction) { + context_inter_context_input_layers.emplace_back(layer_info.release()); + } else { + context_inter_context_output_layers.emplace_back(layer_info.release()); + } + + return HAILO_SUCCESS; +} + +hailo_status HefUtils::fill_ddr_layers_info( + const ProtoHEFNetworkGroup &network_group_proto, + const uint8_t context_index, + const ProtoHEFEdgeLayer &layer, + const NetworkGroupSupportedFeatures &supported_features, + std::vector &context_ddr_input_layers, + std::vector &context_ddr_output_layers) +{ + auto layer_info = get_ddr_layer_info(network_group_proto, context_index, layer, supported_features); + CHECK_EXPECTED_AS_STATUS(layer_info); + + if (HAILO_H2D_STREAM == layer_info->direction) { + context_ddr_input_layers.emplace_back(layer_info.release()); + } else { + context_ddr_output_layers.emplace_back(layer_info.release()); + } + + return HAILO_SUCCESS; +} + +hailo_status HefUtils::check_ddr_pairs_match( + const std::vector &context_ddr_input_layers, + const std::vector &context_ddr_output_layers, + const uint8_t context_index) +{ + CHECK(context_ddr_input_layers.size() == context_ddr_output_layers.size(), HAILO_INVALID_HEF, + "DDR pairs must be equal in size for context {}" ,context_index); + + for (auto const &ddr_output_layer : context_ddr_output_layers) { + auto matching_input_stream = ddr_output_layer.dst_stream_index; + bool found_mathing_layer = false; + for (auto const &ddr_input_layer : context_ddr_input_layers) { + if (ddr_input_layer.stream_index == matching_input_stream) { + CHECK(!found_mathing_layer, HAILO_INVALID_HEF, "Found multiple input DDR streams for single ddr output stream"); + found_mathing_layer = true; + CHECK(ddr_output_layer.nn_stream_config.core_bytes_per_buffer == ddr_input_layer.nn_stream_config.core_bytes_per_buffer, + HAILO_INVALID_HEF, "both sides for DDR pair must have the same core_bytes_per_buffer.\n" + "context index {}. Output stream index - {} output side core_bytes_per_buffer - {}." + "input stream index {}.input size core_bytes_per_buffer - {}", + context_index, ddr_output_layer.stream_index, ddr_output_layer.nn_stream_config.core_bytes_per_buffer, + ddr_input_layer.stream_index, ddr_input_layer.nn_stream_config.core_bytes_per_buffer); + CHECK(ddr_output_layer.total_buffers_per_frame == ddr_input_layer.total_buffers_per_frame, + HAILO_INVALID_HEF, "both sides for DDR pair must have the same total_buffers_per_frame.\n" + "context index {}. Output stream index - {} output side total_buffers_per_frame - {}." + "input stream index {}. input size total_buffers_per_frame - {}", + context_index, ddr_output_layer.stream_index, ddr_output_layer.total_buffers_per_frame, + ddr_input_layer.stream_index, ddr_input_layer.total_buffers_per_frame); + } + } + CHECK(found_mathing_layer, HAILO_INVALID_HEF, "didn't find any match for context {} output stream {}", context_index, ddr_output_layer.stream_index); + } + + return HAILO_SUCCESS; +} + +hailo_status HefUtils::get_all_layers_info(const ProtoHEFNetworkGroup &network_group_proto, + const NetworkGroupSupportedFeatures &supported_features, + std::vector> &boundary_input_layers, + std::vector> &boundary_output_layers, + std::vector> &inter_context_input_layers, + std::vector> &inter_context_output_layers, + std::vector> &ddr_input_layers, + std::vector> &ddr_output_layers) +{ + std::vector boundary_layers_name; for (uint8_t context_index = 0; context_index < network_group_proto.contexts_size(); context_index++) { auto &context_metadata = network_group_proto.contexts(context_index).metadata(); + std::vector context_boundary_input_layers; + std::vector context_boundary_output_layers; + std::vector context_inter_context_input_layers; + std::vector context_inter_context_output_layers; + std::vector context_ddr_input_layers; + std::vector context_ddr_output_layers; for (int i = 0; i < context_metadata.edge_layers_size(); i++) { - // We parse only boundary layers for user usage - if (ProtoHEFEdgeConnectionType::PROTO__EDGE_CONNECTION_TYPE__BOUNDARY != - context_metadata.edge_layers(i).context_switch_info().edge_connection_type()) { - continue; - } - auto layer_info = get_layer_info(network_group_proto, context_metadata.edge_layers(i), supported_features); - CHECK_EXPECTED(layer_info); - - // Validate unique layer names - for (const auto &layer : layers_info) { - CHECK_AS_EXPECTED((layer.name != layer_info->name), HAILO_INVALID_HEF, - "Layer name should be unique. name '{}' appears more than once", layer_info->name); + if (ProtoHEFEdgeConnectionType::PROTO__EDGE_CONNECTION_TYPE__BOUNDARY == + context_metadata.edge_layers(i).context_switch_info().edge_connection_type()) { + auto status = fill_boundary_layers_info(network_group_proto, context_index, context_metadata.edge_layers(i), + supported_features, boundary_layers_name, context_boundary_input_layers, context_boundary_output_layers); + CHECK_SUCCESS(status); + } else if (ProtoHEFEdgeConnectionType::PROTO__EDGE_CONNECTION_TYPE__INTERMEDIATE == + context_metadata.edge_layers(i).context_switch_info().edge_connection_type()) { + auto status = fill_inter_context_layers_info(network_group_proto, context_index, context_metadata.edge_layers(i), + supported_features, context_inter_context_input_layers, context_inter_context_output_layers); + CHECK_SUCCESS(status); + } else if (ProtoHEFEdgeConnectionType::PROTO__EDGE_CONNECTION_TYPE__DDR == + context_metadata.edge_layers(i).context_switch_info().edge_connection_type()) { + auto status = fill_ddr_layers_info(network_group_proto, context_index, context_metadata.edge_layers(i), + supported_features, context_ddr_input_layers, context_ddr_output_layers); + CHECK_SUCCESS(status); } - - layers_info.emplace_back(layer_info.release()); } + + auto status = check_ddr_pairs_match(context_ddr_input_layers, context_ddr_output_layers, context_index); + CHECK_SUCCESS(status); + + // Finished parsing all context's edge layer. Add results to output params + boundary_input_layers.emplace_back(std::move(context_boundary_input_layers)); + boundary_output_layers.emplace_back(std::move(context_boundary_output_layers)); + inter_context_input_layers.emplace_back(std::move(context_inter_context_input_layers)); + inter_context_output_layers.emplace_back(std::move(context_inter_context_output_layers)); + ddr_input_layers.emplace_back(std::move(context_ddr_input_layers)); + ddr_output_layers.emplace_back(std::move(context_ddr_output_layers)); } - return layers_info; + return HAILO_SUCCESS; } Expected HefUtils::parse_proto_nms_info(const ProtoHEFNmsInfo &proto_nms_info) @@ -1159,8 +1390,8 @@ Expected HefUtils::parse_proto_nms_info(const ProtoHEFNmsInfo return nms_info; } -Expected HefUtils::get_layer_info(const ProtoHEFNetworkGroup &net_group, const ProtoHEFEdgeLayer &layer, - const NetworkGroupSupportedFeatures &supported_features) +Expected HefUtils::get_boundary_layer_info(const ProtoHEFNetworkGroup &net_group, const uint8_t context_index, + const ProtoHEFEdgeLayer &layer, const NetworkGroupSupportedFeatures &supported_features) { // We parse only boundary layers for user usage CHECK_AS_EXPECTED( @@ -1179,12 +1410,12 @@ Expected HefUtils::get_layer_info(const ProtoHEFNetworkGroup &net_gro if (ProtoHEFEdgeLayerType::PROTO__EDGE_LAYER_TYPE__INFO == layer.edge_layer_type()) { // TODO: return LayerInfo auto status = fill_layer_info(layer.layer_info(), layer.context_switch_info().edge_connection_type(), - net_group, direction, hw_padding_supported, partial_network_name.value(), result); + net_group, direction, hw_padding_supported, context_index, partial_network_name.value(), network_index, result); CHECK_SUCCESS_AS_EXPECTED(status); } else if (ProtoHEFEdgeLayerType::PROTO__EDGE_LAYER_TYPE__MUX == layer.edge_layer_type()) { // TODO: return LayerInfo auto status = fill_mux_info(layer.layer_mux(), layer.context_switch_info().edge_connection_type(), - net_group, direction, hw_padding_supported, partial_network_name.value(), result); + net_group, direction, hw_padding_supported, context_index, partial_network_name.value(), network_index, result); CHECK_SUCCESS_AS_EXPECTED(status); } else { LOGGER__ERROR("Invalid layer type"); @@ -1197,6 +1428,119 @@ Expected HefUtils::get_layer_info(const ProtoHEFNetworkGroup &net_gro return result; } +Expected HefUtils::get_inter_context_layer_info(const ProtoHEFNetworkGroup &net_group, const uint8_t context_index, + const ProtoHEFEdgeLayer &layer, const NetworkGroupSupportedFeatures &supported_features) +{ + InterContextLayerInfo result = {}; + CHECK_AS_EXPECTED(PROTO__EDGE_LAYER_TYPE__INFO == layer.edge_layer_type(), HAILO_INVALID_HEF, "Inter-context layer can't be mux."); + + auto support_multi_networks = supported_features.multi_network_support; + result.network_index = static_cast((support_multi_networks) ? layer.network_index() : 0); + auto partial_network_name = HefUtils::get_partial_network_name_by_index(net_group, result.network_index, supported_features); + CHECK_EXPECTED(partial_network_name); + result.network_name = HefUtils::get_network_name(net_group, partial_network_name.release()); + result.context_index = context_index; + const bool hw_padding_supported = HefConfigurator::is_hw_padding_supported(layer); + result.name = layer.layer_info().name(); + auto nn_stream_config_exp = HefConfigurator::parse_nn_stream_config(layer.layer_info().edge_layer_base(), + hw_padding_supported, layer.context_switch_info().edge_connection_type()); + CHECK_EXPECTED(nn_stream_config_exp); + result.nn_stream_config = nn_stream_config_exp.release(); + CHECK_AS_EXPECTED(IS_FIT_IN_UINT8(layer.layer_info().edge_layer_base().sys_index()), HAILO_INVALID_HEF, + "Failed to parse HEF. Invalid sys_index: {}.", layer.layer_info().edge_layer_base().sys_index()); + result.stream_index = static_cast(layer.layer_info().edge_layer_base().sys_index()); + CHECK_AS_EXPECTED(IS_FIT_IN_UINT8(layer.layer_info().edge_layer_base().engine_id()), HAILO_INVALID_HEF, + "Failed to parse HEF. Invalid engine_id: {}.", layer.layer_info().edge_layer_base().engine_id()); + result.dma_engine_index = static_cast(layer.layer_info().edge_layer_base().engine_id()); + + result.max_shmifo_size = layer.layer_info().edge_layer_base().max_shmifo_size(); + + CHECK_AS_EXPECTED(IS_FIT_IN_UINT8(layer.context_switch_info().connected_sys_index()), HAILO_INVALID_HEF, + "Failed to parse HEF. Invalid connected_sys_index: {}.", layer.context_switch_info().connected_sys_index()); + CHECK_AS_EXPECTED(IS_FIT_IN_UINT8(layer.context_switch_info().connected_context_index()), HAILO_INVALID_HEF, + "Failed to parse HEF. Invalid connected_context_index: {}.", layer.context_switch_info().connected_context_index()); + result.direction = (ProtoHEFEdgeLayerDirection::PROTO__EDGE_LAYER_DIRECTION__DEVICE_TO_HOST == + layer.direction()) ? HAILO_D2H_STREAM : HAILO_H2D_STREAM; + if ((ProtoHEFEdgeLayerDirection::PROTO__EDGE_LAYER_DIRECTION__HOST_TO_DEVICE == layer.direction())) { + result.src_stream_index = static_cast(layer.context_switch_info().connected_sys_index()); + result.src_context_index = static_cast(layer.context_switch_info().connected_context_index()); + result.dst_stream_index = result.stream_index; + result.dst_context_index = result.context_index; + } else { + result.src_stream_index = result.stream_index; + result.src_context_index = result.context_index; + // HRT-7201 - The system supports one src and multiple dstinations. Right now we're saving only one dstination + result.dst_stream_index = static_cast(layer.context_switch_info().connected_sys_index()); + result.dst_context_index = static_cast(layer.context_switch_info().connected_context_index()); + } + + return result; +} + +Expected HefUtils::get_ddr_layer_info(const ProtoHEFNetworkGroup &net_group, const uint8_t context_index, + const ProtoHEFEdgeLayer &layer, const NetworkGroupSupportedFeatures &supported_features) +{ + DdrLayerInfo result = {}; + CHECK_AS_EXPECTED(PROTO__EDGE_LAYER_TYPE__INFO == layer.edge_layer_type(), HAILO_INVALID_HEF, "DDR layer can't be mux."); + + auto support_multi_networks = supported_features.multi_network_support; + result.network_index = static_cast((support_multi_networks) ? layer.network_index() : 0); + auto partial_network_name = HefUtils::get_partial_network_name_by_index(net_group, result.network_index, supported_features); + CHECK_EXPECTED(partial_network_name); + result.network_name = HefUtils::get_network_name(net_group, partial_network_name.release()); + result.context_index = context_index; + const bool hw_padding_supported = HefConfigurator::is_hw_padding_supported(layer); + result.name = layer.layer_info().name(); + auto nn_stream_config_exp = HefConfigurator::parse_nn_stream_config(layer.layer_info().edge_layer_base(), + hw_padding_supported, layer.context_switch_info().edge_connection_type()); + CHECK_EXPECTED(nn_stream_config_exp); + result.nn_stream_config = nn_stream_config_exp.release(); + CHECK_AS_EXPECTED(IS_FIT_IN_UINT8(layer.layer_info().edge_layer_base().sys_index()), HAILO_INVALID_HEF, + "Failed to parse HEF. Invalid sys_index: {}.", layer.layer_info().edge_layer_base().sys_index()); + result.stream_index = static_cast(layer.layer_info().edge_layer_base().sys_index()); + CHECK_AS_EXPECTED(IS_FIT_IN_UINT8(layer.layer_info().edge_layer_base().engine_id()), HAILO_INVALID_HEF, + "Failed to parse HEF. Invalid engine_id: {}.", layer.layer_info().edge_layer_base().engine_id()); + result.max_shmifo_size = layer.layer_info().edge_layer_base().max_shmifo_size(); + CHECK_AS_EXPECTED(IS_FIT_IN_UINT16(layer.layer_info().edge_layer_base().core_buffers_per_frame()), HAILO_INVALID_HEF, + "Failed to parse HEF. Invalid core_buffers_per_frame: {}.", layer.layer_info().edge_layer_base().core_buffers_per_frame()); + result.total_buffers_per_frame = static_cast(layer.layer_info().edge_layer_base().core_buffers_per_frame()); + + CHECK_AS_EXPECTED(layer.context_switch_info().connected_contexts_size() == 1, HAILO_INVALID_HEF, + "Only single connected context is supported on DDR channels"); + const auto& connected_context = layer.context_switch_info().connected_contexts(0); + CHECK_AS_EXPECTED(IS_FIT_IN_UINT8(layer.context_switch_info().connected_sys_index()), HAILO_INVALID_HEF, + "Failed to parse HEF. Invalid connected_sys_index: {}.", layer.context_switch_info().connected_sys_index()); + CHECK_AS_EXPECTED(IS_FIT_IN_UINT8(connected_context.engine_id()), HAILO_INVALID_HEF, + "Failed to parse HEF. Invalid engine_id: {}. in connected_contexts", connected_context.engine_id()); + CHECK_AS_EXPECTED(IS_FIT_IN_UINT8(layer.context_switch_info().connected_context_index()), HAILO_INVALID_HEF, + "Failed to parse HEF. Invalid connected_context_index: {}.", layer.context_switch_info().connected_context_index()); + result.direction = (ProtoHEFEdgeLayerDirection::PROTO__EDGE_LAYER_DIRECTION__DEVICE_TO_HOST == + layer.direction()) ? HAILO_D2H_STREAM : HAILO_H2D_STREAM; + CHECK_AS_EXPECTED(context_index == static_cast(layer.context_switch_info().connected_context_index()), + HAILO_INVALID_HEF, "for ddr layer, connected_context_index must be same to the edge layer's context"); + if ((ProtoHEFEdgeLayerDirection::PROTO__EDGE_LAYER_DIRECTION__HOST_TO_DEVICE == layer.direction())) { + result.src_stream_index = static_cast(layer.context_switch_info().connected_sys_index()); + result.src_dma_engine_index = static_cast(connected_context.engine_id()); + result.src_context_index = static_cast(layer.context_switch_info().connected_context_index()); + result.dst_stream_index = result.stream_index; + result.dst_dma_engine_index = static_cast(layer.layer_info().edge_layer_base().engine_id()); + result.dst_context_index = result.context_index; + } else { + result.src_stream_index = result.stream_index; + result.src_dma_engine_index = static_cast(layer.layer_info().edge_layer_base().engine_id()); + result.src_context_index = result.context_index; + result.dst_stream_index = static_cast(layer.context_switch_info().connected_sys_index()); + result.dst_dma_engine_index = static_cast(connected_context.engine_id()); + result.dst_context_index = static_cast(layer.context_switch_info().connected_context_index()); + } + + CHECK_AS_EXPECTED(IS_FIT_IN_UINT16(layer.context_switch_info().buffers()), HAILO_INVALID_HEF, + "calculated number of transfers for DDR buffer is out of UINT16_T range"); + result.min_buffered_rows = static_cast(layer.context_switch_info().buffers()); + + return result; +} + Expected> HefUtils::get_sorted_output_names(const ProtoHEFNetworkGroup &net_group) { if (net_group.fused_layers_metadata().network_has_fused_layers()) { @@ -1232,307 +1576,168 @@ inline std::pair old_hef_parse_initial_l3(uint32_t initial_l3 return std::make_pair(initial_l3_cut, initial_l3_offset); } -static uint32_t get_initial_credit_size(const ProtoHEFEdgeLayer &edge_layer_info) +static hailo_status fill_boundary_input_layer(CONTROL_PROTOCOL__context_switch_context_info_t *context_info, + uint8_t **context_meta_data_head_pointer, ResourcesManager &resources_manager, const LayerInfo layer_info) { - // On old HEFs the max shmifo is not defined, so 0 wil be returned and the firmware will choose the default value. - return edge_layer_info.layer_info().edge_layer_base().max_shmifo_size(); -} + const auto channel_id = resources_manager.get_available_channel_id(to_layer_identifier(layer_info), + VdmaChannel::Direction::H2D, layer_info.dma_engine_index); + CHECK_EXPECTED_AS_STATUS(channel_id); -static hailo_status fill_boundary_input_layer(CONTROL_PROTOCOL__context_switch_context_info_t *context_info, - uint8_t **context_meta_data_head_pointer, uint8_t stream_index, const ProtoHEFEdgeLayer &edge_layer_info, - const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, - ContextSwitchChannelsParsingInfo &channels_parsing_info, ResourcesManager &resources_manager, - const std::string &layer_name, const std::string &network_name, uint8_t network_index, - uint32_t frame_credits_in_bytes) -{ - auto channel_index = resources_manager.get_available_channel_index( - channels_parsing_info.H2D_channels_in_use, ChannelInfo::Type::BOUNDARY, VdmaChannel::Direction::H2D, layer_name); - CHECK_EXPECTED_AS_STATUS(channel_index); - - // Mark the channel info with the stream_index - auto channel_info = resources_manager.get_channel_info(channel_index.value()); - CHECK_EXPECTED_AS_STATUS(channel_info); - channel_info->get().set_pcie_stream_index(stream_index); - - auto vdma_channel = resources_manager.create_boundary_vdma_channel(channel_index.value(), frame_credits_in_bytes, - network_name, layer_name, VdmaChannel::Direction::H2D); - CHECK_EXPECTED_AS_STATUS(vdma_channel); + const auto frame_credits_in_bytes = (layer_info.nn_stream_config.periph_bytes_per_buffer * + layer_info.nn_stream_config.core_buffers_per_frame); - // Lock the channel for further use in this net_group - channels_parsing_info.H2D_channels_in_use.insert(channel_index.value()); + auto vdma_channel = resources_manager.create_boundary_vdma_channel(channel_id.value(), frame_credits_in_bytes, + layer_info.network_name, layer_info.name, VdmaChannel::Direction::H2D); + CHECK_EXPECTED_AS_STATUS(vdma_channel); + auto buffer_info = vdma_channel.value()->get_boundary_buffer_info(frame_credits_in_bytes); + CHECK_EXPECTED_AS_STATUS(buffer_info); - LOGGER__DEBUG("Boundary input stream: {} h2d_pcie_channel: {}.", stream_index, channel_index.value()); + LOGGER__DEBUG("Boundary input stream: {} h2d_channel: {}.", layer_info.stream_index, channel_id.value()); /* Update metadata */ - const uint32_t initial_credit_size = get_initial_credit_size(edge_layer_info); auto status = HEF_METADATA__add_network_boundary_input_edge_layer(context_info, - context_meta_data_head_pointer, stream_index, channel_index.value(), network_index, nn_stream_config, - vdma_channel.value()->get_page_size(), initial_credit_size); + context_meta_data_head_pointer, channel_id.value(), layer_info.stream_index, layer_info.network_index, + layer_info.nn_stream_config, buffer_info.value(), layer_info.max_shmifo_size); CHECK_SUCCESS(status); return HAILO_SUCCESS; } static hailo_status fill_inter_context_input_layer(CONTROL_PROTOCOL__context_switch_context_info_t *context_info, - uint8_t **context_meta_data_head_pointer, ResourcesManager &resources_manager, uint8_t dst_context, - uint8_t stream_index, const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, - const ProtoHEFEdgeLayer *edge_layer_info, ContextSwitchChannelsParsingInfo &channels_parsing_info, - std::set &channels_to_unlock, - uint8_t network_index) + uint8_t **context_meta_data_head_pointer, ResourcesManager &resources_manager, const InterContextLayerInfo &layer_info) { - uint8_t src_context = 0; - uint8_t src_stream_index = 0; - - // Used to log layer info - debug only - (void)dst_context; - - if (!(IS_FIT_IN_UINT8(edge_layer_info->context_switch_info().connected_context_index()))) { - LOGGER__ERROR("Failed to parse HEF. Invalid connected_context index: {}.", - edge_layer_info->context_switch_info().connected_context_index()); - return HAILO_INVALID_HEF; - } - src_context = static_cast(edge_layer_info->context_switch_info().connected_context_index()); - - if(!(IS_FIT_IN_UINT8(edge_layer_info->context_switch_info().connected_sys_index()))) { - LOGGER__ERROR("Failed to parse HEF. Invalid connected_sys index: {}.", - edge_layer_info->context_switch_info().connected_sys_index()); - return HAILO_INVALID_HEF; - } - src_stream_index = static_cast(edge_layer_info->context_switch_info().connected_sys_index()); - - /* Find next available h2d_channel, and mark it to unlock at the end of the context */ - auto h2d_channel_index = resources_manager.get_available_channel_index(channels_parsing_info.H2D_channels_in_use, - ChannelInfo::Type::INTER_CONTEXT, VdmaChannel::Direction::H2D); - CHECK_EXPECTED_AS_STATUS(h2d_channel_index); - - channels_parsing_info.H2D_channels_in_use.insert(h2d_channel_index.value()); - channels_to_unlock.insert(h2d_channel_index.value()); + const auto channel_id = resources_manager.get_available_channel_id(to_layer_identifier(layer_info), + VdmaChannel::Direction::H2D, layer_info.dma_engine_index); + CHECK_EXPECTED_AS_STATUS(channel_id); /* Get inter context buffer previously created */ - auto intermediate_buffer_key = std::make_pair(src_context, src_stream_index); - auto intermediate_buffer_exp = resources_manager.get_intermediate_buffer(intermediate_buffer_key); - CHECK_EXPECTED_AS_STATUS(intermediate_buffer_exp, "Failed to find intermediate buffer for src context {}, src_stream_index {}", - src_context, src_stream_index); - auto &intermediate_buffer = intermediate_buffer_exp->get(); + auto intermediate_buffer_key = std::make_pair(layer_info.src_context_index, layer_info.src_stream_index); + auto inter_context_buffer_exp = resources_manager.get_inter_context_buffer(intermediate_buffer_key); + CHECK_EXPECTED_AS_STATUS(inter_context_buffer_exp, "Failed to find inter context buffer for src context {}, src_stream_index {}", + layer_info.src_context_index, layer_info.src_stream_index); + auto &inter_context_buffer = inter_context_buffer_exp->get(); - LOGGER__DEBUG("Intermediate input stream {}, src_context:{}, dst_context: {}, h2d_pcie_channel {}.", - stream_index, src_context, dst_context, h2d_channel_index.value()); + LOGGER__DEBUG("Intermediate input stream {}, src_context:{}, dst_context: {}, h2d_channel {}.", + layer_info.stream_index, layer_info.src_context_index, layer_info.dst_context_index, channel_id.value()); /* Update metadata */ - const uint32_t initial_credit_size = get_initial_credit_size(*edge_layer_info); return HEF_METADATA__add_inter_context_input_edge_layer(context_info, context_meta_data_head_pointer, - stream_index, h2d_channel_index.value(), network_index, nn_stream_config, - intermediate_buffer.get_host_buffer_info(), initial_credit_size); + channel_id.value(), layer_info.stream_index, layer_info.network_index, layer_info.nn_stream_config, + inter_context_buffer.get_host_buffer_info(), layer_info.max_shmifo_size); } static hailo_status fill_boundary_output_layer(CONTROL_PROTOCOL__context_switch_context_info_t *context_info, - uint8_t **context_meta_data_head_pointer, ResourcesManager &resources_manager, uint8_t stream_index, - const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, uint32_t frame_credits_in_bytes, - ContextSwitchChannelsParsingInfo &channels_parsing_info, const std::string &layer_name, uint8_t network_index, - const std::string &network_name) + uint8_t **context_meta_data_head_pointer, ResourcesManager &resources_manager, const LayerInfo &layer_info) { - auto channel_index = resources_manager.get_available_channel_index(channels_parsing_info.D2H_channels_in_use, - ChannelInfo::Type::BOUNDARY, VdmaChannel::Direction::D2H, layer_name); - CHECK_EXPECTED_AS_STATUS(channel_index); - - // Mark the channel info with the stream_index - auto channel_info = resources_manager.get_channel_info(channel_index.value()); - CHECK_EXPECTED_AS_STATUS(channel_info); - channel_info->get().set_pcie_stream_index(stream_index); + const auto channel_id = resources_manager.get_available_channel_id(to_layer_identifier(layer_info), + VdmaChannel::Direction::D2H, layer_info.dma_engine_index); + CHECK_EXPECTED_AS_STATUS(channel_id); + const auto frame_credits_in_bytes = (layer_info.nn_stream_config.periph_bytes_per_buffer * + layer_info.nn_stream_config.core_buffers_per_frame); - auto vdma_channel = resources_manager.create_boundary_vdma_channel(channel_index.value(), frame_credits_in_bytes, - network_name, layer_name, VdmaChannel::Direction::D2H); + auto vdma_channel = resources_manager.create_boundary_vdma_channel(channel_id.value(), frame_credits_in_bytes, + layer_info.network_name, layer_info.name, VdmaChannel::Direction::D2H); CHECK_EXPECTED_AS_STATUS(vdma_channel); - auto page_size = vdma_channel.value()->get_page_size(); - - // Lock the channel for further use in this net_group - channels_parsing_info.D2H_channels_in_use.insert(channel_index.value()); + auto buffer_info = vdma_channel.value()->get_boundary_buffer_info(frame_credits_in_bytes); + CHECK_EXPECTED_AS_STATUS(buffer_info); - LOGGER__DEBUG("Boundary output stream: {} d2h_pcie_channel: {}.", stream_index, channel_index.value()); + LOGGER__DEBUG("Boundary output stream: {} d2h_channel: {}.", layer_info.stream_index, channel_id.value()); /* Update metadata */ auto status = HEF_METADATA__add_network_boundary_output_edge_layer(context_info, - context_meta_data_head_pointer, stream_index, channel_index.value(), network_index, - nn_stream_config, frame_credits_in_bytes, page_size); + context_meta_data_head_pointer, channel_id.value(), layer_info.stream_index, layer_info.network_index, + layer_info.nn_stream_config, buffer_info.value()); CHECK_SUCCESS(status); return HAILO_SUCCESS; } static hailo_status fill_inter_context_output_layer(CONTROL_PROTOCOL__context_switch_context_info_t *context_info, - uint8_t **context_meta_data_head_pointer, ResourcesManager &resources_manager, uint8_t src_context, - uint8_t stream_index, const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, uint32_t frame_credits_in_bytes, - const ProtoHEFEdgeLayer *edge_layer_info, ContextSwitchChannelsParsingInfo &channels_parsing_info, - std::set &channels_to_unlock, uint8_t network_index, const std::string &network_name) + uint8_t **context_meta_data_head_pointer, ResourcesManager &resources_manager, const InterContextLayerInfo &layer_info) { - std::vector connected_h2d_channels; + const auto channel_id = resources_manager.get_available_channel_id(to_layer_identifier(layer_info), + VdmaChannel::Direction::D2H, layer_info.dma_engine_index); + CHECK_EXPECTED_AS_STATUS(channel_id); - CHECK(IS_FIT_IN_UINT8(edge_layer_info->context_switch_info().connected_context_index()), - HAILO_INVALID_HEF, "Failed to parse HEF. Invalid connected_context index: {}.", - edge_layer_info->context_switch_info().connected_context_index()); + const auto frame_credits_in_bytes = (layer_info.nn_stream_config.periph_bytes_per_buffer * + layer_info.nn_stream_config.core_buffers_per_frame); - /* Find next available d2h_channel, and mark it to unlock at the end of the context */ - auto d2h_channel_index = resources_manager.get_available_channel_index( - channels_parsing_info.D2H_channels_in_use, ChannelInfo::Type::INTER_CONTEXT, VdmaChannel::Direction::D2H); - CHECK_EXPECTED_AS_STATUS(d2h_channel_index); + auto inter_context_buffer_exp = resources_manager.create_inter_context_buffer(frame_credits_in_bytes, + layer_info.stream_index, layer_info.src_context_index, layer_info.network_name); + CHECK_EXPECTED_AS_STATUS(inter_context_buffer_exp); + auto &inter_context_buffer = inter_context_buffer_exp->get(); - channels_parsing_info.D2H_channels_in_use.insert(d2h_channel_index.value()); - channels_to_unlock.insert(d2h_channel_index.value()); - - auto intermediate_buffer_exp = resources_manager.create_inter_context_buffer(frame_credits_in_bytes, - stream_index, src_context, network_name); - CHECK_EXPECTED_AS_STATUS(intermediate_buffer_exp); - auto &intermediate_buffer = intermediate_buffer_exp->get(); - - LOGGER__DEBUG("Intermediate output stream {}, src_context:{}, d2h_pcie_channel {}.", - stream_index, src_context, d2h_channel_index.value()); + LOGGER__DEBUG("Inter-context output stream {}, src_context:{}, d2h_channel {}.", + layer_info.stream_index, layer_info.src_context_index, channel_id.value()); /* Update metadata */ auto status = HEF_METADATA__add_inter_context_output_edge_layer(context_info, context_meta_data_head_pointer, - stream_index, d2h_channel_index.value(), network_index, nn_stream_config, intermediate_buffer.get_host_buffer_info()); + channel_id.value(), layer_info.stream_index, layer_info.network_index, layer_info.nn_stream_config, + inter_context_buffer.get_host_buffer_info()); CHECK_SUCCESS(status); return HAILO_SUCCESS; } -static hailo_status fill_ddr_layer_multi_context(CONTROL_PROTOCOL__context_switch_context_info_t *context_switch_info, - uint8_t **context_meta_data_head_pointer, ResourcesManager &resources_manager, uint8_t context_index, - const ProtoHEFEdgeLayer &edge_layer_proto, const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, - hailo_stream_direction_t direction, ContextSwitchChannelsParsingInfo &channels_parsing_info, uint32_t frame_credits_in_bytes, - std::set &channels_to_unlock, uint8_t network_index) +static hailo_status fill_ddr_output_layer(CONTROL_PROTOCOL__context_switch_context_info_t *context_info, + uint8_t **context_meta_data_head_pointer, ResourcesManager &resources_manager, const DdrLayerInfo &layer_info) { - /* Find out if the connected layer has already been parsed */ - uint8_t channel_index = 0; - CHECK(resources_manager.get_supported_features().padded_ddr_buffers, HAILO_INVALID_HEF, "Failed opening non-compatible HEF that uses the following deprecated features: host-managed DDR buffers." "Please re-compile the HEF using a newer Dataflow Compiler version (v3.11.0 or newer)"); - - if (HAILO_H2D_STREAM == direction) { - auto channel_index_expected = resources_manager.get_available_channel_index(channels_parsing_info.H2D_channels_in_use, - ChannelInfo::Type::DDR, VdmaChannel::Direction::H2D); - CHECK_EXPECTED_AS_STATUS(channel_index_expected); - channel_index = channel_index_expected.value(); - channels_parsing_info.H2D_channels_in_use.insert(channel_index); - channels_to_unlock.insert(channel_index); - } else if (HAILO_D2H_STREAM == direction) { - auto channel_index_expected = resources_manager.get_available_channel_index(channels_parsing_info.D2H_channels_in_use, - ChannelInfo::Type::DDR, VdmaChannel::Direction::D2H); - CHECK_EXPECTED_AS_STATUS(channel_index_expected); - channel_index = channel_index_expected.value(); - channels_parsing_info.D2H_channels_in_use.insert(channel_index); - channels_to_unlock.insert(channel_index); - } else { - LOGGER__ERROR("Invalid layer direction"); - return HAILO_INVALID_ARGUMENT; - } - - for (auto &ddr_info : resources_manager.ddr_infos()) { - if (HAILO_H2D_STREAM == direction) { - /* we have the info already, just validate saved info, add ch_index, and return success! */ - if ((ddr_info.row_size == edge_layer_proto.layer_info().edge_layer_base().core_bytes_per_buffer()) - && (ddr_info.context_index == context_index) - && (ddr_info.d2h_stream_index == edge_layer_proto.context_switch_info().connected_sys_index()) - && (ddr_info.h2d_stream_index == static_cast(edge_layer_proto.layer_info().edge_layer_base().sys_index()))) { - ddr_info.h2d_channel_index = channel_index; - LOGGER__DEBUG("DDR layer: input stream_index: {}, output stream_index: {}, h2d_pcie_channel {}, d2h_pcie_channel: {}.", - ddr_info.h2d_stream_index, ddr_info.d2h_stream_index, ddr_info.h2d_channel_index, ddr_info.d2h_channel_index); - const uint32_t initial_credit_size = get_initial_credit_size(edge_layer_proto); - return HEF_METADATA__add_ddr_buffer_input_edge_layer(context_switch_info, - context_meta_data_head_pointer, ddr_info.h2d_stream_index, ddr_info.h2d_channel_index, network_index, - nn_stream_config, ddr_info.intermediate_buffer->dma_address(), ddr_info.intermediate_buffer->depth(), - initial_credit_size); - } - } else if (HAILO_D2H_STREAM == direction) { - /* we have the info already, just validate saved info, add ch_index, and return success! */ - if ((ddr_info.row_size == edge_layer_proto.layer_info().edge_layer_base().core_bytes_per_buffer()) - && (ddr_info.context_index == context_index) - && (ddr_info.h2d_stream_index == edge_layer_proto.context_switch_info().connected_sys_index()) - && (ddr_info.d2h_stream_index == static_cast(edge_layer_proto.layer_info().edge_layer_base().sys_index()))) { - ddr_info.d2h_channel_index = channel_index; - LOGGER__DEBUG("DDR layer: input stream_index: {}, output stream_index: {}, h2d_pcie_channel {}, d2h_pcie_channel: {}.", - ddr_info.h2d_stream_index, ddr_info.d2h_stream_index, ddr_info.h2d_channel_index, ddr_info.d2h_channel_index); - return HEF_METADATA__add_ddr_buffer_output_edge_layer(context_switch_info, - context_meta_data_head_pointer, ddr_info.d2h_stream_index, ddr_info.d2h_channel_index, network_index, - nn_stream_config, frame_credits_in_bytes, ddr_info.intermediate_buffer->dma_address(), - ddr_info.intermediate_buffer->desc_page_size(), ddr_info.intermediate_buffer->depth(), - ddr_info.min_buffered_rows); - } - } else { - LOGGER__ERROR("Invalid layer direction"); - return HAILO_INVALID_ARGUMENT; - } - } + // Allocate resources and prepare ddr_info - /* Allocate resources and prepare ddr_info */ - DdrChannelsInfo local_info = {}; - local_info.context_index = context_index; - CHECK(IS_FIT_IN_UINT8(edge_layer_proto.context_switch_info().connected_sys_index()), - HAILO_INVALID_HEF, "Failed to parse HEF. Invalid connected_sys_index: {}.", - edge_layer_proto.context_switch_info().connected_sys_index()); - auto connected_sys_index = static_cast(edge_layer_proto.context_switch_info().connected_sys_index()); - if (HAILO_H2D_STREAM == direction) { - local_info.h2d_channel_index = channel_index; - local_info.h2d_stream_index = static_cast(edge_layer_proto.layer_info().edge_layer_base().sys_index()); - local_info.d2h_stream_index = connected_sys_index; - } else if (HAILO_D2H_STREAM == direction) { - local_info.h2d_stream_index = connected_sys_index; - local_info.d2h_channel_index = channel_index; - local_info.d2h_stream_index = static_cast(edge_layer_proto.layer_info().edge_layer_base().sys_index()); - } else { - LOGGER__ERROR("Invalid layer direction"); - return HAILO_INVALID_ARGUMENT; - } - - local_info.row_size = static_cast(edge_layer_proto.layer_info().edge_layer_base().core_bytes_per_buffer()); - // We count on local_info.min_buffered_rows to be aligned to DDR_NUMBER_OF_ROWS_PER_INTERRUPT in the ddr threads - local_info.min_buffered_rows = edge_layer_proto.context_switch_info().buffers(); - auto leftover = (local_info.min_buffered_rows % DDR_NUMBER_OF_ROWS_PER_INTERRUPT); - if (0 != leftover) { - local_info.min_buffered_rows += (DDR_NUMBER_OF_ROWS_PER_INTERRUPT - leftover); - } - - /* Create descs list */ - auto ddr_buffer = resources_manager.create_ddr_buffer(local_info, context_index); - CHECK_EXPECTED_AS_STATUS(ddr_buffer); - - local_info.intermediate_buffer = &ddr_buffer->get(); - - CHECK(0 == (DEFAULT_DESC_PAGE_SIZE % local_info.intermediate_buffer->desc_page_size()), HAILO_INTERNAL_FAILURE, - "In padded DDR buffers, desc list page size must be dividor of {}", DEFAULT_DESC_PAGE_SIZE); - CHECK(0 == (local_info.row_size % local_info.intermediate_buffer->desc_page_size()), HAILO_INTERNAL_FAILURE, - "If HEF supports padded DDR buffers, row size must be a multiple of descriptor page size"); - local_info.descriptors_per_frame = (local_info.row_size / local_info.intermediate_buffer->desc_page_size()) * - edge_layer_proto.layer_info().edge_layer_base().core_buffers_per_frame(); - - auto programed_descs = ddr_buffer->get().program_ddr(); - CHECK_EXPECTED_AS_STATUS(programed_descs); - local_info.initial_programed_descs = programed_descs.release(); - local_info.desc_list_size_mask = static_cast(local_info.intermediate_buffer->descs_count() - 1); - - // Add layer to metadata - if (HAILO_H2D_STREAM == direction) { - const uint32_t initial_credit_size = get_initial_credit_size(edge_layer_proto); - auto status = HEF_METADATA__add_ddr_buffer_input_edge_layer(context_switch_info, - context_meta_data_head_pointer, local_info.h2d_stream_index, local_info.h2d_channel_index, network_index, - nn_stream_config, local_info.intermediate_buffer->dma_address(), local_info.intermediate_buffer->depth(), - initial_credit_size); - CHECK_SUCCESS(status); - } else if (HAILO_D2H_STREAM == direction) { - auto status = HEF_METADATA__add_ddr_buffer_output_edge_layer(context_switch_info, - context_meta_data_head_pointer, local_info.d2h_stream_index, local_info.d2h_channel_index, - network_index, nn_stream_config, frame_credits_in_bytes, local_info.intermediate_buffer->dma_address(), - local_info.intermediate_buffer->desc_page_size(), local_info.intermediate_buffer->depth(), - local_info.min_buffered_rows); - CHECK_SUCCESS(status); - } else { - LOGGER__ERROR("Invalid layer direction"); - return HAILO_INVALID_ARGUMENT; - } + DdrChannelsInfo ddr_pair_info = {}; + ddr_pair_info.h2d_stream_index = layer_info.dst_stream_index; + ddr_pair_info.d2h_stream_index = layer_info.src_stream_index; - resources_manager.ddr_infos().push_back(local_info); - return HAILO_SUCCESS; + // It is assumed that output channels are parsed before input channels. + // Allocate vdma channel index for both edges + const LayerIdentifier h2d_layer_identifier = to_layer_identifier(layer_info, VdmaChannel::Direction::H2D); + const auto h2d_channel_id = resources_manager.get_available_channel_id(h2d_layer_identifier, + VdmaChannel::Direction::H2D, layer_info.src_dma_engine_index); + CHECK_EXPECTED_AS_STATUS(h2d_channel_id); + ddr_pair_info.h2d_channel_id = h2d_channel_id.value(); + + const LayerIdentifier d2h_layer_identifier = to_layer_identifier(layer_info, VdmaChannel::Direction::D2H); + const auto d2h_channel_id = resources_manager.get_available_channel_id(d2h_layer_identifier, + VdmaChannel::Direction::D2H, layer_info.dst_context_index); + CHECK_EXPECTED_AS_STATUS(d2h_channel_id); + ddr_pair_info.d2h_channel_id = d2h_channel_id.value(); + + ddr_pair_info.row_size = layer_info.nn_stream_config.core_bytes_per_buffer; + ddr_pair_info.min_buffered_rows = layer_info.min_buffered_rows; + ddr_pair_info.total_buffers_per_frame = layer_info.total_buffers_per_frame; + + // Create the ddr buffer + auto ddr_channels_pair = resources_manager.create_ddr_channels_pair(ddr_pair_info, layer_info.context_index); + CHECK_EXPECTED_AS_STATUS(ddr_channels_pair); + + return HEF_METADATA__add_ddr_buffer_output_edge_layer(context_info, + context_meta_data_head_pointer, d2h_channel_id.value(), layer_info.stream_index, + layer_info.network_index, layer_info.nn_stream_config, ddr_channels_pair->get().get_host_buffer_info(), + layer_info.min_buffered_rows); +} + +static hailo_status fill_ddr_input_layer(CONTROL_PROTOCOL__context_switch_context_info_t *context_info, + uint8_t **context_meta_data_head_pointer, ResourcesManager &resources_manager, const DdrLayerInfo &layer_info) +{ + auto ddr_channels_pair = resources_manager.get_ddr_channels_pair(layer_info.context_index, layer_info.src_stream_index); + CHECK(ddr_channels_pair, HAILO_INVALID_HEF, "Mathing DDR layer as not found for context {} src stream {}", + layer_info.context_index, layer_info.src_stream_index); + + const auto ddr_info = ddr_channels_pair->get().info(); + LOGGER__DEBUG("DDR layer: input stream_index: {}, output stream_index: {}, h2d_channel {}, d2h_channel: {}.", + ddr_info.h2d_stream_index, ddr_info.d2h_stream_index, ddr_info.h2d_channel_id, ddr_info.d2h_channel_id); + + CHECK(layer_info.dst_stream_index == ddr_info.h2d_stream_index, HAILO_INVALID_HEF, "DDR channel pair mismatch in h2d channel"); + CHECK(layer_info.src_stream_index == ddr_info.d2h_stream_index, HAILO_INVALID_HEF, "DDR channel pair mismatch in d2h channel"); + + return HEF_METADATA__add_ddr_buffer_input_edge_layer(context_info, + context_meta_data_head_pointer, ddr_info.h2d_channel_id, ddr_info.h2d_stream_index, layer_info.network_index, + layer_info.nn_stream_config, ddr_channels_pair->get().get_host_buffer_info(), layer_info.max_shmifo_size, + ddr_info.d2h_channel_id); } Expected HefUtils::get_partial_network_name_by_index(const ProtoHEFNetworkGroup &network_group_proto, uint8_t network_index, @@ -1559,206 +1764,110 @@ std::string HefUtils::get_network_name(const ProtoHEFNetworkGroup &net_group, co return HefUtils::get_network_name(net_group.network_group_metadata().network_group_name(), partial_network_name); } -static hailo_status parse_and_fill_h2d_layer_multi_context( - CONTROL_PROTOCOL__context_switch_context_info_t *context_info, - uint8_t **context_meta_data_head_pointer, const ProtoHEFEdgeLayer &edge_layer_proto, - ContextSwitchChannelsParsingInfo &channels_parsing_info, ResourcesManager &resources_manager, - uint8_t context_index, std::set &channels_to_unlock, const std::string &network_name, - uint8_t network_index) +static hailo_status add_ddr_buffers_info(std::vector &configuration_actions, + const ResourcesManager &resources_manager, uint8_t context_index) { - uint8_t stream_index = 0; - uint32_t frame_credits_in_bytes = 0; - - CHECK(ProtoHEFEdgeLayerType::PROTO__EDGE_LAYER_TYPE__INFO == edge_layer_proto.edge_layer_type(), HAILO_INVALID_HEF, - "H2D layers must be info_layer"); - - CHECK(IS_FIT_IN_UINT8(edge_layer_proto.layer_info().edge_layer_base().sys_index()), HAILO_INVALID_HEF, - "Failed to parse HEF. Invalid sys_index: {}.", edge_layer_proto.layer_info().edge_layer_base().sys_index()); - stream_index = static_cast(edge_layer_proto.layer_info().edge_layer_base().sys_index()); - - const bool hw_padding_supported = HefConfigurator::is_hw_padding_supported(edge_layer_proto); - - auto nn_stream_config = HefConfigurator::parse_nn_stream_config(edge_layer_proto.layer_info().edge_layer_base(), hw_padding_supported, - edge_layer_proto.context_switch_info().edge_connection_type()); - CHECK_EXPECTED_AS_STATUS(nn_stream_config, "Failed parse nn stream config"); - auto layer_name = edge_layer_proto.layer_info().name(); - - /* credits work on periph bytes */ - frame_credits_in_bytes = (nn_stream_config->periph_bytes_per_buffer * nn_stream_config->core_buffers_per_frame); - - switch (edge_layer_proto.context_switch_info().edge_connection_type()) { - case ProtoHEFEdgeConnectionType::PROTO__EDGE_CONNECTION_TYPE__BOUNDARY: - return fill_boundary_input_layer(context_info, context_meta_data_head_pointer, stream_index, - edge_layer_proto, *nn_stream_config, channels_parsing_info, resources_manager, layer_name, - network_name, network_index, frame_credits_in_bytes); + bool start_fw_ddr_buffer_task = false; + for (auto& ddr_channels_pair : resources_manager.get_ddr_channel_pairs_per_context(context_index)) { + if (ddr_channels_pair.get().need_manual_credit_management()) { + const auto ddr_info = ddr_channels_pair.get().info(); + auto ddr_pair_action = DdrPairInfoAction::create(ddr_info.h2d_channel_id, ddr_info.d2h_channel_id, + ddr_channels_pair.get().descriptors_per_frame(), ddr_channels_pair.get().descs_count()); + CHECK_EXPECTED_AS_STATUS(ddr_pair_action); + configuration_actions.emplace_back(ddr_pair_action.release()); - case ProtoHEFEdgeConnectionType::PROTO__EDGE_CONNECTION_TYPE__INTERMEDIATE: - return fill_inter_context_input_layer(context_info, context_meta_data_head_pointer, resources_manager, - context_index, stream_index, *nn_stream_config, &edge_layer_proto, channels_parsing_info, - channels_to_unlock, network_index); - - case ProtoHEFEdgeConnectionType::PROTO__EDGE_CONNECTION_TYPE__DDR: - return fill_ddr_layer_multi_context(context_info, context_meta_data_head_pointer, resources_manager, context_index, - edge_layer_proto, *nn_stream_config, HAILO_H2D_STREAM, channels_parsing_info, frame_credits_in_bytes, - channels_to_unlock, network_index); - - default: - LOGGER__ERROR("Invalid edge connection type"); - return HAILO_INTERNAL_FAILURE; - } -} - -static hailo_status parse_and_fill_d2h_layer_multi_context( - CONTROL_PROTOCOL__context_switch_context_info_t *context_info, - uint8_t **context_meta_data_head_pointer, const ProtoHEFEdgeLayer &edge_layer_proto, - ContextSwitchChannelsParsingInfo &channels_parsing_info, ResourcesManager &resources_manager, - uint8_t context_index, std::set &channels_to_unlock, const std::string &network_name, - uint8_t network_index) -{ - uint8_t stream_index = 0; - uint32_t frame_credits_in_bytes = 0; - bool is_mux = false; - std::string layer_name; - CONTROL_PROTOCOL__nn_stream_config_t nn_stream_config = {}; - - const bool hw_padding_supported = HefConfigurator::is_hw_padding_supported(edge_layer_proto); - - if (ProtoHEFEdgeLayerType::PROTO__EDGE_LAYER_TYPE__INFO == edge_layer_proto.edge_layer_type()) { - CHECK(IS_FIT_IN_UINT8(edge_layer_proto.layer_info().edge_layer_base().sys_index()), HAILO_INVALID_HEF, - "Failed to parse HEF. Invalid sys_index: {}.", edge_layer_proto.layer_info().edge_layer_base().sys_index()); - stream_index = static_cast(edge_layer_proto.layer_info().edge_layer_base().sys_index()); - auto nn_stream_config_expected = HefConfigurator::parse_nn_stream_config(edge_layer_proto.layer_info().edge_layer_base(), - hw_padding_supported, edge_layer_proto.context_switch_info().edge_connection_type()); - CHECK_EXPECTED_AS_STATUS(nn_stream_config_expected, "Failed parse nn stream config"); - nn_stream_config = nn_stream_config_expected.release(); - frame_credits_in_bytes = (nn_stream_config.periph_bytes_per_buffer * nn_stream_config.core_buffers_per_frame); - is_mux = false; - layer_name = edge_layer_proto.layer_info().name(); - } else if (ProtoHEFEdgeLayerType::PROTO__EDGE_LAYER_TYPE__MUX == edge_layer_proto.edge_layer_type()) { - CHECK(IS_FIT_IN_UINT8(edge_layer_proto.layer_mux().edge_layer_base().sys_index()), HAILO_INVALID_HEF, - "Failed to parse HEF. Invalid sys_index: {}.", edge_layer_proto.layer_mux().edge_layer_base().sys_index()); - stream_index = static_cast(edge_layer_proto.layer_mux().edge_layer_base().sys_index()); - auto nn_stream_config_expected = HefConfigurator::parse_nn_stream_config(edge_layer_proto.layer_mux().edge_layer_base(), - hw_padding_supported, edge_layer_proto.context_switch_info().edge_connection_type()); - CHECK_EXPECTED_AS_STATUS(nn_stream_config_expected, "Failed parse nn stream config"); - nn_stream_config = nn_stream_config_expected.release(); - frame_credits_in_bytes = (nn_stream_config.periph_bytes_per_buffer * nn_stream_config.core_buffers_per_frame); - is_mux = true; - layer_name = edge_layer_proto.layer_mux().name(); - } else { - LOGGER__ERROR("Invalid layer type."); - return HAILO_INVALID_HEF; - } - - switch (edge_layer_proto.context_switch_info().edge_connection_type()) { - case ProtoHEFEdgeConnectionType::PROTO__EDGE_CONNECTION_TYPE__BOUNDARY: - return fill_boundary_output_layer(context_info, context_meta_data_head_pointer, resources_manager, - stream_index, nn_stream_config, frame_credits_in_bytes, channels_parsing_info, layer_name, - network_index, network_name); - - case ProtoHEFEdgeConnectionType::PROTO__EDGE_CONNECTION_TYPE__INTERMEDIATE: - CHECK(!is_mux, HAILO_INVALID_HEF, "Inter-context layer can't be mux."); - return fill_inter_context_output_layer(context_info, context_meta_data_head_pointer, resources_manager, - context_index, stream_index, nn_stream_config, frame_credits_in_bytes, - &edge_layer_proto, channels_parsing_info, channels_to_unlock, network_index, network_name); - - case ProtoHEFEdgeConnectionType::PROTO__EDGE_CONNECTION_TYPE__DDR: - return fill_ddr_layer_multi_context(context_info, context_meta_data_head_pointer, resources_manager, context_index, - edge_layer_proto, nn_stream_config, HAILO_D2H_STREAM, channels_parsing_info, frame_credits_in_bytes, - channels_to_unlock, network_index); - - default: - LOGGER__ERROR("Invalid edge connection type"); - return HAILO_INTERNAL_FAILURE; - } -} - -static hailo_status fill_ddr_buffers_info(CONTROL_PROTOCOL__context_switch_context_info_t *context_info, - uint8_t **context_meta_data_head_pointer, ResourcesManager &resources_manager, uint8_t context_index) -{ - const CONTROL_PROTOCOL__TRIGGER_t none_trigger = HEF_METADATA__build_none_trigger(); - bool found_ddr_pair_for_context = false; - // See: HRT-5373 - static const bool NOT_REPEATED = false; - - for (auto& ddr_info : resources_manager.ddr_infos()) { - if (context_index == ddr_info.context_index) { - /* Any action must have a trigger */ - auto status = HEF_METADATA__add_trigger_to_trigger_group(context_info, context_meta_data_head_pointer, - &none_trigger); - CHECK_SUCCESS(status, "failed to add NONE trigger before ddr buffer pair infos"); - /* Add ddr pair info action */ - status = HEF_METADATA__add_ddr_pair_info(context_info, context_meta_data_head_pointer, - ddr_info.h2d_channel_index, ddr_info.d2h_channel_index, ddr_info.descriptors_per_frame, - ddr_info.initial_programed_descs, NOT_REPEATED); - CHECK_SUCCESS(status,"failed to add ddr pair info"); - found_ddr_pair_for_context = true; + start_fw_ddr_buffer_task = true; } } - if (found_ddr_pair_for_context) { - /* No need to add NONE trigger. This action can be inside the last none trigger of the last DDR pair */ - auto status = HEF_METADATA__add_ddr_buffering_start(context_info, context_meta_data_head_pointer, NOT_REPEATED); - CHECK_SUCCESS(status,"failed to add ddr buffer start action"); + if (start_fw_ddr_buffer_task) { + auto start_ddr_buffering_action = StartDdrBufferingTaskAction::create(); + CHECK_EXPECTED_AS_STATUS(start_ddr_buffering_action); + configuration_actions.emplace_back(start_ddr_buffering_action.release()); } return HAILO_SUCCESS; } -static hailo_status parse_and_fill_edge_layers_mapping(ProtoHEFNetworkGroupPtr network_group_proto, - CONTROL_PROTOCOL__context_switch_context_info_t *context_info, - uint8_t **context_meta_data_head_pointer, const ProtoHEFContextMetadata *context_metadata, - ContextSwitchChannelsParsingInfo &channels_parsing_info, ResourcesManager &resources_manager, uint8_t context_index) +static hailo_status parse_and_fill_edge_layers_mapping( + CONTROL_PROTOCOL__context_switch_context_info_t *context_info, + uint8_t **context_meta_data_head_pointer, const ProtoHEFContextMetadata *context_metadata, + ResourcesManager &resources_manager, std::shared_ptr network_group_metadata, + uint8_t context_index) { hailo_status status = HAILO_UNINITIALIZED; - // We use those sets to unlock resources at the end of each context parsing to prevent reuse within the same context - std::set channels_to_unlock = {}; const auto number_of_edge_layers = context_metadata->edge_layers_size(); CHECK(0 < number_of_edge_layers, HAILO_INVALID_HEF, "No edge layers in this context"); CHECK(IS_FIT_IN_UINT8(number_of_edge_layers), HAILO_INVALID_HEF, "Failed to parse HEF. Invalid edge_layers_size: {}.", number_of_edge_layers); - for (const auto &edge_layer_proto : context_metadata->edge_layers()) { - auto support_multi_networks = resources_manager.get_supported_features().multi_network_support; - auto network_index = static_cast((support_multi_networks) ? edge_layer_proto.network_index() : 0); - auto partial_network_name = HefUtils::get_partial_network_name_by_index(*network_group_proto, network_index, - resources_manager.get_supported_features()); - CHECK_EXPECTED_AS_STATUS(partial_network_name); - auto network_name = HefUtils::get_network_name(*network_group_proto, partial_network_name.value()); - - if (ProtoHEFEdgeLayerDirection::PROTO__EDGE_LAYER_DIRECTION__HOST_TO_DEVICE == edge_layer_proto.direction()) { - status = parse_and_fill_h2d_layer_multi_context(context_info, context_meta_data_head_pointer, - edge_layer_proto, channels_parsing_info, resources_manager, context_index, channels_to_unlock, - network_name, network_index); - CHECK_SUCCESS(status); - } else if (ProtoHEFEdgeLayerDirection::PROTO__EDGE_LAYER_DIRECTION__DEVICE_TO_HOST == edge_layer_proto.direction()) { - status = parse_and_fill_d2h_layer_multi_context(context_info, context_meta_data_head_pointer, - edge_layer_proto, channels_parsing_info, resources_manager, context_index, channels_to_unlock, - network_name, network_index); - CHECK_SUCCESS(status); - } else { - LOGGER__ERROR("Invalid argument: stream_direction"); - return HAILO_INVALID_ARGUMENT; - } + auto boundary_output_layers = network_group_metadata->get_boundary_output_layer_infos(context_index); + auto boundary_input_layers = network_group_metadata->get_boundary_input_layer_infos(context_index); + auto inter_context_output_layers = network_group_metadata->get_inter_context_output_layer_infos(context_index); + auto inter_context_input_layers = network_group_metadata->get_inter_context_input_layer_infos(context_index); + auto ddr_output_layers = network_group_metadata->get_ddr_output_layer_infos(context_index); + auto ddr_input_layers = network_group_metadata->get_ddr_input_layer_infos(context_index); + + // Parse the edge layer by order - first output edge layers, then ddr inputs and only then the input edge layers + // In order to insure that input data can enter the chip only after all other elements are configured. + // We parse ddr inputs before boundary/inter-context because otherwise on C2C mode we may lose some credit. + + for (const auto &output_layer_info : ddr_output_layers) { + status = fill_ddr_output_layer(context_info, context_meta_data_head_pointer, + resources_manager, output_layer_info); + CHECK_SUCCESS(status); + } + + for (const auto &output_layer_info : boundary_output_layers) { + status = fill_boundary_output_layer(context_info, context_meta_data_head_pointer, + resources_manager, output_layer_info); + CHECK_SUCCESS(status); + } + + for (const auto &output_layer_info : inter_context_output_layers) { + status = fill_inter_context_output_layer(context_info, context_meta_data_head_pointer, + resources_manager, output_layer_info); + CHECK_SUCCESS(status); + } + + for (const auto &input_layer_info : ddr_input_layers) { + status = fill_ddr_input_layer(context_info, context_meta_data_head_pointer, + resources_manager, input_layer_info); + CHECK_SUCCESS(status); + } + + for (const auto &input_layer_info : boundary_input_layers) { + status = fill_boundary_input_layer(context_info, context_meta_data_head_pointer, + resources_manager, input_layer_info); + CHECK_SUCCESS(status); + } + + for (const auto &input_layer_info : inter_context_input_layers) { + status = fill_inter_context_input_layer(context_info, context_meta_data_head_pointer, + resources_manager, input_layer_info); + CHECK_SUCCESS(status); } /* UN-Lock resources at the end of the context - - d2h inter-context and DDR buffer channels, h2d inter-context and DDR buffer channels */ - for (auto& channel_index : channels_to_unlock) { - if (contains(channels_parsing_info.D2H_channels_in_use, channel_index)) { - channels_parsing_info.D2H_channels_in_use.erase(channel_index); - } else if (contains(channels_parsing_info.H2D_channels_in_use, channel_index)) { - channels_parsing_info.H2D_channels_in_use.erase(channel_index); - } else { - LOGGER__ERROR("channel_index {} was marked for unlocking, but is not marked as in use in context {}", - channel_index, context_index); - return HAILO_INTERNAL_FAILURE; - } + h2d inter-context, d2h inter-context and DDR buffer channels */ + for (const auto &input_layer_info : inter_context_input_layers) { + status = resources_manager.free_channel_index(to_layer_identifier(input_layer_info)); + CHECK_SUCCESS(status); } - status = fill_ddr_buffers_info(context_info, context_meta_data_head_pointer, resources_manager, context_index); - CHECK_SUCCESS(status); + for (const auto &output_layer_info : inter_context_output_layers) { + status = resources_manager.free_channel_index(to_layer_identifier(output_layer_info)); + CHECK_SUCCESS(status); + } + + for (const auto &output_layer_info : ddr_output_layers) { + status = resources_manager.free_channel_index(to_layer_identifier(output_layer_info, VdmaChannel::Direction::H2D)); + CHECK_SUCCESS(status); + + status = resources_manager.free_channel_index(to_layer_identifier(output_layer_info, VdmaChannel::Direction::D2H)); + CHECK_SUCCESS(status); + } return HAILO_SUCCESS; } @@ -1845,67 +1954,95 @@ static std::map get_start_indexes_of_repeated_actions( return result; } -static std::set get_end_indexes_of_action_type( +static std::set> get_indexes_of_action_type( const std::vector &actions, const std::vector> &repeated_indexes, const ContextSwitchConfigAction::Type &required_action_type) { - std::set result; + std::set> result; for (const auto &index_pair : repeated_indexes) { const auto curr_action_type = actions[index_pair.first]->get_type(); if (required_action_type != curr_action_type) { continue; } + result.emplace(index_pair); + } + + return result; +} + +static std::set get_end_indexes_of_action_type( + const std::vector &actions, + const std::vector> &repeated_indexes, + const ContextSwitchConfigAction::Type &required_action_type) +{ + std::set result; + for (const auto &index_pair : get_indexes_of_action_type(actions, repeated_indexes, required_action_type)) { result.insert(index_pair.second); } return result; } +static hailo_status push_fetch_config_actions( + std::vector &config_resources, const std::set &pending_config_stream_indexes, + std::vector &total_ccw_bursts, const bool support_pre_fetch, + std::vector &processed_configuration_actions) +{ + CHECK(total_ccw_bursts.size() == config_resources.size(), HAILO_INTERNAL_FAILURE, "Invalid cfg channels count"); + for (const auto config_stream_index : pending_config_stream_indexes) { + CHECK(config_stream_index < config_resources.size(), HAILO_INTERNAL_FAILURE, "Invalid cfg channel index"); + + auto fetch_config_action = support_pre_fetch ? + AddCcwBurstAction::create(config_stream_index, total_ccw_bursts[config_stream_index]) : + CreateConfigDescAndFetchAction::create(config_stream_index, config_resources[config_stream_index]); + CHECK_EXPECTED_AS_STATUS(fetch_config_action); + + // Add the current action + processed_configuration_actions.emplace_back(fetch_config_action.release()); + } + + return HAILO_SUCCESS; +} + static hailo_status proccess_write_ccw_action(const ContextSwitchConfigActionPtr &configuration_action, - std::vector &config_resources, std::set &pending_cfg_ch_buffer, + std::vector &config_resources, std::set &pending_config_stream_indexes, std::vector &total_ccw_bursts, const std::set &end_indexes_of_write_ccw_actions, const uint32_t &action_index, const bool support_pre_fetch, std::vector &processed_configuration_actions) { - // Add the config channel index of the current WriteDataCcwAction - const auto cfg_channel_index = configuration_action->get_proto_action().write_data_ccw().cfg_channel_index(); - CHECK(IS_FIT_IN_UINT8(cfg_channel_index), HAILO_INVALID_HEF, - "Failed to parse HEF. Invalid cfg_channel_index: {}.", cfg_channel_index); - pending_cfg_ch_buffer.insert(static_cast(cfg_channel_index)); + // Add the config stream index of the current WriteDataCcwAction + const auto config_stream_index = configuration_action->get_proto_action().write_data_ccw().cfg_channel_index(); + CHECK(IS_FIT_IN_UINT8(config_stream_index), HAILO_INVALID_HEF, + "Failed to parse HEF. Invalid config_stream_index: {}.", config_stream_index); + pending_config_stream_indexes.insert(static_cast(config_stream_index)); - /* TODO - get CCW headers from proto (need to add it into the proto) */ - //const auto ccw_bursts = configuration_action->get_proto_action().write_data_ccw().ccw_bursts(); + // TODO: get CCW headers from proto (need to add it into the proto) + // const auto ccw_bursts = configuration_action->get_proto_action().write_data_ccw().ccw_bursts(); const uint16_t ccw_bursts = 1; - auto accum_ccw_bursts = total_ccw_bursts[cfg_channel_index] + ccw_bursts; + auto accum_ccw_bursts = total_ccw_bursts[config_stream_index] + ccw_bursts; CHECK(IS_FIT_IN_UINT16(accum_ccw_bursts), HAILO_INTERNAL_FAILURE, "Failed to parse HEF. action fetch ccw burst supports only to 2^16 bursts."); - total_ccw_bursts[cfg_channel_index] = static_cast(accum_ccw_bursts); + total_ccw_bursts[config_stream_index] = static_cast(accum_ccw_bursts); // At the end of a consecutive group of WriteDataCcwActions, we program the // descriptors for all the config channels used. if (contains(end_indexes_of_write_ccw_actions, action_index)) { - /* Add the last CCW write into the buffer */ + // Add the last CCW write into the buffer processed_configuration_actions.emplace_back(configuration_action); - CHECK(total_ccw_bursts.size() == config_resources.size(), HAILO_INTERNAL_FAILURE, "Invalid cfg channels count"); - for (const auto cfg_channel : pending_cfg_ch_buffer) { - CHECK(cfg_channel < config_resources.size(), HAILO_INTERNAL_FAILURE, "Invalid cfg channel index"); - - auto fetch_config_action = support_pre_fetch ? - AddCcwBurstAction::create(cfg_channel, total_ccw_bursts[cfg_channel]) : - CreateConfigDescAndFetchAction::create(cfg_channel, config_resources[cfg_channel]); - CHECK_EXPECTED_AS_STATUS(fetch_config_action); - processed_configuration_actions.emplace_back(fetch_config_action.release()); - } + auto status = push_fetch_config_actions(config_resources, pending_config_stream_indexes, total_ccw_bursts, + support_pre_fetch, processed_configuration_actions); + CHECK_SUCCESS(status); - /* Cleanups */ - pending_cfg_ch_buffer.clear(); + // Cleanups + pending_config_stream_indexes.clear(); for (uint8_t cleanup_ch_index = 0; cleanup_ch_index < total_ccw_bursts.size(); cleanup_ch_index++) { total_ccw_bursts[cleanup_ch_index] = 0; } } else { + // Add the current action processed_configuration_actions.emplace_back(configuration_action); } @@ -1913,13 +2050,29 @@ static hailo_status proccess_write_ccw_action(const ContextSwitchConfigActionPtr } static hailo_status proccess_trigger_new_data_input_action(const ContextSwitchConfigActionPtr &configuration_action, - const std::set &end_indexes_of_trigger_new_data_input_actions, - const uint32_t &action_index, std::vector &processed_configuration_actions) + uint32_t trigger_new_data_from_input_group_start, + uint32_t trigger_new_data_from_input_group_end, + const uint32_t &action_index, + const ResourcesManager &resources_manager, + uint8_t context_index, + std::vector &processed_configuration_actions) { + if (trigger_new_data_from_input_group_start == action_index) { + auto action = EdgeLayerActivationActionsPositionMarker::create(); + CHECK_EXPECTED_AS_STATUS(action); + processed_configuration_actions.emplace_back(action.release()); + + // DDR buffer info actions need to happen after the edge layer activation actions. + const auto status = add_ddr_buffers_info(processed_configuration_actions, resources_manager, context_index); + CHECK_SUCCESS(status); + } + + // Add the current action + processed_configuration_actions.emplace_back(configuration_action); + // At the end of a consecutive group of TriggerNewDataFromDataInput actions, we can trigger the BurstCreditsTask // in the FW, via StartBurstCreditsTaskAction. - processed_configuration_actions.emplace_back(configuration_action); - if (contains(end_indexes_of_trigger_new_data_input_actions, action_index)) { + if (trigger_new_data_from_input_group_end == action_index) { auto start_burst_credits_task_action = StartBurstCreditsTaskAction::create(); CHECK_EXPECTED_AS_STATUS(start_burst_credits_task_action); processed_configuration_actions.emplace_back(start_burst_credits_task_action.release()); @@ -1928,59 +2081,118 @@ static hailo_status proccess_trigger_new_data_input_action(const ContextSwitchCo return HAILO_SUCCESS; } -// Adds context switch configuration actions that don't appear in the HEF: -// * If groups of consecutive actions can be "merged" as repeated actions (saving room the FW's -// action list) a RepeatedHeaderAction is placed before the relevant actions. -// See also: CONTROL_PROTOCOL__REPEATED_ACTION_t's documnetion in control_protocol.h. -// * At the end of each consecutive group of WriteDataCcwAction, a CreateConfigDescAndFetchAction is added. -// * At the end of each consecutive group of TriggerNewDataFromDataInput, a StartBurstCreditsTaskAction is added. -static Expected> process_configuration_actions( - std::vector &input_configuration_actions, - std::vector &config_resources, const bool support_pre_fetch) +// At the end of each consecutive group of WriteDataCcwAction, a CreateConfigDescAndFetchAction is added. +static hailo_status add_fetch_config_actions(std::vector &configuration_actions, + std::vector &config_resources, bool support_pre_fetch) { - std::vector processed_configuration_actions; + const auto repeated_indexes = get_repreated_actions_boundary_indices(configuration_actions); + const auto end_indexes_of_write_ccws = get_end_indexes_of_action_type(configuration_actions, + repeated_indexes, ContextSwitchConfigAction::Type::WriteDataCcw); - std::set pending_cfg_ch_buffer; + std::set pending_config_stream_indexes; std::vector total_ccw_bursts(config_resources.size(), 0); + std::vector processed_configuration_actions; + for (uint32_t action_index = 0; action_index < configuration_actions.size(); action_index++) { + const auto &configuration_action = configuration_actions[action_index]; + if (ContextSwitchConfigAction::Type::WriteDataCcw == configuration_action->get_type()) { + auto status = proccess_write_ccw_action(configuration_action, config_resources, pending_config_stream_indexes, + total_ccw_bursts, end_indexes_of_write_ccws, action_index, support_pre_fetch, processed_configuration_actions); + CHECK_SUCCESS(status); + } else { + // Add the current action + processed_configuration_actions.emplace_back(configuration_action); + } + } - const auto repeated_indexes = get_repreated_actions_boundary_indices(input_configuration_actions); + // Replace the original configuration actions with the processed ones. + configuration_actions = processed_configuration_actions; + + return HAILO_SUCCESS; +} + +// For any context with edge layers (the preliminary context when in preliminary_run_asap mode or dynamic contexts), +// we need to add the following: +// * Edge layer activation actions - the fw places the edge layer activation actions in the action list based on the +// postion marked by the EdgeLayerActivationActionsPositionMarker. In both dynamic and preliminary contexts, these +// actions should ocurr right before the first TriggerNewDataFromDataInput action. Hence, the +// EdgeLayerActivationActionsPositionMarker will be placed at the first occurence of a TriggerNewDataFromDataInput action. +// * DdrPairInfoActions - need to happen after the edge layer activation actions. We'll place them right after the +// EdgeLayerActivationActionsPositionMarker. +// * StartBurstCreditsTaskAction - needs to happen after all the DdrPairInfoActions +static hailo_status handle_edge_layer_activation_actions(std::vector &configuration_actions, + const ResourcesManager &resources_manager, uint8_t context_index, bool is_preliminary_context, bool is_first_operation) +{ + if (is_preliminary_context && !resources_manager.get_supported_features().preliminary_run_asap) { + // Nothing to do - no edge layers in the preliminary context if not running in preliminary_run_asap mode. + return HAILO_SUCCESS; + } + if (!is_preliminary_context && !is_first_operation) { + // Nothing to do - edge layers in dynamic contexts only appear in the first operation. + return HAILO_SUCCESS; + } + + const auto repeated_indexes = get_repreated_actions_boundary_indices(configuration_actions); + const auto trigger_new_data_from_input_group_indexes = get_indexes_of_action_type( + configuration_actions, repeated_indexes, ContextSwitchConfigAction::Type::TriggerNewDataFromDataInput); + CHECK(trigger_new_data_from_input_group_indexes.size() == 1, HAILO_INTERNAL_FAILURE, + "Expected only one group of TriggerNewDataFromDataInput actions"); + const auto trigger_new_data_from_input_group_start = trigger_new_data_from_input_group_indexes.cbegin()->first; + const auto trigger_new_data_from_input_group_end = trigger_new_data_from_input_group_indexes.cbegin()->second; + + std::vector processed_configuration_actions; + for (uint32_t action_index = 0; action_index < configuration_actions.size(); action_index++) { + const auto &configuration_action = configuration_actions[action_index]; + if (ContextSwitchConfigAction::Type::TriggerNewDataFromDataInput == configuration_action->get_type()) { + auto status = proccess_trigger_new_data_input_action(configuration_action, + trigger_new_data_from_input_group_start, trigger_new_data_from_input_group_end, action_index, + resources_manager, context_index, processed_configuration_actions); + CHECK_SUCCESS(status); + } else { + // Add the current action + processed_configuration_actions.emplace_back(configuration_action); + } + } + + // Replace the original configuration actions with the processed ones. + configuration_actions = processed_configuration_actions; + + return HAILO_SUCCESS; +} + +// If groups of consecutive actions can be "merged" as repeated actions (saving room the FW's +// action list) a RepeatedHeaderAction is placed before the relevant actions. +// See also: CONTROL_PROTOCOL__REPEATED_ACTION_t's documnetion in control_protocol.h. +static hailo_status handle_repeated_actions(std::vector &configuration_actions) +{ + const auto repeated_indexes = get_repreated_actions_boundary_indices(configuration_actions); const auto start_indexes_of_repeated_actions = get_start_indexes_of_repeated_actions( - input_configuration_actions, repeated_indexes); - const auto end_indexes_of_write_ccws = get_end_indexes_of_action_type(input_configuration_actions, - repeated_indexes, ContextSwitchConfigAction::Type::WriteDataCcw); - const auto end_indexes_of_trigger_new_data_from_inputs = get_end_indexes_of_action_type( - input_configuration_actions, repeated_indexes, ContextSwitchConfigAction::Type::TriggerNewDataFromDataInput); - for (uint32_t action_index = 0; action_index < input_configuration_actions.size(); action_index++) { - // A group of actions can be "merged" as repeated actions. - // Hence we add a RepeatedHeaderAction and mark all the actions in this group as "reapted" + configuration_actions, repeated_indexes); + + std::vector processed_configuration_actions; + processed_configuration_actions.reserve(configuration_actions.size() + start_indexes_of_repeated_actions.size()); + for (uint32_t action_index = 0; action_index < configuration_actions.size(); action_index++) { if (contains(start_indexes_of_repeated_actions, action_index)) { + // A group of actions can be "merged" as repeated actions. + // Add a RepeatedHeaderAction const auto num_repeates = start_indexes_of_repeated_actions.at(action_index); - auto create_repeated_action = RepeatedHeaderAction::create( - input_configuration_actions[action_index]->get_action_list_type(), num_repeates); - CHECK_EXPECTED(create_repeated_action); - processed_configuration_actions.emplace_back(create_repeated_action.release()); + const auto sub_action_type = configuration_actions[action_index]->get_action_list_type(); + auto repeated_header_action = RepeatedHeaderAction::create(sub_action_type, num_repeates); + CHECK_EXPECTED_AS_STATUS(repeated_header_action); + processed_configuration_actions.emplace_back(repeated_header_action.release()); + // Mark all the actions in this group as "reapted" for (uint32_t repeated_offset = 0; repeated_offset < num_repeates; repeated_offset++) { - input_configuration_actions[action_index + repeated_offset]->set_is_in_repeated_block(true); + configuration_actions[action_index + repeated_offset]->set_is_in_repeated_block(true); } } // Add the current action - const auto &configuration_action = input_configuration_actions[action_index]; - if (ContextSwitchConfigAction::Type::WriteDataCcw == configuration_action->get_type()) { - auto status = proccess_write_ccw_action(configuration_action, config_resources, pending_cfg_ch_buffer, - total_ccw_bursts, end_indexes_of_write_ccws, action_index, support_pre_fetch, processed_configuration_actions); - CHECK_SUCCESS_AS_EXPECTED(status); - } else if (ContextSwitchConfigAction::Type::TriggerNewDataFromDataInput == configuration_action->get_type()) { - auto status = proccess_trigger_new_data_input_action(configuration_action, end_indexes_of_trigger_new_data_from_inputs, - action_index, processed_configuration_actions); - CHECK_SUCCESS_AS_EXPECTED(status); - } else { - // Add the current action - processed_configuration_actions.emplace_back(configuration_action); - } + processed_configuration_actions.emplace_back(configuration_actions[action_index]); } - return processed_configuration_actions; + // Replace the original configuration actions with the processed ones. + configuration_actions = processed_configuration_actions; + + return HAILO_SUCCESS; } static bool is_mercury_device_type(const ProtoHEFHwArch &hw_arch) @@ -1991,65 +2203,77 @@ static bool is_mercury_device_type(const ProtoHEFHwArch &hw_arch) } static hailo_status parse_actions_in_operation(const ProtoHEFOperation &operation, - Device &device, const ProtoHEFHwArch &hw_arch, std::vector &config_resources, - const ResourcesManager &resources_manager, ProtoHEFNetworkGroupPtr network_group_proto, + Device &device, const ProtoHEFHwArch &hw_arch, uint8_t context_index, bool is_preliminary_context, + bool is_first_operation, std::vector &config_resources, + const ResourcesManager &resources_manager, + const ProtoHEFNetworkGroup &network_group_proto, CONTROL_PROTOCOL__context_switch_context_info_t &context_info, uint8_t **context_meta_data_head_pointer) { - auto support_pre_fetch = is_mercury_device_type(hw_arch); - + const auto support_pre_fetch = is_mercury_device_type(hw_arch); // First, the context switch configuration actions from the HEF are added in their order of // appearance (which is chronological). std::vector configuration_actions; + configuration_actions.reserve(operation.actions_size()); for (const auto &proto_action : operation.actions()) { auto configuration_action = ContextSwitchConfigAction::create(proto_action, device, config_resources, - resources_manager, *network_group_proto, support_pre_fetch); + resources_manager, network_group_proto, support_pre_fetch); CHECK_EXPECTED_AS_STATUS(configuration_action); configuration_actions.emplace_back(configuration_action.release()); } - // Next, we process the actions from the HEF, adding 'CreateConfigDescAndFetchAction's and 'RepeatedHeaderAction's. - // The resulting vector contains the configuration actions to be executed in chronological order. - const auto processed_configuration_actions = process_configuration_actions(configuration_actions, config_resources, - support_pre_fetch); - CHECK_EXPECTED_AS_STATUS(processed_configuration_actions); + // Next, we process the actions from the HEF. The resulting vector contains the configuration actions to be + // executed in chronological order. + auto status = add_fetch_config_actions(configuration_actions, config_resources, support_pre_fetch); + CHECK_SUCCESS(status); + status = handle_edge_layer_activation_actions(configuration_actions, resources_manager, context_index, + is_preliminary_context, is_first_operation); + CHECK_SUCCESS(status); + status = handle_repeated_actions(configuration_actions); + CHECK_SUCCESS(status); // Finally, we execute the context switch configuration actions. - for (const auto &configuration_action : processed_configuration_actions.value()) { - const auto status = configuration_action->execute(&context_info, context_meta_data_head_pointer); + for (const auto &configuration_action : configuration_actions) { + status = configuration_action->execute(&context_info, context_meta_data_head_pointer); CHECK_SUCCESS(status); } return HAILO_SUCCESS; } -static hailo_status fill_context_recepies_for_multi_context(ProtoHEFNetworkGroupPtr network_group_proto, +static hailo_status fill_context_recepies_for_multi_context(const ProtoHEFNetworkGroup &network_group_proto, const ProtoHEFHwArch &hw_arch, CONTROL_PROTOCOL__context_switch_context_info_t &context_info, ResourcesManager &resources_manager, uint8_t context_index, const ProtoHEFContext &proto_context, - Device &device, ContextSwitchChannelsParsingInfo &channels_parsing_info) + std::shared_ptr network_group_metadata, + Device &device) { hailo_status status = HAILO_UNINITIALIZED; uint8_t *context_meta_data_head_pointer = context_info.context_network_data; // Add edge layers mapping - status = parse_and_fill_edge_layers_mapping(network_group_proto, &context_info, &context_meta_data_head_pointer, - &proto_context.metadata(), channels_parsing_info, resources_manager, context_index); + status = parse_and_fill_edge_layers_mapping(&context_info, &context_meta_data_head_pointer, + &proto_context.metadata(), resources_manager, network_group_metadata, context_index); CHECK_SUCCESS(status); CHECK(IS_FIT_IN_UINT8(proto_context.operations_size()), HAILO_INVALID_HEF, "Failed to parse HEF. Invalid operations_count: {}.", proto_context.operations_size()); - context_info.context_stream_remap_data.should_use_stream_remap = static_cast(proto_context.metadata().shmiglue_info().should_use_shmiglue()); + context_info.context_stream_remap_data.should_use_stream_remap = static_cast( + proto_context.metadata().shmiglue_info().should_use_shmiglue()); // Parse context + bool first_operation = true; for (const auto &operation : proto_context.operations()) { const auto operation_trigger = ContextSwitchTrigger::create(operation.trigger()); CHECK_EXPECTED_AS_STATUS(operation_trigger); operation_trigger->add_to_trigger_group(&context_info, &context_meta_data_head_pointer); - status = parse_actions_in_operation(operation, device, hw_arch, - resources_manager.dynamic_config(context_index), resources_manager, network_group_proto, context_info, - &context_meta_data_head_pointer); + static const auto NOT_PRELIMINARY_CONTEXT = false; + status = parse_actions_in_operation(operation, device, hw_arch, context_index, NOT_PRELIMINARY_CONTEXT, + first_operation, resources_manager.dynamic_config(context_index), resources_manager, network_group_proto, + context_info, &context_meta_data_head_pointer); CHECK_SUCCESS(status); + + first_operation = false; } // update context_network_data_length per context, and dynamic_contexts_descriptors count in main header @@ -2061,8 +2285,9 @@ static hailo_status fill_context_recepies_for_multi_context(ProtoHEFNetworkGroup static hailo_status fill_preliminary_config_recepies_for_multi_context(const ProtoHEFHwArch &hw_arch, CONTROL_PROTOCOL__context_switch_context_info_t &context_info, ResourcesManager &resources_manager, - ContextSwitchChannelsParsingInfo &channels_parsing_info, ProtoHEFNetworkGroupPtr network_group_proto, - const ProtoHEFPreliminaryConfig &proto_preliminary_config, Device &device) + const ProtoHEFNetworkGroup &network_group_proto, + const ProtoHEFPreliminaryConfig &proto_preliminary_config, + std::shared_ptr network_group_metadata, Device &device) { uint8_t *context_meta_data_head_pointer = context_info.context_network_data; @@ -2072,22 +2297,27 @@ static hailo_status fill_preliminary_config_recepies_for_multi_context(const Pro if (resources_manager.get_supported_features().preliminary_run_asap) { // Add edge layers mapping (only preliminary_run_asap networks have edge layers in the preliminary context) static const auto PRELIMINARY_CONTEXT_INDEX = 0; - auto status = parse_and_fill_edge_layers_mapping(network_group_proto, &context_info, &context_meta_data_head_pointer, - &(network_group_proto->contexts(PRELIMINARY_CONTEXT_INDEX).metadata()), channels_parsing_info, resources_manager, - PRELIMINARY_CONTEXT_INDEX); + auto status = parse_and_fill_edge_layers_mapping(&context_info, &context_meta_data_head_pointer, + &(network_group_proto.contexts(PRELIMINARY_CONTEXT_INDEX).metadata()), resources_manager, + network_group_metadata, PRELIMINARY_CONTEXT_INDEX); CHECK_SUCCESS(status); } // Parse preliminary config + bool first_operation = true; for (const auto &operation_proto : proto_preliminary_config.operation()) { const auto operation_trigger = ContextSwitchTrigger::create(operation_proto.trigger()); CHECK_EXPECTED_AS_STATUS(operation_trigger); operation_trigger->add_to_trigger_group(&context_info, &context_meta_data_head_pointer); - const auto status = parse_actions_in_operation(operation_proto, device, hw_arch, - resources_manager.preliminary_config(), resources_manager, network_group_proto, context_info, - &context_meta_data_head_pointer); + static const auto PRELIMINARY_CONTEXT_INDEX = 0; + static const auto PRELIMINARY_CONTEXT = true; + const auto status = parse_actions_in_operation(operation_proto, device, hw_arch, PRELIMINARY_CONTEXT_INDEX, + PRELIMINARY_CONTEXT, first_operation, resources_manager.preliminary_config(), resources_manager, + network_group_proto, context_info, &context_meta_data_head_pointer); CHECK_SUCCESS(status); + + first_operation = false; } // Update context_network_data_length per context, and preliminary_context_descriptors count in main header @@ -2098,21 +2328,19 @@ static hailo_status fill_preliminary_config_recepies_for_multi_context(const Pro } Expected> Hef::Impl::create_resources_manager( - ProtoHEFNetworkGroupPtr network_group_proto, uint8_t net_group_index, + const ProtoHEFNetworkGroup &network_group_proto, uint8_t net_group_index, VdmaDevice &device, HailoRTDriver &driver, const ConfigureNetworkParams &config_params, std::shared_ptr network_group_metadata, const ProtoHEFHwArch &hw_arch) { - CHECK_ARG_NOT_NULL_AS_EXPECTED(network_group_proto); - - CHECK(network_group_proto->contexts_size() <= MAX_CONTEXTS_COUNT, make_unexpected(HAILO_INVALID_HEF), - "App '{}' contains more contexts than allowed ({} > {})", network_group_proto->network_group_metadata().network_group_name(), - network_group_proto->contexts_size(), MAX_CONTEXTS_COUNT); + CHECK(network_group_proto.contexts_size() <= MAX_CONTEXTS_COUNT, make_unexpected(HAILO_INVALID_HEF), + "App '{}' contains more contexts than allowed ({} > {})", network_group_proto.network_group_metadata().network_group_name(), + network_group_proto.contexts_size(), MAX_CONTEXTS_COUNT); for (auto &network_params : config_params.network_params_by_name) { CHECK(HAILO_MAX_BATCH_SIZE >= network_params.second.batch_size, make_unexpected(HAILO_INVALID_ARGUMENT), "Given batch size ({}) for network group {}, network {} is bigger than max allowed ({})", network_params.second.batch_size, - network_group_proto->network_group_metadata().network_group_name(), network_params.first, HAILO_MAX_BATCH_SIZE); + network_group_proto.network_group_metadata().network_group_name(), network_params.first, HAILO_MAX_BATCH_SIZE); } auto parsing_info = Hef::Impl::get_parsing_info(network_group_proto); @@ -2125,32 +2353,23 @@ Expected> Hef::Impl::create_resources_manager( auto preliminary_context = resources_manager->add_new_context(); CHECK_EXPECTED(preliminary_context); - // TODO: Support sharing of ContextSwitchChannelsParsingInfo between fill_preliminary_config_recepies_for_multi_context - // and fill_context_recepies_for_multi_context (HRT-6683). - // When running in preliminary_run_asap mode the channels used by the first dynamic context and by the preliminary - // context are the same channels. We need to add the sane edge_layers for both contexts (look for calls to - // hef_metadata__add_edge_layer_header) so that the needed actions will be added to the action list. However, - // due to the logic in ResourceManager::get_available_channel_index's blacklist channels that should be the same - // will get different indexes. Need to refactor the logic of that function and the entire channel index allocation logic. - ContextSwitchChannelsParsingInfo channels_parsing_info_preliminary{}; auto status = fill_preliminary_config_recepies_for_multi_context(hw_arch, preliminary_context.value().get(), - resources_manager.value(), channels_parsing_info_preliminary, network_group_proto, network_group_proto->preliminary_config(), - device); + resources_manager.value(), network_group_proto, network_group_proto.preliminary_config(), + network_group_metadata, device); CHECK_SUCCESS_AS_EXPECTED(status); resources_manager->update_preliminary_config_buffer_info(); - ContextSwitchChannelsParsingInfo channels_parsing_info_dynamic{}; - for (uint8_t context_index = 0; context_index < network_group_proto->contexts_size(); ++context_index) { + for (uint8_t context_index = 0; context_index < network_group_proto.contexts_size(); ++context_index) { auto new_context = resources_manager->add_new_context(); CHECK_EXPECTED(new_context); status = fill_context_recepies_for_multi_context(network_group_proto, hw_arch, new_context.value().get(), resources_manager.value(), - context_index, network_group_proto->contexts(context_index), device, channels_parsing_info_dynamic); + context_index, network_group_proto.contexts(context_index), network_group_metadata, device); CHECK_SUCCESS_AS_EXPECTED(status); } resources_manager->update_dynamic_contexts_buffer_info(); - status = resources_manager->create_internal_vdma_channels(); + status = resources_manager->create_fw_managed_vdma_channels(); CHECK_SUCCESS_AS_EXPECTED(status); auto resources_manager_ptr = make_shared_nothrow(resources_manager.release()); @@ -2623,8 +2842,8 @@ hailo_status WriteDataCcwAction::pad_with_nops() /* If buffer does not fit info descriptor, the host must pad the buffer with CCW NOPs. */ auto nop_count = (buffer_residue == 0) ? 0 : ((page_size - buffer_residue) / CCW_HEADER_SIZE); for (uint8_t nop_index = 0; nop_index < nop_count; nop_index++) { - /* Generate nop tranaction. - CCW of all zeros (64’h0) should be treated as NOP – ignore CCW and expect CCW in next 64b word. + /* Generate nop transaction. + CCW of all zeros (64'h0) should be treated as NOP - ignore CCW and expect CCW in next 64b word. When CSM recognize it is a NOP it pops it from the channel FIFO without forward any address/data/command, does not contribute to CRC calculations but return credits to the peripheral as usual. */ m_config.write(reinterpret_cast(&CCW_NOP), sizeof(CCW_NOP)); @@ -2660,16 +2879,16 @@ bool WriteDataCcwAction::supports_repeated_block() const return false; } -Expected AddCcwBurstAction::create(uint8_t channel_index, uint16_t ccw_bursts) +Expected AddCcwBurstAction::create(uint8_t config_stream_index, uint16_t ccw_bursts) { - auto result = ContextSwitchConfigActionPtr(new (std::nothrow) AddCcwBurstAction(channel_index, ccw_bursts)); + auto result = ContextSwitchConfigActionPtr(new (std::nothrow) AddCcwBurstAction(config_stream_index, ccw_bursts)); CHECK_AS_EXPECTED((nullptr != result), HAILO_OUT_OF_HOST_MEMORY); return result; } -AddCcwBurstAction::AddCcwBurstAction(uint8_t channel_index, uint16_t ccw_bursts) : +AddCcwBurstAction::AddCcwBurstAction(uint8_t config_stream_index, uint16_t ccw_bursts) : ContextSwitchConfigAction(Type::AddCcwBurst), - m_channel_index(channel_index), + m_config_stream_index(config_stream_index), m_ccw_bursts(ccw_bursts) {} @@ -2677,7 +2896,7 @@ hailo_status AddCcwBurstAction::execute(CONTROL_PROTOCOL__context_switch_context uint8_t **context_meta_data_head_pointer) { return HEF_METADATA__add_ccw_bursts_action(context_info, context_meta_data_head_pointer, - m_ccw_bursts, m_channel_index, m_is_in_repeated_block); + m_ccw_bursts, m_config_stream_index, m_is_in_repeated_block); } bool AddCcwBurstAction::supports_repeated_block() const @@ -2685,16 +2904,18 @@ bool AddCcwBurstAction::supports_repeated_block() const return false; } -Expected CreateConfigDescAndFetchAction::create(uint8_t channel_index, ConfigBuffer &config_buffer) +Expected CreateConfigDescAndFetchAction::create(uint8_t config_stream_index, + ConfigBuffer &config_buffer) { - auto result = ContextSwitchConfigActionPtr(new (std::nothrow) CreateConfigDescAndFetchAction(channel_index, config_buffer)); + auto result = ContextSwitchConfigActionPtr(new (std::nothrow) CreateConfigDescAndFetchAction(config_stream_index, + config_buffer)); CHECK_AS_EXPECTED((nullptr != result), HAILO_OUT_OF_HOST_MEMORY); return result; } -CreateConfigDescAndFetchAction::CreateConfigDescAndFetchAction(uint8_t channel_index, ConfigBuffer &config_buffer) : +CreateConfigDescAndFetchAction::CreateConfigDescAndFetchAction(uint8_t config_stream_index, ConfigBuffer &config_buffer) : ContextSwitchConfigAction(Type::CreateDescForCcw), - m_channel_index(channel_index), + m_config_stream_index(config_stream_index), m_config_buffer(config_buffer) {} @@ -2707,7 +2928,7 @@ hailo_status CreateConfigDescAndFetchAction::execute(CONTROL_PROTOCOL__context_s CHECK(IS_FIT_IN_UINT16(desc_count.value()), HAILO_INVALID_OPERATION, "On cfg with continuous mode, max descriptors size must fit in uint16_t"); return HEF_METADATA__add_read_vdma_action(context_info, context_meta_data_head_pointer, - static_cast(desc_count.value()), m_channel_index, m_is_in_repeated_block); + static_cast(desc_count.value()), m_config_stream_index, m_is_in_repeated_block); } bool CreateConfigDescAndFetchAction::supports_repeated_block() const @@ -3043,6 +3264,86 @@ bool WaitForModuleConfigDoneAction::supports_repeated_block() const return false; } +Expected DdrPairInfoAction::create(const vdma::ChannelId &h2d_channel_id, + const vdma::ChannelId &d2h_channel_id, uint32_t descriptors_per_frame, uint16_t descs_count) +{ + auto result = ContextSwitchConfigActionPtr(new (std::nothrow) DdrPairInfoAction( + h2d_channel_id, d2h_channel_id, descriptors_per_frame, descs_count)); + CHECK_AS_EXPECTED((nullptr != result), HAILO_OUT_OF_HOST_MEMORY); + return result; +} + +DdrPairInfoAction::DdrPairInfoAction(const vdma::ChannelId &h2d_channel_id, const vdma::ChannelId &d2h_channel_id, + uint32_t descriptors_per_frame, uint16_t descs_count) : + ContextSwitchConfigAction(Type::DdrPairInfo, CONTROL_PROTOCOL__CONTEXT_SWITCH_ACTION_ADD_DDR_PAIR_INFO), + m_h2d_channel_id(h2d_channel_id), + m_d2h_channel_id(d2h_channel_id), + m_descriptors_per_frame(descriptors_per_frame), + m_descs_count(descs_count) +{} + +hailo_status DdrPairInfoAction::execute(CONTROL_PROTOCOL__context_switch_context_info_t *context_info, + uint8_t **context_meta_data_head_pointer) +{ + return HEF_METADATA__add_ddr_pair_info(context_info, context_meta_data_head_pointer, m_h2d_channel_id, + m_d2h_channel_id, m_descriptors_per_frame, m_descs_count, m_is_in_repeated_block); +} + +bool DdrPairInfoAction::supports_repeated_block() const +{ + return true; +} + +Expected StartDdrBufferingTaskAction::create() +{ + auto result = ContextSwitchConfigActionPtr(new (std::nothrow) StartDdrBufferingTaskAction()); + CHECK_AS_EXPECTED((nullptr != result), HAILO_OUT_OF_HOST_MEMORY); + return result; +} + +StartDdrBufferingTaskAction::StartDdrBufferingTaskAction() : + ContextSwitchConfigAction(Type::StartDdrBufferingTask, CONTROL_PROTOCOL__CONTEXT_SWITCH_ACTION_ADD_DDR_BUFFERING_START) +{} + +hailo_status StartDdrBufferingTaskAction::execute(CONTROL_PROTOCOL__context_switch_context_info_t *context_info, + uint8_t **context_meta_data_head_pointer) +{ + return HEF_METADATA__add_ddr_buffering_start(context_info, context_meta_data_head_pointer, m_is_in_repeated_block); +} + +bool StartDdrBufferingTaskAction::supports_repeated_block() const +{ + // There should only be one "start ddr buffering task action" per context, + // so there's no need to support repeated blocks. + return false; +} + +Expected EdgeLayerActivationActionsPositionMarker::create() +{ + auto result = ContextSwitchConfigActionPtr(new (std::nothrow) EdgeLayerActivationActionsPositionMarker()); + CHECK_AS_EXPECTED((nullptr != result), HAILO_OUT_OF_HOST_MEMORY); + return result; +} + +EdgeLayerActivationActionsPositionMarker::EdgeLayerActivationActionsPositionMarker() : + ContextSwitchConfigAction(Type::EdgeLayerActivationActionsPositionMarker, + CONTROL_PROTOCOL__CONTEXT_SWITCH_ACTION_EDGE_LAYER_ACTIVATION_ACTIONS_POSITION) +{} + +hailo_status EdgeLayerActivationActionsPositionMarker::execute( + CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **context_meta_data_head_pointer) +{ + return HEF_METADATA__edge_layer_activation_actions_position_marker(context_info, context_meta_data_head_pointer, + m_is_in_repeated_block); +} + +bool EdgeLayerActivationActionsPositionMarker::supports_repeated_block() const +{ + // There should only be one "edge layer activation actions position marker" per context, + // so there's no need to support repeated blocks. + return false; +} + Expected Hef::Impl::get_vstream_name_from_original_name_mux(const std::string &original_name, const ProtoHefEdge &layer) { switch (layer.edge_case()) { @@ -3157,13 +3458,11 @@ Expected> Hef::Impl::get_original_names_from_vstream_na return make_unexpected(HAILO_NOT_FOUND); } -hailo_status Hef::Impl::validate_net_group_unique_layer_names(ProtoHEFNetworkGroupPtr net_group) +hailo_status Hef::Impl::validate_net_group_unique_layer_names(const ProtoHEFNetworkGroup &net_group) { - CHECK_ARG_NOT_NULL(net_group); - std::set edge_layer_names; std::string layer_name; - for (auto &context : net_group->contexts()) { + for (auto &context : net_group.contexts()) { for (auto &layer : context.metadata().edge_layers()) { // TODO: remove check for boundary layer after fix will be pushed in SDK if (ProtoHEFEdgeConnectionType::PROTO__EDGE_CONNECTION_TYPE__BOUNDARY == @@ -3220,19 +3519,17 @@ Expected get_config_buffer_info( return results; } -Expected Hef::Impl::get_parsing_info(ProtoHEFNetworkGroupPtr net_group) +Expected Hef::Impl::get_parsing_info(const ProtoHEFNetworkGroup &net_group) { - assert(nullptr != net_group); - // Parse preliminary config - auto preliminary_config_buffer_infos = get_config_buffer_info(net_group->preliminary_config().operation()); + auto preliminary_config_buffer_infos = get_config_buffer_info(net_group.preliminary_config().operation()); CHECK_EXPECTED(preliminary_config_buffer_infos); HefParsingInfo parsing_info; parsing_info.cfg_infos_preliminary_config = preliminary_config_buffer_infos.release(); // Parse dynamic contexts - for (const auto &context : net_group->contexts()) { + for (const auto &context : net_group.contexts()) { auto dynamic_ctxt_config_buffer_infos = get_config_buffer_info(context.operations()); CHECK_EXPECTED(dynamic_ctxt_config_buffer_infos); @@ -3282,13 +3579,23 @@ Expected Hef::create_configure_params_mipi_input(hailo_s return pimpl->create_configure_params_mipi_input(output_interface, mipi_params, network_group_name); } +std::string Hef::hash() const +{ + const auto &md5 = pimpl->md5(); + const bool LOWERCASE = false; + return StringUtils::to_hex_string(md5, MD5_DIGEST_LENGTH, LOWERCASE); +} + std::vector Hef::Impl::get_network_groups_names() { std::vector results; results.reserve(m_groups.size()); for (const auto &net_group : m_groups) { - results.push_back(net_group->network_group_metadata().network_group_name()); + auto &network_group_name = (ProtoHEFHwArch::PROTO__HW_ARCH__HAILO8L == get_device_arch()) ? + net_group->partial_network_groups(0).network_group().network_group_metadata().network_group_name() + : net_group->network_group_metadata().network_group_name(); + results.push_back(network_group_name); } return results; } @@ -3305,7 +3612,9 @@ Expected> Hef::Impl::get_network_groups_ for (const auto &net_group : m_groups) { hailo_network_group_info_t info = {}; - auto &network_group_name = net_group->network_group_metadata().network_group_name(); + auto &network_group_name = (ProtoHEFHwArch::PROTO__HW_ARCH__HAILO8L == get_device_arch()) ? + net_group->partial_network_groups(0).network_group().network_group_metadata().network_group_name() + : net_group->network_group_metadata().network_group_name(); CHECK_AS_EXPECTED(HAILO_MAX_NETWORK_GROUP_NAME_SIZE >= (network_group_name.length() + 1), HAILO_INTERNAL_FAILURE, "The network group '{}' has a too long name (max is HAILO_MAX_NETWORK_GROUP_NAME_SIZE)", network_group_name); strncpy(info.name, network_group_name.c_str(), network_group_name.length() + 1); @@ -3547,20 +3856,23 @@ Expected> Hef::Impl::create_str return results; } -NetworkGroupMetadata::NetworkGroupMetadata(const std::string &network_group_name, std::vector &&layer_infos, - std::vector &&sorted_output_names, NetworkGroupSupportedFeatures &supported_features, - const std::vector &sorted_network_names) - : m_network_group_name(network_group_name), m_sorted_output_names(std::move(sorted_output_names)), m_supported_features(supported_features), - m_sorted_network_names(sorted_network_names) -{ - for (auto &layer_info : layer_infos) { - if (HAILO_H2D_STREAM == layer_info.direction) { - m_input_layer_infos[layer_info.network_name].push_back(layer_info); - } else { - m_output_layer_infos[layer_info.network_name].push_back(layer_info); - } - } -} +NetworkGroupMetadata::NetworkGroupMetadata(const std::string &network_group_name, + std::vector> &&boundary_input_layers, + std::vector> &&boundary_output_layers, + std::vector> &&inter_context_input_layers, + std::vector> &&inter_context_output_layers, + std::vector> &&ddr_input_layers, + std::vector> &&ddr_output_layers, + std::vector &&sorted_output_names, + NetworkGroupSupportedFeatures &supported_features, const std::vector &sorted_network_names) + : m_boundary_input_layers(std::move(boundary_input_layers)), + m_boundary_output_layers(std::move(boundary_output_layers)), + m_inter_context_input_layers(std::move(inter_context_input_layers)), + m_inter_context_output_layers(std::move(inter_context_output_layers)), + m_ddr_input_layers(std::move(ddr_input_layers)), + m_ddr_output_layers(std::move(ddr_output_layers)), + m_network_group_name(network_group_name), m_sorted_output_names(std::move(sorted_output_names)), + m_supported_features(supported_features), m_sorted_network_names(sorted_network_names) {} Expected NetworkGroupMetadata::get_layer_info_by_stream_name(const std::string &stream_name) const { @@ -3577,30 +3889,62 @@ Expected NetworkGroupMetadata::get_layer_info_by_stream_name(const st Expected> NetworkGroupMetadata::get_input_layer_infos(const std::string &network_name) const { - CHECK_AS_EXPECTED((network_name.empty()) || (network_name == default_network_name()) || contains(m_input_layer_infos, network_name), - HAILO_NOT_FOUND, "Network name {} is not found in networks metadata", network_name); std::vector res; - for (auto &layer_infos_pair : m_input_layer_infos) { - if ((network_name == layer_infos_pair.first) || (network_name.empty()) || (network_name == default_network_name())) { - res.insert(res.end(), layer_infos_pair.second.begin(), layer_infos_pair.second.end()); + for (auto &context_layer_infos : m_boundary_input_layers) { + for (auto &layer_info : context_layer_infos) { + if ((layer_info.network_name == network_name) || (network_name.empty()) || (network_name == default_network_name())) { + res.emplace_back(layer_info); + } } } + CHECK_AS_EXPECTED(res.size() > 0, HAILO_NOT_FOUND, "Network name {} is not found in networks metadata", network_name); return res; } Expected> NetworkGroupMetadata::get_output_layer_infos(const std::string &network_name) const { - CHECK_AS_EXPECTED((network_name.empty()) || (network_name == default_network_name()) || contains(m_output_layer_infos, network_name), - HAILO_NOT_FOUND, "Network name {} is not found in networks metadata", network_name); std::vector res; - for (auto &layer_infos_pair : m_output_layer_infos) { - if ((network_name == layer_infos_pair.first) || (network_name.empty()) || (network_name == default_network_name())) { - res.insert(res.end(), layer_infos_pair.second.begin(), layer_infos_pair.second.end()); + for (auto &context_layer_infos : m_boundary_output_layers) { + for (auto &layer_info : context_layer_infos) { + if ((layer_info.network_name == network_name) || (network_name.empty()) || (network_name == default_network_name())) { + res.emplace_back(layer_info); + } } } + CHECK_AS_EXPECTED(res.size() > 0, HAILO_NOT_FOUND, "Network name {} is not found in networks metadata", network_name); return res; } +std::vector NetworkGroupMetadata::get_boundary_input_layer_infos(const uint8_t context_index) const +{ + return m_boundary_input_layers[context_index]; +} + +std::vector NetworkGroupMetadata::get_boundary_output_layer_infos(const uint8_t context_index) const +{ + return m_boundary_output_layers[context_index]; +} + +std::vector NetworkGroupMetadata::get_inter_context_input_layer_infos(const uint8_t context_index) const +{ + return m_inter_context_input_layers[context_index]; +} + +std::vector NetworkGroupMetadata::get_inter_context_output_layer_infos(const uint8_t context_index) const +{ + return m_inter_context_output_layers[context_index]; +} + +std::vector NetworkGroupMetadata::get_ddr_input_layer_infos(const uint8_t context_index) const +{ + return m_ddr_input_layers[context_index]; +} + +std::vector NetworkGroupMetadata::get_ddr_output_layer_infos(const uint8_t context_index) const +{ + return m_ddr_output_layers[context_index]; +} + Expected> NetworkGroupMetadata::get_all_layer_infos(const std::string &network_name) const { auto input_layer_infos = get_input_layer_infos(network_name); diff --git a/hailort/libhailort/src/hef_internal.hpp b/hailort/libhailort/src/hef_internal.hpp index 31b93c2..0fb83ad 100644 --- a/hailort/libhailort/src/hef_internal.hpp +++ b/hailort/libhailort/src/hef_internal.hpp @@ -87,7 +87,7 @@ static const std::vector SUPPORTED_EXTENSIONS = { TRANSPOSE_COMPONENT, IS_NMS_MULTI_CONTEXT, OFFLOAD_ARGMAX, - PRELIMINARY_RUN_ASAP // Extention added in platform 4.8 release + KO_RUN_ASAP // Extention added in platform 4.8 release }; struct HefParsingInfo @@ -106,6 +106,8 @@ struct NetworkGroupSupportedFeatures { bool preliminary_run_asap; }; +static uint32_t PARTIAL_CLUSTERS_LAYOUT_IGNORE = static_cast(-1); + static inline bool is_h2d_boundary_info_layer(const ProtoHEFEdgeLayer& layer) { return ((ProtoHEFEdgeLayerDirection::PROTO__EDGE_LAYER_DIRECTION__HOST_TO_DEVICE == layer.direction()) && @@ -151,13 +153,29 @@ class HailoRTDriver; class NetworkGroupMetadata { public: - NetworkGroupMetadata(const std::string &network_group_name, std::vector &&layer_infos, std::vector &&sorted_output_names, - NetworkGroupSupportedFeatures &supported_features, const std::vector &sorted_network_names); + NetworkGroupMetadata() = default; + NetworkGroupMetadata(const std::string &network_group_name, + std::vector> &&boundary_input_layers, + std::vector> &&boundary_output_layers, + std::vector> &&inter_context_input_layers, + std::vector> &&inter_context_output_layers, + std::vector> &&ddr_input_layers, + std::vector> &&ddr_output_layers, + std::vector &&sorted_output_names, + NetworkGroupSupportedFeatures &supported_features, + const std::vector &sorted_network_names); Expected> get_input_layer_infos(const std::string &network_name = "") const; Expected> get_output_layer_infos(const std::string &network_name = "") const; Expected> get_all_layer_infos(const std::string &network_name = "") const; Expected get_layer_info_by_stream_name(const std::string &stream_name) const; + + std::vector get_boundary_input_layer_infos(const uint8_t context_index) const; + std::vector get_boundary_output_layer_infos(const uint8_t context_index) const; + std::vector get_inter_context_input_layer_infos(const uint8_t context_index) const; + std::vector get_inter_context_output_layer_infos(const uint8_t context_index) const; + std::vector get_ddr_input_layer_infos(const uint8_t context_index) const; + std::vector get_ddr_output_layer_infos(const uint8_t context_index) const; Expected> get_input_stream_infos(const std::string &network_name = "") const; Expected> get_output_stream_infos(const std::string &network_name = "") const; @@ -201,9 +219,13 @@ private: std::vector convert_layer_infos_to_stream_infos(const std::vector &layer_infos) const; std::vector convert_layer_infos_to_vstream_infos(const std::vector &layer_infos) const; - // Per network - std::map> m_input_layer_infos; - std::map> m_output_layer_infos; + // vector of edge layers Per context + std::vector> m_boundary_input_layers; + std::vector> m_boundary_output_layers; + std::vector> m_inter_context_input_layers; + std::vector> m_inter_context_output_layers; + std::vector> m_ddr_input_layers; + std::vector> m_ddr_output_layers; std::string m_network_group_name; std::vector m_sorted_output_names; @@ -211,6 +233,33 @@ private: std::vector m_sorted_network_names; }; +class NetworkGroupMetadataPerArch +{ +public: + NetworkGroupMetadataPerArch() = default; + Expected get_metadata(uint32_t partial_clusters_layout_bitmap) + { + if (PARTIAL_CLUSTERS_LAYOUT_IGNORE == partial_clusters_layout_bitmap) { + // Passing PARTIAL_CLUSTERS_LAYOUT_IGNORE is magic for getting one of the metadata + assert(0 != m_metadata_per_arch.size()); + auto result = m_metadata_per_arch.begin()->second; + return result; + } + if (contains(m_metadata_per_arch, partial_clusters_layout_bitmap)) { + auto result = m_metadata_per_arch[partial_clusters_layout_bitmap]; + return result; + } + LOGGER__ERROR("NetworkGroupMetadataPerArch does not contain metadata for partial_clusters_layout_bitmap {}", partial_clusters_layout_bitmap); + return make_unexpected(HAILO_INTERNAL_FAILURE); + } + void add_metadata(const NetworkGroupMetadata &metadata, uint32_t partial_clusters_layout_bitmap) + { + m_metadata_per_arch[partial_clusters_layout_bitmap] = metadata; + } +private: + std::map m_metadata_per_arch; +}; + class Hef::Impl final { public: @@ -248,7 +297,7 @@ public: ProtoHEFHwArch get_device_arch(); Expected get_bottleneck_fps(const std::string &net_group_name=""); static bool contains_ddr_layers(const ProtoHEFNetworkGroup& net_group); - static hailo_status validate_net_group_unique_layer_names(ProtoHEFNetworkGroupPtr net_group); + static hailo_status validate_net_group_unique_layer_names(const ProtoHEFNetworkGroup &net_group); Expected> get_network_input_vstream_infos(const std::string &net_group_name="", const std::string &network_name=""); @@ -274,7 +323,7 @@ public: /* TODO HRT-5067 - work with hailo_device_architecture_t instead of ProtoHEFHwArch */ static Expected> create_resources_manager( - ProtoHEFNetworkGroupPtr network_group_proto, uint8_t net_group_index, + const ProtoHEFNetworkGroup &network_group_proto, uint8_t net_group_index, VdmaDevice &device, HailoRTDriver &driver, const ConfigureNetworkParams &network_group_params, std::shared_ptr network_group_metadata, const ProtoHEFHwArch &hw_arch); @@ -305,14 +354,24 @@ public: std::vector &name_to_format_info, bool quantized, hailo_format_type_t format_type, uint32_t timeout_ms, uint32_t queue_size); - Expected get_network_group_metadata(const std::string &network_group_name) + Expected get_network_group_metadata(const std::string &network_group_name, uint32_t partial_clusters_layout_bitmap = PARTIAL_CLUSTERS_LAYOUT_IGNORE) { - CHECK_AS_EXPECTED(contains(m_network_group_metadata, network_group_name), HAILO_NOT_FOUND, + CHECK_AS_EXPECTED(contains(m_network_group_metadata_per_arch, network_group_name), HAILO_NOT_FOUND, "Network group with name {} wasn't found", network_group_name); - auto metadata = m_network_group_metadata.at(network_group_name); + auto metadata_per_arch = m_network_group_metadata_per_arch.at(network_group_name); + auto metadata = metadata_per_arch.get_metadata(partial_clusters_layout_bitmap); return metadata; } + const MD5_SUM_t &md5() const + { + return m_md5; + } + +#ifdef HAILO_SUPPORT_MULTI_PROCESS + const MemoryView get_hef_memview(); +#endif // HAILO_SUPPORT_MULTI_PROCESS + private: Impl(const std::string &hef_path, hailo_status &status); Impl(const MemoryView &hef_memview, hailo_status &status); @@ -322,6 +381,7 @@ private: hailo_status transfer_protobuf_field_ownership(ProtoHEFHef &hef_message); hailo_status fill_networks_metadata(); void fill_extensions_bitset(); + void init_md5(MD5_SUM_t &calculated_md5); static bool check_hef_extension(const ProtoHEFExtensionType &extension, const ProtoHEFHeader &header, const std::vector &hef_extensions, const ProtoHEFIncludedFeatures &included_features); @@ -334,7 +394,7 @@ private: hailo_status validate_hef_extensions(); static hailo_status validate_hef_header(const hef__header_t &header, MD5_SUM_t &calculated_md5, size_t proto_size); - static Expected get_parsing_info(ProtoHEFNetworkGroupPtr net_group); + static Expected get_parsing_info(const ProtoHEFNetworkGroup &net_group); Expected> get_inputs_vstream_names_and_format_info( const std::string &net_group_name, const std::string &network_name); @@ -344,6 +404,8 @@ private: static Expected get_vstream_name_from_original_name_mux(const std::string &original_name, const ProtoHefEdge &layer); static Expected> get_original_names_from_vstream_name_mux(const std::string &vstream_name, const ProtoHefEdge &layer); + Expected create_metadata_per_arch(const ProtoHEFNetworkGroup &network_group, NetworkGroupSupportedFeatures &supported_features); + // Hef information ProtoHEFHeader m_header; ProtoHEFIncludedFeatures m_included_features; @@ -351,9 +413,14 @@ private: std::vector m_hef_extensions; std::vector m_hef_optional_extensions; std::bitset m_supported_extensions_bitset; + MD5_SUM_t m_md5; + +#ifdef HAILO_SUPPORT_MULTI_PROCESS + Buffer m_hef_buffer; +#endif // HAILO_SUPPORT_MULTI_PROCESS // NetworkGroups information - std::map m_network_group_metadata; + std::map m_network_group_metadata_per_arch; }; // TODO: Make this part of a namespace? (HRT-2881) @@ -385,11 +452,49 @@ class HefUtils final public: HefUtils() = delete; - static Expected> get_all_layers_info(const ProtoHEFNetworkGroup &network_group_proto, - const NetworkGroupSupportedFeatures &supported_features); + static hailo_status fill_boundary_layers_info( + const ProtoHEFNetworkGroup &network_group_proto, + const uint8_t context_index, + const ProtoHEFEdgeLayer &layer, + const NetworkGroupSupportedFeatures &supported_features, + std::vector &layer_name, + std::vector &context_boundary_input_layers, + std::vector &context_boundary_output_layers); + static Expected get_inter_context_layer_info( + const ProtoHEFNetworkGroup &net_group, const uint8_t context_index, + const ProtoHEFEdgeLayer &layer, const NetworkGroupSupportedFeatures &supported_features); + static hailo_status fill_inter_context_layers_info( + const ProtoHEFNetworkGroup &network_group_proto, + const uint8_t context_index, + const ProtoHEFEdgeLayer &layer, + const NetworkGroupSupportedFeatures &supported_features, + std::vector &context_inter_context_input_layers, + std::vector &context_inter_context_output_layers); + static Expected get_ddr_layer_info( + const ProtoHEFNetworkGroup &net_group, const uint8_t context_index, + const ProtoHEFEdgeLayer &layer, const NetworkGroupSupportedFeatures &supported_features); + static hailo_status fill_ddr_layers_info( + const ProtoHEFNetworkGroup &network_group_proto, + const uint8_t context_index, + const ProtoHEFEdgeLayer &layer, + const NetworkGroupSupportedFeatures &supported_features, + std::vector &context_ddr_input_layers, + std::vector &context_ddr_output_layers); + static hailo_status check_ddr_pairs_match( + const std::vector &context_ddr_input_layers, + const std::vector &context_ddr_output_layers, + const uint8_t context_index); + static hailo_status get_all_layers_info(const ProtoHEFNetworkGroup &network_group_proto, + const NetworkGroupSupportedFeatures &supported_features, + std::vector> &boundary_input_layers, + std::vector> &boundary_output_layers, + std::vector> &inter_context_input_layers, + std::vector> &inter_context_output_layers, + std::vector> &ddr_input_layers, + std::vector> &ddr_output_layers); static Expected parse_proto_nms_info(const ProtoHEFNmsInfo &proto_nms_info); - static Expected get_layer_info(const ProtoHEFNetworkGroup &net_group, const ProtoHEFEdgeLayer &layer, - const NetworkGroupSupportedFeatures &supported_features); + static Expected get_boundary_layer_info(const ProtoHEFNetworkGroup &net_group, const uint8_t context_index, + const ProtoHEFEdgeLayer &layer, const NetworkGroupSupportedFeatures &supported_features); static Expected> get_sorted_output_names(const ProtoHEFNetworkGroup &net_group); static Expected get_partial_network_name_by_index(const ProtoHEFNetworkGroup &network_group_proto, uint8_t network_index, @@ -404,17 +509,20 @@ public: private: static hailo_status fill_layer_info_with_base_info(const ProtoHEFEdgeLayerBase &base_info, const ProtoHEFEdgeConnectionType &edge_connection_type, - const ProtoHEFNetworkGroupMetadata &network_group_proto, bool hw_padding_supported, bool transposed, LayerInfo &layer_info); + const ProtoHEFNetworkGroupMetadata &network_group_proto, bool hw_padding_supported, bool transposed, + const uint8_t context_index, const uint8_t network_index, LayerInfo &layer_info); static hailo_status fill_layer_info(const ProtoHEFEdgeLayerInfo &info, const ProtoHEFEdgeConnectionType &edge_connection_type, const ProtoHEFNetworkGroup &net_group, hailo_stream_direction_t direction, - bool hw_padding_supported, const std::string &partial_network_name, LayerInfo &layer_info); + bool hw_padding_supported, const uint8_t context_index, const std::string &partial_network_name, + uint8_t network_index, LayerInfo &layer_info); static hailo_status fill_fused_nms_info(const ProtoHEFEdgeLayerFused &info, LayerInfo &layer_info, hailo_quant_info_t &defuse_quant_info, const std::string &network_name); static hailo_status fill_mux_info(const ProtoHEFEdgeLayerMux &info, const ProtoHEFEdgeConnectionType &edge_connection_type, const ProtoHEFNetworkGroup &net_group, hailo_stream_direction_t direction, - bool hw_padding_supported, const std::string &partial_network_name, LayerInfo &layer_info); + bool hw_padding_supported, const uint8_t context_index, const std::string &partial_network_name, + uint8_t network_index, LayerInfo &layer_info); }; class ContextSwitchTrigger final @@ -459,10 +567,11 @@ public: EnableLcuDefault, DisableLcu, WaitForModuleConfigDone, - AddDdrPairInfo, - AddDdrBufferingStart, + DdrPairInfo, + StartDdrBufferingTask, AddRrepeated, - StartBurstCreditsTask + StartBurstCreditsTask, + EdgeLayerActivationActionsPositionMarker }; static Expected create(const ProtoHEFAction &proto_action, Device &device, @@ -563,23 +672,23 @@ private: class AddCcwBurstAction : public ContextSwitchConfigAction { public: - static Expected create(uint8_t channel_index, uint16_t ccw_bursts); + static Expected create(uint8_t config_stream_index, uint16_t ccw_bursts); virtual hailo_status execute(CONTROL_PROTOCOL__context_switch_context_info_t *context_info, uint8_t **context_meta_data_head_pointer) override; virtual bool supports_repeated_block() const override; private: - AddCcwBurstAction(uint8_t channel_index, uint16_t ccw_bursts); + AddCcwBurstAction(uint8_t config_stream_index, uint16_t ccw_bursts); - const uint8_t m_channel_index; + const uint8_t m_config_stream_index; const uint16_t m_ccw_bursts; }; class CreateConfigDescAndFetchAction : public ContextSwitchConfigAction { public: - static Expected create(uint8_t channel_index, ConfigBuffer &config_buffer); + static Expected create(uint8_t config_stream_index, ConfigBuffer &config_buffer); CreateConfigDescAndFetchAction(CreateConfigDescAndFetchAction &&) = default; CreateConfigDescAndFetchAction(const CreateConfigDescAndFetchAction &) = delete; @@ -592,9 +701,9 @@ public: virtual bool supports_repeated_block() const override; private: - CreateConfigDescAndFetchAction(uint8_t channel_index, ConfigBuffer &config_buffer); + CreateConfigDescAndFetchAction(uint8_t config_stream_index, ConfigBuffer &config_buffer); - const uint8_t m_channel_index; + const uint8_t m_config_stream_index; ConfigBuffer &m_config_buffer; }; @@ -760,6 +869,67 @@ private: WaitForModuleConfigDoneAction(const ProtoHEFAction& proto_action); }; +class DdrPairInfoAction : public ContextSwitchConfigAction +{ +public: + static Expected create(const vdma::ChannelId &h2d_channel_id, + const vdma::ChannelId &d2h_channel_id, uint32_t descriptors_per_frame, uint16_t descs_count); + DdrPairInfoAction(DdrPairInfoAction &&) = default; + DdrPairInfoAction(const DdrPairInfoAction &) = delete; + DdrPairInfoAction &operator=(DdrPairInfoAction &&) = delete; + DdrPairInfoAction &operator=(const DdrPairInfoAction &) = delete; + virtual ~DdrPairInfoAction() = default; + + virtual hailo_status execute(CONTROL_PROTOCOL__context_switch_context_info_t *context_info, + uint8_t **context_meta_data_head_pointer) override; + virtual bool supports_repeated_block() const override; + +private: + DdrPairInfoAction(const vdma::ChannelId &h2d_channel_id, const vdma::ChannelId &d2h_channel_id, + uint32_t descriptors_per_frame, uint16_t descs_count); + + const vdma::ChannelId m_h2d_channel_id; + const vdma::ChannelId m_d2h_channel_id; + const uint32_t m_descriptors_per_frame; + const uint16_t m_descs_count; +}; + +class StartDdrBufferingTaskAction : public ContextSwitchConfigAction +{ +public: + static Expected create(); + StartDdrBufferingTaskAction(StartDdrBufferingTaskAction &&) = default; + StartDdrBufferingTaskAction(const StartDdrBufferingTaskAction &) = delete; + StartDdrBufferingTaskAction &operator=(StartDdrBufferingTaskAction &&) = delete; + StartDdrBufferingTaskAction &operator=(const StartDdrBufferingTaskAction &) = delete; + virtual ~StartDdrBufferingTaskAction() = default; + + virtual hailo_status execute(CONTROL_PROTOCOL__context_switch_context_info_t *context_info, + uint8_t **context_meta_data_head_pointer) override; + virtual bool supports_repeated_block() const override; + +private: + StartDdrBufferingTaskAction(); +}; + +class EdgeLayerActivationActionsPositionMarker : public ContextSwitchConfigAction +{ +public: + static Expected create(); + EdgeLayerActivationActionsPositionMarker(EdgeLayerActivationActionsPositionMarker &&) = default; + EdgeLayerActivationActionsPositionMarker(const EdgeLayerActivationActionsPositionMarker &) = delete; + EdgeLayerActivationActionsPositionMarker &operator=(EdgeLayerActivationActionsPositionMarker &&) = delete; + EdgeLayerActivationActionsPositionMarker &operator=(const EdgeLayerActivationActionsPositionMarker &) = delete; + virtual ~EdgeLayerActivationActionsPositionMarker() = default; + + virtual hailo_status execute(CONTROL_PROTOCOL__context_switch_context_info_t *context_info, + uint8_t **context_meta_data_head_pointer) override; + virtual bool supports_repeated_block() const override; + +private: + EdgeLayerActivationActionsPositionMarker(); +}; + } /* namespace hailort */ #endif /* _HEF_INTERNAL_HPP_ */ diff --git a/hailort/libhailort/src/hw_consts.hpp b/hailort/libhailort/src/hw_consts.hpp index 86e87c5..c3598e1 100644 --- a/hailort/libhailort/src/hw_consts.hpp +++ b/hailort/libhailort/src/hw_consts.hpp @@ -25,18 +25,10 @@ #define PCIE_BRIDGE_CONFIG__ATR_PARAM_ATR1_PCIE_WIN1__SOURCE_ADDR__MODIFY(dst, src) (dst) = ((dst) & ~0xFFFFF000L) | (((uint32_t)(src) << 12) & 0xFFFFF000L) // ::PCIE_BRIDGE_CONFIG__ATR_PARAM_ATR1_PCIE_WIN1__SOURCE_ADDR__MODIFY /** Vdma Channel registers ***************************************************/ -#define VDMA_CHANNEL_OFFSET(ch, is_src) (((ch) << 5) + ((is_src) ? (0x00) : 0x10)) #define VDMA_CHANNEL_CONTROL_OFFSET (0x00) -#define VDMA_CHANNEL_DEPTH_ID_OFFSET (0x01) #define VDMA_CHANNEL_NUM_AVAIL_OFFSET (0x02) #define VDMA_CHANNEL_NUM_PROC_OFFSET (0x04) -#define VDMA_CHANNEL_NUM_ONGOING_OFFSET (0x04) #define VDMA_CHANNEL_ERROR_OFFSET (0x08) -#define VDMA_CHANNEL_ADDRESS_L_OFFSET (0x0A) -#define VDMA_CHANNEL_ADDRESS_H_OFFSET (0x0C) - -#define VDMA_CHANNEL_ID_AXI (0) -#define VDMA_CHANNEL_ID_PCIE (4) #endif /* _HAILO_HW_CONSTS_HPP_ */ diff --git a/hailort/libhailort/src/inference_pipeline.cpp b/hailort/libhailort/src/inference_pipeline.cpp index 45d1ce6..173a0cf 100644 --- a/hailort/libhailort/src/inference_pipeline.cpp +++ b/hailort/libhailort/src/inference_pipeline.cpp @@ -19,10 +19,12 @@ namespace hailort { -InferVStreams::InferVStreams(std::vector &&inputs, std::vector &&outputs, bool is_multi_context) : +InferVStreams::InferVStreams(std::vector &&inputs, std::vector &&outputs, bool is_multi_context, + uint16_t batch_size) : m_inputs(std::move(inputs)), m_outputs(std::move(outputs)), - m_is_multi_context(is_multi_context) + m_is_multi_context(is_multi_context), + m_batch_size(batch_size) { for (auto &input : m_inputs) { if (contains(m_network_name_to_input_count, input.network_name())) { @@ -100,13 +102,32 @@ Expected InferVStreams::create(ConfiguredNetworkGroup &net_group, { auto network_infos = net_group.get_network_infos(); CHECK_EXPECTED(network_infos); - auto is_multi_context = (dynamic_cast(net_group)).get_configured_network()->get_supported_features().multi_context; + + auto is_multi_context = net_group.is_multi_context(); std::map> input_param_count_per_network; size_t total_inputs_found = 0; size_t total_outputs_found = 0; + + uint16_t batch_size = 0; + if (is_multi_context) { + const auto &config_params = net_group.get_config_params(); + batch_size = config_params.batch_size; + + if (HAILO_DEFAULT_BATCH_SIZE == batch_size) { + uint16_t network_batch_size = config_params.network_params_by_name.begin()->second.batch_size; + for (const auto &name_params_pair : config_params.network_params_by_name) { + CHECK_AS_EXPECTED(network_batch_size == name_params_pair.second.batch_size, HAILO_INVALID_ARGUMENT, + "Batch size of each network must be the same!"); + } + + batch_size = network_batch_size; + } + } + for (const auto &network_info : network_infos.value()) { auto input_vstream_infos_per_network = net_group.get_input_vstream_infos(network_info.name); CHECK_EXPECTED(input_vstream_infos_per_network); + size_t input_counter = 0; for (const auto &vstream_info : input_vstream_infos_per_network.value()) { if (contains(input_params, std::string(vstream_info.name))) { @@ -117,6 +138,7 @@ Expected InferVStreams::create(ConfiguredNetworkGroup &net_group, auto output_vstream_infos_per_network = net_group.get_output_vstream_infos(network_info.name); CHECK_EXPECTED(output_vstream_infos_per_network); + size_t output_counter = 0; for (const auto &vstream_info : output_vstream_infos_per_network.value()) { if (contains(output_params, std::string(vstream_info.name))) { @@ -125,7 +147,7 @@ Expected InferVStreams::create(ConfiguredNetworkGroup &net_group, } } - if (0 != input_counter || 0 != output_counter) { + if ((0 != input_counter) || (0 != output_counter)) { CHECK_AS_EXPECTED(input_counter == input_vstream_infos_per_network->size(), HAILO_INVALID_ARGUMENT, "Found only partial inputs for network {}", network_info.name); CHECK_AS_EXPECTED(output_counter == output_vstream_infos_per_network->size(), HAILO_INVALID_ARGUMENT, @@ -139,30 +161,37 @@ Expected InferVStreams::create(ConfiguredNetworkGroup &net_group, if (total_inputs_found != input_params.size()) { auto all_input_vstream_infos = net_group.get_input_vstream_infos(); CHECK_EXPECTED(all_input_vstream_infos); + auto status = verify_vstream_params_in_vstream_infos(input_params, all_input_vstream_infos.release()); CHECK_SUCCESS_AS_EXPECTED(status); } if (total_outputs_found != output_params.size()) { auto all_output_vstream_infos = net_group.get_input_vstream_infos(); CHECK_EXPECTED(all_output_vstream_infos); + auto status = verify_vstream_params_in_vstream_infos(output_params, all_output_vstream_infos.release()); CHECK_SUCCESS_AS_EXPECTED(status); } auto input_vstreams = VStreamsBuilder::create_input_vstreams(net_group, input_params); CHECK_EXPECTED(input_vstreams); + auto output_vstreams = VStreamsBuilder::create_output_vstreams(net_group, output_params); CHECK_EXPECTED(output_vstreams); - return InferVStreams(input_vstreams.release(), output_vstreams.release(), is_multi_context); + return InferVStreams(input_vstreams.release(), output_vstreams.release(), is_multi_context, batch_size); } hailo_status InferVStreams::infer(const std::map& input_data, - std::map& output_data, size_t batch_size) + std::map& output_data, size_t frames_count) { auto status = verify_network_inputs_and_outputs(input_data, output_data); CHECK_SUCCESS(status); - status = verify_memory_view_size(input_data, output_data, batch_size); + + status = verify_memory_view_size(input_data, output_data, frames_count); + CHECK_SUCCESS(status); + + status = verify_frames_count(frames_count); CHECK_SUCCESS(status); std::vector> results; @@ -173,9 +202,9 @@ hailo_status InferVStreams::infer(const std::map& input CHECK_EXPECTED_AS_STATUS(input_vstream_exp); auto &input_vstream = input_vstream_exp.release().get(); results.emplace_back(std::make_unique>( - [&input_vstream, &input_name_to_data_pair, batch_size]() -> hailo_status { + [&input_vstream, &input_name_to_data_pair, frames_count]() -> hailo_status { const auto &input_buffer = input_name_to_data_pair.second; - for (uint32_t i = 0; i < batch_size; i++) { + for (uint32_t i = 0; i < frames_count; i++) { const size_t offset = i * input_vstream.get_frame_size(); auto status = input_vstream.write(MemoryView::create_const( input_buffer.data() + offset, @@ -195,8 +224,8 @@ hailo_status InferVStreams::infer(const std::map& input CHECK_EXPECTED_AS_STATUS(output_vstream_exp); auto &output_vstream = output_vstream_exp.release().get(); results.emplace_back(std::make_unique>( - [&output_vstream, &output_name_to_data_pair, batch_size]() { - for (size_t i = 0; i < batch_size; i++) { + [&output_vstream, &output_name_to_data_pair, frames_count]() { + for (size_t i = 0; i < frames_count; i++) { auto status = output_vstream.read(MemoryView(output_name_to_data_pair.second.data() + i * output_vstream.get_frame_size(), output_vstream.get_frame_size())); if (HAILO_SUCCESS != status) { return status; @@ -227,28 +256,35 @@ hailo_status InferVStreams::infer(const std::map& input } hailo_status InferVStreams::verify_memory_view_size(const std::map& inputs_name_mem_view_map, - const std::map& outputs_name_mem_view_map, size_t batch_count) + const std::map& outputs_name_mem_view_map, size_t frames_count) { for (const auto &input_name_to_memview : inputs_name_mem_view_map) { auto input_vstream_exp = get_input_by_name(input_name_to_memview.first); CHECK_EXPECTED_AS_STATUS(input_vstream_exp); auto &input_vstream = input_vstream_exp.release().get(); - CHECK(batch_count * input_vstream.get_frame_size() == input_name_to_memview.second.size(), HAILO_INVALID_ARGUMENT, + CHECK(frames_count * input_vstream.get_frame_size() == input_name_to_memview.second.size(), HAILO_INVALID_ARGUMENT, "Memory size of vstream {} does not match the frame count! (Expected {}, got {})", - input_vstream.name(), batch_count * input_vstream.get_frame_size(), input_name_to_memview.second.size()); + input_vstream.name(), frames_count * input_vstream.get_frame_size(), input_name_to_memview.second.size()); } for (const auto &output_name_to_memview : outputs_name_mem_view_map) { auto output_vstream_exp = get_output_by_name(output_name_to_memview.first); CHECK_EXPECTED_AS_STATUS(output_vstream_exp); auto &output_vstream = output_vstream_exp.release().get(); - CHECK(batch_count * output_vstream.get_frame_size() == output_name_to_memview.second.size(), HAILO_INVALID_ARGUMENT, + CHECK(frames_count * output_vstream.get_frame_size() == output_name_to_memview.second.size(), HAILO_INVALID_ARGUMENT, "Memory size of vstream {} does not match the frame count! (Expected {}, got {})", - output_vstream.name(), batch_count * output_vstream.get_frame_size(), output_name_to_memview.second.size()); + output_vstream.name(), frames_count * output_vstream.get_frame_size(), output_name_to_memview.second.size()); } return HAILO_SUCCESS; } +hailo_status InferVStreams::verify_frames_count(size_t frames_count) +{ + CHECK((!m_is_multi_context) || (frames_count % m_batch_size == 0), HAILO_INVALID_ARGUMENT, + "Frames count is not a multiplier of the batch size! ({} % {} != 0)", frames_count, m_batch_size); + return HAILO_SUCCESS; +} + Expected> InferVStreams::get_input_by_name(const std::string &name) { for (auto &input_vstream : m_inputs) { diff --git a/hailort/libhailort/src/intermediate_buffer.cpp b/hailort/libhailort/src/inter_context_buffer.cpp similarity index 53% rename from hailort/libhailort/src/intermediate_buffer.cpp rename to hailort/libhailort/src/inter_context_buffer.cpp index 540ee6a..4b42705 100644 --- a/hailort/libhailort/src/intermediate_buffer.cpp +++ b/hailort/libhailort/src/inter_context_buffer.cpp @@ -3,12 +3,12 @@ * Distributed under the MIT license (https://opensource.org/licenses/MIT) **/ /** - * @file intermediate_buffer.cpp - * @brief Manages intermediate buffer for inter-context or ddr channels. + * @file inter_context_buffer.cpp + * @brief Manages inter-context buffer. */ #include "context_switch/multi_context/resource_manager.hpp" -#include "intermediate_buffer.hpp" +#include "inter_context_buffer.hpp" #include "vdma/sg_buffer.hpp" #include "vdma/continuous_buffer.hpp" @@ -16,35 +16,31 @@ namespace hailort { -Expected IntermediateBuffer::create(HailoRTDriver &driver, - ChannelType channel_type, const uint32_t transfer_size, const uint16_t batch_size) +Expected InterContextBuffer::create(HailoRTDriver &driver, uint32_t transfer_size, + uint16_t max_batch_size) { - auto buffer_ptr = should_use_ccb(driver, channel_type) ? - create_ccb_buffer(driver, transfer_size, batch_size) : - create_sg_buffer(driver, transfer_size, batch_size); - CHECK_EXPECTED(buffer_ptr); + auto buffer_exp = should_use_ccb(driver) ? + create_ccb_buffer(driver, transfer_size, max_batch_size) : + create_sg_buffer(driver, transfer_size, max_batch_size); + CHECK_EXPECTED(buffer_exp); + auto buffer_ptr = buffer_exp.release(); - return IntermediateBuffer(buffer_ptr.release(), transfer_size, batch_size); -} - -hailo_status IntermediateBuffer::program_inter_context() -{ size_t acc_offset = 0; - for (uint16_t i = 0; i < m_max_batch_size; i++) { + for (uint16_t i = 0; i < max_batch_size; i++) { const auto first_desc_interrupts_domain = VdmaInterruptsDomain::NONE; - const auto last_desc_interrupts_domain = ((m_max_batch_size - 1) == i) ? + const auto last_desc_interrupts_domain = ((max_batch_size - 1) == i) ? VdmaInterruptsDomain::DEVICE : VdmaInterruptsDomain::NONE; static const auto BUFFER_NOT_CIRCULAR = false; - auto desc_count_local = m_buffer->program_descriptors(m_transfer_size, first_desc_interrupts_domain, + auto desc_count_local = buffer_ptr->program_descriptors(transfer_size, first_desc_interrupts_domain, last_desc_interrupts_domain, acc_offset, BUFFER_NOT_CIRCULAR); - CHECK_EXPECTED_AS_STATUS(desc_count_local, - "Failed to program descs for intermediate channels. Given batch_size is too big."); + CHECK_EXPECTED(desc_count_local, "Failed to program descs for inter context channels. Given max_batch_size is too big."); acc_offset += desc_count_local.value(); } - return HAILO_SUCCESS; + + return InterContextBuffer(std::move(buffer_ptr), transfer_size, max_batch_size); } -hailo_status IntermediateBuffer::reprogram_inter_context(uint16_t batch_size) +hailo_status InterContextBuffer::reprogram(uint16_t batch_size) { const auto prev_batch_size = m_dynamic_batch_size; auto status = set_dynamic_batch_size(batch_size); @@ -67,52 +63,7 @@ hailo_status IntermediateBuffer::reprogram_inter_context(uint16_t batch_size) return HAILO_SUCCESS; } -Expected IntermediateBuffer::program_ddr() -{ - const auto interrupts_domain = VdmaInterruptsDomain::NONE; - const auto total_size = m_buffer->descs_count() * m_buffer->desc_page_size(); - - auto desc_count_local = m_buffer->program_descriptors(total_size, - interrupts_domain, interrupts_domain, 0, true); - CHECK_EXPECTED(desc_count_local); - - return static_cast(desc_count_local.release()); -} - -uint64_t IntermediateBuffer::dma_address() const -{ - return m_buffer->dma_address(); -} - -uint32_t IntermediateBuffer::descriptors_in_frame() const -{ - return m_buffer->descriptors_in_buffer(m_transfer_size); -} - -uint16_t IntermediateBuffer::desc_page_size() const -{ - return m_buffer->desc_page_size(); -} - -uint32_t IntermediateBuffer::descs_count() const -{ - return m_buffer->descs_count(); -} - -uint8_t IntermediateBuffer::depth() -{ - // TODO HRT-6763: remove this function, use desc count instead - auto count = m_buffer->descs_count(); - - uint32_t depth = 0; - while (count >>= 1) { - ++depth; - } - assert(IS_FIT_IN_UINT8(depth)); - return static_cast(depth); -} - -Expected IntermediateBuffer::read() +Expected InterContextBuffer::read() { const auto size = m_transfer_size * m_dynamic_batch_size; assert(size <= m_buffer->size()); @@ -126,45 +77,12 @@ Expected IntermediateBuffer::read() return res.release(); } -CONTROL_PROTOCOL__host_buffer_info_t IntermediateBuffer::get_host_buffer_info() const -{ - CONTROL_PROTOCOL__host_buffer_info_t buffer_info = {}; - - buffer_info.buffer_type = static_cast((m_buffer->type() == vdma::VdmaBuffer::Type::SCATTER_GATHER) ? - CONTROL_PROTOCOL__HOST_BUFFER_TYPE_EXTERNAL_DESC : - CONTROL_PROTOCOL__HOST_BUFFER_TYPE_CCB); - buffer_info.dma_address = dma_address(); - buffer_info.desc_page_size = desc_page_size(); - buffer_info.total_desc_count = descs_count(); - buffer_info.bytes_in_pattern = m_transfer_size; - - return buffer_info; -} - -Expected> IntermediateBuffer::create_sg_buffer(HailoRTDriver &driver, - const uint32_t transfer_size, const uint16_t batch_size) +CONTROL_PROTOCOL__host_buffer_info_t InterContextBuffer::get_host_buffer_info() const { - auto desc_sizes_pair = VdmaDescriptorList::get_desc_buffer_sizes_for_single_transfer(driver, - batch_size, batch_size, transfer_size); - CHECK_EXPECTED(desc_sizes_pair); - auto desc_page_size = desc_sizes_pair->first; - auto descs_count = desc_sizes_pair->second; - - // On dram-dma all descriptors should have channel index, until we implement CCB, - // we use some fake channel index. Currently the channel_index is not in used by - // the hw. TODO HRT-5835: remove channel_index. - const uint8_t channel_index = 0; - auto buffer = vdma::SgBuffer::create(driver, descs_count, desc_page_size, - HailoRTDriver::DmaDirection::BOTH, channel_index); - CHECK_EXPECTED(buffer); - - auto buffer_ptr = make_unique_nothrow(buffer.release()); - CHECK_NOT_NULL_AS_EXPECTED(buffer_ptr, HAILO_OUT_OF_HOST_MEMORY); - - return std::unique_ptr(std::move(buffer_ptr)); + return m_buffer->get_host_buffer_info(m_transfer_size); } -IntermediateBuffer::IntermediateBuffer(std::unique_ptr &&buffer, uint32_t transfer_size, +InterContextBuffer::InterContextBuffer(std::unique_ptr &&buffer, uint32_t transfer_size, uint16_t batch_size) : m_buffer(std::move(buffer)), m_transfer_size(transfer_size), @@ -172,7 +90,7 @@ IntermediateBuffer::IntermediateBuffer(std::unique_ptr &&buffe m_dynamic_batch_size(batch_size) {} -hailo_status IntermediateBuffer::set_dynamic_batch_size(uint16_t batch_size) +hailo_status InterContextBuffer::set_dynamic_batch_size(uint16_t batch_size) { if (CONTROL_PROTOCOL__IGNORE_DYNAMIC_BATCH_SIZE == batch_size) { LOGGER__TRACE("Received CONTROL_PROTOCOL__IGNORE_DYNAMIC_BATCH_SIZE == batch_size; " @@ -189,8 +107,27 @@ hailo_status IntermediateBuffer::set_dynamic_batch_size(uint16_t batch_size) return HAILO_SUCCESS; } -Expected> IntermediateBuffer::create_ccb_buffer(HailoRTDriver &driver, - const uint32_t transfer_size, const uint16_t batch_size) +Expected> InterContextBuffer::create_sg_buffer(HailoRTDriver &driver, + uint32_t transfer_size, uint16_t batch_size) +{ + auto desc_sizes_pair = VdmaDescriptorList::get_desc_buffer_sizes_for_single_transfer(driver, + batch_size, batch_size, transfer_size); + CHECK_EXPECTED(desc_sizes_pair); + auto desc_page_size = desc_sizes_pair->first; + auto descs_count = desc_sizes_pair->second; + + auto buffer = vdma::SgBuffer::create(driver, descs_count, desc_page_size, + HailoRTDriver::DmaDirection::BOTH); + CHECK_EXPECTED(buffer); + + auto buffer_ptr = make_unique_nothrow(buffer.release()); + CHECK_NOT_NULL_AS_EXPECTED(buffer_ptr, HAILO_OUT_OF_HOST_MEMORY); + + return std::unique_ptr(std::move(buffer_ptr)); +} + +Expected> InterContextBuffer::create_ccb_buffer(HailoRTDriver &driver, + uint32_t transfer_size, uint16_t batch_size) { // The first 12 channels in D2H CCB ("regular channels") requires that the amount of descriptors will be a power // of 2. Altough the 4 last channels ("enhanced channels") don't have this requirements, we keep the code the same. @@ -204,22 +141,18 @@ Expected> IntermediateBuffer::create_ccb_buffe return std::unique_ptr(std::move(buffer_ptr)); } -bool IntermediateBuffer::should_use_ccb(HailoRTDriver &driver, ChannelType channel_type) +bool InterContextBuffer::should_use_ccb(HailoRTDriver &driver) { - if (ChannelType::DDR == channel_type) { - // TODO HRT-6645: remove this if - return false; - } - switch (driver.dma_type()) { case HailoRTDriver::DmaType::PCIE: return false; case HailoRTDriver::DmaType::DRAM: return true; - default: - assert(true); - return false; } + + // Shouldn't reach here + assert(false); + return false; } } /* namespace hailort */ diff --git a/hailort/libhailort/src/inter_context_buffer.hpp b/hailort/libhailort/src/inter_context_buffer.hpp new file mode 100644 index 0000000..37dcce0 --- /dev/null +++ b/hailort/libhailort/src/inter_context_buffer.hpp @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + **/ +/** + * @file inter_context_buffer.hpp + * @brief Manages inter-context buffer. + */ + +#ifndef _HAILO_INTER_CONTEXT_BUFFER_HPP_ +#define _HAILO_INTER_CONTEXT_BUFFER_HPP_ + +#include "os/hailort_driver.hpp" +#include "vdma/vdma_buffer.hpp" +#include "hailo/expected.hpp" +#include "hailo/buffer.hpp" +#include "control_protocol.h" + + +namespace hailort +{ + +class InterContextBuffer final { +public: + static Expected create(HailoRTDriver &driver, uint32_t transfer_size, + uint16_t max_batch_size); + + hailo_status reprogram(uint16_t batch_size); + Expected read(); + + CONTROL_PROTOCOL__host_buffer_info_t get_host_buffer_info() const; + +private: + InterContextBuffer(std::unique_ptr &&buffer, uint32_t transfer_size, uint16_t batch_size); + hailo_status set_dynamic_batch_size(uint16_t batch_size); + + static Expected> create_sg_buffer(HailoRTDriver &driver, + uint32_t transfer_size, uint16_t batch_size); + static Expected> create_ccb_buffer(HailoRTDriver &driver, + uint32_t transfer_size, uint16_t batch_size); + + static bool should_use_ccb(HailoRTDriver &driver); + + std::unique_ptr m_buffer; + const uint32_t m_transfer_size; + const uint16_t m_max_batch_size; + uint16_t m_dynamic_batch_size; +}; + +} /* namespace hailort */ + +#endif /* _HAILO_INTER_CONTEXT_BUFFER_HPP_ */ \ No newline at end of file diff --git a/hailort/libhailort/src/intermediate_buffer.hpp b/hailort/libhailort/src/intermediate_buffer.hpp deleted file mode 100644 index 647733d..0000000 --- a/hailort/libhailort/src/intermediate_buffer.hpp +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. - * Distributed under the MIT license (https://opensource.org/licenses/MIT) - **/ -/** - * @file intermediate_buffer.hpp - * @brief Manages intermediate buffer for inter-context or ddr channels. - */ - -#ifndef _HAILO_INTERMEDIATE_BUFFER_HPP_ -#define _HAILO_INTERMEDIATE_BUFFER_HPP_ - -#include "os/hailort_driver.hpp" -#include "vdma/vdma_buffer.hpp" -#include "hailo/expected.hpp" -#include "hailo/buffer.hpp" -#include "control_protocol.h" - - -namespace hailort -{ - -class IntermediateBuffer final { -public: - enum class ChannelType { - INTER_CONTEXT, - DDR - }; - - static Expected create(HailoRTDriver &driver, - ChannelType channel_type, const uint32_t transfer_size, const uint16_t batch_size); - // TODO: create two subclasses - one for ddr buffers and one for intercontext buffers (HRT-6784) - - hailo_status program_inter_context(); - // This is to be called after program_inter_context - hailo_status reprogram_inter_context(uint16_t batch_size); - // Returns the amount of programed descriptors - Expected program_ddr(); - uint64_t dma_address() const; - uint32_t descriptors_in_frame() const; - uint16_t desc_page_size() const; - uint32_t descs_count() const; - uint8_t depth(); - Expected read(); - - CONTROL_PROTOCOL__host_buffer_info_t get_host_buffer_info() const; - -private: - IntermediateBuffer(std::unique_ptr &&buffer, uint32_t transfer_size, uint16_t batch_size); - hailo_status set_dynamic_batch_size(uint16_t batch_size); - - static Expected> create_sg_buffer(HailoRTDriver &driver, - const uint32_t transfer_size, const uint16_t batch_size); - static Expected> create_ccb_buffer(HailoRTDriver &driver, - const uint32_t transfer_size, const uint16_t batch_size); - - static bool should_use_ccb(HailoRTDriver &driver, ChannelType channel_type); - - std::unique_ptr m_buffer; - const uint32_t m_transfer_size; - const uint16_t m_max_batch_size; - uint16_t m_dynamic_batch_size; -}; - -} /* namespace hailort */ - -#endif /* _HAILO_INTERMEDIATE_BUFFER_HPP_ */ \ No newline at end of file diff --git a/hailort/libhailort/src/layer_info.hpp b/hailort/libhailort/src/layer_info.hpp index 82628e9..ff55337 100644 --- a/hailort/libhailort/src/layer_info.hpp +++ b/hailort/libhailort/src/layer_info.hpp @@ -13,6 +13,8 @@ #include "hailo/hailort.h" #include "hailo/hailort_common.hpp" #include "hailort_defaults.hpp" +#include "control_protocol.h" +#include "os/hailort_driver.hpp" #include #include @@ -21,6 +23,15 @@ namespace hailort { +enum class LayerType +{ + NOT_SET = 0, + BOUNDARY = 1, + INTER_CONTEXT = 2, + DDR = 3, + CFG = 4 +}; + struct BufferIndices { uint32_t index; uint32_t cluster_index; @@ -37,22 +48,81 @@ struct LayerInfo { uint32_t hw_data_bytes; hailo_format_t format; hailo_stream_direction_t direction; - uint8_t index; + uint8_t stream_index; + uint8_t dma_engine_index; std::string name; hailo_quant_info_t quant_info; hailo_nms_info_t nms_info; uint32_t height_gcd; std::vector height_ratios; std::string network_name; + uint8_t network_index; + CONTROL_PROTOCOL__nn_stream_config_t nn_stream_config; + uint32_t max_shmifo_size; + uint8_t context_index; // Simulation Info BufferIndices buffer_indices; +}; - // HW Info - uint16_t core_bytes_per_buffer; - uint16_t core_buffers_per_frame; +struct InterContextLayerInfo { + std::string name; + uint8_t stream_index; + uint8_t dma_engine_index; + uint8_t context_index; + hailo_stream_direction_t direction; + CONTROL_PROTOCOL__nn_stream_config_t nn_stream_config; + std::string network_name; + uint8_t network_index; + uint32_t max_shmifo_size; + uint8_t src_context_index; + uint8_t src_stream_index; + // HRT-7201 - The system supports one src and multiple dstinations. Right now we're saving only one dstination + uint8_t dst_context_index; + uint8_t dst_stream_index; }; +struct DdrLayerInfo { + std::string name; + uint8_t stream_index; + uint8_t context_index; + hailo_stream_direction_t direction; + CONTROL_PROTOCOL__nn_stream_config_t nn_stream_config; + std::string network_name; + uint8_t network_index; + uint32_t max_shmifo_size; + uint16_t min_buffered_rows; + // total_buffers_per_frame not same as core_buffer_per frame. + //(In DDR core buffer per frame is 1). Used to calc total host descriptors_per_frame. + uint16_t total_buffers_per_frame; + uint8_t src_context_index; + uint8_t src_dma_engine_index; + uint8_t src_stream_index; + uint8_t dst_context_index; + uint8_t dst_dma_engine_index; + uint8_t dst_stream_index; +}; + +// LayerIdentifier = +using LayerIdentifier = std::tuple; + +inline LayerIdentifier to_layer_identifier(const LayerInfo &info) +{ + return std::make_tuple(LayerType::BOUNDARY, info.name, info.stream_index); +} + +inline LayerIdentifier to_layer_identifier(const InterContextLayerInfo &info) +{ + return std::make_tuple(LayerType::INTER_CONTEXT, info.name, info.stream_index); +} + +inline LayerIdentifier to_layer_identifier(const DdrLayerInfo &info, HailoRTDriver::DmaDirection direction) +{ + const auto stream_index = (direction == HailoRTDriver::DmaDirection::H2D) ? info.src_stream_index : + info.dst_stream_index; + return std::make_tuple(LayerType::DDR, info.name, stream_index); +} + class LayerInfoUtils { public: static hailo_stream_info_t get_stream_info_from_layer_info(const LayerInfo &layer_info) @@ -75,7 +145,7 @@ public: res.hw_shape.height * res.hw_shape.width * res.hw_shape.features * res.hw_data_bytes; } res.direction = layer_info.direction; - res.index = layer_info.index; + res.index = layer_info.stream_index; assert(layer_info.name.length() < HAILO_MAX_NAME_SIZE); strncpy(res.name, layer_info.name.c_str(), layer_info.name.length() + 1); res.quant_info = layer_info.quant_info; diff --git a/hailort/libhailort/src/network_group_client.cpp b/hailort/libhailort/src/network_group_client.cpp new file mode 100644 index 0000000..b8ac31d --- /dev/null +++ b/hailort/libhailort/src/network_group_client.cpp @@ -0,0 +1,275 @@ +/** + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + **/ +/** + * @file network_group_client.cpp + * @brief: Network group client object + **/ + +#include "context_switch/network_group_internal.hpp" +#include "common/utils.hpp" +#include "hailort_defaults.hpp" +#include "hailo/vstream.hpp" +#include "vstream_internal.hpp" + +namespace hailort +{ + +ConfiguredNetworkGroupClient::ConfiguredNetworkGroupClient(std::unique_ptr client, uint32_t handle) : + m_client(std::move(client)), + m_handle(handle) +{ + auto reply = m_client->ConfiguredNetworkGroup_get_name(m_handle); + if (reply.status() != HAILO_SUCCESS) { + LOGGER__ERROR("get_network_group_name failed with status {}", reply.status()); + return; + } + m_network_group_name = reply.value(); +} + +ConfiguredNetworkGroupClient::~ConfiguredNetworkGroupClient() +{ + auto reply = m_client->ConfiguredNetworkGroup_release(m_handle); + if (reply != HAILO_SUCCESS) { + LOGGER__CRITICAL("ConfiguredNetworkGroup_release failed!"); + } +} + +Expected> ConfiguredNetworkGroupClient::activate( + const hailo_activate_network_group_params_t &network_group_params) +{ + // TODO: HRT-6606 + (void)network_group_params; + LOGGER__ERROR("activate is not supported when using multi process service"); + return make_unexpected(HAILO_INVALID_OPERATION); +} + +/* Network group base functions */ +Expected ConfiguredNetworkGroupClient::get_latency_measurement(const std::string &network_name) +{ + return m_client->ConfiguredNetworkGroup_get_latency_measurement(m_handle, network_name); +} + +Expected> ConfiguredNetworkGroupClient::activate_internal( + const hailo_activate_network_group_params_t &network_group_params, uint16_t dynamic_batch_size) +{ + // TODO: HRT-6606 + (void)network_group_params; + (void)dynamic_batch_size; + return make_unexpected(HAILO_INVALID_OPERATION); +} + +const std::string &ConfiguredNetworkGroupClient::get_network_group_name() const +{ + return m_network_group_name; +} + +const std::string &ConfiguredNetworkGroupClient::name() const +{ + return m_network_group_name; +} + +Expected ConfiguredNetworkGroupClient::get_default_streams_interface() +{ + return m_client->ConfiguredNetworkGroup_get_default_stream_interface(m_handle); +} + +std::vector> ConfiguredNetworkGroupClient::get_input_streams_by_interface(hailo_stream_interface_t) +{ + LOGGER__ERROR("get_input_streams_by_interface is not supported when using multi process service"); + std::vector> empty_vec; + return empty_vec; +} + +std::vector> ConfiguredNetworkGroupClient::get_output_streams_by_interface(hailo_stream_interface_t) +{ + LOGGER__ERROR("get_output_streams_by_interface is not supported when using multi process service"); + std::vector> empty_vec; + return empty_vec; +} + +ExpectedRef ConfiguredNetworkGroupClient::get_input_stream_by_name(const std::string&) +{ + LOGGER__ERROR("get_input_stream_by_name is not supported when using multi process service"); + return make_unexpected(HAILO_INVALID_OPERATION); +} + +ExpectedRef ConfiguredNetworkGroupClient::get_output_stream_by_name(const std::string&) +{ + LOGGER__ERROR("get_output_stream_by_name is not supported when using multi process service"); + return make_unexpected(HAILO_INVALID_OPERATION); +} + +Expected ConfiguredNetworkGroupClient::get_input_streams_by_network(const std::string&) +{ + LOGGER__ERROR("get_input_streams_by_network is not supported when using multi process service"); + return make_unexpected(HAILO_INVALID_OPERATION); +} + +Expected ConfiguredNetworkGroupClient::get_output_streams_by_network(const std::string&) +{ + LOGGER__ERROR("get_output_streams_by_network is not supported when using multi process service"); + return make_unexpected(HAILO_INVALID_OPERATION); +} + +InputStreamRefVector ConfiguredNetworkGroupClient::get_input_streams() +{ + LOGGER__ERROR("get_input_streams is not supported when using multi process service"); + InputStreamRefVector empty_vec; + return empty_vec; +} + +OutputStreamRefVector ConfiguredNetworkGroupClient::get_output_streams() +{ + LOGGER__ERROR("get_output_streams is not supported when using multi process service"); + OutputStreamRefVector empty_vec; + return empty_vec; +} + +Expected ConfiguredNetworkGroupClient::get_output_streams_from_vstream_names(const std::map&) +{ + LOGGER__ERROR("get_output_streams_from_vstream_names is not supported when using multi process service"); + return make_unexpected(HAILO_INVALID_OPERATION); +} + +hailo_status ConfiguredNetworkGroupClient::wait_for_activation(const std::chrono::milliseconds&) +{ + LOGGER__ERROR("wait_for_activation is not supported when using multi process service"); + return HAILO_NOT_IMPLEMENTED; +} + +Expected>> ConfiguredNetworkGroupClient::get_output_vstream_groups() +{ + return m_client->ConfiguredNetworkGroup_get_output_vstream_groups(m_handle); +} + +Expected>> ConfiguredNetworkGroupClient::make_output_vstream_params_groups( + bool quantized, hailo_format_type_t format_type, uint32_t timeout_ms, uint32_t queue_size) +{ + // TODO: HRT-6606 + LOGGER__ERROR("make_output_vstream_params_groups is not supported when using multi process service"); + (void)quantized; + (void)format_type; + (void)timeout_ms; + (void)queue_size; + return make_unexpected(HAILO_NOT_IMPLEMENTED); +} + +Expected> ConfiguredNetworkGroupClient::make_input_vstream_params( + bool quantized, hailo_format_type_t format_type, uint32_t timeout_ms, uint32_t queue_size, + const std::string &network_name) +{ + return m_client->ConfiguredNetworkGroup_make_input_vstream_params(m_handle, + quantized, format_type, timeout_ms, queue_size, network_name); +} + +Expected> ConfiguredNetworkGroupClient::make_output_vstream_params( + bool quantized, hailo_format_type_t format_type, uint32_t timeout_ms, uint32_t queue_size, + const std::string &network_name) +{ + return m_client->ConfiguredNetworkGroup_make_output_vstream_params(m_handle, + quantized, format_type, timeout_ms, queue_size, network_name); +} + +Expected> ConfiguredNetworkGroupClient::get_all_stream_infos(const std::string &network_name) const +{ + return m_client->ConfiguredNetworkGroup_get_all_stream_infos(m_handle, network_name); +} + +Expected> ConfiguredNetworkGroupClient::get_network_infos() const +{ + return m_client->ConfiguredNetworkGroup_get_network_infos(m_handle); +} + +Expected> ConfiguredNetworkGroupClient::get_input_vstream_infos( + const std::string &network_name) const +{ + return m_client->ConfiguredNetworkGroup_get_input_vstream_infos(m_handle, network_name); +} + +Expected> ConfiguredNetworkGroupClient::get_output_vstream_infos( + const std::string &network_name) const +{ + return m_client->ConfiguredNetworkGroup_get_output_vstream_infos(m_handle, network_name); +} + +Expected> ConfiguredNetworkGroupClient::get_all_vstream_infos( + const std::string &network_name) const +{ + return m_client->ConfiguredNetworkGroup_get_all_vstream_infos(m_handle, network_name); +} + +hailo_status ConfiguredNetworkGroupClient::set_scheduler_timeout(const std::chrono::milliseconds &timeout, const std::string &network_name) +{ + return m_client->ConfiguredNetworkGroup_set_scheduler_timeout(m_handle, timeout, network_name); +} + +hailo_status ConfiguredNetworkGroupClient::set_scheduler_threshold(uint32_t threshold, const std::string &network_name) +{ + return m_client->ConfiguredNetworkGroup_set_scheduler_threshold(m_handle, threshold, network_name); +} + +AccumulatorPtr ConfiguredNetworkGroupClient::get_activation_time_accumulator() const +{ + LOGGER__ERROR("get_activation_time_accumulator is not supported when using multi process service"); + // TODO: HRT-6606 + return nullptr; +} + +AccumulatorPtr ConfiguredNetworkGroupClient::get_deactivation_time_accumulator() const +{ + LOGGER__ERROR("get_deactivation_time_accumulator is not supported when using multi process service"); + // TODO: HRT-6606 + return nullptr; +} + +bool ConfiguredNetworkGroupClient::is_multi_context() const +{ + LOGGER__ERROR("is_multi_context is not supported when using multi process service"); + // TODO: HRT-6606 + return false; +} + +const ConfigureNetworkParams ConfiguredNetworkGroupClient::get_config_params() const +{ + LOGGER__ERROR("get_config_params is not supported when using multi process service"); + // TODO: HRT-6606 + return {}; +} + +Expected> ConfiguredNetworkGroupClient::create_input_vstreams(const std::map &inputs_params) +{ + auto reply = m_client->InputVStreams_create(m_handle, inputs_params, getpid()); + CHECK_EXPECTED(reply); + auto input_vstreams_handles = reply.release(); + std::vector vstreams; + vstreams.reserve(input_vstreams_handles.size()); + + for (uint32_t handle : input_vstreams_handles) { + auto vstream_client = InputVStreamClient::create(handle); + CHECK_EXPECTED(vstream_client); + auto vstream = VStreamsBuilderUtils::create_input(vstream_client.release()); + vstreams.push_back(std::move(vstream)); + } + return vstreams; +} + +Expected> ConfiguredNetworkGroupClient::create_output_vstreams(const std::map &outputs_params) +{ + auto reply = m_client->OutputVStreams_create(m_handle, outputs_params, getpid()); + CHECK_EXPECTED(reply); + auto output_vstreams_handles = reply.release(); + std::vector vstreams; + vstreams.reserve(output_vstreams_handles.size()); + + for(uint32_t handle : output_vstreams_handles) { + auto vstream_client = OutputVStreamClient::create(handle); + CHECK_EXPECTED(vstream_client); + auto vstream = VStreamsBuilderUtils::create_output(vstream_client.release()); + vstreams.push_back(std::move(vstream)); + } + return vstreams; +} + +} /* namespace hailort */ \ No newline at end of file diff --git a/hailort/libhailort/src/network_group_scheduler.cpp b/hailort/libhailort/src/network_group_scheduler.cpp index 164d232..d92bd81 100644 --- a/hailort/libhailort/src/network_group_scheduler.cpp +++ b/hailort/libhailort/src/network_group_scheduler.cpp @@ -10,67 +10,284 @@ #include "network_group_scheduler.hpp" #include "context_switch/network_group_internal.hpp" #include "hef_internal.hpp" -#include "vdevice_stream.hpp" -#include "vdma_stream.hpp" +#include "vdevice_stream_wrapper.hpp" + +#include namespace hailort { -Expected NetworkGroupScheduler::create_shared(hailo_scheduling_algorithm_t algorithm) +NetworkGroupScheduler::NetworkGroupScheduler(hailo_scheduling_algorithm_t algorithm) : + m_is_switching_network_group(true), + m_current_network_group(INVALID_NETWORK_GROUP_HANDLE), + m_next_network_group(INVALID_NETWORK_GROUP_HANDLE), + m_algorithm(algorithm), + m_before_read_write_mutex(), + m_current_batch_size(0), + m_write_read_cv(), + m_should_monitor(false) +#if defined(__GNUC__) + , m_mon_tmp_output() +#endif +{ + // TODO: HRT-7391 - Change scheduler monitor to work only when MON command is active + m_should_monitor = SchedulerMon::should_monitor(); + if (m_should_monitor) { + auto status = start_mon(); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to initiate hailo monitor of networks, with status {}", status); + } + } +} + +NetworkGroupScheduler::~NetworkGroupScheduler() +{ + if (m_should_monitor) { + m_should_monitor = false; + m_mon_shutdown_event->signal(); + if (m_mon_thread.joinable()) { + m_mon_thread.join(); + } + } +} + +Expected NetworkGroupScheduler::create_round_robin() { - auto ptr = make_shared_nothrow(algorithm); + auto ptr = make_shared_nothrow(); CHECK_AS_EXPECTED(nullptr != ptr, HAILO_OUT_OF_HOST_MEMORY); - return ptr; + return std::static_pointer_cast(ptr); } -hailo_status NetworkGroupScheduler::SchedulerIdleGuard::set_scheduler(std::shared_ptr scheduler) +std::string get_curr_pid_as_str() { - CHECK(nullptr != scheduler, HAILO_INTERNAL_FAILURE); - m_scheduler = scheduler; - m_scheduler->force_idle_state(); +#ifdef _WIN32 + auto pid = GetCurrentProcessId(); +#else + auto pid = getpid(); +#endif + return std::to_string(pid); +} + +hailo_status NetworkGroupScheduler::start_mon() +{ +#if defined(__GNUC__) + m_last_measured_timestamp = std::chrono::steady_clock::now(); + m_mon_shutdown_event = Event::create_shared(Event::State::not_signalled); + CHECK(nullptr != m_mon_shutdown_event, HAILO_OUT_OF_HOST_MEMORY); + + auto tmp_file = open_temp_mon_file(); + CHECK_EXPECTED_AS_STATUS(tmp_file); + m_mon_tmp_output = tmp_file.release(); + + m_mon_thread = std::thread([this] () + { + while (m_should_monitor) { + auto status = m_mon_shutdown_event->wait(DEFAULT_SCHEDULER_MON_INTERVAL); + if (HAILO_TIMEOUT == status) { + dump_state(); + } else if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Scheduler monitor failed with status {}", status); + return; + } + } + return; + }); + return HAILO_SUCCESS; +#else + return HAILO_NOT_IMPLEMENTED; +#endif +} + +#if defined(__GNUC__) +Expected> NetworkGroupScheduler::open_temp_mon_file() +{ + std::string file_name = get_curr_pid_as_str(); + auto tmp_file = TempFile::create(file_name, SCHEDULER_MON_TMP_DIR); + CHECK_EXPECTED(tmp_file); + + auto tmp_file_ptr = make_shared_nothrow(tmp_file.release()); + CHECK_AS_EXPECTED(nullptr != tmp_file_ptr, HAILO_OUT_OF_HOST_MEMORY); + + return tmp_file_ptr; } -NetworkGroupScheduler::SchedulerIdleGuard::~SchedulerIdleGuard() +void NetworkGroupScheduler::dump_state() { - if (m_scheduler) { - m_scheduler->resume_from_idle_state(); + auto file = LockedFile::create(m_mon_tmp_output->name(), "w"); + if (HAILO_SUCCESS != file.status()) { + LOGGER__ERROR("Failed to open and lock file {}, with status: {}", m_mon_tmp_output->name(), file.status()); + return; + } + + ProtoMon mon; + mon.set_pid(get_curr_pid_as_str()); + log_monitor_networks_infos(mon); + log_monitor_frames_infos(mon); + + // Clear accumulators + for (auto &handle_duration_pair : m_active_duration) { + handle_duration_pair.second = 0; + } + for (auto &handle_fps_pair : m_fps_accumulator) { + handle_fps_pair.second = 0; + } + + if (!mon.SerializeToFileDescriptor(file->get_fd())) { + LOGGER__ERROR("Failed to SerializeToFileDescriptor(), with errno: {}", errno); } } +#endif -void NetworkGroupScheduler::force_idle_state() +std::string NetworkGroupScheduler::get_network_group_name(const scheduler_ng_handle_t network_group_handle) { - { - std::unique_lock lock(m_before_read_write_mutex); - m_write_read_cv.wait(lock, [this] { - return has_current_ng_finished(); - }); - deactivate_network_group(); - m_forced_idle_state = true; + auto cng = m_cngs[network_group_handle].lock(); + if (nullptr == cng) { + LOGGER__CRITICAL("Configured network group is null!"); + return ""; } - m_write_read_cv.notify_all(); + return cng->name(); } -void NetworkGroupScheduler::resume_from_idle_state() +// TODO: HRT-7392 - Reduce core percentage when scheduler is idle +void NetworkGroupScheduler::log_monitor_networks_infos(ProtoMon &mon) { - { - std::unique_lock lock(m_before_read_write_mutex); - m_forced_idle_state = false; + auto curr_time = std::chrono::steady_clock::now(); + const auto measurement_duration = std::chrono::duration_cast>(curr_time - m_last_measured_timestamp).count(); + + for (uint32_t network_group_handle = 0; network_group_handle < m_last_measured_activation_timestamp.size(); network_group_handle++) { + assert(contains(m_active_duration, network_group_handle)); + auto curr_ng_core = m_active_duration[network_group_handle]; + + if (network_group_handle == m_current_network_group) { + // Network is currently active + auto time_diff = std::chrono::duration_cast>( + curr_time - m_last_measured_activation_timestamp[m_current_network_group]).count(); + curr_ng_core += time_diff; + m_last_measured_activation_timestamp[m_current_network_group] = curr_time; + } + + auto core_utilization = ((curr_ng_core * 100) / measurement_duration); + auto outputs_count = static_cast(m_allowed_read[network_group_handle].size()); + auto fps = static_cast((m_fps_accumulator[network_group_handle] / outputs_count) / measurement_duration); + + auto net_info = mon.add_networks_infos(); + net_info->set_network_name(get_network_group_name(network_group_handle)); + net_info->set_core_utilization(core_utilization); + net_info->set_fps(fps); } - m_write_read_cv.notify_all(); + + m_last_measured_timestamp = curr_time; +} + +void NetworkGroupScheduler::log_monitor_frames_infos(ProtoMon &mon) +{ + for (uint32_t network_group_handle = 0; network_group_handle < m_cngs.size(); network_group_handle++) { + auto net_frames_info = mon.add_net_frames_infos(); + net_frames_info->set_network_name(get_network_group_name(network_group_handle)); + + assert(contains(m_requested_write, network_group_handle)); + for (auto &streams_requested_write_pair : m_requested_write[network_group_handle]) { + auto &stream_name = streams_requested_write_pair.first; + + auto stream_frames_info = net_frames_info->add_streams_frames_infos(); + stream_frames_info->set_stream_name(stream_name); + stream_frames_info->set_stream_direction(PROTO__STREAM_DIRECTION__HOST_TO_DEVICE); + auto status = set_h2d_frames_counters(network_group_handle, stream_name, *stream_frames_info); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to set stream's {} frames count, status = {}", stream_name, status); + continue; + } + } + + assert(contains(m_allowed_read, network_group_handle)); + for (auto &streams_requested_read_pair : m_allowed_read[network_group_handle]) { + auto &stream_name = streams_requested_read_pair.first; + + auto stream_frames_info = net_frames_info->add_streams_frames_infos(); + stream_frames_info->set_stream_name(stream_name); + stream_frames_info->set_stream_direction(PROTO__STREAM_DIRECTION__DEVICE_TO_HOST); + auto status = set_d2h_frames_counters(network_group_handle, stream_name, *stream_frames_info); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to set stream's {} frames count, status = {}", stream_name, status); + continue; + } + } + } +} + +hailo_status NetworkGroupScheduler::set_h2d_frames_counters(scheduler_ng_handle_t network_group_handle, const std::string &stream_name, + ProtoMonStreamFramesInfo &stream_frames_info) +{ + assert(m_cngs.size() > network_group_handle); + auto current_cng = m_cngs[network_group_handle].lock(); + CHECK(current_cng, HAILO_INTERNAL_FAILURE); + + auto input_stream = current_cng->get_input_stream_by_name(stream_name); + CHECK_EXPECTED_AS_STATUS(input_stream); + + InputStreamBase &vdevice_input = static_cast(input_stream->get()); + auto buffer_frames_size = vdevice_input.get_buffer_frames_size(); + if (HAILO_SUCCESS == buffer_frames_size.status()) { + stream_frames_info.set_buffer_frames_size(static_cast(buffer_frames_size.value())); + } else { + stream_frames_info.set_buffer_frames_size(SCHEDULER_MON_NAN_VAL); + } + + auto pending_frames_count = vdevice_input.get_pending_frames_count(); + if (HAILO_SUCCESS == pending_frames_count.status()) { + stream_frames_info.set_pending_frames_count(static_cast(pending_frames_count.value())); + } else { + stream_frames_info.set_pending_frames_count(SCHEDULER_MON_NAN_VAL); + } + + return HAILO_SUCCESS; +} + +hailo_status NetworkGroupScheduler::set_d2h_frames_counters(scheduler_ng_handle_t network_group_handle, const std::string &stream_name, + ProtoMonStreamFramesInfo &stream_frames_info) +{ + assert(m_cngs.size() > network_group_handle); + auto current_cng = m_cngs[network_group_handle].lock(); + CHECK(current_cng, HAILO_INTERNAL_FAILURE); + + auto output_stream = current_cng->get_output_stream_by_name(stream_name); + CHECK_EXPECTED_AS_STATUS(output_stream); + + OutputStreamBase &vdevice_output = static_cast(output_stream->get()); + auto buffer_frames_size = vdevice_output.get_buffer_frames_size(); + if (HAILO_SUCCESS == buffer_frames_size.status()) { + stream_frames_info.set_buffer_frames_size(static_cast(buffer_frames_size.value())); + } else { + stream_frames_info.set_buffer_frames_size(SCHEDULER_MON_NAN_VAL); + } + + auto pending_frames_count = vdevice_output.get_pending_frames_count(); + if (HAILO_SUCCESS == pending_frames_count.status()) { + stream_frames_info.set_pending_frames_count(static_cast(pending_frames_count.value())); + } else { + stream_frames_info.set_pending_frames_count(SCHEDULER_MON_NAN_VAL); + } + + return HAILO_SUCCESS; } -Expected NetworkGroupScheduler::add_network_group(std::weak_ptr added_cng) +Expected NetworkGroupScheduler::add_network_group(std::weak_ptr added_cng) { - network_group_handle_t network_group_handle = INVALID_NETWORK_GROUP_HANDLE; + scheduler_ng_handle_t network_group_handle = INVALID_NETWORK_GROUP_HANDLE; { std::unique_lock lock(m_before_read_write_mutex); network_group_handle = static_cast(m_cngs.size()); m_cngs.emplace_back(added_cng); - m_timeout_per_network_group[network_group_handle] = DEFAULT_SCHEDULER_TIMEOUT; - m_last_run_time_stamp[network_group_handle] = {}; // Default c'tor to mark this network_group didn't run yet + m_last_measured_activation_timestamp[network_group_handle] = {}; + m_active_duration[network_group_handle] = 0; + m_fps_accumulator[network_group_handle] = 0; + m_last_run_time_stamp[network_group_handle] = std::chrono::steady_clock::now(); + m_frame_was_sent_per_network_group[network_group_handle] = false; + m_timeout_per_network_group[network_group_handle] = make_shared_nothrow(DEFAULT_SCHEDULER_TIMEOUT); + CHECK_AS_EXPECTED(nullptr != m_timeout_per_network_group[network_group_handle], HAILO_OUT_OF_HOST_MEMORY); auto added_cng_ptr = added_cng.lock(); CHECK_AS_EXPECTED(added_cng_ptr, HAILO_INTERNAL_FAILURE); @@ -78,6 +295,17 @@ Expected NetworkGroupScheduler::add_network_group(std::w auto stream_infos = added_cng_ptr->get_all_stream_infos(); CHECK_EXPECTED(stream_infos); + m_max_batch_size[network_group_handle] = CONTROL_PROTOCOL__IGNORE_DYNAMIC_BATCH_SIZE; + auto cng_base = std::dynamic_pointer_cast(added_cng_ptr); + if (cng_base->get_supported_features().multi_context) { + auto batch_size = cng_base->get_stream_batch_size(stream_infos.value()[0].name); + CHECK_EXPECTED(batch_size); + + if (batch_size.value() > HAILO_DEFAULT_BATCH_SIZE) { + m_max_batch_size[network_group_handle] = batch_size.release(); + } + } + // Prepare empty counters for the added cng for (const auto &stream_info : stream_infos.value()) { m_should_ng_stop[network_group_handle][stream_info.name] = false; @@ -86,6 +314,7 @@ Expected NetworkGroupScheduler::add_network_group(std::w m_requested_write[network_group_handle][stream_info.name] = 0; m_written_buffer[network_group_handle][stream_info.name] = 0; m_sent_pending_buffer[network_group_handle][stream_info.name] = 0; + m_current_sent_pending_buffer[network_group_handle][stream_info.name] = 0; m_finished_sent_pending_buffer[network_group_handle][stream_info.name] = 0; auto event = Event::create_shared(Event::State::signalled); @@ -93,8 +322,11 @@ Expected NetworkGroupScheduler::add_network_group(std::w m_write_buffer_events[network_group_handle][stream_info.name] = event; } else { + m_requested_read[network_group_handle][stream_info.name] = 0; m_allowed_read[network_group_handle][stream_info.name] = 0; m_finished_read[network_group_handle][stream_info.name] = 0; + m_current_finished_read[network_group_handle][stream_info.name] = 0; + m_pending_read[network_group_handle][stream_info.name] = 0; } } } @@ -102,48 +334,127 @@ Expected NetworkGroupScheduler::add_network_group(std::w return network_group_handle; } -hailo_status NetworkGroupScheduler::wait_for_write(const network_group_handle_t &network_group_handle, const std::string &stream_name) +hailo_status NetworkGroupScheduler::wait_for_write(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name) { - { - std::unique_lock lock(m_before_read_write_mutex); - assert(contains(m_requested_write, network_group_handle)); - assert(contains(m_write_buffer_events, network_group_handle)); - if ((nullptr != m_ang) && m_switching_network_group && - (m_requested_write[network_group_handle][stream_name] == get_max_value_of_unordered_map(m_requested_write[network_group_handle]))) { - auto status = m_write_buffer_events[network_group_handle][stream_name]->reset(); - CHECK_SUCCESS(status); + while (true) { + auto status = block_write_if_needed(network_group_handle, stream_name); + if (HAILO_STREAM_INTERNAL_ABORT == status) { + return HAILO_STREAM_INTERNAL_ABORT; + } + CHECK_SUCCESS(status); + m_write_read_cv.notify_all(); + + status = m_write_buffer_events[network_group_handle][stream_name]->wait(std::chrono::milliseconds(HAILO_INFINITE)); + CHECK_SUCCESS(status); + + { + std::unique_lock lock(m_before_read_write_mutex); + + auto should_wait_again = should_wait_for_write(network_group_handle, stream_name); + if (HAILO_STREAM_INTERNAL_ABORT == should_wait_again.status()) { + return HAILO_STREAM_INTERNAL_ABORT; + } + CHECK_EXPECTED_AS_STATUS(should_wait_again); + + if (!should_wait_again.value()) { + if (!m_frame_was_sent_per_network_group[network_group_handle]) { + m_frame_was_sent_per_network_group[network_group_handle] = true; + } + m_requested_write[network_group_handle][stream_name]++; + status = allow_writes_for_other_inputs_if_needed(network_group_handle); + CHECK_SUCCESS(status); + break; + } } } + m_write_read_cv.notify_all(); - auto status = m_write_buffer_events[network_group_handle][stream_name]->wait(std::chrono::milliseconds(HAILO_INFINITE)); - CHECK_SUCCESS(status); + return HAILO_SUCCESS; +} - { - std::unique_lock lock(m_before_read_write_mutex); +hailo_status NetworkGroupScheduler::block_write_if_needed(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name) +{ + std::unique_lock lock(m_before_read_write_mutex); + assert(contains(m_write_buffer_events, network_group_handle)); + auto should_wait = should_wait_for_write(network_group_handle, stream_name); + if (HAILO_STREAM_INTERNAL_ABORT == should_wait.status()) { + return HAILO_STREAM_INTERNAL_ABORT; + } + CHECK_EXPECTED_AS_STATUS(should_wait); + if (should_wait.value()) { + auto status = m_write_buffer_events[network_group_handle][stream_name]->reset(); + CHECK_SUCCESS(status); + } + return HAILO_SUCCESS; +} - assert(contains(m_should_ng_stop, network_group_handle)); - if (m_should_ng_stop[network_group_handle][stream_name]) { - return HAILO_STREAM_INTERNAL_ABORT; +bool NetworkGroupScheduler::has_enough_space_in_read_buffers(const scheduler_ng_handle_t &network_group_handle, uint32_t ongoing_frames) +{ + assert(network_group_handle < m_cngs.size()); + auto cng = m_cngs[network_group_handle].lock(); + assert(cng); + + auto output_streams = cng->get_output_streams(); + for (auto &output_stream : output_streams) { + OutputStreamBase &vdevice_output = static_cast(output_stream.get()); + if (auto pending_frames_size = vdevice_output.get_buffer_frames_size()) { + if (pending_frames_size.value() <= ongoing_frames) { + return false; + } + // If couldnt get pending frames size and count (e.g. NMS layer), assume we have space - scheduler switch will prevent deadlocks here } + } + return true; +} - m_requested_write[network_group_handle][stream_name]++; +bool NetworkGroupScheduler::has_input_written_most_frames(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name) +{ + return m_requested_write[network_group_handle][stream_name] == get_max_value_of_unordered_map(m_requested_write[network_group_handle]); +} - if ((nullptr != m_ang) && (m_current_network_group == network_group_handle)) { - m_has_current_ng_finished = false; +bool NetworkGroupScheduler::should_ng_stop(const scheduler_ng_handle_t &network_group_handle) +{ + assert(contains(m_should_ng_stop, network_group_handle)); + for (const auto &name_flag_pair : m_should_ng_stop[network_group_handle]) { + if (name_flag_pair.second) { + return true; } + } - status = allow_writes_for_other_inputs_if_needed(network_group_handle); - CHECK_SUCCESS(status); + return false; +} + +Expected NetworkGroupScheduler::should_wait_for_write(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name) +{ + if (should_ng_stop(network_group_handle)) { + return make_unexpected(HAILO_STREAM_INTERNAL_ABORT); } - m_write_read_cv.notify_all(); + assert(contains(m_requested_write, network_group_handle)); + assert(contains(m_sent_pending_buffer, network_group_handle)); + assert(contains(m_max_batch_size, network_group_handle)); - return HAILO_SUCCESS; + auto pending_buffers = m_requested_write[network_group_handle][stream_name] - m_sent_pending_buffer[network_group_handle][stream_name]; + bool has_written_max_batch_size = (is_ng_multicontext(network_group_handle) && (m_max_batch_size[network_group_handle] == pending_buffers)); + + bool should_stop_writing_because_switching = ((nullptr != m_ang) && m_is_switching_network_group && + (network_group_handle == m_current_network_group) && has_input_written_most_frames(network_group_handle, stream_name)); + + auto min_finished_read = get_min_value_of_unordered_map(m_finished_read[network_group_handle]); + auto ongoing_frames = (min_finished_read < m_requested_write[network_group_handle][stream_name]) ? + (m_requested_write[network_group_handle][stream_name] - min_finished_read) : 0; + bool has_enough_space_for_writes = has_enough_space_in_read_buffers(network_group_handle, ongoing_frames); + + if (has_written_max_batch_size || should_stop_writing_because_switching || (!has_enough_space_for_writes)) { + return true; + } + + return false; } -hailo_status NetworkGroupScheduler::allow_writes_for_other_inputs_if_needed(const network_group_handle_t &network_group_handle) +hailo_status NetworkGroupScheduler::allow_writes_for_other_inputs_if_needed(const scheduler_ng_handle_t &network_group_handle) { - if (!m_has_current_ng_finished && m_switching_network_group) { + if (!has_ng_finished(network_group_handle) && m_is_switching_network_group) { auto max_write = get_max_value_of_unordered_map(m_requested_write[network_group_handle]); for (auto &name_event_pair : m_write_buffer_events[network_group_handle]) { if (m_requested_write[network_group_handle][name_event_pair.first] < max_write) { @@ -155,16 +466,15 @@ hailo_status NetworkGroupScheduler::allow_writes_for_other_inputs_if_needed(cons return HAILO_SUCCESS; } -hailo_status NetworkGroupScheduler::signal_write_finish(const network_group_handle_t &network_group_handle, const std::string &stream_name) +hailo_status NetworkGroupScheduler::signal_write_finish(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name) { { std::unique_lock lock(m_before_read_write_mutex); - assert(contains(m_should_ng_stop, network_group_handle)); - if (m_should_ng_stop[network_group_handle][stream_name]) { + if (should_ng_stop(network_group_handle)) { return HAILO_STREAM_INTERNAL_ABORT; } - + assert(contains(m_written_buffer, network_group_handle)); assert(contains(m_written_buffer[network_group_handle], stream_name)); m_written_buffer[network_group_handle][stream_name]++; @@ -187,41 +497,124 @@ hailo_status NetworkGroupScheduler::signal_write_finish(const network_group_hand return HAILO_SUCCESS; } -hailo_status NetworkGroupScheduler::switch_network_group_if_idle(const network_group_handle_t &network_group_handle, std::unique_lock &read_write_lock) +hailo_status NetworkGroupScheduler::switch_network_group_if_idle(const scheduler_ng_handle_t &network_group_handle, + std::unique_lock &read_write_lock) { - if (!m_forced_idle_state && !m_switching_network_group && m_has_current_ng_finished && - ((nullptr == m_ang) || (network_group_handle != m_current_network_group)) && is_network_group_ready(network_group_handle)) { + const bool check_threshold = false; + if (!m_is_switching_network_group && has_ng_drained_everything(m_current_network_group) && + ((nullptr == m_ang) || (network_group_handle != m_current_network_group)) && is_network_group_ready(network_group_handle, check_threshold)) { auto status = activate_network_group(network_group_handle, read_write_lock); if (HAILO_STREAM_INTERNAL_ABORT == status) { return status; } CHECK_SUCCESS(status); + + return HAILO_SUCCESS; } + auto status = try_change_multicontext_ng_batch_size(network_group_handle, read_write_lock); + CHECK_SUCCESS(status); + return HAILO_SUCCESS; } -hailo_status NetworkGroupScheduler::activate_network_group(const network_group_handle_t &network_group_handle, std::unique_lock &read_write_lock) +bool NetworkGroupScheduler::has_pending_frames(const scheduler_ng_handle_t &network_group_handle) { - deactivate_network_group(); + uint32_t transferred_frames = get_max_value_of_unordered_map(m_sent_pending_buffer[network_group_handle]); + for (auto &name_counter_pair : m_finished_read[network_group_handle]) { + if (name_counter_pair.second < transferred_frames) { + return true; + } + } + return false; +} - m_switching_network_group = false; - auto status = allow_all_writes(); - CHECK_SUCCESS(status); +hailo_status NetworkGroupScheduler::try_change_multicontext_ng_batch_size(const scheduler_ng_handle_t &network_group_handle, + std::unique_lock &read_write_lock) +{ + if ((nullptr != m_ang) && (network_group_handle == m_current_network_group) && is_ng_multicontext(network_group_handle) && has_ng_finished(network_group_handle)) { + if (get_buffered_frames_count() > 0) { + hailo_status status = activate_network_group(network_group_handle, read_write_lock, true); + CHECK_SUCCESS(status); + } + } - assert(m_cngs.size() > network_group_handle); - auto cng = m_cngs[network_group_handle].lock(); - CHECK(cng, HAILO_INTERNAL_FAILURE); + return HAILO_SUCCESS; +} + +hailo_status NetworkGroupScheduler::activate_network_group(const scheduler_ng_handle_t &network_group_handle, + std::unique_lock &read_write_lock, bool keep_nn_config) +{ + for (auto &name_counter_pair : m_current_sent_pending_buffer[network_group_handle]) { + name_counter_pair.second = 0; + } - auto cng_base = std::dynamic_pointer_cast(cng); - auto expected_ang = cng_base->force_activate(); - CHECK_EXPECTED_AS_STATUS(expected_ang); - m_last_run_time_stamp[network_group_handle] = std::chrono::steady_clock::now(); // Mark last timestamp on activation + for (auto &name_counter_pair : m_current_finished_read[network_group_handle]) { + name_counter_pair.second = 0; + } - m_ang = expected_ang.release(); + uint16_t batch_size = CONTROL_PROTOCOL__IGNORE_DYNAMIC_BATCH_SIZE; + if (is_ng_multicontext(network_group_handle)) { + batch_size = static_cast(get_buffered_frames_count()); + } + if (CONTROL_PROTOCOL__IGNORE_DYNAMIC_BATCH_SIZE == batch_size) { + batch_size = 1; + } + + bool has_same_batch_size_as_previous = (m_current_batch_size == batch_size); + m_current_batch_size = batch_size; + + if (m_current_network_group != INVALID_NETWORK_GROUP_HANDLE) { + if ((network_group_handle != m_current_network_group) || (!has_same_batch_size_as_previous)) { + if (nullptr != m_ang) { + auto status = m_ang->set_keep_nn_config_during_reset(keep_nn_config); + CHECK_SUCCESS(status); + } + + deactivate_network_group(); + } else { + reset_current_ng_timestamps(); + } + } + + m_last_measured_activation_timestamp[network_group_handle] = std::chrono::steady_clock::now(); + + if (m_current_network_group != network_group_handle) { + m_is_switching_network_group = false; + } + + auto status = allow_all_writes(); + CHECK_SUCCESS(status); + + if ((network_group_handle != m_current_network_group) || (!has_same_batch_size_as_previous)) { + assert(m_cngs.size() > network_group_handle); + auto cng = m_cngs[network_group_handle].lock(); + CHECK(cng, HAILO_INTERNAL_FAILURE); + + auto cng_base = std::dynamic_pointer_cast(cng); + auto expected_ang = cng_base->force_activate(batch_size); + CHECK_EXPECTED_AS_STATUS(expected_ang); + + m_ang = expected_ang.release(); + + // Register to get interrupts - has to be after network group is activated + for (auto &output_stream : cng->get_output_streams()) { + OutputStreamBase &vdevice_output = static_cast(output_stream.get()); + status = vdevice_output.register_for_d2h_interrupts( + [this, network_group_handle, name = output_stream.get().name(), format = vdevice_output.get_layer_info().format.order](uint32_t frames) { + if(hailo_format_order_t::HAILO_FORMAT_ORDER_HAILO_NMS != format) { + std::unique_lock lock(m_before_read_write_mutex); + m_pending_read[network_group_handle][name] += frames; + } + m_write_read_cv.notify_all(); + }); + CHECK_SUCCESS(status); + } + } + + m_last_run_time_stamp[network_group_handle] = std::chrono::steady_clock::now(); // Mark timestamp on activation m_current_network_group = network_group_handle; - m_has_current_ng_finished = false; status = send_all_pending_buffers(network_group_handle, read_write_lock); if (HAILO_STREAM_INTERNAL_ABORT == status) { @@ -235,8 +628,8 @@ hailo_status NetworkGroupScheduler::activate_network_group(const network_group_h hailo_status NetworkGroupScheduler::allow_all_writes() { - for (auto &name_dict_pair : m_write_buffer_events) { - for (auto &name_event_pair : name_dict_pair.second) { + for (auto &handle_dict_pair : m_write_buffer_events) { + for (auto &name_event_pair : handle_dict_pair.second) { auto status = name_event_pair.second->signal(); CHECK_SUCCESS(status); } @@ -244,7 +637,8 @@ hailo_status NetworkGroupScheduler::allow_all_writes() return HAILO_SUCCESS; } -hailo_status NetworkGroupScheduler::send_all_pending_buffers(const network_group_handle_t &network_group_handle, std::unique_lock &read_write_lock) +hailo_status NetworkGroupScheduler::send_all_pending_buffers(const scheduler_ng_handle_t &network_group_handle, + std::unique_lock &read_write_lock) { if ((nullptr == m_ang) || (m_current_network_group != network_group_handle)) { return HAILO_SUCCESS; @@ -253,7 +647,8 @@ hailo_status NetworkGroupScheduler::send_all_pending_buffers(const network_group while (true) { uint32_t finished_sending_count = 0; for (auto &name_counter_pair : m_written_buffer[network_group_handle]) { - if (m_sent_pending_buffer[network_group_handle][name_counter_pair.first] < name_counter_pair.second) { + if ((m_sent_pending_buffer[network_group_handle][name_counter_pair.first] < name_counter_pair.second) + && ((!is_ng_multicontext(network_group_handle)) || (m_current_sent_pending_buffer[network_group_handle][name_counter_pair.first] < m_current_batch_size))) { auto status = send_pending_buffer(network_group_handle, name_counter_pair.first, read_write_lock); if (HAILO_STREAM_INTERNAL_ABORT == status) { LOGGER__INFO("send_pending_buffer has failed with status=HAILO_STREAM_INTERNAL_ABORT"); @@ -272,7 +667,7 @@ hailo_status NetworkGroupScheduler::send_all_pending_buffers(const network_group return HAILO_SUCCESS; } -hailo_status NetworkGroupScheduler::send_pending_buffer(const network_group_handle_t &network_group_handle, const std::string &stream_name, +hailo_status NetworkGroupScheduler::send_pending_buffer(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name, std::unique_lock &read_write_lock) { assert(m_cngs.size() > network_group_handle); @@ -282,27 +677,29 @@ hailo_status NetworkGroupScheduler::send_pending_buffer(const network_group_hand auto input_stream = current_cng->get_input_stream_by_name(stream_name); CHECK_EXPECTED_AS_STATUS(input_stream); - m_has_current_ng_finished = false; - - VDeviceInputStream &vdevice_input = dynamic_cast(input_stream->get()); + VDeviceInputStreamWrapper &vdevice_input = static_cast(input_stream->get()); auto pending_buffer_state = vdevice_input.send_pending_buffer(); CHECK_EXPECTED_AS_STATUS(pending_buffer_state); assert(contains(m_sent_pending_buffer, network_group_handle)); m_sent_pending_buffer[network_group_handle][stream_name]++; + assert(contains(m_current_sent_pending_buffer, network_group_handle)); + m_current_sent_pending_buffer[network_group_handle][stream_name]++; + auto status = pending_buffer_state->finish(vdevice_input.get_timeout(), read_write_lock); if (HAILO_STREAM_INTERNAL_ABORT == status) { LOGGER__INFO("finish has failed with status=HAILO_STREAM_INTERNAL_ABORT"); return status; } CHECK_SUCCESS(status); - + assert(contains(m_finished_sent_pending_buffer, network_group_handle)); m_finished_sent_pending_buffer[network_group_handle][stream_name]++; - // Update m_has_current_ng_finished here because after finishing send pending buffer the network group can actually be finished - m_has_current_ng_finished = has_current_ng_finished(); + if (should_ng_stop(network_group_handle)) { + return HAILO_STREAM_INTERNAL_ABORT; + } return HAILO_SUCCESS; } @@ -310,63 +707,92 @@ hailo_status NetworkGroupScheduler::send_pending_buffer(const network_group_hand void NetworkGroupScheduler::deactivate_network_group() { if (m_ang) { - reset_current_ng_counters(); m_ang.reset(); - m_last_run_time_stamp[m_current_network_group] = std::chrono::steady_clock::now(); // Mark last timestamp on deactivation } + + reset_current_ng_timestamps(); +} + +void NetworkGroupScheduler::reset_current_ng_timestamps() +{ + m_last_run_time_stamp[m_current_network_group] = std::chrono::steady_clock::now(); // Mark timestamp on de-activation + assert(contains(m_last_measured_activation_timestamp, m_current_network_group)); + const auto active_duration_sec = std::chrono::duration_cast>( + std::chrono::steady_clock::now() - m_last_measured_activation_timestamp[m_current_network_group]).count(); + m_active_duration[m_current_network_group] += active_duration_sec; } -hailo_status NetworkGroupScheduler::switch_network_group_if_should_be_next(const network_group_handle_t &network_group_handle, +hailo_status NetworkGroupScheduler::switch_network_group_if_should_be_next(const scheduler_ng_handle_t &network_group_handle, std::unique_lock &read_write_lock) { - // Checking (nullptr == m_ang) for activating the first time the scheduler is running - if (!m_forced_idle_state && m_switching_network_group && m_has_current_ng_finished && is_network_group_ready(network_group_handle) && - ((nullptr == m_ang) || (m_next_network_group == network_group_handle))) { + const bool check_threshold = false; + /* Checking (nullptr == m_ang) for activating the first time the scheduler is running. + In this case we don't want to check threshold. */ + if (m_is_switching_network_group && has_ng_drained_everything(m_current_network_group) && + (((nullptr == m_ang) && is_network_group_ready(network_group_handle, check_threshold)) || (m_next_network_group == network_group_handle))) { auto status = activate_network_group(network_group_handle, read_write_lock); if (HAILO_STREAM_INTERNAL_ABORT == status) { return status; } + CHECK_SUCCESS(status); } - return HAILO_SUCCESS; } -bool NetworkGroupScheduler::is_network_group_ready(const network_group_handle_t &network_group_handle) +bool NetworkGroupScheduler::is_network_group_ready(const scheduler_ng_handle_t &network_group_handle, bool check_threshold) { assert(contains(m_written_buffer, network_group_handle)); - assert(contains(m_timeout_per_network_group, network_group_handle)); assert(contains(m_min_threshold_per_stream, network_group_handle)); + assert(contains(m_last_run_time_stamp, network_group_handle)); - // TODO: move inside the for-loop when timeout-per-network will be supported - bool timeout_passed = (m_timeout_per_network_group[network_group_handle] < - (std::chrono::steady_clock::now() - m_last_run_time_stamp[network_group_handle])); + // Check if there arent any write requests + bool has_pending_writes = false; + uint32_t written_frames = get_max_value_of_unordered_map(m_requested_write[network_group_handle]); + for (const auto &name_counter_pair : m_pending_read[network_group_handle]) { + uint32_t finished_read_frames = m_finished_read[network_group_handle][name_counter_pair.first]; + if ((finished_read_frames + name_counter_pair.second) < written_frames) { + has_pending_writes = true; + break; + } + } - for (auto &name_counter_pair : m_written_buffer[network_group_handle]) { - // Check if there arent any write requests - if (0 == name_counter_pair.second) { - return false; + // Check if there arent any read requests + bool has_pending_reads = false; + uint32_t read_requests = get_max_value_of_unordered_map(m_requested_read[network_group_handle]); + for (const auto &name_counter_pair : m_allowed_read[network_group_handle]) { + if (name_counter_pair.second < read_requests) { + has_pending_reads = true; + break; } + } - // Check if there arent enough write requests and timeout didnt passed - if ((name_counter_pair.second < m_min_threshold_per_stream[network_group_handle][name_counter_pair.first]) && (!timeout_passed)) { - return false; + if (check_threshold) { + for (auto &name_counter_pair : m_written_buffer[network_group_handle]) { + // Check if there arent enough write requests to reach threshold and timeout didnt passed + if ((name_counter_pair.second < m_min_threshold_per_stream[network_group_handle][name_counter_pair.first]) && + ((*(m_timeout_per_network_group[network_group_handle]) > (std::chrono::steady_clock::now() - m_last_run_time_stamp[network_group_handle])))) { + return false; + } } } - return true; + return has_pending_writes && has_pending_reads && (!has_pending_frames(network_group_handle)); } -hailo_status NetworkGroupScheduler::wait_for_read(const network_group_handle_t &network_group_handle, const std::string &stream_name) +hailo_status NetworkGroupScheduler::wait_for_read(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name) { { std::unique_lock lock(m_before_read_write_mutex); assert(contains(m_allowed_read, network_group_handle)); assert(contains(m_allowed_read[network_group_handle], stream_name)); + assert(contains(m_requested_read, network_group_handle)); + assert(contains(m_requested_read[network_group_handle], stream_name)); + + m_requested_read[network_group_handle][stream_name]++; hailo_status status = HAILO_UNINITIALIZED; m_write_read_cv.wait(lock, [this, network_group_handle, stream_name, &status, &lock] { - assert(contains(m_should_ng_stop, network_group_handle)); - if (m_should_ng_stop[network_group_handle][stream_name]) { + if (should_ng_stop(network_group_handle)) { status = HAILO_STREAM_INTERNAL_ABORT; return true; // return true so that the wait will finish } @@ -388,7 +814,6 @@ hailo_status NetworkGroupScheduler::wait_for_read(const network_group_handle_t & } CHECK_SUCCESS(status); - assert(contains(m_allowed_read, network_group_handle)); m_allowed_read[network_group_handle][stream_name]++; } m_write_read_cv.notify_all(); @@ -396,72 +821,65 @@ hailo_status NetworkGroupScheduler::wait_for_read(const network_group_handle_t & return HAILO_SUCCESS; } -bool NetworkGroupScheduler::can_stream_read(const network_group_handle_t &network_group_handle, const std::string &stream_name) +bool NetworkGroupScheduler::can_stream_read(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name) { - if (nullptr == m_ang) { - return false; - } - - if (m_current_network_group != network_group_handle) { - return false; - } - - if (m_has_current_ng_finished) { - return false; - } - assert(contains(m_allowed_read, network_group_handle)); assert(contains(m_sent_pending_buffer, network_group_handle)); return m_allowed_read[network_group_handle][stream_name].load() < get_max_value_of_unordered_map(m_sent_pending_buffer[network_group_handle]); } -hailo_status NetworkGroupScheduler::signal_read_finish(const network_group_handle_t &network_group_handle, const std::string &stream_name) +hailo_status NetworkGroupScheduler::signal_read_finish(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name) { { std::unique_lock lock(m_before_read_write_mutex); assert(contains(m_finished_read, network_group_handle)); assert(contains(m_finished_read[network_group_handle], stream_name)); + // Prevent integer underflow in nms + if (m_pending_read[network_group_handle][stream_name] > 0) { + m_pending_read[network_group_handle][stream_name]--; + } m_finished_read[network_group_handle][stream_name]++; - hailo_status status = mark_switching_ng_if_ready(); + m_current_finished_read[network_group_handle][stream_name]++; + m_fps_accumulator[network_group_handle]++; + + hailo_status status = choose_next_network_group(); CHECK_SUCCESS(status); + + if (!is_ng_multicontext(network_group_handle)) { + // Prevents integer overflow of the counters + decrease_current_ng_counters(); + } else { + status = try_change_multicontext_ng_batch_size(network_group_handle, lock); + CHECK_SUCCESS(status); + } } + + auto status = allow_all_writes(); + CHECK_SUCCESS(status); m_write_read_cv.notify_all(); return HAILO_SUCCESS; } -hailo_status NetworkGroupScheduler::mark_switching_ng_if_ready() +bool NetworkGroupScheduler::has_ng_finished(scheduler_ng_handle_t network_group_handle) { - if (!m_switching_network_group) { - for (uint32_t i = 0; i < m_cngs.size() - 1; i++) { - network_group_handle_t handle = m_current_network_group + i + 1; - handle %= static_cast(m_cngs.size()); + if (INVALID_NETWORK_GROUP_HANDLE == network_group_handle) { + return true; // If no network group is running, consider it as finished + } - if (is_network_group_ready(handle)) { - m_switching_network_group = true; - m_next_network_group = handle; - break; + if (is_ng_multicontext(network_group_handle)) { + for (auto &name_counter_pair : m_current_finished_read[network_group_handle]) { + if (name_counter_pair.second < m_current_batch_size) { + return false; } } - } - m_has_current_ng_finished = has_current_ng_finished(); - - // Prevents integer overflow of the counters - decrease_current_ng_counters(); - - return HAILO_SUCCESS; -} -bool NetworkGroupScheduler::has_current_ng_finished() -{ - uint32_t written_frames = get_max_value_of_unordered_map(m_requested_write[m_current_network_group]); - for (auto &name_counter_pair : m_finished_read[m_current_network_group]) { - if (name_counter_pair.second < written_frames) { - return false; - } + return true; } - for (auto &name_counter_pair : m_finished_sent_pending_buffer[m_current_network_group]) { + + uint32_t written_frames = get_max_value_of_unordered_map(m_requested_write[network_group_handle]); + for (auto &name_counter_pair : m_finished_read[network_group_handle]) { if (name_counter_pair.second < written_frames) { return false; } @@ -469,33 +887,28 @@ bool NetworkGroupScheduler::has_current_ng_finished() return true; } -void NetworkGroupScheduler::reset_current_ng_counters() +bool NetworkGroupScheduler::has_ng_drained_everything(scheduler_ng_handle_t network_group_handle) { - uint32_t written_frames = get_max_value_of_unordered_map(m_sent_pending_buffer[m_current_network_group]); - - for (auto &name_counter_pair : m_requested_write[m_current_network_group]) { - name_counter_pair.second -= written_frames; + if (INVALID_NETWORK_GROUP_HANDLE == network_group_handle) { + // If no network group is running, consider it as drained + return true; } - for (auto &name_counter_pair : m_written_buffer[m_current_network_group]) { - assert(name_counter_pair.second == written_frames); - name_counter_pair.second = 0; - } - for (auto &name_counter_pair : m_sent_pending_buffer[m_current_network_group]) { - assert(name_counter_pair.second == written_frames); - name_counter_pair.second = 0; - } - for (auto &name_counter_pair : m_finished_sent_pending_buffer[m_current_network_group]) { - assert(name_counter_pair.second == written_frames); - name_counter_pair.second = 0; - } - for (auto &name_counter_pair : m_allowed_read[m_current_network_group]) { - // TODO (HRT-6811): Recover from timeout, verify counters - name_counter_pair.second = 0; + + uint32_t written_frames = get_max_value_of_unordered_map(m_requested_write[network_group_handle]); + for (auto &name_counter_pair : m_finished_sent_pending_buffer[network_group_handle]) { + if (name_counter_pair.second < written_frames) { + return false; + } } - for (auto &name_counter_pair : m_finished_read[m_current_network_group]) { - // TODO (HRT-6811): Recover from timeout, verify counters - name_counter_pair.second = 0; + + assert(contains(m_finished_read, network_group_handle)); + for (const auto &name_counter_pair : m_pending_read[network_group_handle]) { + uint32_t finished_read_frames = m_finished_read[network_group_handle][name_counter_pair.first]; + if ((finished_read_frames + name_counter_pair.second) < written_frames) { + return false; + } } + return true; } void NetworkGroupScheduler::decrease_current_ng_counters() @@ -525,6 +938,11 @@ void NetworkGroupScheduler::decrease_current_ng_counters() return; } } + for (auto &name_counter_pair : m_requested_read[m_current_network_group]) { + if (name_counter_pair.second <= 1) { + return; + } + } for (auto &name_counter_pair : m_allowed_read[m_current_network_group]) { if (name_counter_pair.second <= 1) { return; @@ -548,6 +966,9 @@ void NetworkGroupScheduler::decrease_current_ng_counters() for (auto &name_counter_pair : m_finished_sent_pending_buffer[m_current_network_group]) { name_counter_pair.second--; } + for (auto &name_counter_pair : m_requested_read[m_current_network_group]) { + name_counter_pair.second--; + } for (auto &name_counter_pair : m_allowed_read[m_current_network_group]) { name_counter_pair.second--; } @@ -556,7 +977,22 @@ void NetworkGroupScheduler::decrease_current_ng_counters() } } -hailo_status NetworkGroupScheduler::enable_stream(const network_group_handle_t &network_group_handle, const std::string &stream_name) +bool NetworkGroupScheduler::is_ng_multicontext(const scheduler_ng_handle_t &network_group_handle) +{ + return (CONTROL_PROTOCOL__IGNORE_DYNAMIC_BATCH_SIZE != m_max_batch_size[network_group_handle]); +} + +uint32_t NetworkGroupScheduler::get_buffered_frames_count() +{ + std::unordered_map buffered_frames; + for (const auto &name_counter_pair : m_requested_write[m_current_network_group]) { + buffered_frames[name_counter_pair.first] = name_counter_pair.second - m_sent_pending_buffer[m_current_network_group][name_counter_pair.first]; + } + + return get_max_value_of_unordered_map(buffered_frames); +} + +hailo_status NetworkGroupScheduler::enable_stream(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name) { { std::unique_lock lock(m_before_read_write_mutex); @@ -572,11 +1008,10 @@ hailo_status NetworkGroupScheduler::enable_stream(const network_group_handle_t & return HAILO_SUCCESS; } -hailo_status NetworkGroupScheduler::disable_stream(const network_group_handle_t &network_group_handle, const std::string &stream_name) +hailo_status NetworkGroupScheduler::disable_stream(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name) { { std::unique_lock lock(m_before_read_write_mutex); - assert(contains(m_should_ng_stop, network_group_handle)); if (m_should_ng_stop[network_group_handle][stream_name]) { return HAILO_SUCCESS; @@ -595,33 +1030,35 @@ hailo_status NetworkGroupScheduler::disable_stream(const network_group_handle_t return HAILO_SUCCESS; } -hailo_status NetworkGroupScheduler::set_timeout(const network_group_handle_t &network_group_handle, const std::chrono::milliseconds &timeout, const std::string &network_name) +hailo_status NetworkGroupScheduler::set_timeout(const scheduler_ng_handle_t &network_group_handle, const std::chrono::milliseconds &timeout, const std::string &network_name) { (void)network_name; assert(contains(m_timeout_per_network_group, network_group_handle)); assert(contains(m_last_run_time_stamp, network_group_handle)); - CHECK((std::chrono::time_point() == m_last_run_time_stamp[network_group_handle]), HAILO_INVALID_OPERATION, + assert(contains(m_frame_was_sent_per_network_group, network_group_handle)); + CHECK(!m_frame_was_sent_per_network_group[network_group_handle], HAILO_INVALID_OPERATION, "Setting scheduler timeout is allowed only before sending / receiving frames on the network group."); - m_timeout_per_network_group[network_group_handle] = timeout; + *(m_timeout_per_network_group[network_group_handle]) = timeout; assert(m_cngs.size() > network_group_handle); auto cng = m_cngs[network_group_handle].lock(); CHECK(cng, HAILO_INTERNAL_FAILURE); - auto name = (network_name.empty()) ? cng->get_network_group_name() : network_name; + auto name = (network_name.empty()) ? cng->name() : network_name; LOGGER__INFO("Setting scheduler timeout of {} to {}ms", name, timeout.count()); return HAILO_SUCCESS; } -hailo_status NetworkGroupScheduler::set_threshold(const network_group_handle_t &network_group_handle, uint32_t threshold, const std::string &network_name) +hailo_status NetworkGroupScheduler::set_threshold(const scheduler_ng_handle_t &network_group_handle, uint32_t threshold, const std::string &network_name) { (void)network_name; assert(contains(m_min_threshold_per_stream, network_group_handle)); assert(contains(m_last_run_time_stamp, network_group_handle)); - CHECK((std::chrono::time_point() == m_last_run_time_stamp[network_group_handle]), HAILO_INVALID_OPERATION, + assert(contains(m_frame_was_sent_per_network_group, network_group_handle)); + CHECK(!m_frame_was_sent_per_network_group[network_group_handle], HAILO_INVALID_OPERATION, "Setting scheduler threshold is allowed only before sending / receiving frames on the network group."); for (auto &threshold_per_stream_pair : m_min_threshold_per_stream[network_group_handle]) { threshold_per_stream_pair.second = threshold; @@ -631,10 +1068,35 @@ hailo_status NetworkGroupScheduler::set_threshold(const network_group_handle_t & auto cng = m_cngs[network_group_handle].lock(); CHECK(cng, HAILO_INTERNAL_FAILURE); - auto name = (network_name.empty()) ? cng->get_network_group_name() : network_name; + auto name = (network_name.empty()) ? cng->name() : network_name; LOGGER__INFO("Setting scheduler threshold of {} to {} frames", name, threshold); return HAILO_SUCCESS; } +hailo_status NetworkGroupScheduler::choose_next_network_group() +{ + if (!m_is_switching_network_group) { + bool check_threshold = true; + if (find_next_network_group_by_algorithm(check_threshold)) { + return HAILO_SUCCESS; + } + } + return HAILO_SUCCESS; +} + +bool NetworkGroupSchedulerRoundRobin::find_next_network_group_by_algorithm(bool check_threshold) +{ + for (uint32_t i = 0; i < m_cngs.size() - 1; i++) { + uint32_t index = m_current_network_group + i + 1; + index %= static_cast(m_cngs.size()); + if (is_network_group_ready(index, check_threshold)) { + m_is_switching_network_group = true; + m_next_network_group = index; + return true; + } + } + return false; +} + } /* namespace hailort */ diff --git a/hailort/libhailort/src/network_group_scheduler.hpp b/hailort/libhailort/src/network_group_scheduler.hpp index b7b0b20..f8f86fb 100644 --- a/hailort/libhailort/src/network_group_scheduler.hpp +++ b/hailort/libhailort/src/network_group_scheduler.hpp @@ -12,8 +12,10 @@ #include "hailo/hailort.h" #include "hailo/expected.hpp" -#include "common/utils.hpp" #include "hailo/network_group.hpp" +#include "common/utils.hpp" +#include "common/filesystem.hpp" +#include "scheduler_mon.hpp" #include @@ -24,9 +26,7 @@ namespace hailort { #define INVALID_NETWORK_GROUP_HANDLE (UINT32_MAX) -using network_group_handle_t = uint32_t; - -const auto SCHEDULER_REFRESH_INTERVAL = std::chrono::milliseconds(500); +using scheduler_ng_handle_t = uint32_t; class NetworkGroupScheduler; using NetworkGroupSchedulerPtr = std::shared_ptr; @@ -35,108 +35,142 @@ using NetworkGroupSchedulerPtr = std::shared_ptr; using NetworkGroupSchedulerWeakPtr = std::weak_ptr; using stream_name_t = std::string; -class NetworkGroupScheduler final { + + +class NetworkGroupScheduler +{ public: - static Expected create_shared(hailo_scheduling_algorithm_t algorithm); - NetworkGroupScheduler(hailo_scheduling_algorithm_t algorithm) - : m_algorithm(algorithm), m_before_read_write_mutex(), m_write_read_cv(), m_current_network_group(0), - m_switching_network_group(true), m_has_current_ng_finished(true), m_next_network_group(0), m_forced_idle_state(false) - { - m_should_stop = false; - // Temp solution until we'll implement timeout as timers - m_thread = std::thread([this] () { - while (!m_should_stop.load()) { - m_write_read_cv.notify_all(); - std::this_thread::sleep_for(SCHEDULER_REFRESH_INTERVAL); - } - }); - } - - ~NetworkGroupScheduler() - { - m_should_stop = true; - m_thread.join(); - } + static Expected create_round_robin(); + NetworkGroupScheduler(hailo_scheduling_algorithm_t algorithm); + + virtual ~NetworkGroupScheduler(); + NetworkGroupScheduler(const NetworkGroupScheduler &other) = delete; + NetworkGroupScheduler &operator=(const NetworkGroupScheduler &other) = delete; + NetworkGroupScheduler &operator=(NetworkGroupScheduler &&other) = delete; + NetworkGroupScheduler(NetworkGroupScheduler &&other) noexcept = delete; hailo_scheduling_algorithm_t algorithm() { return m_algorithm; } - Expected add_network_group(std::weak_ptr added_cng); + Expected add_network_group(std::weak_ptr added_cng); - hailo_status wait_for_write(const network_group_handle_t &network_group_handle, const std::string &stream_name); - hailo_status signal_write_finish(const network_group_handle_t &network_group_handle, const std::string &stream_name); - hailo_status wait_for_read(const network_group_handle_t &network_group_handle, const std::string &stream_name); - hailo_status signal_read_finish(const network_group_handle_t &network_group_handle, const std::string &stream_name); + hailo_status wait_for_write(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name); + hailo_status signal_write_finish(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name); + hailo_status wait_for_read(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name); + hailo_status signal_read_finish(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name); - hailo_status enable_stream(const network_group_handle_t &network_group_handle, const std::string &stream_name); - hailo_status disable_stream(const network_group_handle_t &network_group_handle, const std::string &stream_name); + hailo_status enable_stream(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name); + hailo_status disable_stream(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name); - hailo_status set_timeout(const network_group_handle_t &network_group_handle, const std::chrono::milliseconds &timeout, const std::string &network_name); - hailo_status set_threshold(const network_group_handle_t &network_group_handle, uint32_t threshold, const std::string &network_name); + hailo_status set_timeout(const scheduler_ng_handle_t &network_group_handle, const std::chrono::milliseconds &timeout, const std::string &network_name); + hailo_status set_threshold(const scheduler_ng_handle_t &network_group_handle, uint32_t threshold, const std::string &network_name); - class SchedulerIdleGuard final { - /* After 'set_scheduler()' is called, the idle guard will guarantee nothing is running on the device. - Relevant for state and configs changes */ - public: - SchedulerIdleGuard() = default; - ~SchedulerIdleGuard(); - hailo_status set_scheduler(std::shared_ptr scheduler); - private: - std::shared_ptr m_scheduler; - }; +protected: + hailo_status choose_next_network_group(); + virtual bool find_next_network_group_by_algorithm(bool check_threshold) = 0; + bool is_network_group_ready(const scheduler_ng_handle_t &network_group_handle, bool check_threshold); - static std::unique_ptr create_scheduler_idle_guard() - { - auto guard = make_unique_nothrow(); - return guard; - } + std::atomic_bool m_is_switching_network_group; + scheduler_ng_handle_t m_current_network_group; + scheduler_ng_handle_t m_next_network_group; + + std::vector> m_cngs; + std::unique_ptr m_ang; private: - hailo_status switch_network_group_if_idle(const network_group_handle_t &network_group_handle, std::unique_lock &read_write_lock); - hailo_status activate_network_group(const network_group_handle_t &network_group_handle, std::unique_lock &read_write_lock); - hailo_status send_all_pending_buffers(const network_group_handle_t &network_group_handle, std::unique_lock &read_write_lock); - hailo_status send_pending_buffer(const network_group_handle_t &network_group_handle, const std::string &stream_name, std::unique_lock &read_write_lock); - hailo_status switch_network_group_if_should_be_next(const network_group_handle_t &network_group_handle, std::unique_lock &read_write_lock); + hailo_status switch_network_group_if_should_be_next(const scheduler_ng_handle_t &network_group_handle, std::unique_lock &read_write_lock); + hailo_status switch_network_group_if_idle(const scheduler_ng_handle_t &network_group_handle, std::unique_lock &read_write_lock); + hailo_status activate_network_group(const scheduler_ng_handle_t &network_group_handle, std::unique_lock &read_write_lock, + bool keep_nn_config = false); void deactivate_network_group(); - bool is_network_group_ready(const network_group_handle_t &network_group_handle); - hailo_status mark_switching_ng_if_ready(); - bool can_stream_read(const network_group_handle_t &network_group_handle, const std::string &stream_name); - bool has_current_ng_finished(); - void reset_current_ng_counters(); - void decrease_current_ng_counters(); - hailo_status allow_all_writes(); - hailo_status allow_writes_for_other_inputs_if_needed(const network_group_handle_t &network_group_handle); + void reset_current_ng_timestamps(); + hailo_status try_change_multicontext_ng_batch_size(const scheduler_ng_handle_t &network_group_handle, std::unique_lock &read_write_lock); - void force_idle_state(); - void resume_from_idle_state(); + bool has_input_written_most_frames(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name); + bool can_stream_write(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name); + hailo_status block_write_if_needed(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name); + Expected should_wait_for_write(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name); + hailo_status allow_all_writes(); + hailo_status allow_writes_for_other_inputs_if_needed(const scheduler_ng_handle_t &network_group_handle); + hailo_status send_all_pending_buffers(const scheduler_ng_handle_t &network_group_handle, std::unique_lock &read_write_lock); + hailo_status send_pending_buffer(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name, std::unique_lock &read_write_lock); + + bool should_ng_stop(const scheduler_ng_handle_t &network_group_handle); + bool can_stream_read(const scheduler_ng_handle_t &network_group_handle, const std::string &stream_name); + bool has_ng_finished(scheduler_ng_handle_t network_group_handle); + bool has_ng_drained_everything(scheduler_ng_handle_t network_group_handle); + bool has_pending_frames(const scheduler_ng_handle_t &network_group_handle); + bool has_enough_space_in_read_buffers(const scheduler_ng_handle_t &network_group_handle, uint32_t ongoing_frames); + void decrease_current_ng_counters(); + bool is_ng_multicontext(const scheduler_ng_handle_t &network_group_handle); + uint32_t get_buffered_frames_count(); + + std::string get_network_group_name(scheduler_ng_handle_t network_group_handle); + hailo_status start_mon(); + void log_monitor_networks_infos(ProtoMon &mon); + void log_monitor_frames_infos(ProtoMon &mon); + hailo_status set_h2d_frames_counters(scheduler_ng_handle_t network_group_handle, const std::string &stream_name, + ProtoMonStreamFramesInfo &stream_frames_info); + hailo_status set_d2h_frames_counters(scheduler_ng_handle_t network_group_handle, const std::string &stream_name, + ProtoMonStreamFramesInfo &stream_frames_info); +#if defined(__GNUC__) + Expected> open_temp_mon_file(); + void dump_state(); +#endif hailo_scheduling_algorithm_t m_algorithm; std::mutex m_before_read_write_mutex; + std::atomic_uint32_t m_current_batch_size; std::condition_variable m_write_read_cv; - std::unordered_map> m_should_ng_stop; - std::unordered_map> m_requested_write; - std::unordered_map> m_written_buffer; - std::unordered_map> m_write_buffer_events; - std::unordered_map> m_sent_pending_buffer; - std::unordered_map> m_finished_sent_pending_buffer; - std::unordered_map> m_allowed_read; - std::unordered_map> m_finished_read; - network_group_handle_t m_current_network_group; - std::vector> m_cngs; - std::unique_ptr m_ang; - std::atomic_bool m_switching_network_group; - std::atomic_bool m_has_current_ng_finished; - network_group_handle_t m_next_network_group; - std::atomic_bool m_forced_idle_state; - std::thread m_thread; - std::atomic_bool m_should_stop; + std::unordered_map> m_requested_write; + std::unordered_map> m_written_buffer; + std::unordered_map> m_sent_pending_buffer; + std::unordered_map> m_current_sent_pending_buffer; + std::unordered_map> m_finished_sent_pending_buffer; + + std::unordered_map> m_requested_read; + std::unordered_map> m_allowed_read; + std::unordered_map> m_finished_read; + std::unordered_map> m_current_finished_read; + std::unordered_map> m_pending_read; + + std::unordered_map> m_min_threshold_per_stream; + + std::unordered_map> m_last_run_time_stamp; + std::unordered_map> m_timeout_per_network_group; + std::unordered_map m_frame_was_sent_per_network_group; + std::unordered_map m_max_batch_size; + + std::unordered_map> m_should_ng_stop; + std::unordered_map> m_write_buffer_events; + + // Params for the scheduler MON + std::atomic_bool m_should_monitor; + std::thread m_mon_thread; + EventPtr m_mon_shutdown_event; +#if defined(__GNUC__) + std::shared_ptr m_mon_tmp_output; +#endif + std::chrono::time_point m_last_measured_timestamp; + std::unordered_map> m_last_measured_activation_timestamp; + // TODO: Consider adding Accumulator classes for more info (min, max, mean, etc..) + std::unordered_map m_active_duration; + std::unordered_map m_fps_accumulator; +}; + + +class NetworkGroupSchedulerRoundRobin : public NetworkGroupScheduler +{ +public: + NetworkGroupSchedulerRoundRobin() : + NetworkGroupScheduler(HAILO_SCHEDULING_ALGORITHM_ROUND_ROBIN) + {}; - std::unordered_map> m_min_threshold_per_stream; - std::unordered_map m_timeout_per_network_group; - std::unordered_map> m_last_run_time_stamp; +protected: + virtual bool find_next_network_group_by_algorithm(bool check_threshold) override; }; } /* namespace hailort */ diff --git a/hailort/libhailort/src/os/hailort_driver.hpp b/hailort/libhailort/src/os/hailort_driver.hpp index 817a0f7..470e24b 100755 --- a/hailort/libhailort/src/os/hailort_driver.hpp +++ b/hailort/libhailort/src/os/hailort_driver.hpp @@ -15,12 +15,18 @@ #include "hailo/expected.hpp" #include "d2h_event_queue.hpp" #include "os/file_descriptor.hpp" +#include "os/mmap_buffer.hpp" +#include "vdma/channel_id.hpp" #include #include #include #include +#ifdef __QNX__ +#include +#endif // __QNX__ + namespace hailort { @@ -41,6 +47,13 @@ static_assert((0 == ((PENDING_BUFFERS_SIZE - 1) & PENDING_BUFFERS_SIZE)), "PEND #define PCIE_EXPECTED_MD5_LENGTH (16) +constexpr size_t VDMA_CHANNELS_PER_ENGINE = 32; +constexpr uint8_t MIN_H2D_CHANNEL_INDEX = 0; +constexpr uint8_t MAX_H2D_CHANNEL_INDEX = 15; +constexpr uint8_t MIN_D2H_CHANNEL_INDEX = MAX_H2D_CHANNEL_INDEX + 1; +constexpr uint8_t MAX_D2H_CHANNEL_INDEX = 31; + + enum class PciBar { bar0 = 0, bar1, @@ -61,6 +74,17 @@ struct ChannelInterruptTimestampList { size_t count; }; +#if defined(__linux__) || defined(_MSC_VER) +using vdma_mapped_buffer_driver_identifier = uintptr_t; +#elif defined(__QNX__) +struct vdma_mapped_buffer_driver_identifier { + shm_handle_t shm_handle; + int shm_fd; +}; +#else +#error "unsupported platform!" +#endif // defined(__linux__) || defined(_MSC_VER) + class HailoRTDriver final { public: @@ -101,23 +125,30 @@ public: static Expected create(const std::string &dev_path); +// TODO: HRT-7309 add implementation for Windows +#if defined(__linux__) || defined(__QNX__) + static hailo_status hailo_ioctl(int fd, int request, void* request_struct, int &error_status); +#endif // defined(__linux__) || defined(__QNX__) + static Expected> scan_pci(); hailo_status read_bar(PciBar bar, off_t offset, size_t size, void *buf); hailo_status write_bar(PciBar bar, off_t offset, size_t size, const void *buf); - Expected read_vdma_channel_registers(off_t offset, size_t size); - hailo_status write_vdma_channel_registers(off_t offset, size_t size, uint32_t data); + Expected read_vdma_channel_register(vdma::ChannelId channel_id, DmaDirection data_direction, size_t offset, + size_t reg_size); + hailo_status write_vdma_channel_register(vdma::ChannelId channel_id, DmaDirection data_direction, size_t offset, + size_t reg_size, uint32_t data); hailo_status vdma_buffer_sync(VdmaBufferHandle buffer, DmaDirection sync_direction, void *address, size_t buffer_size); - Expected vdma_channel_enable(uint32_t channel_index, DmaDirection data_direction, + Expected vdma_channel_enable(vdma::ChannelId channel_id, DmaDirection data_direction, uintptr_t desc_list_handle, bool enable_timestamps_measure); - hailo_status vdma_channel_disable(uint32_t channel_index, VdmaChannelHandle channel_handle); - Expected wait_channel_interrupts(uint32_t channel_index, VdmaChannelHandle channel_handle, - const std::chrono::milliseconds &timeout); - hailo_status vdma_channel_abort(uint32_t channel_index, VdmaChannelHandle channel_handle); - hailo_status vdma_channel_clear_abort(uint32_t channel_index, VdmaChannelHandle channel_handle); + hailo_status vdma_channel_disable(vdma::ChannelId channel_index, VdmaChannelHandle channel_handle); + Expected wait_channel_interrupts(vdma::ChannelId channel_id, + VdmaChannelHandle channel_handle, const std::chrono::milliseconds &timeout); + hailo_status vdma_channel_abort(vdma::ChannelId channel_id, VdmaChannelHandle channel_handle); + hailo_status vdma_channel_clear_abort(vdma::ChannelId channel_id, VdmaChannelHandle channel_handle); Expected read_notification(); hailo_status disable_notifications(); @@ -148,7 +179,7 @@ public: * of user allocated buffer */ Expected vdma_buffer_map(void *user_address, size_t required_size, DmaDirection data_direction, - uintptr_t driver_buff_handle); + vdma_mapped_buffer_driver_identifier &driver_buff_handle); /** * Unmaps user buffer mapped using HailoRTDriver::map_buffer. @@ -222,10 +253,27 @@ public: hailo_status mark_as_used(); +#ifdef __QNX__ + inline pid_t resource_manager_pid() const + { + return m_resource_manager_pid; + } +#endif // __QNX__ + inline bool allocate_driver_buffer() const { return m_allocate_driver_buffer; } + inline uint16_t desc_max_page_size() const + { + return m_desc_max_page_size; + } + + inline size_t dma_engines_count() const + { + return m_dma_engines_count; + } + HailoRTDriver(const HailoRTDriver &other) = delete; HailoRTDriver &operator=(const HailoRTDriver &other) = delete; HailoRTDriver(HailoRTDriver &&other) noexcept = default; @@ -239,12 +287,18 @@ private: HailoRTDriver(const std::string &dev_path, FileDescriptor &&fd, hailo_status &status); + bool is_valid_channel_id(const vdma::ChannelId &channel_id); + FileDescriptor m_fd; std::string m_dev_path; uint16_t m_desc_max_page_size; BoardType m_board_type; DmaType m_dma_type; bool m_allocate_driver_buffer; + size_t m_dma_engines_count; +#ifdef __QNX__ + pid_t m_resource_manager_pid; +#endif // __QNX__ }; } /* namespace hailort */ diff --git a/hailort/libhailort/src/os/posix/hailort_driver.cpp b/hailort/libhailort/src/os/posix/hailort_driver.cpp index acffa8e..f6cd762 100755 --- a/hailort/libhailort/src/os/posix/hailort_driver.cpp +++ b/hailort/libhailort/src/os/posix/hailort_driver.cpp @@ -20,6 +20,10 @@ namespace hailort { + +static_assert(VDMA_CHANNELS_PER_ENGINE == MAX_VDMA_CHANNELS_PER_ENGINE, "Driver and libhailort parameters mismatch"); +static_assert(MIN_D2H_CHANNEL_INDEX == VDMA_DEST_CHANNELS_START, "Driver and libhailort parameters mismatch"); + constexpr hailo_dma_data_direction direction_to_dma_data_direction(HailoRTDriver::DmaDirection direction) { switch (direction){ case HailoRTDriver::DmaDirection::H2D: @@ -85,6 +89,31 @@ Expected HailoRTDriver::create(const std::string &dev_path) return object; } +hailo_status HailoRTDriver::hailo_ioctl(int fd, int request, void* request_struct, int &error_status) +{ + int res = ioctl(fd, request, request_struct); + if (0 > res) { +#if defined(__linux__) + error_status = errno; +#elif defined(__QNX__) + error_status = -res; +#else +#error "unsupported platform!" +#endif // __linux__ + switch (error_status) { + case ETIMEDOUT: + return HAILO_TIMEOUT; + case ECONNABORTED: + return HAILO_STREAM_INTERNAL_ABORT; + case ECONNRESET: + return HAILO_STREAM_NOT_ACTIVATED; + default: + return HAILO_PCIE_DRIVER_FAIL; + } + } + return HAILO_SUCCESS; +} + static hailo_status validate_driver_version(const hailo_driver_info &driver_info) { hailo_version_t library_version{}; @@ -105,9 +134,9 @@ HailoRTDriver::HailoRTDriver(const std::string &dev_path, FileDescriptor &&fd, h m_allocate_driver_buffer(false) { hailo_driver_info driver_info = {}; - if (0 > ioctl(m_fd, HAILO_QUERY_DRIVER_INFO, &driver_info)) { - LOGGER__ERROR("Failed query driver info, errno {}", errno); - status = HAILO_PCIE_DRIVER_FAIL; + int err = 0; + if (HAILO_SUCCESS != (status = hailo_ioctl(m_fd, HAILO_QUERY_DRIVER_INFO, &driver_info, err))) { + LOGGER__ERROR("Failed query driver info, errno {}", err); return; } LOGGER__INFO("Hailo PCIe driver version {}.{}.{}", driver_info.major_version, @@ -120,14 +149,14 @@ HailoRTDriver::HailoRTDriver(const std::string &dev_path, FileDescriptor &&fd, h } hailo_device_properties device_properties = {}; - if (0 > ioctl(m_fd, HAILO_QUERY_DEVICE_PROPERTIES, &device_properties)) { - LOGGER__ERROR("Failed query pcie device properties, errno {}", errno); - status = HAILO_PCIE_DRIVER_FAIL; + if (HAILO_SUCCESS != (status = hailo_ioctl(m_fd, HAILO_QUERY_DEVICE_PROPERTIES, &device_properties, err))) { + LOGGER__ERROR("Failed query pcie device properties, errno {}", err); return; } m_desc_max_page_size = device_properties.desc_max_page_size; m_allocate_driver_buffer = (HAILO_ALLOCATION_MODE_DRIVER == device_properties.allocation_mode); + m_dma_engines_count = device_properties.dma_engines_count; switch (device_properties.board_type) { case HAILO8: m_board_type = BoardType::HAILO8; @@ -154,6 +183,10 @@ HailoRTDriver::HailoRTDriver(const std::string &dev_path, FileDescriptor &&fd, h return; } +#ifdef __QNX__ + m_resource_manager_pid = device_properties.resource_manager_pid; +#endif // __QNX__ + status = HAILO_SUCCESS; } @@ -162,8 +195,9 @@ Expected HailoRTDriver::read_notification() hailo_d2h_notification notification_buffer = {}; D2H_EVENT_MESSAGE_t notification; - auto rc = ioctl(this->m_fd, HAILO_READ_NOTIFICATION, ¬ification_buffer); - if (0 > rc) { + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_READ_NOTIFICATION, ¬ification_buffer, err); + if (HAILO_SUCCESS != status) { return make_unexpected(HAILO_PCIE_DRIVER_FAIL); } @@ -176,8 +210,12 @@ Expected HailoRTDriver::read_notification() hailo_status HailoRTDriver::disable_notifications() { - auto rc = ioctl(this->m_fd, HAILO_DISABLE_NOTIFICATION, 0); - CHECK(0 <= rc, HAILO_PCIE_DRIVER_FAIL, "HAILO_DISABLE_NOTIFICATION failed with errno:{}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_DISABLE_NOTIFICATION, 0, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("HAILO_DISABLE_NOTIFICATION failed with errno: {}", err); + return HAILO_PCIE_DRIVER_FAIL; + } return HAILO_SUCCESS; } @@ -217,34 +255,48 @@ Expected> HailoRTDriver::scan_pci() static_assert(true, "Error, Unsupported Platform"); #endif //defined (__linux__) -Expected HailoRTDriver::read_vdma_channel_registers(off_t offset, size_t size) +Expected HailoRTDriver::read_vdma_channel_register(vdma::ChannelId channel_id, DmaDirection data_direction, + size_t offset, size_t reg_size) { - hailo_channel_registers_params params = { - .transfer_direction = TRANSFER_READ, + CHECK_AS_EXPECTED(is_valid_channel_id(channel_id), HAILO_INVALID_ARGUMENT, "Invalid channel id {} given", channel_id); + CHECK_AS_EXPECTED(data_direction != DmaDirection::BOTH, HAILO_INVALID_ARGUMENT, "Invalid direction given"); + hailo_vdma_channel_read_register_params params = { + .engine_index = channel_id.engine_index, + .channel_index = channel_id.channel_index, + .direction = direction_to_dma_data_direction(data_direction), .offset = offset, - .size = size, + .reg_size = reg_size, .data = 0 }; - if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_REGISTERS, ¶ms)) { - LOGGER__ERROR("HailoRTDriver::read_vdma_channel_registers failed with errno:{}", errno); + int err = 0; + auto status = hailo_ioctl(m_fd, HAILO_VDMA_CHANNEL_READ_REGISTER, ¶ms, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("HailoRTDriver::read_vdma_channel_register failed with errno:{}", err); return make_unexpected(HAILO_PCIE_DRIVER_FAIL); } return std::move(params.data); } -hailo_status HailoRTDriver::write_vdma_channel_registers(off_t offset, size_t size, uint32_t data) +hailo_status HailoRTDriver::write_vdma_channel_register(vdma::ChannelId channel_id, DmaDirection data_direction, + size_t offset, size_t reg_size, uint32_t data) { - hailo_channel_registers_params params = { - .transfer_direction = TRANSFER_WRITE, + CHECK(is_valid_channel_id(channel_id), HAILO_INVALID_ARGUMENT, "Invalid channel id {} given", channel_id); + CHECK(data_direction != DmaDirection::BOTH, HAILO_INVALID_ARGUMENT, "Invalid direction given"); + hailo_vdma_channel_write_register_params params = { + .engine_index = channel_id.engine_index, + .channel_index = channel_id.channel_index, + .direction = direction_to_dma_data_direction(data_direction), .offset = offset, - .size = size, + .reg_size = reg_size, .data = data }; - if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_REGISTERS, ¶ms)) { - LOGGER__ERROR("HailoRTDriver::write_vdma_channel_registers failed with errno:{}", errno); + int err = 0; + auto status = hailo_ioctl(m_fd, HAILO_VDMA_CHANNEL_WRITE_REGISTER, ¶ms, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("HailoRTDriver::write_vdma_channel_register failed with errno:{}", err); return HAILO_PCIE_DRIVER_FAIL; } @@ -276,8 +328,10 @@ hailo_status HailoRTDriver::read_bar(PciBar bar, off_t offset, size_t size, void return HAILO_INVALID_ARGUMENT; } - if (0 > ioctl(this->m_fd, HAILO_BAR_TRANSFER, &transfer)) { - LOGGER__ERROR("HailoRTDriver::read_bar failed with errno:{}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_BAR_TRANSFER, &transfer, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("HailoRTDriver::read_bar failed with errno:{}", err); return HAILO_PCIE_DRIVER_FAIL; } @@ -313,8 +367,10 @@ hailo_status HailoRTDriver::write_bar(PciBar bar, off_t offset, size_t size, con memcpy(transfer.buffer, buf, transfer.count); - if (0 > ioctl(this->m_fd, HAILO_BAR_TRANSFER, &transfer)) { - LOGGER__ERROR("HailoRTDriver::write_bar failed with errno:{}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_BAR_TRANSFER, &transfer, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("HailoRTDriver::write_bar failed with errno:{}", err); return HAILO_PCIE_DRIVER_FAIL; } @@ -324,6 +380,7 @@ hailo_status HailoRTDriver::write_bar(PciBar bar, off_t offset, size_t size, con hailo_status HailoRTDriver::vdma_buffer_sync(VdmaBufferHandle handle, DmaDirection sync_direction, void *address, size_t buffer_size) { +#if defined(__linux__) CHECK(sync_direction != DmaDirection::BOTH, HAILO_INVALID_ARGUMENT, "Can't sync vdma data both host and device"); hailo_vdma_buffer_sync_params sync_info{ .handle = handle, @@ -331,84 +388,114 @@ hailo_status HailoRTDriver::vdma_buffer_sync(VdmaBufferHandle handle, DmaDirecti .buffer_address = address, .buffer_size = buffer_size }; - if (0 > ioctl(this->m_fd, HAILO_VDMA_BUFFER_SYNC, &sync_info)) { - LOGGER__ERROR("HAILO_VDMA_BUFFER_SYNC failed with errno:{}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_VDMA_BUFFER_SYNC, &sync_info, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("HAILO_VDMA_BUFFER_SYNC failed with errno:{}", err); return HAILO_PCIE_DRIVER_FAIL; } return HAILO_SUCCESS; +// TODO: HRT-6717 - Remove ifdef when Implement sync ioctl (if determined needed in qnx) +#elif defined( __QNX__) + (void) handle; + (void) sync_direction; + (void) address; + (void) buffer_size; + return HAILO_SUCCESS; +#else +#error "unsupported platform!" +#endif // __linux__ } -Expected HailoRTDriver::vdma_channel_enable(uint32_t channel_index, +Expected HailoRTDriver::vdma_channel_enable(vdma::ChannelId channel_id, DmaDirection data_direction, uintptr_t desc_list_handle, bool enable_timestamps_measure) { - CHECK_AS_EXPECTED(data_direction != DmaDirection::BOTH, HAILO_INVALID_ARGUMENT); + CHECK_AS_EXPECTED(is_valid_channel_id(channel_id), HAILO_INVALID_ARGUMENT, "Invalid channel id {} given", channel_id); + CHECK_AS_EXPECTED(data_direction != DmaDirection::BOTH, HAILO_INVALID_ARGUMENT, "Invalid direction given"); hailo_vdma_channel_enable_params params { - .channel_index = channel_index, + .engine_index = channel_id.engine_index, + .channel_index = channel_id.channel_index, .direction = direction_to_dma_data_direction(data_direction), .desc_list_handle = desc_list_handle, .enable_timestamps_measure = enable_timestamps_measure, .channel_handle = INVALID_CHANNEL_HANDLE_VALUE, }; - if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_ENABLE, ¶ms)) { - LOGGER__ERROR("Failed to enable interrupt for channel {} with errno:{}", channel_index, errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_VDMA_CHANNEL_ENABLE, ¶ms, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to enable interrupt for channel {} with errno:{}", channel_id, err); return make_unexpected(HAILO_PCIE_DRIVER_FAIL); } return VdmaChannelHandle(params.channel_handle); } -hailo_status HailoRTDriver::vdma_channel_disable(uint32_t channel_index, VdmaChannelHandle channel_handle) +hailo_status HailoRTDriver::vdma_channel_disable(vdma::ChannelId channel_id, VdmaChannelHandle channel_handle) { + CHECK(is_valid_channel_id(channel_id), HAILO_INVALID_ARGUMENT, "Invalid channel id {} given", channel_id); hailo_vdma_channel_disable_params params { - .channel_index = channel_index, + .engine_index = channel_id.engine_index, + .channel_index = channel_id.channel_index, .channel_handle = channel_handle }; - if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_DISABLE, ¶ms)) { - LOGGER__ERROR("Failed to disable interrupt for channel {} with errno:{}", channel_index, errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_VDMA_CHANNEL_DISABLE, ¶ms, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to disable interrupt for channel {} with errno:{}", channel_id, err); return HAILO_PCIE_DRIVER_FAIL; } return HAILO_SUCCESS; } -Expected HailoRTDriver::wait_channel_interrupts(uint32_t channel_index, VdmaChannelHandle channel_handle, - const std::chrono::milliseconds &timeout) +Expected HailoRTDriver::wait_channel_interrupts(vdma::ChannelId channel_id, + VdmaChannelHandle channel_handle, const std::chrono::milliseconds &timeout) { + CHECK_AS_EXPECTED(is_valid_channel_id(channel_id), HAILO_INVALID_ARGUMENT, "Invalid channel id {} given", channel_id); CHECK_AS_EXPECTED(timeout.count() >= 0, HAILO_INVALID_ARGUMENT); - const uint32_t timestamps_count = MAX_IRQ_TIMESTAMPS_SIZE; - struct hailo_channel_interrupt_timestamp timestamps[timestamps_count]; +#if defined(__linux__) + struct hailo_channel_interrupt_timestamp timestamps[MAX_IRQ_TIMESTAMPS_SIZE]; +#endif hailo_vdma_channel_wait_params data { - .channel_index = channel_index, + .engine_index = channel_id.engine_index, + .channel_index = channel_id.channel_index, .channel_handle = channel_handle, .timeout_ms = static_cast(timeout.count()), + .timestamps_count = MAX_IRQ_TIMESTAMPS_SIZE, +// In linux send address to local buffer because there isnt room on stack for array +#if defined(__linux__) .timestamps = timestamps, - .timestamps_count = timestamps_count +#elif defined(__QNX__) + .timestamps = {} +#else +#error "unsupported platform!" +#endif // __linux__ }; - if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_WAIT_INT, &data)) { - const auto ioctl_errno = errno; - if (ETIMEDOUT == ioctl_errno) { - LOGGER__ERROR("Waiting for interrupt for channel {} timed-out (errno=ETIMEDOUT)", channel_index); - return make_unexpected(HAILO_TIMEOUT); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_VDMA_CHANNEL_WAIT_INT, &data, err); + if (HAILO_SUCCESS != status) { + if (HAILO_TIMEOUT == status) { + LOGGER__ERROR("Waiting for interrupt for channel {} timed-out (errno=ETIMEDOUT)", channel_id); + return make_unexpected(status); } - if (ECONNABORTED == ioctl_errno) { - LOGGER__INFO("Channel (index={}) was aborted!", channel_index); - return make_unexpected(HAILO_STREAM_INTERNAL_ABORT); + if (HAILO_STREAM_INTERNAL_ABORT == status) { + LOGGER__INFO("Channel (index={}) was aborted!", channel_id); + return make_unexpected(status); } - if (ECONNRESET == ioctl_errno) { - LOGGER__INFO("Channel (index={}) was deactivated!", channel_index); - return make_unexpected(HAILO_STREAM_NOT_ACTIVATED); + if (HAILO_STREAM_NOT_ACTIVATED == status) { + LOGGER__INFO("Channel (index={}) was deactivated!", channel_id); + return make_unexpected(status); } - LOGGER__ERROR("Failed to wait interrupt for channel {} with errno:{}", channel_index, ioctl_errno); + LOGGER__ERROR("Failed to wait interrupt for channel {} with errno:{}", channel_id, err); return make_unexpected(HAILO_PCIE_DRIVER_FAIL); } - return create_interrupt_timestamp_list(data); } @@ -430,8 +517,10 @@ hailo_status HailoRTDriver::fw_control(const void *request, size_t request_len, memcpy(&command.buffer, request, request_len); command.timeout_ms = static_cast(timeout.count()); command.cpu_id = translate_cpu_id(cpu_id); - if (0 > ioctl(this->m_fd, HAILO_FW_CONTROL, &command)) { - LOGGER__ERROR("HAILO_FW_CONTROL failed with errno:{}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_FW_CONTROL, &command, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("HAILO_FW_CONTROL failed with errno:{}", err); return HAILO_FW_CONTROL_FAILURE; } @@ -462,8 +551,10 @@ hailo_status HailoRTDriver::read_log(uint8_t *buffer, size_t buffer_size, size_t CHECK(buffer_size <= sizeof(params.buffer), HAILO_PCIE_DRIVER_FAIL, "Given buffer size {} is bigger than buffer size used to read logs {}", buffer_size, sizeof(params.buffer)); - if (0 > ioctl(this->m_fd, HAILO_READ_LOG, ¶ms)) { - LOGGER__ERROR("Failed to read log with errno:{}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_READ_LOG, ¶ms, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to read log with errno:{}", err); return HAILO_PCIE_DRIVER_FAIL; } @@ -478,8 +569,10 @@ hailo_status HailoRTDriver::read_log(uint8_t *buffer, size_t buffer_size, size_t hailo_status HailoRTDriver::reset_nn_core() { - if (0 > ioctl(this->m_fd, HAILO_RESET_NN_CORE, nullptr)) { - LOGGER__ERROR("Failed to reset nn core with errno:{}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_RESET_NN_CORE, nullptr, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to reset nn core with errno:{}", err); return HAILO_PCIE_DRIVER_FAIL; } @@ -487,8 +580,10 @@ hailo_status HailoRTDriver::reset_nn_core() } Expected HailoRTDriver::vdma_buffer_map(void *user_address, size_t required_size, - DmaDirection data_direction, uintptr_t driver_buff_handle) + DmaDirection data_direction, vdma_mapped_buffer_driver_identifier &driver_buff_handle) { + +#if defined(__linux__) hailo_vdma_buffer_map_params map_user_buffer_info { .user_address = user_address, .size = required_size, @@ -496,9 +591,24 @@ Expected HailoRTDriver::vdma_buffer_map(void *u .allocated_buffer_handle = driver_buff_handle, .mapped_handle = 0 }; +#elif defined( __QNX__) + hailo_vdma_buffer_map_params map_user_buffer_info { + .shared_memory_handle = driver_buff_handle.shm_handle, + .size = required_size, + .data_direction = direction_to_dma_data_direction(data_direction), + .allocated_buffer_handle = INVALID_DRIVER_HANDLE_VALUE, + .mapped_handle = 0 + }; - if (0 > ioctl(this->m_fd, HAILO_VDMA_BUFFER_MAP, &map_user_buffer_info)) { - LOGGER__ERROR("Failed to map user buffer with errno:{}", errno); + (void)user_address; +#else +#error "unsupported platform!" +#endif // __linux__ + + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_VDMA_BUFFER_MAP, &map_user_buffer_info, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to map user buffer with errno:{}", err); return make_unexpected(HAILO_PCIE_DRIVER_FAIL); } @@ -507,8 +617,14 @@ Expected HailoRTDriver::vdma_buffer_map(void *u hailo_status HailoRTDriver::vdma_buffer_unmap(VdmaBufferHandle handle) { - if (0 > ioctl(this->m_fd, HAILO_VDMA_BUFFER_UNMAP, handle)) { - LOGGER__ERROR("Failed to unmap user buffer with errno:{}", errno); + hailo_vdma_buffer_unmap_params unmap_user_buffer_info { + .mapped_handle = handle + }; + + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_VDMA_BUFFER_UNMAP, &unmap_user_buffer_info, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to unmap user buffer with errno:{}", err); return HAILO_PCIE_DRIVER_FAIL; } @@ -519,8 +635,10 @@ Expected> HailoRTDriver::descriptors_list_create( { hailo_desc_list_create_params create_desc_info {.desc_count = desc_count, .desc_handle = 0, .dma_address = 0 }; - if (0 > ioctl(this->m_fd, HAILO_DESC_LIST_CREATE, &create_desc_info)) { - LOGGER__ERROR("Failed to create descriptors list with errno:{}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_DESC_LIST_CREATE, &create_desc_info, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to create descriptors list with errno:{}", err); return make_unexpected(HAILO_PCIE_DRIVER_FAIL); } @@ -529,8 +647,10 @@ Expected> HailoRTDriver::descriptors_list_create( hailo_status HailoRTDriver::descriptors_list_release(uintptr_t desc_handle) { - if (0 > ioctl(this->m_fd, HAILO_DESC_LIST_RELEASE, desc_handle)) { - LOGGER__ERROR("Failed to release descriptors list with errno: {}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_DESC_LIST_RELEASE, &desc_handle, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to release descriptors list with errno: {}", err); return HAILO_PCIE_DRIVER_FAIL; } @@ -546,29 +666,35 @@ hailo_status HailoRTDriver::descriptors_list_bind_vdma_buffer(uintptr_t desc_han config_info.desc_page_size = desc_page_size; config_info.channel_index = channel_index; - if (0 > ioctl(this->m_fd, HAILO_DESC_LIST_BIND_VDMA_BUFFER, &config_info)) { - LOGGER__ERROR("Failed to bind vdma buffer to descriptors list with errno: {}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_DESC_LIST_BIND_VDMA_BUFFER, &config_info, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to bind vdma buffer to descriptors list with errno: {}", err); return HAILO_PCIE_DRIVER_FAIL; } return HAILO_SUCCESS; } -hailo_status HailoRTDriver::vdma_channel_abort(uint32_t channel_index, VdmaChannelHandle channel_handle) +hailo_status HailoRTDriver::vdma_channel_abort(vdma::ChannelId channel_id, VdmaChannelHandle channel_handle) { + CHECK(is_valid_channel_id(channel_id), HAILO_INVALID_ARGUMENT, "Invalid channel id {} given", channel_id); + hailo_vdma_channel_abort_params params = { - .channel_index = channel_index, + .engine_index = channel_id.engine_index, + .channel_index = channel_id.channel_index, .channel_handle = channel_handle }; - if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_ABORT, ¶ms)) { - const auto ioctl_errno = errno; - if (ECONNRESET == ioctl_errno) { - LOGGER__DEBUG("Channel (index={}) was deactivated!", channel_index); - return HAILO_STREAM_NOT_ACTIVATED; + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_VDMA_CHANNEL_ABORT, ¶ms, err); + if (HAILO_SUCCESS != status) { + if (HAILO_STREAM_NOT_ACTIVATED == status) { + LOGGER__DEBUG("Channel (index={}) was deactivated!", channel_id); + return status; } else { - LOGGER__ERROR("Failed to abort vdma channel (index={}) with errno: {}", channel_index, errno); + LOGGER__ERROR("Failed to abort vdma channel (index={}) with errno: {}", channel_id, err); return HAILO_PCIE_DRIVER_FAIL; } } @@ -576,21 +702,25 @@ hailo_status HailoRTDriver::vdma_channel_abort(uint32_t channel_index, VdmaChann return HAILO_SUCCESS; } -hailo_status HailoRTDriver::vdma_channel_clear_abort(uint32_t channel_index, VdmaChannelHandle channel_handle) +hailo_status HailoRTDriver::vdma_channel_clear_abort(vdma::ChannelId channel_id, VdmaChannelHandle channel_handle) { + CHECK(is_valid_channel_id(channel_id), HAILO_INVALID_ARGUMENT, "Invalid channel id {} given", channel_id); + hailo_vdma_channel_clear_abort_params params = { - .channel_index = channel_index, + .engine_index = channel_id.engine_index, + .channel_index = channel_id.channel_index, .channel_handle = channel_handle }; - if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_CLEAR_ABORT, ¶ms)) { - const auto ioctl_errno = errno; - if (ECONNRESET == ioctl_errno) { - LOGGER__DEBUG("Channel (index={}) was deactivated!", channel_index); - return HAILO_STREAM_NOT_ACTIVATED; + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_VDMA_CHANNEL_CLEAR_ABORT, ¶ms, err); + if (HAILO_SUCCESS != status) { + if (HAILO_STREAM_NOT_ACTIVATED == status) { + LOGGER__DEBUG("Channel (index={}) was deactivated!", channel_id); + return status; } else { - LOGGER__ERROR("Failed to clear abort vdma channel (index={}) with errno: {}", channel_index, errno); + LOGGER__ERROR("Failed to clear abort vdma channel (index={}) with errno: {}", channel_id, err); return HAILO_PCIE_DRIVER_FAIL; } } @@ -608,8 +738,10 @@ Expected HailoRTDriver::vdma_low_memory_buffer_alloc(size_t size) .buffer_handle = 0 }; - if (0 > ioctl(this->m_fd, HAILO_VDMA_LOW_MEMORY_BUFFER_ALLOC, &allocate_params)) { - LOGGER__ERROR("Failed to allocate buffer with errno: {}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_VDMA_LOW_MEMORY_BUFFER_ALLOC, &allocate_params, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to allocate buffer with errno: {}", err); return make_unexpected(HAILO_PCIE_DRIVER_FAIL); } @@ -621,8 +753,10 @@ hailo_status HailoRTDriver::vdma_low_memory_buffer_free(uintptr_t buffer_handle) CHECK(m_allocate_driver_buffer, HAILO_INVALID_OPERATION, "Tried to free allocated buffer from driver even though operation is not supported"); - if (0 > ioctl(this->m_fd, HAILO_VDMA_LOW_MEMORY_BUFFER_FREE, buffer_handle)) { - LOGGER__ERROR("Failed to free allocated buffer with errno: {}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_VDMA_LOW_MEMORY_BUFFER_FREE, (void*)buffer_handle, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to free allocated buffer with errno: {}", err); return HAILO_PCIE_DRIVER_FAIL; } @@ -633,8 +767,10 @@ Expected> HailoRTDriver::vdma_continuous_buffer_a { hailo_allocate_continuous_buffer_params params { .buffer_size = size, .buffer_handle = 0, .dma_address = 0 }; - if (0 > ioctl(this->m_fd, HAILO_VDMA_CONTINUOUS_BUFFER_ALLOC, ¶ms)) { - LOGGER__ERROR("Failed allocate continuous buffer with errno:{}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_VDMA_CONTINUOUS_BUFFER_ALLOC, ¶ms, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed allocate continuous buffer with errno:{}", err); return make_unexpected(HAILO_PCIE_DRIVER_FAIL); } @@ -643,8 +779,10 @@ Expected> HailoRTDriver::vdma_continuous_buffer_a hailo_status HailoRTDriver::vdma_continuous_buffer_free(uintptr_t buffer_handle) { - if (0 > ioctl(this->m_fd, HAILO_VDMA_CONTINUOUS_BUFFER_FREE, buffer_handle)) { - LOGGER__ERROR("Failed to free continuous buffer with errno: {}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_VDMA_CONTINUOUS_BUFFER_FREE, (void*)buffer_handle, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to free continuous buffer with errno: {}", err); return HAILO_PCIE_DRIVER_FAIL; } @@ -656,8 +794,10 @@ hailo_status HailoRTDriver::mark_as_used() hailo_mark_as_in_use_params params = { .in_use = false }; - if (0 > ioctl(this->m_fd, HAILO_MARK_AS_IN_USE, ¶ms)) { - LOGGER__ERROR("Failed to mark device as in use with errno: {}", errno); + int err = 0; + auto status = hailo_ioctl(this->m_fd, HAILO_MARK_AS_IN_USE, ¶ms, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("Failed to mark device as in use with errno: {}", err); return HAILO_PCIE_DRIVER_FAIL; } if (params.in_use) { @@ -666,4 +806,9 @@ hailo_status HailoRTDriver::mark_as_used() return HAILO_SUCCESS; } +bool HailoRTDriver::is_valid_channel_id(const vdma::ChannelId &channel_id) +{ + return (channel_id.engine_index < m_dma_engines_count) && (channel_id.channel_index < MAX_VDMA_CHANNELS_PER_ENGINE); +} + } /* namespace hailort */ diff --git a/hailort/libhailort/src/os/posix/mmap_buffer.cpp b/hailort/libhailort/src/os/posix/mmap_buffer.cpp index fbf7988..e26ae19 100644 --- a/hailort/libhailort/src/os/posix/mmap_buffer.cpp +++ b/hailort/libhailort/src/os/posix/mmap_buffer.cpp @@ -8,6 +8,9 @@ **/ #include "os/mmap_buffer.hpp" +#include "os/hailort_driver.hpp" +#include "hailo_ioctl_common.h" +#include #include #include @@ -41,8 +44,31 @@ Expected MmapBufferImpl::create_shared_memory(size_t length) Expected MmapBufferImpl::create_file_map(size_t length, FileDescriptor &file, uintptr_t offset) { +#ifdef __linux__ void *address = mmap(nullptr, length, PROT_WRITE | PROT_READ, MAP_SHARED, file, (off_t)offset); CHECK_AS_EXPECTED(INVALID_ADDR != address, HAILO_INTERNAL_FAILURE, "Failed to mmap buffer fd with errno:{}", errno); +#elif defined(__QNX__) + + // TODO change name of struct - using this sturct because itis exact fields we need ro qnx mmap too (where user address is physical addr) + struct hailo_non_linux_desc_list_mmap_params map_vdma_list_params { + .desc_handle = offset, + .size = length, + .user_address = nullptr, + }; + + int err = 0; + auto status = HailoRTDriver::hailo_ioctl(file, HAILO_NON_LINUX_DESC_LIST_MMAP, &map_vdma_list_params, err); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("HAILO_NON_LINUX_DESC_LIST_MMAP failed with errno:{}", err); + return make_unexpected(HAILO_PCIE_DRIVER_FAIL); + } + + void *address = mmap(nullptr, length, PROT_WRITE | PROT_READ | PROT_NOCACHE, MAP_SHARED | MAP_PHYS, NOFD, (off_t)map_vdma_list_params.user_address); + CHECK_AS_EXPECTED(INVALID_ADDR != address, HAILO_INTERNAL_FAILURE, "Failed to mmap buffer fd with errno:{}", errno); +#else +#error "unsupported platform!" +#endif // __linux__ + return MmapBufferImpl(address, length); } diff --git a/hailort/libhailort/src/os/posix/qnx/event.cpp b/hailort/libhailort/src/os/posix/qnx/event.cpp index d205f0c..ed6892d 100644 --- a/hailort/libhailort/src/os/posix/qnx/event.cpp +++ b/hailort/libhailort/src/os/posix/qnx/event.cpp @@ -92,7 +92,7 @@ hailo_status Event::wait(std::chrono::milliseconds timeout) hailo_status Event::signal() { const auto result = neosmart::SetEvent(m_handle); - CHECK(0 != result, HAILO_INTERNAL_FAILURE, "SetEvent failed with error {}" , result); + CHECK(0 == result, HAILO_INTERNAL_FAILURE, "SetEvent failed with error {}" , result); return HAILO_SUCCESS; } @@ -105,14 +105,14 @@ bool Event::is_auto_reset() hailo_status Event::reset() { const auto result = neosmart::ResetEvent(m_handle); - CHECK(0 != result, HAILO_INTERNAL_FAILURE, "ResetEvent failed with error {}", result); + CHECK(0 == result, HAILO_INTERNAL_FAILURE, "ResetEvent failed with error {}", result); return HAILO_SUCCESS; } underlying_waitable_handle_t Event::open_event_handle(const State& initial_state) { - static const auto manual_reset = true; + const bool manual_reset = true; const bool state = (initial_state == State::signalled ? true : false); auto event = neosmart::CreateEvent(manual_reset, state); if (INVALID_EVENT_HANDLE == event) { @@ -183,8 +183,8 @@ bool Semaphore::is_auto_reset() underlying_waitable_handle_t Semaphore::open_semaphore_handle(uint32_t initial_count) { - static const auto manual_reset = false; - static const auto state = (initial_count > 0 ? true : false); + const bool manual_reset = false; + const bool state = (initial_count > 0 ? true : false); auto event = neosmart::CreateEvent(manual_reset, state); if (INVALID_EVENT_HANDLE == event) { LOGGER__ERROR("Call to CreateEvent failed"); diff --git a/hailort/libhailort/src/os/windows/hailort_driver.cpp b/hailort/libhailort/src/os/windows/hailort_driver.cpp index 92e1ce6..bf29c92 100644 --- a/hailort/libhailort/src/os/windows/hailort_driver.cpp +++ b/hailort/libhailort/src/os/windows/hailort_driver.cpp @@ -20,7 +20,10 @@ namespace hailort { -//TODO: unify +static_assert(VDMA_CHANNELS_PER_ENGINE == MAX_VDMA_CHANNELS_PER_ENGINE, "Driver and libhailort parameters mismatch"); +static_assert(MIN_D2H_CHANNEL_INDEX == VDMA_DEST_CHANNELS_START, "Driver and libhailort parameters mismatch"); + +//TODO HRT-7309: merge with posix constexpr hailo_dma_data_direction direction_to_dma_data_direction(HailoRTDriver::DmaDirection direction) { switch (direction){ case HailoRTDriver::DmaDirection::H2D: @@ -434,7 +437,7 @@ private: HANDLE m_Handle = NULL; }; - +// TODO: HRT-7309 : implement hailo_ioctl for windows static int ioctl(HANDLE h, ULONG val, tCompatibleHailoIoctlData *ioctl_data) { ioctl_data->Parameters.u.value = val; @@ -500,6 +503,7 @@ HailoRTDriver::HailoRTDriver(const std::string &dev_path, FileDescriptor &&fd, h } m_desc_max_page_size = device_properties.desc_max_page_size; + m_dma_engines_count = device_properties.dma_engines_count; switch (device_properties.board_type) { case HAILO8: m_board_type = BoardType::HAILO8; @@ -651,34 +655,46 @@ hailo_status HailoRTDriver::write_bar(PciBar bar, off_t offset, size_t size, con return HAILO_SUCCESS; } -Expected HailoRTDriver::read_vdma_channel_registers(off_t offset, size_t size) +Expected HailoRTDriver::read_vdma_channel_register(vdma::ChannelId channel_id, DmaDirection data_direction, + size_t offset, size_t reg_size) { + CHECK_AS_EXPECTED(is_valid_channel_id(channel_id), HAILO_INVALID_ARGUMENT, "Invalid channel id {} given", channel_id); + CHECK_AS_EXPECTED(data_direction != DmaDirection::BOTH, HAILO_INVALID_ARGUMENT, "Invalid direction given"); + tCompatibleHailoIoctlData data = {}; - hailo_channel_registers_params& params = data.Buffer.ChannelRegisters; - params.transfer_direction = TRANSFER_READ; + auto& params = data.Buffer.ChannelRegisterRead; + params.engine_index = channel_id.engine_index; + params.channel_index = channel_id.channel_index; + params.direction = direction_to_dma_data_direction(data_direction); params.offset = offset; - params.size = size; + params.reg_size = reg_size; params.data = 0; - if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_REGISTERS, &data)) { - LOGGER__ERROR("HailoRTDriver::read_vdma_channel_registers failed with errno: {}", errno); + if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_READ_REGISTER, &data)) { + LOGGER__ERROR("HailoRTDriver::read_vdma_channel_register failed with errno: {}", errno); return make_unexpected(HAILO_PCIE_DRIVER_FAIL); } return std::move(params.data); } -hailo_status HailoRTDriver::write_vdma_channel_registers(off_t offset, size_t size, uint32_t value) +hailo_status HailoRTDriver::write_vdma_channel_register(vdma::ChannelId channel_id, DmaDirection data_direction, + size_t offset, size_t reg_size, uint32_t value) { + CHECK(is_valid_channel_id(channel_id), HAILO_INVALID_ARGUMENT, "Invalid channel id {} given", channel_id); + CHECK(data_direction != DmaDirection::BOTH, HAILO_INVALID_ARGUMENT, "Invalid direction given"); + tCompatibleHailoIoctlData data = {}; - hailo_channel_registers_params& params = data.Buffer.ChannelRegisters; - params.transfer_direction = TRANSFER_WRITE; + auto& params = data.Buffer.ChannelRegisterWrite; + params.engine_index = channel_id.engine_index; + params.channel_index = channel_id.channel_index; + params.direction = direction_to_dma_data_direction(data_direction); params.offset = offset; - params.size = size; + params.reg_size = reg_size; params.data = value; - if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_REGISTERS, &data)) { - LOGGER__ERROR("HailoRTDriver::write_vdma_channel_registers failed with errno: {}", errno); + if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_WRITE_REGISTER, &data)) { + LOGGER__ERROR("HailoRTDriver::write_vdma_channel_register failed with errno: {}", errno); return HAILO_PCIE_DRIVER_FAIL; } @@ -702,34 +718,38 @@ hailo_status HailoRTDriver::vdma_buffer_sync(VdmaBufferHandle handle, DmaDirecti return HAILO_SUCCESS; } -Expected HailoRTDriver::vdma_channel_enable(uint32_t channel_index, +Expected HailoRTDriver::vdma_channel_enable(vdma::ChannelId channel_id, DmaDirection data_direction, uintptr_t desc_list_handle, bool enable_timestamps_measure) { - CHECK_AS_EXPECTED(data_direction != DmaDirection::BOTH, HAILO_INVALID_ARGUMENT); + CHECK_AS_EXPECTED(is_valid_channel_id(channel_id), HAILO_INVALID_ARGUMENT, "Invalid channel id {} given", channel_id); + CHECK_AS_EXPECTED(data_direction != DmaDirection::BOTH, HAILO_INVALID_ARGUMENT, "Invalid direction given"); tCompatibleHailoIoctlData data = {}; hailo_vdma_channel_enable_params& params = data.Buffer.ChannelEnable; - params.channel_index = channel_index; + params.engine_index = channel_id.engine_index; + params.channel_index = channel_id.channel_index; params.direction = direction_to_dma_data_direction(data_direction); params.desc_list_handle = desc_list_handle, params.enable_timestamps_measure = enable_timestamps_measure; if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_ENABLE, &data)) { - LOGGER__ERROR("Failed to enable interrupt for channel {} with errno: {}", channel_index, errno); + LOGGER__ERROR("Failed to enable interrupt for channel {} with errno: {}", channel_id.channel_index, errno); return make_unexpected(HAILO_PCIE_DRIVER_FAIL); } return std::move(params.channel_handle); } -hailo_status HailoRTDriver::vdma_channel_disable(uint32_t channel_index, VdmaChannelHandle channel_handle) +hailo_status HailoRTDriver::vdma_channel_disable(vdma::ChannelId channel_id, VdmaChannelHandle channel_handle) { + CHECK(is_valid_channel_id(channel_id), HAILO_INVALID_ARGUMENT, "Invalid channel id {} given", channel_id); tCompatibleHailoIoctlData data = {}; hailo_vdma_channel_disable_params& params = data.Buffer.ChannelDisable; - params.channel_index = channel_index; + params.engine_index = channel_id.engine_index; + params.channel_index = channel_id.channel_index; params.channel_handle = channel_handle; if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_DISABLE, &data)) { - LOGGER__ERROR("Failed to disable interrupt for channel {} with errno: {}", channel_index, errno); + LOGGER__ERROR("Failed to disable interrupt for channel {} with errno: {}", channel_id, errno); return HAILO_PCIE_DRIVER_FAIL; } @@ -750,40 +770,39 @@ static Expected create_interrupt_timestamp_list(h return std::move(timestamp_list); } -Expected HailoRTDriver::wait_channel_interrupts(uint32_t channel_index, VdmaChannelHandle channel_handle, - const std::chrono::milliseconds &timeout) +Expected HailoRTDriver::wait_channel_interrupts(vdma::ChannelId channel_id, + VdmaChannelHandle channel_handle, const std::chrono::milliseconds &timeout) { + CHECK_AS_EXPECTED(is_valid_channel_id(channel_id), HAILO_INVALID_ARGUMENT, "Invalid channel id {} given", channel_id); CHECK_AS_EXPECTED(timeout.count() >= 0, HAILO_INVALID_ARGUMENT); - const uint32_t timestamps_count = MAX_IRQ_TIMESTAMPS_SIZE; - struct hailo_channel_interrupt_timestamp timestamps[timestamps_count]; tCompatibleHailoIoctlData data = {}; - hailo_vdma_channel_wait_params& wait = data.Buffer.ChannelWait; - wait.channel_index = channel_index; - wait.channel_handle = channel_handle; - wait.timeout_ms = static_cast(timeout.count()); - wait.timestamps = timestamps; - wait.timestamps_count = timestamps_count; + hailo_vdma_channel_wait_params& params = data.Buffer.ChannelWait; + params.engine_index = channel_id.engine_index; + params.channel_index = channel_id.channel_index; + params.channel_handle = channel_handle; + params.timeout_ms = static_cast(timeout.count()); + params.timestamps_count = MAX_IRQ_TIMESTAMPS_SIZE; if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_WAIT_INT, &data)) { const auto ioctl_errno = errno; if (ERROR_SEM_TIMEOUT == ioctl_errno) { - LOGGER__ERROR("Waiting for interrupt for channel {} timed-out", channel_index); + LOGGER__ERROR("Waiting for interrupt for channel {} timed-out", channel_id); return make_unexpected(HAILO_TIMEOUT); } if (ERROR_OPERATION_ABORTED == ioctl_errno) { - LOGGER__INFO("Stream (index={}) was aborted!", channel_index); + LOGGER__INFO("Stream (index={}) was aborted!", channel_id); return make_unexpected(HAILO_STREAM_INTERNAL_ABORT); } if (ERROR_NOT_READY == ioctl_errno) { - LOGGER__INFO("Channel (index={}) was deactivated!", channel_index); + LOGGER__INFO("Channel (index={}) was deactivated!", channel_id); return make_unexpected(HAILO_STREAM_NOT_ACTIVATED); } - LOGGER__ERROR("Failed to wait interrupt for channel {} with errno: {}", channel_index, ioctl_errno); + LOGGER__ERROR("Failed to wait interrupt for channel {} with errno: {}", channel_id, ioctl_errno); return make_unexpected(HAILO_PCIE_DRIVER_FAIL); } - return create_interrupt_timestamp_list(wait); + return create_interrupt_timestamp_list(params); } hailo_status HailoRTDriver::fw_control(const void *request, size_t request_len, const uint8_t request_md5[PCIE_EXPECTED_MD5_LENGTH], @@ -832,7 +851,8 @@ hailo_status read_log(uint8_t *buffer, size_t buffer_size, size_t *read_bytes, h return HAILO_PCIE_NOT_SUPPORTED_ON_PLATFORM; } -Expected HailoRTDriver::vdma_buffer_map(void *user_address, size_t required_size, DmaDirection data_direction, uintptr_t driver_buff_handle) +Expected HailoRTDriver::vdma_buffer_map(void *user_address, size_t required_size, DmaDirection data_direction, + vdma_mapped_buffer_driver_identifier &driver_buff_handle) { tCompatibleHailoIoctlData data = {}; hailo_vdma_buffer_map_params& map_user_buffer_info = data.Buffer.VdmaBufferMap; @@ -850,10 +870,11 @@ Expected HailoRTDriver::vdma_buffer_map(void *user_address, size_t requi return std::move(map_user_buffer_info.mapped_handle); } -hailo_status HailoRTDriver::vdma_buffer_unmap(size_t handle) +hailo_status HailoRTDriver::vdma_buffer_unmap(VdmaBufferHandle handle) { tCompatibleHailoIoctlData data = {}; - data.Value = handle; + hailo_vdma_buffer_unmap_params& unmap_user_buffer_info = data.Buffer.VdmaBufferUnmap; + unmap_user_buffer_info.mapped_handle = handle; if (0 > ioctl(this->m_fd, HAILO_VDMA_BUFFER_UNMAP, &data)) { LOGGER__ERROR("Failed to unmap user buffer with errno: {}", errno); return HAILO_PCIE_DRIVER_FAIL; @@ -881,7 +902,8 @@ Expected> HailoRTDriver::descriptors_list_create( hailo_status HailoRTDriver::descriptors_list_release(uintptr_t desc_handle) { tCompatibleHailoIoctlData data = {}; - data.Value = desc_handle; + uintptr_t& release_desc_info = data.Buffer.DescListReleaseParam; + release_desc_info = desc_handle; if (0 > ioctl(this->m_fd, HAILO_DESC_LIST_RELEASE, &data)) { LOGGER__ERROR("Failed to release descriptors list with errno: {}", errno); return HAILO_PCIE_DRIVER_FAIL; @@ -908,28 +930,32 @@ hailo_status HailoRTDriver::descriptors_list_bind_vdma_buffer(uintptr_t desc_han return HAILO_SUCCESS; } -hailo_status HailoRTDriver::vdma_channel_abort(uint32_t channel_index, VdmaChannelHandle channel_handle) +hailo_status HailoRTDriver::vdma_channel_abort(vdma::ChannelId channel_id, VdmaChannelHandle channel_handle) { + CHECK(is_valid_channel_id(channel_id), HAILO_INVALID_ARGUMENT, "Invalid channel id {} given", channel_id); tCompatibleHailoIoctlData data = {}; - hailo_vdma_channel_abort_params& abort_params = data.Buffer.ChannelAbort; - abort_params.channel_index = channel_index; - abort_params.channel_handle = channel_handle; + hailo_vdma_channel_abort_params& params = data.Buffer.ChannelAbort; + params.engine_index = channel_id.engine_index; + params.channel_index = channel_id.channel_index; + params.channel_handle = channel_handle; if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_ABORT, &data)) { - LOGGER__ERROR("Failed to abort vdma channel (index={}) with errno: {}", channel_index, errno); + LOGGER__ERROR("Failed to abort vdma channel (index={}) with errno: {}", channel_id, errno); return HAILO_PCIE_DRIVER_FAIL; } return HAILO_SUCCESS; } -hailo_status HailoRTDriver::vdma_channel_clear_abort(uint32_t channel_index, VdmaChannelHandle channel_handle) +hailo_status HailoRTDriver::vdma_channel_clear_abort(vdma::ChannelId channel_id, VdmaChannelHandle channel_handle) { + CHECK(is_valid_channel_id(channel_id), HAILO_INVALID_ARGUMENT, "Invalid channel id {} given", channel_id); tCompatibleHailoIoctlData data = {}; - hailo_vdma_channel_clear_abort_params& clear_params = data.Buffer.ChannelClearAbort; - clear_params.channel_index = channel_index; - clear_params.channel_handle = channel_handle; + hailo_vdma_channel_clear_abort_params& params = data.Buffer.ChannelClearAbort; + params.engine_index = channel_id.engine_index; + params.channel_index = channel_id.channel_index; + params.channel_handle = channel_handle; if (0 > ioctl(this->m_fd, HAILO_VDMA_CHANNEL_CLEAR_ABORT, &data)) { - LOGGER__ERROR("Failed to clear abort vdma channel (index={}) with errno: {}", channel_index, errno); + LOGGER__ERROR("Failed to clear abort vdma channel (index={}) with errno: {}", channel_id, errno); return HAILO_PCIE_DRIVER_FAIL; } @@ -956,7 +982,7 @@ Expected MmapBufferImpl::create_file_map(size_t length, FileDesc tCompatibleHailoIoctlData data = {}; data.Buffer.DescListMmap.desc_handle = offset; data.Buffer.DescListMmap.size = length; - if (0 > ioctl(file, HAILO_WINDOWS_DESC_LIST_MMAP, &data)) { + if (0 > ioctl(file, HAILO_NON_LINUX_DESC_LIST_MMAP, &data)) { LOGGER__ERROR("Failed to map physical memory with errno: {}", errno); return make_unexpected(HAILO_PCIE_DRIVER_FAIL); } @@ -1000,4 +1026,10 @@ hailo_status HailoRTDriver::mark_as_used() return HAILO_SUCCESS; } +// TODO: HRT-7309 merge with posix +bool HailoRTDriver::is_valid_channel_id(const vdma::ChannelId &channel_id) +{ + return (channel_id.engine_index < m_dma_engines_count) && (channel_id.channel_index < MAX_VDMA_CHANNELS_PER_ENGINE); +} + } /* namespace hailort */ diff --git a/hailort/libhailort/src/pcie_device.cpp b/hailort/libhailort/src/pcie_device.cpp index d71a5c3..51482d3 100644 --- a/hailort/libhailort/src/pcie_device.cpp +++ b/hailort/libhailort/src/pcie_device.cpp @@ -19,6 +19,7 @@ #include "common/compiler_extensions_compat.hpp" #include "os/hailort_driver.hpp" #include "context_switch/multi_context/resource_manager.hpp" +#include "context_switch/multi_context/vdma_config_manager.hpp" #include #include @@ -120,7 +121,8 @@ static constexpr int DEVICE_ID_STRING_LENGTH_LONG = 12; // Length without null t static constexpr int DEVICE_ID_MAX_STRING_LENGTH = std::max(DEVICE_ID_STRING_LENGTH_SHORT, DEVICE_ID_STRING_LENGTH_LONG); -Expected PcieDevice::parse_pcie_device_info(const std::string &device_info_str) +Expected PcieDevice::parse_pcie_device_info(const std::string &device_info_str, + bool log_on_failure) { hailo_pcie_device_info_t device_info{}; int scanf_res = sscanf(device_info_str.c_str(), DEVICE_ID_STRING_FMT_LONG, @@ -130,9 +132,12 @@ Expected PcieDevice::parse_pcie_device_info(const std: device_info.domain = HAILO_PCIE_ANY_DOMAIN; scanf_res = sscanf(device_info_str.c_str(), DEVICE_ID_STRING_FMT_SHORT, &device_info.bus, &device_info.device, &device_info.func); - CHECK_AS_EXPECTED(DEVICE_ID_ELEMENTS_COUNT_SHORT == scanf_res, - HAILO_INVALID_ARGUMENT, - "Invalid device info string (format is []...) {}"); + if (DEVICE_ID_ELEMENTS_COUNT_SHORT != scanf_res) { + if (log_on_failure) { + LOGGER__ERROR("Invalid device info string (format is []...) {}", device_info_str); + } + return make_unexpected(HAILO_INVALID_ARGUMENT); + } } return device_info; @@ -161,7 +166,8 @@ Expected PcieDevice::pcie_device_info_to_string(const hailo_pcie_de PcieDevice::PcieDevice(HailoRTDriver &&driver, const hailo_pcie_device_info_t &device_info, hailo_status &status) : VdmaDevice::VdmaDevice(std::move(driver), Device::Type::PCIE), m_fw_up(false), - m_device_info(device_info) + m_device_info(device_info), + m_context_switch_manager(nullptr) { // Send identify if FW is loaded status = HAILO_PCIE__read_atr_to_validate_fw_is_up(m_driver, &m_fw_up); @@ -207,6 +213,7 @@ PcieDevice::~PcieDevice() hailo_status PcieDevice::fw_interact_impl(uint8_t *request_buffer, size_t request_size, uint8_t *response_buffer, size_t *response_size, hailo_cpu_id_t cpu_id) { + // TODO: HRT-7535 return HAILO_PCIE__fw_interact(m_driver, request_buffer, (uint32_t)request_size, response_buffer, response_size, PCIE_DEFAULT_TIMEOUT_MS, cpu_id); } @@ -236,6 +243,22 @@ hailo_status PcieDevice::direct_read_memory(uint32_t address, void *buffer, uint return HAILO_PCIE__read_memory(m_driver, address, buffer, size); } +ExpectedRef PcieDevice::get_config_manager() +{ + auto status = mark_as_used(); + CHECK_SUCCESS_AS_EXPECTED(status); + + if (!m_context_switch_manager) { + auto local_context_switch_manager = VdmaConfigManager::create(*this); + CHECK_EXPECTED(local_context_switch_manager); + + m_context_switch_manager = make_unique_nothrow(local_context_switch_manager.release()); + CHECK_AS_EXPECTED(nullptr != m_context_switch_manager, HAILO_OUT_OF_HOST_MEMORY); + } + + return std::ref(*m_context_switch_manager); +} + const char *PcieDevice::get_dev_id() const { return m_device_id.c_str(); @@ -246,22 +269,25 @@ hailo_status PcieDevice::close_all_vdma_channels() auto status = HAILO_UNINITIALIZED; // TODO: Add one icotl to stop all channels at once (HRT-6097) - for (int channel_index = 0; channel_index <= MAX_H2D_CHANNEL_INDEX; channel_index++) { - auto host_registers = VdmaChannelRegs(m_driver, channel_index, HailoRTDriver::DmaDirection::H2D); + constexpr uint8_t PCIE_DEFAULT_ENGINE_INDEX = 0; + for (uint8_t channel_index = 0; channel_index <= MAX_H2D_CHANNEL_INDEX; channel_index++) { + const vdma::ChannelId channel_id = { PCIE_DEFAULT_ENGINE_INDEX, channel_index }; + auto host_registers = VdmaChannelRegs(m_driver, channel_id, HailoRTDriver::DmaDirection::H2D); status = host_registers.stop_channel(); CHECK_SUCCESS(status); - auto device_registers = VdmaChannelRegs(m_driver, channel_index, HailoRTDriver::DmaDirection::D2H); + auto device_registers = VdmaChannelRegs(m_driver, channel_id, HailoRTDriver::DmaDirection::D2H); status = device_registers.stop_channel(); CHECK_SUCCESS(status); } - for (int channel_index = MIN_D2H_CHANNEL_INDEX; channel_index <= MAX_D2H_CHANNEL_INDEX; channel_index++) { - auto host_registers = VdmaChannelRegs(m_driver, channel_index, HailoRTDriver::DmaDirection::D2H); + for (uint8_t channel_index = MIN_D2H_CHANNEL_INDEX; channel_index <= MAX_D2H_CHANNEL_INDEX; channel_index++) { + const vdma::ChannelId channel_id = { PCIE_DEFAULT_ENGINE_INDEX, channel_index }; + auto host_registers = VdmaChannelRegs(m_driver, channel_id, HailoRTDriver::DmaDirection::D2H); status = host_registers.stop_channel(); CHECK_SUCCESS(status); - auto device_registers = VdmaChannelRegs(m_driver, channel_index, HailoRTDriver::DmaDirection::H2D); + auto device_registers = VdmaChannelRegs(m_driver, channel_id, HailoRTDriver::DmaDirection::H2D); status = device_registers.stop_channel(); CHECK_SUCCESS(status); } diff --git a/hailort/libhailort/src/pcie_device.hpp b/hailort/libhailort/src/pcie_device.hpp index 1cf280d..4498923 100644 --- a/hailort/libhailort/src/pcie_device.hpp +++ b/hailort/libhailort/src/pcie_device.hpp @@ -23,11 +23,11 @@ namespace hailort class PcieDevice : public VdmaDevice { public: - static Expected> scan(); static Expected> create(); static Expected> create(const hailo_pcie_device_info_t &device_info); - static Expected parse_pcie_device_info(const std::string &device_info_str); + static Expected parse_pcie_device_info(const std::string &device_info_str, + bool log_on_failure); static Expected pcie_device_info_to_string(const hailo_pcie_device_info_t &device_info); virtual ~PcieDevice(); @@ -52,6 +52,7 @@ public: return false; } } + virtual ExpectedRef get_config_manager() override; // TODO: used for tests void set_is_control_version_supported(bool value); @@ -71,6 +72,12 @@ private: bool m_fw_up; const hailo_pcie_device_info_t m_device_info; std::string m_device_id; + // TODO: (HRT-7535) This member needs to be held in the object that impls fw_interact_impl func, + // because VdmaConfigManager calls a control (which in turn calls fw_interact_impl). + // (otherwise we'll get a "pure virtual method called" runtime error in the Device's dtor) + // Once we merge CoreDevice::fw_interact_impl and PcieDevice::fw_interact_impl we can + // move the m_context_switch_manager member and get_config_manager() func to VdmaDevice. + std::unique_ptr m_context_switch_manager; }; } /* namespace hailort */ diff --git a/hailort/libhailort/src/pcie_stream.hpp b/hailort/libhailort/src/pcie_stream.hpp index 40a331b..5bd5035 100644 --- a/hailort/libhailort/src/pcie_stream.hpp +++ b/hailort/libhailort/src/pcie_stream.hpp @@ -36,8 +36,6 @@ private: uint16_t batch_size, const std::chrono::milliseconds &transfer_timeout, hailo_status &status); - - friend class VDeviceInputStream; }; class PcieOutputStream : public VdmaOutputStream { @@ -51,8 +49,6 @@ public: virtual hailo_stream_interface_t get_interface() const override { return HAILO_STREAM_INTERFACE_PCIE; } - friend class VDeviceOutputStream; - private: explicit PcieOutputStream( PcieDevice &device, diff --git a/hailort/libhailort/src/pipeline.cpp b/hailort/libhailort/src/pipeline.cpp index de3ae91..1b478e6 100644 --- a/hailort/libhailort/src/pipeline.cpp +++ b/hailort/libhailort/src/pipeline.cpp @@ -217,6 +217,7 @@ Expected BufferPool::get_available_buffer(PipelineBuffer &&optio hailo_status BufferPool::release_buffer(Buffer &&buffer) { + std::unique_lock lock(m_release_buffer_mutex); // This can be called after the shutdown event was signaled so we ignore it here return m_free_buffers.enqueue(std::move(buffer), true); } @@ -391,6 +392,21 @@ hailo_status PipelinePad::flush() return m_element.flush(); } +hailo_status PipelinePad::abort() +{ + return m_element.abort(); +} + +void PipelinePad::wait_for_finish() +{ + m_element.wait_for_finish(); +} + +hailo_status PipelinePad::resume() +{ + return m_element.resume(); +} + hailo_status PipelinePad::run_push(PipelineBuffer &&buffer) { if (m_push_complete_callback) { @@ -500,6 +516,11 @@ hailo_status IntermediateElement::flush() return next_pad().flush(); } +void IntermediateElement::wait_for_finish() +{ + next_pad().wait_for_finish(); +} + PipelineElement::PipelineElement(const std::string &name, DurationCollector &&duration_collector, std::shared_ptr> &&pipeline_status) : PipelineObject(name), @@ -576,6 +597,16 @@ hailo_status FilterElement::clear() return next_pad().clear(); } +hailo_status FilterElement::abort() +{ + return next_pad().abort(); +} + +hailo_status FilterElement::resume() +{ + return next_pad().resume(); +} + hailo_status FilterElement::run_push(PipelineBuffer &&buffer) { auto output = action(std::move(buffer), PipelineBuffer()); @@ -624,7 +655,8 @@ BaseQueueElement::BaseQueueElement(SpscQueue &&queue, EventPtr s m_is_thread_running(true), m_activation_event(std::move(activation_event)), m_deactivation_event(std::move(deactivation_event)), - m_queue_size_accumulator(std::move(queue_size_accumulator)) + m_queue_size_accumulator(std::move(queue_size_accumulator)), + m_is_run_in_thread_running(false) {} void BaseQueueElement::start_thread() @@ -643,7 +675,19 @@ void BaseQueueElement::start_thread() break; } if (HAILO_SUCCESS == status) { + { + std::unique_lock lock(m_mutex); + m_is_run_in_thread_running = true; + } + m_cv.notify_all(); + status = run_in_thread(); + + { + std::unique_lock lock(m_mutex); + m_is_run_in_thread_running = false; + } + m_cv.notify_all(); } if (HAILO_SUCCESS != status) { @@ -740,6 +784,27 @@ hailo_status BaseQueueElement::clear() return status; } +hailo_status BaseQueueElement::abort() +{ + return next_pad().abort(); +} + +void BaseQueueElement::wait_for_finish() +{ + std::unique_lock lock(m_mutex); + m_cv.wait(lock, [this] () { + return !m_is_run_in_thread_running; + }); +} + +hailo_status BaseQueueElement::resume() +{ + auto status = m_shutdown_event->reset(); + CHECK_SUCCESS(status); + m_pipeline_status->store(HAILO_SUCCESS); + return next_pad().resume(); +} + hailo_status BaseQueueElement::set_timeout(std::chrono::milliseconds timeout) { m_timeout = timeout; @@ -783,7 +848,8 @@ Expected> PushQueueElement::create(const std:: auto deactivation_event = Event::create(Event::State::not_signalled); CHECK_EXPECTED(deactivation_event); - auto duration_collector = DurationCollector::create(flags); + // TODO: Support fps/latency collection for queue elems (HRT-7711) + auto duration_collector = DurationCollector::create(HAILO_PIPELINE_ELEM_STATS_NONE); CHECK_EXPECTED(duration_collector); AccumulatorPtr queue_size_accumulator = nullptr; @@ -826,6 +892,7 @@ PushQueueElement::~PushQueueElement() hailo_status PushQueueElement::run_push(PipelineBuffer &&buffer) { + // TODO: Support fps/latency collection for queue elems (HRT-7711) if (nullptr != m_queue_size_accumulator) { m_queue_size_accumulator->add_data_point(static_cast(m_queue.size_approx())); } @@ -918,7 +985,8 @@ Expected> PullQueueElement::create(const std:: auto deactivation_event = Event::create(Event::State::not_signalled); CHECK_EXPECTED(deactivation_event); - auto duration_collector = DurationCollector::create(flags); + // TODO: Support fps/latency collection for queue elems (HRT-7711) + auto duration_collector = DurationCollector::create(HAILO_PIPELINE_ELEM_STATS_NONE); CHECK_EXPECTED(duration_collector); AccumulatorPtr queue_size_accumulator = nullptr; @@ -963,8 +1031,14 @@ hailo_status PullQueueElement::run_push(PipelineBuffer &&/*buffer*/) return HAILO_INVALID_OPERATION; } +hailo_status PullQueueElement::resume() +{ + return next_pad().resume(); +} + Expected PullQueueElement::run_pull(PipelineBuffer &&optional, const PipelinePad &/*sink*/) { + // TODO: Support fps/latency collection for queue elems (HRT-7711) CHECK_AS_EXPECTED(!optional, HAILO_INVALID_ARGUMENT, "Optional buffer is not allowed in queue element!"); if (nullptr != m_queue_size_accumulator) { @@ -1042,7 +1116,8 @@ Expected> UserBufferQueueElement::create auto deactivation_event = Event::create(Event::State::not_signalled); CHECK_EXPECTED(deactivation_event); - auto duration_collector = DurationCollector::create(flags); + // TODO: Support fps/latency collection for queue elems (HRT-7711) + auto duration_collector = DurationCollector::create(HAILO_PIPELINE_ELEM_STATS_NONE); CHECK_EXPECTED(duration_collector); AccumulatorPtr queue_size_accumulator = nullptr; @@ -1082,6 +1157,7 @@ UserBufferQueueElement::UserBufferQueueElement(SpscQueue &&queue Expected UserBufferQueueElement::run_pull(PipelineBuffer &&optional, const PipelinePad &/*source*/) { + // TODO: Support fps/latency collection for queue elems (HRT-7711) CHECK_AS_EXPECTED(optional, HAILO_INVALID_ARGUMENT, "Optional buffer must be valid in {}!", name()); hailo_status status = m_queue.enqueue(std::move(optional), m_timeout); @@ -1233,6 +1309,31 @@ hailo_status BaseMuxElement::clear() return status; } +hailo_status BaseMuxElement::abort() +{ + for (auto &sink : m_sinks) { + hailo_status status = sink.prev()->abort(); + CHECK_SUCCESS(status); + } + return HAILO_SUCCESS; +} + +void BaseMuxElement::wait_for_finish() +{ + for (auto &sink : m_sinks) { + sink.prev()->wait_for_finish(); + } +} + +hailo_status BaseMuxElement::resume() +{ + for (auto &sink : m_sinks) { + hailo_status status = sink.prev()->resume(); + CHECK_SUCCESS(status); + } + return HAILO_SUCCESS; +} + hailo_status BaseMuxElement::flush() { hailo_status status = HAILO_SUCCESS; @@ -1383,6 +1484,22 @@ hailo_status BaseDemuxElement::flush() return next_pad().flush(); } +hailo_status BaseDemuxElement::abort() +{ + m_was_stream_aborted = true; + return next_pad().abort(); +} + +void BaseDemuxElement::wait_for_finish() +{ + next_pad().wait_for_finish(); +} + +hailo_status BaseDemuxElement::resume() +{ + return next_pad().resume(); +} + PipelinePad &BaseDemuxElement::next_pad() { // Note: The next elem to be run is upstream from this elem (i.e. buffers are pulled) diff --git a/hailort/libhailort/src/pipeline.hpp b/hailort/libhailort/src/pipeline.hpp index 585dd3c..9895c01 100644 --- a/hailort/libhailort/src/pipeline.hpp +++ b/hailort/libhailort/src/pipeline.hpp @@ -111,6 +111,7 @@ private: const bool m_measure_vstream_latency; SpscQueue m_free_buffers; AccumulatorPtr m_queue_size_accumulator; + std::mutex m_release_buffer_mutex; friend class PipelineBuffer; }; @@ -204,6 +205,9 @@ public: virtual hailo_status post_deactivate(); virtual hailo_status clear(); virtual hailo_status flush(); + virtual hailo_status abort(); + virtual void wait_for_finish(); + virtual hailo_status resume(); virtual hailo_status run_push(PipelineBuffer &&buffer); virtual Expected run_pull(PipelineBuffer &&optional = PipelineBuffer()); void set_push_complete_callback(PushCompleteCallback push_complete_callback); @@ -249,6 +253,9 @@ public: virtual hailo_status post_deactivate() = 0; virtual hailo_status clear() = 0; virtual hailo_status flush() = 0; + virtual hailo_status abort() = 0; + virtual void wait_for_finish() = 0; + virtual hailo_status resume() = 0; virtual hailo_status run_push(PipelineBuffer &&buffer) = 0; virtual Expected run_pull(PipelineBuffer &&optional, const PipelinePad &source) = 0; AccumulatorPtr get_fps_accumulator(); @@ -260,11 +267,24 @@ public: const std::vector &sources() const; virtual std::string description() const; + virtual void set_on_cant_pull_callback(std::function callback) + { + m_cant_pull_callback = callback; + } + + virtual void set_on_can_pull_callback(std::function callback) + { + m_can_pull_callback = callback; + } + protected: DurationCollector m_duration_collector; std::shared_ptr> m_pipeline_status; std::vector m_sinks; std::vector m_sources; + + std::function m_cant_pull_callback; + std::function m_can_pull_callback; }; // An element with one source pad only (generates data) @@ -292,6 +312,7 @@ public: IntermediateElement(const std::string &name, DurationCollector &&duration_collector, std::shared_ptr> &&pipeline_status); virtual PipelinePad &next_pad() = 0; + virtual void wait_for_finish() override; virtual hailo_status flush() override; }; @@ -306,6 +327,8 @@ public: virtual hailo_status deactivate() override; virtual hailo_status post_deactivate() override; virtual hailo_status clear() override; + virtual hailo_status abort() override; + virtual hailo_status resume() override; virtual hailo_status run_push(PipelineBuffer &&buffer) override; virtual Expected run_pull(PipelineBuffer &&optional, const PipelinePad &source) override; @@ -323,6 +346,9 @@ public: virtual hailo_status deactivate() = 0; virtual hailo_status post_deactivate() override; virtual hailo_status clear() override; + virtual hailo_status abort() override; + virtual void wait_for_finish() override; + virtual hailo_status resume() override; hailo_status set_timeout(std::chrono::milliseconds timeout); virtual std::string description() const override; @@ -356,6 +382,9 @@ protected: Event m_activation_event; Event m_deactivation_event; AccumulatorPtr m_queue_size_accumulator; + std::atomic_bool m_is_run_in_thread_running; + std::condition_variable m_cv; + std::mutex m_mutex; }; class PushQueueElement : public BaseQueueElement @@ -396,8 +425,25 @@ public: virtual hailo_status run_push(PipelineBuffer &&buffer) override; virtual Expected run_pull(PipelineBuffer &&optional, const PipelinePad &source) override; virtual hailo_status deactivate() override; + virtual hailo_status resume() override; virtual PipelinePad &next_pad() override; + virtual void set_on_cant_pull_callback(std::function callback) override + { + m_cant_pull_callback = callback; + m_queue.set_on_cant_enqueue_callback([this] () { + m_cant_pull_callback(); + }); + } + + virtual void set_on_can_pull_callback(std::function callback) override + { + m_can_pull_callback = callback; + m_queue.set_on_can_enqueue_callback([this] () { + m_can_pull_callback(); + }); + } + protected: virtual hailo_status run_in_thread() override; }; @@ -416,6 +462,16 @@ public: virtual Expected run_pull(PipelineBuffer &&optional, const PipelinePad &source) override; virtual hailo_status clear() override; + virtual void set_on_cant_pull_callback(std::function callback) override + { + m_cant_pull_callback = callback; + } + + virtual void set_on_can_pull_callback(std::function callback) override + { + m_can_pull_callback = callback; + } + protected: virtual hailo_status run_in_thread() override; @@ -437,6 +493,9 @@ public: virtual hailo_status post_deactivate() override; virtual hailo_status clear() override; virtual hailo_status flush() override; + virtual hailo_status abort() override; + virtual void wait_for_finish() override; + virtual hailo_status resume() override; protected: virtual Expected action(std::vector &&inputs, PipelineBuffer &&optional) = 0; @@ -458,6 +517,9 @@ public: virtual hailo_status post_deactivate() override; virtual hailo_status clear() override; virtual hailo_status flush() override; + virtual hailo_status abort() override; + virtual void wait_for_finish() override; + virtual hailo_status resume() override; hailo_status set_timeout(std::chrono::milliseconds timeout); protected: diff --git a/hailort/libhailort/src/rpc_client_utils.hpp b/hailort/libhailort/src/rpc_client_utils.hpp new file mode 100644 index 0000000..7336248 --- /dev/null +++ b/hailort/libhailort/src/rpc_client_utils.hpp @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + **/ +/** + * @file hailort_common.hpp + * @brief Utility functions for rpc client communication + **/ + +#ifndef _HAILO_HAILORT_RPC_CLIENT_UTILS_HPP_ +#define _HAILO_HAILORT_RPC_CLIENT_UTILS_HPP_ + +#include "hailo/hailort.h" +#include "hailo/expected.hpp" +#include "hailort_defaults.hpp" +#include "common/async_thread.hpp" +#include "hailort_rpc_client.hpp" +#include "rpc/rpc_definitions.hpp" +#include + +namespace hailort +{ + +class HailoRtRpcClientUtils final +{ +public: + static HailoRtRpcClientUtils& get_instance() + { + static HailoRtRpcClientUtils instance; + return instance; + } + + hailo_status init_client_service_communication() + { + std::unique_lock lock(m_mutex); + if (!m_initialized) { + + auto channel = grpc::CreateChannel(hailort::HAILO_DEFAULT_UDS_ADDR, grpc::InsecureChannelCredentials()); + auto client = make_shared_nothrow(channel); + CHECK(client != nullptr, HAILO_OUT_OF_HOST_MEMORY); + m_initialized = true; + auto reply = client->get_service_version(); + CHECK_EXPECTED_AS_STATUS(reply); + hailo_version_t client_version = {}; + auto status = hailo_get_library_version(&client_version); + CHECK_SUCCESS(status); + auto service_version = reply.value(); + auto are_equal = [](auto version1, auto version2) { + return version1.major == version2.major + && version1.minor == version2.minor + && version1.revision == version2.revision; + }; + CHECK(are_equal(service_version, client_version), HAILO_INVALID_SERVICE_VERSION, "Invalid libhailort version on service: " + "client version {}.{}.{}, service version {}.{}.{}", + service_version.major, service_version.minor, service_version.revision, + client_version.major, client_version.minor, client_version.revision); + + m_keep_alive_thread = make_unique_nothrow>([client] () { + auto pid = getpid(); + auto status = client->client_keep_alive(pid); + CHECK_SUCCESS(status); + return HAILO_SUCCESS; + }); + + } + return HAILO_SUCCESS; + } + +private: + ~HailoRtRpcClientUtils() + { + m_keep_alive_thread.release(); + } + + std::mutex m_mutex; + AsyncThreadPtr m_keep_alive_thread; + bool m_initialized = false; +}; + +} /* namespace hailort */ + +#endif /* _HAILO_HAILORT_RPC_CLIENT_UTILS_HPP_ */ \ No newline at end of file diff --git a/hailort/libhailort/src/scheduler_mon.hpp b/hailort/libhailort/src/scheduler_mon.hpp new file mode 100644 index 0000000..158064b --- /dev/null +++ b/hailort/libhailort/src/scheduler_mon.hpp @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) +**/ +/** + * @file scheduler_mon.hpp + * @brief Defines for scheduler monitor of networks. + **/ + +#ifndef _HAILO_SCHEDULER_MON_HPP_ +#define _HAILO_SCHEDULER_MON_HPP_ + +#include "hailo/hailort.hpp" +#include "common/filesystem.hpp" + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4244 4267 4127) +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif +#include "scheduler_mon.pb.h" +#if defined(_MSC_VER) +#pragma warning( pop ) +#else +#pragma GCC diagnostic pop +#endif + +#include +#include + +namespace hailort +{ + +#define SCHEDULER_MON_TMP_DIR ("/tmp/hmon_files/") +#define SCHEDULER_MON_ENV_VAR ("SCHEDULER_MONITOR") +#define DEFAULT_SCHEDULER_MON_INTERVAL (std::chrono::seconds(1)) +#define SCHEDULER_MON_NAN_VAL (-1) + +class SchedulerMon +{ +public: + + static bool should_monitor() + { + #if defined(__GNUC__) + auto mon_var = std::getenv(SCHEDULER_MON_ENV_VAR); + return (mon_var != nullptr) && strncmp(mon_var, "1", 1) == 0; + #else + // TODO: HRT-7304 - Add support for windows + return false; + #endif + } +}; + +} /* namespace hailort */ + +#endif /* _HAILO_SCHEDULER_MON_HPP_ */ diff --git a/hailort/libhailort/src/shared_resource_manager.hpp b/hailort/libhailort/src/shared_resource_manager.hpp new file mode 100644 index 0000000..9dfdd30 --- /dev/null +++ b/hailort/libhailort/src/shared_resource_manager.hpp @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + **/ +/** + * @file shared_resource_manager.hpp + * @brief holds and manages shared resource objects mapped by a key. + * + **/ + +#ifndef HAILO_SHARED_RESOURCE_MANAGER_HPP_ +#define HAILO_SHARED_RESOURCE_MANAGER_HPP_ + +#include "hailo/expected.hpp" +#include "common/utils.hpp" + +#include +#include +#include +#include +#include + +namespace hailort +{ + +#define HAILO_MAX_SHARED_RESOURCES (32) +#define HAILO_UNIQUE_RESOURCE_KEY (0) + +template +struct ResourceRef { + ResourceRef(Key user_key, std::shared_ptr resource) + : user_key(user_key), count(0), resource(std::move(resource)) + {} + + Key user_key; + uint32_t count; + std::shared_ptr resource; +}; + +template +class SharedResourceManager +{ +public: + static SharedResourceManager& get_instance() + { + static SharedResourceManager instance; + return instance; + } + + Expected> resource_lookup(uint32_t handle) + { + std::unique_lock lock(m_mutex); + auto resource = m_resources.at(handle)->resource; + return resource; + } + + template + Expected register_resource(Key user_key, CreateFunc create) + { + std::unique_lock lock(m_mutex); + uint32_t available_index = static_cast(m_resources.size()); + uint32_t match_index = static_cast(m_resources.size()); + for (uint32_t i = 0; i < m_resources.size(); ++i) { + if (m_resources.at(i) == nullptr) { + available_index = i; + } else { + if (m_resources.at(i)->user_key == user_key) { + // Resource already registered + match_index = i; + break; + } + } + } + bool should_create = match_index == m_resources.size() || user_key == unique_key(); + CHECK_AS_EXPECTED(available_index < m_resources.size() || !should_create, HAILO_NOT_AVAILABLE, + "Tried to create more than {} shared resources of type {}", max_resources(), typeid(T).name()); + if (should_create) { + // Create a new resource and register + auto expected_resource = create(); + CHECK_EXPECTED(expected_resource); + m_resources.at(available_index) = std::make_shared>(user_key, expected_resource.release()); + m_resources.at(available_index)->count++; + return available_index; + } + m_resources.at(match_index)->count++; + return match_index; + } + + void release_resource(uint32_t handle) + { + std::unique_lock lock(m_mutex); + m_resources.at(handle)->count--; + if (!m_resources.at(handle)->count) { + m_resources.at(handle) = nullptr; + } + } + +private: + SharedResourceManager() + : m_resources(max_resources(), nullptr) + {} + + static uint32_t max_resources() + { + // This method can be "overriden" with template specialization + // to set another MAX for specific managers. + return HAILO_MAX_SHARED_RESOURCES; + } + + static Key unique_key() + { + // This method can be "overriden" with template specialization + // to set another UNIQUE for specific managers. + return HAILO_UNIQUE_RESOURCE_KEY; + } + + std::mutex m_mutex; + std::vector>> m_resources; +}; + +} + +#endif /* HAILO_SHARED_RESOURCE_MANAGER_HPP_ */ diff --git a/hailort/libhailort/src/stream.cpp b/hailort/libhailort/src/stream.cpp index 039e81b..d758a69 100644 --- a/hailort/libhailort/src/stream.cpp +++ b/hailort/libhailort/src/stream.cpp @@ -28,8 +28,8 @@ hailo_status InputStream::flush() hailo_status InputStream::write(const MemoryView &buffer) { MICROPROFILE_SCOPEI("Stream", "Write", 0); - CHECK((buffer.size() % m_stream_info.hw_frame_size) == 0, HAILO_INVALID_ARGUMENT, - "write size {} must be a multiple of hw size {}", buffer.size(), m_stream_info.hw_frame_size); + CHECK((buffer.size() % get_info().hw_frame_size) == 0, HAILO_INVALID_ARGUMENT, + "write size {} must be a multiple of hw size {}", buffer.size(), get_info().hw_frame_size); CHECK(((buffer.size() % HailoRTCommon::HW_DATA_ALIGNMENT) == 0), HAILO_INVALID_ARGUMENT, "Input must be aligned to {} (got {})", HailoRTCommon::HW_DATA_ALIGNMENT, buffer.size()); @@ -42,26 +42,26 @@ hailo_status InputStream::write(const MemoryView &buffer) std::string InputStream::to_string() const { std::stringstream string_stream; - string_stream << "InputStream(index=" << static_cast(m_stream_info.index) - << ", name=" << m_stream_info.name << ")"; + string_stream << "InputStream(index=" << static_cast(get_info().index) + << ", name=" << get_info().name << ")"; return string_stream.str(); } -OutputStream::OutputStream(OutputStream &&other) : m_stream_info(std::move(other.m_stream_info)), +OutputStream::OutputStream(OutputStream &&other) : m_stream_info(std::move(other.get_info())), m_dataflow_manager_id(std::move(other.m_dataflow_manager_id)), m_invalid_frames_count(static_cast(other.m_invalid_frames_count)) {} hailo_status OutputStream::read_nms(void *buffer, size_t offset, size_t size) { - uint32_t num_of_classes = m_stream_info.nms_info.number_of_classes; - uint32_t max_bboxes_per_class = m_stream_info.nms_info.max_bboxes_per_class; - uint32_t chunks_per_frame = m_stream_info.nms_info.chunks_per_frame; - size_t bbox_size = m_stream_info.nms_info.bbox_size; + uint32_t num_of_classes = get_info().nms_info.number_of_classes; + uint32_t max_bboxes_per_class = get_info().nms_info.max_bboxes_per_class; + uint32_t chunks_per_frame = get_info().nms_info.chunks_per_frame; + size_t bbox_size = get_info().nms_info.bbox_size; size_t transfer_size = bbox_size; - CHECK(size == m_stream_info.hw_frame_size, HAILO_INSUFFICIENT_BUFFER, - "On nms stream buffer size should be {} (given size {})", m_stream_info.hw_frame_size, size); + CHECK(size == get_info().hw_frame_size, HAILO_INSUFFICIENT_BUFFER, + "On nms stream buffer size should be {} (given size {})", get_info().hw_frame_size, size); for (uint32_t chunk_index = 0; chunk_index < chunks_per_frame; chunk_index++) { for (uint32_t class_index = 0; class_index < num_of_classes; class_index++) { @@ -105,10 +105,10 @@ hailo_status OutputStream::read_nms(void *buffer, size_t offset, size_t size) hailo_status OutputStream::read(MemoryView buffer) { MICROPROFILE_SCOPEI("Stream", "Read", 0); - CHECK((buffer.size() % m_stream_info.hw_frame_size) == 0, HAILO_INVALID_ARGUMENT, - "When read size {} must be a multiple of hw size {}", buffer.size(), m_stream_info.hw_frame_size); + CHECK((buffer.size() % get_info().hw_frame_size) == 0, HAILO_INVALID_ARGUMENT, + "When read size {} must be a multiple of hw size {}", buffer.size(), get_info().hw_frame_size); - if (m_stream_info.format.order == HAILO_FORMAT_ORDER_HAILO_NMS){ + if (get_info().format.order == HAILO_FORMAT_ORDER_HAILO_NMS){ return read_nms(buffer.data(), 0, buffer.size()); } else { return this->read_all(buffer); @@ -118,8 +118,8 @@ hailo_status OutputStream::read(MemoryView buffer) std::string OutputStream::to_string() const { std::stringstream string_stream; - string_stream << "OutputStream(index=" << static_cast(m_stream_info.index) - << ", name=" << m_stream_info.name << ")"; + string_stream << "OutputStream(index=" << static_cast(get_info().index) + << ", name=" << get_info().name << ")"; return string_stream.str(); } diff --git a/hailort/libhailort/src/stream_internal.cpp b/hailort/libhailort/src/stream_internal.cpp index 652b326..372b339 100644 --- a/hailort/libhailort/src/stream_internal.cpp +++ b/hailort/libhailort/src/stream_internal.cpp @@ -17,6 +17,13 @@ namespace hailort { +InputStreamBase::InputStreamBase(const hailo_stream_info_t &stream_info, + const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, const EventPtr &network_group_activated_event) : + m_nn_stream_config(nn_stream_config), m_network_group_activated_event(network_group_activated_event) +{ + m_stream_info = stream_info; +} + EventPtr &InputStreamBase::get_network_group_activated_event() { return m_network_group_activated_event; @@ -27,6 +34,13 @@ bool InputStreamBase::is_scheduled() return false; } +OutputStreamBase::OutputStreamBase(const LayerInfo &layer_info, const hailo_stream_info_t &stream_info, + const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, const EventPtr &network_group_activated_event) : + m_nn_stream_config(nn_stream_config), m_layer_info(layer_info), m_network_group_activated_event(network_group_activated_event) +{ + m_stream_info = stream_info; +} + EventPtr &OutputStreamBase::get_network_group_activated_event() { return m_network_group_activated_event; diff --git a/hailort/libhailort/src/stream_internal.hpp b/hailort/libhailort/src/stream_internal.hpp index 85ce20e..51d9f60 100644 --- a/hailort/libhailort/src/stream_internal.hpp +++ b/hailort/libhailort/src/stream_internal.hpp @@ -10,20 +10,20 @@ * Hence, the hierarchy is as follows: * * InputStream (External "interface") - * └── InputStreamBase (Base class) - * ├── VdmaInputStream (Base class for vdma streams) - * │ ├── PcieInputStream - * │ └── CoreInputStream - * ├── EthernetInputStream - * └── MipiInputStream + * |-- InputStreamBase (Base class) + * |-- VdmaInputStream (Base class for vdma streams) + * | |-- PcieInputStream + * | |-- CoreInputStream + * |-- EthernetInputStream + * |-- MipiInputStream * * * OutputStream (External "interface") - * └── OutputStreamBase (Base class) - * ├── VdmaOutputStream (Base class for vdma streams) - * │ ├── PcieOutputStream - * │ └── CoreOutputStream - * └── EthernetOutputStream + * |-- OutputStreamBase (Base class) + * |-- VdmaOutputStream (Base class for vdma streams) + * | |-- PcieOutputStream + * | |-- CoreOutputStream + * |-- EthernetOutputStream * **/ @@ -36,6 +36,7 @@ #include "hef_internal.hpp" #include "control_protocol.hpp" #include "layer_info.hpp" +#include "vdma_channel.hpp" namespace hailort { @@ -52,6 +53,9 @@ typedef struct hailo_mux_info_t{ void* buffer; } hailo_mux_info_t; +class InputStreamWrapper; +class OutputStreamWrapper; + class InputStreamBase : public InputStream { public: @@ -61,6 +65,26 @@ public: InputStreamBase& operator=(const InputStreamBase&) = delete; InputStreamBase(InputStreamBase&&) = default; + virtual const CONTROL_PROTOCOL__nn_stream_config_t &get_nn_stream_config() + { + return m_nn_stream_config; + }; + + virtual Expected send_pending_buffer() + { + return make_unexpected(HAILO_INVALID_OPERATION); + } + + virtual Expected get_buffer_frames_size() const + { + return make_unexpected(HAILO_INVALID_OPERATION); + } + + virtual Expected get_pending_frames_count() const + { + return make_unexpected(HAILO_INVALID_OPERATION); + } + CONTROL_PROTOCOL__nn_stream_config_t m_nn_stream_config; protected: @@ -82,10 +106,15 @@ protected: status = HAILO_SUCCESS; } + InputStreamBase(const hailo_stream_info_t &stream_info, + const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, const EventPtr &network_group_activated_event); + virtual EventPtr &get_network_group_activated_event() override; virtual bool is_scheduled() override; private: + friend class InputStreamWrapper; + EventPtr m_network_group_activated_event; }; @@ -99,11 +128,31 @@ public: OutputStreamBase& operator=(const OutputStreamBase&) = delete; OutputStreamBase(OutputStreamBase&&) = default; + virtual const CONTROL_PROTOCOL__nn_stream_config_t &get_nn_stream_config() + { + return m_nn_stream_config; + }; + virtual const LayerInfo& get_layer_info() override { return m_layer_info; }; + virtual Expected get_buffer_frames_size() const + { + return make_unexpected(HAILO_INVALID_OPERATION); + } + + virtual Expected get_pending_frames_count() const + { + return make_unexpected(HAILO_INVALID_OPERATION); + } + + virtual hailo_status register_for_d2h_interrupts(const std::function &/*callback*/) + { + return HAILO_INVALID_OPERATION; + } + CONTROL_PROTOCOL__nn_stream_config_t m_nn_stream_config; protected: @@ -124,12 +173,17 @@ protected: status = HAILO_SUCCESS; } + OutputStreamBase(const LayerInfo &layer_info, const hailo_stream_info_t &stream_info, + const CONTROL_PROTOCOL__nn_stream_config_t &nn_stream_config, const EventPtr &network_group_activated_event); + virtual EventPtr &get_network_group_activated_event() override; virtual bool is_scheduled() override; LayerInfo m_layer_info; private: + friend class OutputStreamWrapper; + EventPtr m_network_group_activated_event; }; diff --git a/hailort/libhailort/src/thread_safe_map.hpp b/hailort/libhailort/src/thread_safe_map.hpp index c2c341c..757105e 100644 --- a/hailort/libhailort/src/thread_safe_map.hpp +++ b/hailort/libhailort/src/thread_safe_map.hpp @@ -43,6 +43,11 @@ public: return m_map.at(k); } + std::size_t size() { + std::unique_lock lock(m_mutex); + return m_map.size(); + } + typename std::map::iterator find(K& k) { std::unique_lock lock(m_mutex); return m_map.find(k); @@ -53,6 +58,11 @@ public: return m_map.find(k); } + bool contains(const K &k) { + std::unique_lock lock(m_mutex); + return m_map.find(k) != m_map.end(); + } + void clear() { std::unique_lock lock(m_mutex); m_map.clear(); diff --git a/hailort/libhailort/src/thread_safe_queue.hpp b/hailort/libhailort/src/thread_safe_queue.hpp index 1c45b56..28ec72e 100644 --- a/hailort/libhailort/src/thread_safe_queue.hpp +++ b/hailort/libhailort/src/thread_safe_queue.hpp @@ -129,11 +129,26 @@ public: m_items_enqueued_sema(items_enqueued_sema), m_items_dequeued_sema_or_shutdown(items_dequeued_sema, shutdown_event), m_items_dequeued_sema(items_dequeued_sema), - m_default_timeout(default_timeout) + m_default_timeout(default_timeout), + m_size(max_size), + m_enqueues_count(0), + m_callback_mutex() {} virtual ~SpscQueue() = default; - SpscQueue(SpscQueue&&) = default; + SpscQueue(SpscQueue &&other) : + m_inner(std::move(other.m_inner)), + m_items_enqueued_sema_or_shutdown(std::move(other.m_items_enqueued_sema_or_shutdown)), + m_items_enqueued_sema(std::move(other.m_items_enqueued_sema)), + m_items_dequeued_sema_or_shutdown(std::move(other.m_items_dequeued_sema_or_shutdown)), + m_items_dequeued_sema(std::move(other.m_items_dequeued_sema)), + m_default_timeout(std::move(other.m_default_timeout)), + m_size(std::move(other.m_size)), + m_enqueues_count(std::move(other.m_enqueues_count.load())), + m_cant_enqueue_callback(std::move(other.m_cant_enqueue_callback)), + m_can_enqueue_callback(std::move(other.m_can_enqueue_callback)), + m_callback_mutex() + {} static Expected create(size_t max_size, const EventPtr& shutdown_event, std::chrono::milliseconds default_timeout = std::chrono::milliseconds(1000)) @@ -152,15 +167,10 @@ public: // -1 for each enqueued item // Blocks when the queue is full (which happens when it's value reaches zero, hence it starts at queue size) const auto items_enqueued_sema = Semaphore::create_shared(0); - if (nullptr == items_enqueued_sema) { - LOGGER__ERROR("Failed creating items_enqueued_sema semaphore"); - return make_unexpected(HAILO_INTERNAL_FAILURE); - } + CHECK_AS_EXPECTED(nullptr != items_enqueued_sema, HAILO_OUT_OF_HOST_MEMORY, "Failed creating items_enqueued_sema semaphore"); + const auto items_dequeued_sema = Semaphore::create_shared(static_cast(max_size)); - if (nullptr == items_dequeued_sema) { - LOGGER__ERROR("Failed creating items_dequeued_sema semaphore"); - return make_unexpected(HAILO_INTERNAL_FAILURE); - } + CHECK_AS_EXPECTED(nullptr != items_dequeued_sema, HAILO_OUT_OF_HOST_MEMORY, "Failed creating items_dequeued_sema semaphore"); return SpscQueue(max_size, items_enqueued_sema, items_dequeued_sema, shutdown_event, default_timeout); } @@ -217,6 +227,14 @@ public: assert(success); AE_UNUSED(success); + { + std::unique_lock lock(m_callback_mutex); + if ((m_size == m_enqueues_count) && m_can_enqueue_callback) { + m_can_enqueue_callback(); + } + m_enqueues_count--; + } + const auto signal_result = m_items_dequeued_sema_or_shutdown.signal(); if (HAILO_SUCCESS != signal_result) { return make_unexpected(signal_result); @@ -250,6 +268,14 @@ public: assert(success); AE_UNUSED(success); + { + std::unique_lock lock(m_callback_mutex); + m_enqueues_count++; + if ((m_size == m_enqueues_count) && m_cant_enqueue_callback) { + m_cant_enqueue_callback(); + } + } + return m_items_enqueued_sema_or_shutdown.signal(); } @@ -286,6 +312,14 @@ public: assert(success); AE_UNUSED(success); + { + std::unique_lock lock(m_callback_mutex); + m_enqueues_count++; + if ((m_size == m_enqueues_count) && m_cant_enqueue_callback) { + m_cant_enqueue_callback(); + } + } + return m_items_enqueued_sema_or_shutdown.signal(); } @@ -314,6 +348,16 @@ public: return status; } + void set_on_cant_enqueue_callback(std::function callback) + { + m_cant_enqueue_callback = callback; + } + + void set_on_can_enqueue_callback(std::function callback) + { + m_can_enqueue_callback = callback; + } + private: ReaderWriterQueue m_inner; WaitOrShutdown m_items_enqueued_sema_or_shutdown; @@ -321,6 +365,12 @@ private: WaitOrShutdown m_items_dequeued_sema_or_shutdown; SemaphorePtr m_items_dequeued_sema; std::chrono::milliseconds m_default_timeout; + + const size_t m_size; + std::atomic_uint32_t m_enqueues_count; + std::function m_cant_enqueue_callback; + std::function m_can_enqueue_callback; + std::mutex m_callback_mutex; }; } /* namespace hailort */ diff --git a/hailort/libhailort/src/transform.cpp b/hailort/libhailort/src/transform.cpp index 509c198..ee50d20 100644 --- a/hailort/libhailort/src/transform.cpp +++ b/hailort/libhailort/src/transform.cpp @@ -77,6 +77,8 @@ bool TransformContextUtils::should_reorder(const hailo_3d_image_shape_t &src_ima case HAILO_FORMAT_ORDER_HAILO_NMS: case HAILO_FORMAT_ORDER_RGB888: case HAILO_FORMAT_ORDER_NCHW: + case HAILO_FORMAT_ORDER_NV12: + case HAILO_FORMAT_ORDER_NV21: return true; default: LOGGER__WARN("Hailo Internal warning - Unrecognised order. Transformation optimization would not be activated"); @@ -135,10 +137,12 @@ std::string TransformContextUtils::make_transpose_description(hailo_3d_image_sha return transpose_description.str(); } -void copy_output_buffer_float32(float32_t *dst_ptr, uint32_t frame_size) +template +void cast_elements_inplace(T *dst_ptr, uint32_t frame_size) { + static_assert(sizeof(T) >= sizeof(Q), "cast_elements_inplace() cannot cast to smaller size"); for (int32_t i = (int32_t)frame_size - 1; i >= 0; i--) { - dst_ptr[i] = (float32_t)(*((uint8_t*)dst_ptr + i)); + dst_ptr[i] = (T)(*((Q*)dst_ptr + i)); } } @@ -247,6 +251,42 @@ void transform__d2h_NHWC_to_NHWC(const T *src_ptr, hailo_3d_image_shape_t *src_i } } +template +void transform__h2d_NV12_to_NV12(const T *src_ptr, hailo_3d_image_shape_t *src_image_shape, T *dst_ptr, hailo_3d_image_shape_t *dst_image_shape) +{ + /* Validate arguments */ + ASSERT(NULL != src_ptr); + ASSERT(NULL != dst_ptr); + uint32_t rows_count = src_image_shape->height * src_image_shape->features; + ASSERT(0 == fmod(rows_count, 1.5)); + ASSERT(0 == (src_image_shape->width % 2)); + + auto row_leftover = dst_image_shape->width - src_image_shape->width; + + size_t src_offset_y = 0; + size_t src_offset_uv = ((static_cast(rows_count / 1.5)) * src_image_shape->width); + size_t dst_offset = 0; + + for(uint32_t h = 0; h < (static_cast(rows_count / 1.5)); h += 2) { + /* Copy 2 rows of Y for each row of U,V */ + // Copy Y + for (auto i = 0; i < 2; i++) { + memcpy(dst_ptr + dst_offset, src_ptr + src_offset_y, (src_image_shape->width * sizeof(T))); + src_offset_y += (src_image_shape->width); + dst_offset += (src_image_shape->width); + memset((dst_ptr + dst_offset), 0, (row_leftover * sizeof(T))); + dst_offset += row_leftover; + } + + // Copy U, V + memcpy(dst_ptr + dst_offset, (src_ptr + src_offset_uv), (src_image_shape->width * sizeof(T))); + src_offset_uv += src_image_shape->width; + dst_offset += src_image_shape->width; + memset((dst_ptr + dst_offset), 0, (row_leftover * sizeof(T))); + dst_offset += row_leftover; + } +} + template void transform__h2d_NHWC_to_NHCW(const T *src_ptr, hailo_3d_image_shape_t *src_image_shape, T *dst_ptr, hailo_3d_image_shape_t *dst_image_shape) @@ -768,7 +808,15 @@ hailo_status FrameOutputTransformContext::quantize_stream(const void *dst_ptr) return HAILO_INVALID_OPERATION; } } else { - copy_output_buffer_float32((float32_t*)dst_ptr, shape_size); + if (m_src_format.type == HAILO_FORMAT_TYPE_UINT8) { + cast_elements_inplace((float32_t*)dst_ptr, shape_size); + } + else if (m_src_format.type == HAILO_FORMAT_TYPE_UINT16) { + cast_elements_inplace((float32_t*)dst_ptr, shape_size); + } + else { + return HAILO_INVALID_OPERATION; + } } break; default: @@ -943,6 +991,24 @@ hailo_status reorder_input_stream(const void *src_ptr, hailo_3d_image_shape_t sr } } + if (((HAILO_FORMAT_ORDER_NV12 == src_format.order) && + (HAILO_FORMAT_ORDER_HAILO_YYUV) == dst_format.order) || + ((HAILO_FORMAT_ORDER_NV21 == src_format.order) && + (HAILO_FORMAT_ORDER_HAILO_YYVU) == dst_format.order)) { + switch (src_format.type) { + case HAILO_FORMAT_TYPE_UINT8: + transform__h2d_NV12_to_NV12((uint8_t*)src_ptr, &src_image_shape, (uint8_t*)dst_ptr, &dst_image_shape); + break; + case HAILO_FORMAT_TYPE_UINT16: + transform__h2d_NV12_to_NV12((uint16_t*)src_ptr, &src_image_shape, (uint16_t*)dst_ptr, &dst_image_shape); + break; + default: + LOGGER__ERROR("Invalid src-buffer's type format {}", src_format.type); + return HAILO_INVALID_ARGUMENT; + } + return HAILO_SUCCESS; + } + LOGGER__ERROR("Unsupported input stream transformation from hailo_format_order_t " "{} to hailo_format_order_t {}", src_format.order, dst_format.order); return HAILO_INVALID_OPERATION; @@ -1092,8 +1158,9 @@ hailo_status reorder_output_stream(const void *src_ptr, hailo_3d_image_shape_t s return HAILO_INVALID_ARGUMENT; } } else { - LOGGER__INFO("Unsupported output stream transformation from hailo_format_order_t " - "{} to hailo_format_order_t {}", src_format.order, dst_format.order); + LOGGER__ERROR("Unsupported output stream transformation from hailo_format_order_t " + "{} to hailo_format_order_t {}", HailoRTCommon::get_format_order_str(src_format.order), + HailoRTCommon::get_format_order_str(dst_format.order)); return HAILO_INVALID_OPERATION; } diff --git a/hailort/libhailort/src/vdevice.cpp b/hailort/libhailort/src/vdevice.cpp index 5fdf5e0..6509e36 100644 --- a/hailort/libhailort/src/vdevice.cpp +++ b/hailort/libhailort/src/vdevice.cpp @@ -13,22 +13,253 @@ #include "hailo/vdevice.hpp" #include "vdevice_internal.hpp" #include "pcie_device.hpp" +#include "core_device.hpp" #include "hailort_defaults.hpp" +#include "shared_resource_manager.hpp" +#include "context_switch/network_group_internal.hpp" + +#ifdef HAILO_SUPPORT_MULTI_PROCESS +#include "rpc_client_utils.hpp" +#include "rpc/rpc_definitions.hpp" +#endif // HAILO_SUPPORT_MULTI_PROCESS namespace hailort { +template<> +std::string SharedResourceManager::unique_key() +{ + return HAILO_UNIQUE_VDEVICE_GROUP_ID; +} + +static hailo_status validate_device_ids_match(const hailo_vdevice_params_t ¶ms, + const std::set &old_ids) +{ + std::set new_ids; + for (uint32_t i = 0; i < params.device_count; i++) { + // TODO: maybe needs to normalize domain? + new_ids.insert(params.device_ids[i].id); + } + + CHECK(old_ids == new_ids, HAILO_INVALID_OPERATION, "Different VDevice ids used by group_id {}", (nullptr == params.group_id ? "NULL" : params.group_id)); + return HAILO_SUCCESS; +} + +hailo_status validate_same_vdevice(const hailo_vdevice_params_t ¶ms, const VDevice &vdevice) +{ + // Validate device ids + if (params.device_ids != nullptr) { + auto old_ids = vdevice.get_physical_devices_ids(); + CHECK_EXPECTED_AS_STATUS(old_ids); + std::set old_ids_set(old_ids->begin(), old_ids->end()); + + auto status = validate_device_ids_match(params, old_ids_set); + CHECK_SUCCESS(status); + } + + // Validate count matches + auto physical_devices = vdevice.get_physical_devices(); + CHECK_EXPECTED_AS_STATUS(physical_devices); + CHECK(params.device_count == physical_devices->size(), HAILO_INVALID_OPERATION, + "Different VDevice device count used by group_id {}", params.group_id); + return HAILO_SUCCESS; +} + +void release_resource_if(bool condition, uint32_t key) { + if (condition) { + SharedResourceManager::get_instance().release_resource(key); + } +} + +VDeviceHandle::VDeviceHandle(uint32_t handle) : m_handle(handle) +{} + +VDeviceHandle::~VDeviceHandle() +{ + SharedResourceManager::get_instance().release_resource(m_handle); +} + +Expected> VDeviceHandle::create(const hailo_vdevice_params_t ¶ms) +{ + auto &manager = SharedResourceManager::get_instance(); + auto create = [¶ms]() { + return VDeviceBase::create(params); + }; + auto expected_handle = manager.register_resource(params.group_id == nullptr ? "" : std::string(params.group_id), create); + CHECK_EXPECTED(expected_handle); + + auto expected_vdevice_base = manager.resource_lookup(expected_handle.value()); + CHECK_EXPECTED(expected_vdevice_base); + + auto same_vdevice_status = validate_same_vdevice(params, *expected_vdevice_base.value()); + release_resource_if(same_vdevice_status != HAILO_SUCCESS, expected_handle.value()); + CHECK_SUCCESS_AS_EXPECTED(same_vdevice_status); + + auto handle_vdevice = std::unique_ptr(new VDeviceHandle(expected_handle.value())); + CHECK_AS_EXPECTED(handle_vdevice != nullptr, HAILO_OUT_OF_HOST_MEMORY); + + return std::unique_ptr(std::move(handle_vdevice)); +} + +Expected VDeviceHandle::configure(Hef &hef, + const NetworkGroupsParamsMap &configure_params) +{ + auto &manager = SharedResourceManager::get_instance(); + auto vdevice = manager.resource_lookup(m_handle); + CHECK_EXPECTED(vdevice); + + return vdevice.value()->configure(hef, configure_params); +} + +Expected>> VDeviceHandle::get_physical_devices() const +{ + auto &manager = SharedResourceManager::get_instance(); + auto vdevice = manager.resource_lookup(m_handle); + CHECK_EXPECTED(vdevice); + + return vdevice.value()->get_physical_devices(); +} + +Expected> VDeviceHandle::get_physical_devices_ids() const +{ + auto &manager = SharedResourceManager::get_instance(); + auto vdevice = manager.resource_lookup(m_handle); + CHECK_EXPECTED(vdevice); + + return vdevice.value()->get_physical_devices_ids(); +} + +#ifdef HAILO_SUPPORT_MULTI_PROCESS + +VDeviceClient::VDeviceClient(std::unique_ptr client, uint32_t handle) + : m_client(std::move(client)) + , m_handle(handle) +{} + +VDeviceClient::~VDeviceClient() +{ + auto reply = m_client->VDevice_release(m_handle); + if (reply != HAILO_SUCCESS) { + LOGGER__CRITICAL("VDevice_release failed!"); + } +} + +Expected> VDeviceClient::create(const hailo_vdevice_params_t ¶ms) +{ + grpc::ChannelArguments ch_args; + ch_args.SetMaxReceiveMessageSize(-1); + auto channel = grpc::CreateCustomChannel(HAILO_DEFAULT_UDS_ADDR, grpc::InsecureChannelCredentials(), ch_args); + CHECK_AS_EXPECTED(channel != nullptr, HAILO_INTERNAL_FAILURE); + + auto client = std::unique_ptr(new HailoRtRpcClient(channel)); + CHECK_AS_EXPECTED(client != nullptr, HAILO_OUT_OF_HOST_MEMORY); + auto init_status = HailoRtRpcClientUtils::get_instance().init_client_service_communication(); + CHECK_SUCCESS_AS_EXPECTED(init_status); + auto reply = client->VDevice_create(params, getpid()); + CHECK_EXPECTED(reply); + + auto client_vdevice = std::unique_ptr(new VDeviceClient(std::move(client), reply.value())); + CHECK_AS_EXPECTED(client_vdevice != nullptr, HAILO_OUT_OF_HOST_MEMORY); + + return std::unique_ptr(std::move(client_vdevice)); +} + +Expected VDeviceClient::configure(Hef &hef, + const NetworkGroupsParamsMap &configure_params) +{ + auto networks_handles = m_client->VDevice_configure(m_handle, hef, getpid(), configure_params); + CHECK_EXPECTED(networks_handles); + + ConfiguredNetworkGroupVector networks; + networks.reserve(networks_handles->size()); + for (auto &handle : networks_handles.value()) { + auto channel = grpc::CreateChannel(HAILO_DEFAULT_UDS_ADDR, grpc::InsecureChannelCredentials()); + CHECK_AS_EXPECTED(channel != nullptr, HAILO_INTERNAL_FAILURE); + + auto client = std::unique_ptr(new HailoRtRpcClient(channel)); + networks.emplace_back(make_shared_nothrow(std::move(client), handle)); + } + return networks; +} + +Expected>> VDeviceClient::get_physical_devices() const +{ + // TODO: HRT-6606 + LOGGER__ERROR("get_physical_devices is not supported when using multi process service"); + return make_unexpected(HAILO_INVALID_OPERATION); +} + +Expected> VDeviceClient::get_physical_devices_ids() const +{ + return m_client->VDevice_get_physical_devices_ids(m_handle); +} + +#endif // HAILO_SUPPORT_MULTI_PROCESS + +static Expected> convert_device_infos_to_ids(const hailo_vdevice_params_t ¶ms) +{ +IGNORE_DEPRECATION_WARNINGS_BEGIN + assert(params.device_infos != nullptr); + + std::vector device_ids; + for (uint32_t i = 0; i < params.device_count; i++) { + auto device_id_str = Device::pcie_device_info_to_string(params.device_infos[i]); + CHECK_EXPECTED(device_id_str); + + auto device_id_struct = HailoRTCommon::to_device_id(device_id_str.value()); + CHECK_EXPECTED(device_id_struct); + + device_ids.push_back(device_id_struct.release()); + } + + return device_ids; +IGNORE_DEPRECATION_WARNINGS_END +} + Expected> VDevice::create(const hailo_vdevice_params_t ¶ms) { CHECK_AS_EXPECTED(0 != params.device_count, HAILO_INVALID_ARGUMENT, "VDevice creation failed. invalid device_count ({}).", params.device_count); - - CHECK_AS_EXPECTED((HAILO_SCHEDULING_ALGORITHM_NONE == params.scheduling_algorithm) || (1 == params.device_count), HAILO_INVALID_ARGUMENT, + + CHECK_AS_EXPECTED((HAILO_SCHEDULING_ALGORITHM_NONE == params.scheduling_algorithm) + || (1 == params.device_count), HAILO_INVALID_ARGUMENT, "Network group scheduler can be active only when using one device in the vDevice!"); - auto vdevice = VDeviceBase::create(params); - CHECK_EXPECTED(vdevice); - // Upcasting to VDevice unique_ptr (from VDeviceBase unique_ptr) + // Convert device_infos to device_ids +IGNORE_DEPRECATION_WARNINGS_BEGIN + // Save device_ids on this scope to guard the memory. + auto local_params = params; + std::vector device_ids; + if (local_params.device_infos != nullptr) { + CHECK_AS_EXPECTED(local_params.device_ids == nullptr, HAILO_INVALID_ARGUMENT, + "Invalid vdevice params. You can set either device_ids or device_infos"); + LOGGER__WARN("Passing 'device_infos' in 'hailo_vdevice_params_t' is deprecated. One should use 'device_ids'"); + + auto device_ids_expected = convert_device_infos_to_ids(local_params); + CHECK_EXPECTED(device_ids_expected); + + device_ids = device_ids_expected.release(); + local_params.device_ids = device_ids.data(); + local_params.device_infos = nullptr; + } +IGNORE_DEPRECATION_WARNINGS_END + + std::unique_ptr vdevice; + if (local_params.multi_process_service) { +#ifdef HAILO_SUPPORT_MULTI_PROCESS + auto expected_vdevice = VDeviceClient::create(local_params); + CHECK_EXPECTED(expected_vdevice); + vdevice = expected_vdevice.release(); +#else + LOGGER__ERROR("multi_process_service requires service compilation with HAILO_BUILD_SERVICE"); + return make_unexpected(HAILO_INVALID_OPERATION); +#endif // HAILO_SUPPORT_MULTI_PROCESS + } else { + auto expected_vdevice = VDeviceHandle::create(local_params); + CHECK_EXPECTED(expected_vdevice); + vdevice = expected_vdevice.release(); + } + // Upcasting to VDevice unique_ptr auto vdevice_ptr = std::unique_ptr(vdevice.release()); return vdevice_ptr; } @@ -39,56 +270,60 @@ Expected> VDevice::create() return create(params); } -Expected> VDeviceBase::create(const hailo_vdevice_params_t ¶ms) +Expected> VDevice::get_physical_devices_infos() const { - NetworkGroupSchedulerPtr scheduler_ptr; - if (HAILO_SCHEDULING_ALGORITHM_NONE != params.scheduling_algorithm) { - auto network_group_scheduler = NetworkGroupScheduler::create_shared(params.scheduling_algorithm); - CHECK_EXPECTED(network_group_scheduler); - scheduler_ptr = network_group_scheduler.release(); + LOGGER__WARN("VDevice get_physical_devices_infos() is deprecated. one should use get_physical_devices_ids()."); + auto device_ids = get_physical_devices_ids(); + CHECK_EXPECTED(device_ids); + + std::vector device_infos; + device_infos.reserve(device_ids->size()); + for (const auto &device_id : device_ids.value()) { + auto device_info = Device::parse_pcie_device_info(device_id); + CHECK_EXPECTED(device_info); + device_infos.emplace_back(device_info.release()); } - auto scan_res = PcieDevice::scan(); - CHECK_EXPECTED(scan_res); + return device_infos; +} - std::vector> devices; - devices.reserve(params.device_count); +Expected> VDevice::create(const std::vector &device_ids) +{ + auto params = HailoRTDefaults::get_vdevice_params(); - hailo_pcie_device_info_t *device_infos_ptr = params.device_infos; - uint32_t devices_pool_count = params.device_count; - if (nullptr == device_infos_ptr) { - /* If params.device_infos is not nullptr, we use a pool of the given device_infos. - Otherwise, we use all available devices */ - device_infos_ptr = scan_res->data(); - devices_pool_count = static_cast(scan_res->size()); - } + auto device_ids_vector = HailoRTCommon::to_device_ids_vector(device_ids); + CHECK_EXPECTED(device_ids_vector); - for (uint32_t i = 0; i < devices_pool_count; i++) { - if (devices.size() == params.device_count) { - break; - } - auto pcie_device = PcieDevice::create(device_infos_ptr[i]); - CHECK_EXPECTED(pcie_device); - auto status = pcie_device.value()->mark_as_used(); - if ((nullptr == params.device_infos) && (HAILO_DEVICE_IN_USE == status)) { - // Continue only if the user didnt ask for specific devices - continue; + params.device_ids = device_ids_vector->data(); + params.device_count = static_cast(device_ids_vector->size()); + + return create(params); +} + +Expected> VDeviceBase::create(const hailo_vdevice_params_t ¶ms) +{ + NetworkGroupSchedulerPtr scheduler_ptr; + if (HAILO_SCHEDULING_ALGORITHM_NONE != params.scheduling_algorithm) { + if (HAILO_SCHEDULING_ALGORITHM_ROUND_ROBIN == params.scheduling_algorithm) { + auto network_group_scheduler = NetworkGroupScheduler::create_round_robin(); + CHECK_EXPECTED(network_group_scheduler); + scheduler_ptr = network_group_scheduler.release(); + } else { + LOGGER__ERROR("Unsupported scheduling algorithm"); + return make_unexpected(HAILO_INVALID_ARGUMENT); } - CHECK_SUCCESS_AS_EXPECTED(status); - devices.emplace_back(pcie_device.release()); } - CHECK_AS_EXPECTED(params.device_count == devices.size(), HAILO_OUT_OF_PHYSICAL_DEVICES, - "Failed to create vdevice. there are not enough free devices. requested: {}, found: {}", - params.device_count, devices.size()); - std::string vdevice_infos = "VDevice Infos:"; - for (const auto &device : devices) { - auto info_str = PcieDevice::pcie_device_info_to_string(device->get_device_info()); - CHECK_EXPECTED(info_str); + auto devices_expected = create_devices(params); + CHECK_EXPECTED(devices_expected); + auto devices = devices_expected.release(); - vdevice_infos += " " + info_str.value(); + std::string vdevice_ids = "VDevice Infos:"; + for (const auto &device : devices) { + auto info_str = device->get_dev_id(); + vdevice_ids += " " + std::string(info_str); } - LOGGER__INFO("{}", vdevice_infos); + LOGGER__INFO("{}", vdevice_ids); auto vdevice = std::unique_ptr(new (std::nothrow) VDeviceBase(std::move(devices), scheduler_ptr)); CHECK_AS_EXPECTED(nullptr != vdevice, HAILO_OUT_OF_HOST_MEMORY); @@ -100,6 +335,7 @@ Expected> VDeviceBase::create(const hailo_vdevice_p Expected VDeviceBase::configure(Hef &hef, const NetworkGroupsParamsMap &configure_params) { + std::unique_lock lock(m_mutex); auto start_time = std::chrono::steady_clock::now(); if (!m_context_switch_manager) { auto local_context_switch_manager = VdmaConfigManager::create(*this); @@ -108,8 +344,12 @@ Expected VDeviceBase::configure(Hef &hef, CHECK_AS_EXPECTED(nullptr != m_context_switch_manager, HAILO_OUT_OF_HOST_MEMORY); } - bool is_scheduler_used = (m_network_group_scheduler != nullptr); - auto network_groups = m_context_switch_manager->add_hef(hef, configure_params, is_scheduler_used); + for (auto &device : m_devices) { + auto status = device->check_hef_is_compatible(hef); + CHECK_SUCCESS_AS_EXPECTED(status); + } + + auto network_groups = m_context_switch_manager->add_hef(hef, configure_params); CHECK_EXPECTED(network_groups); auto elapsed_time_ms = std::chrono::duration(std::chrono::steady_clock::now() - start_time).count(); @@ -118,4 +358,73 @@ Expected VDeviceBase::configure(Hef &hef, return network_groups; } +Expected VDeviceBase::get_device_type() +{ + auto device_type = m_devices[0]->get_type(); + for (auto &dev : m_devices) { + CHECK_AS_EXPECTED(device_type == dev->get_type(), HAILO_INTERNAL_FAILURE, + "vDevice is supported only with homogeneous device type"); + } + return device_type; +} + +Expected>> VDeviceBase::create_devices(const hailo_vdevice_params_t ¶ms) +{ + std::vector> devices; + devices.reserve(params.device_count); + + const bool user_specific_devices = (params.device_ids != nullptr); + + auto device_ids = get_device_ids(params); + CHECK_EXPECTED(device_ids); + + for (const auto &device_id : device_ids.value()) { + if (devices.size() == params.device_count) { + break; + } + auto device = VdmaDevice::create(device_id); + CHECK_EXPECTED(device); + + // Validate That if (device_count != 1), device arch is not H8L. May be changed in SDK-28729 + if (1 != params.device_count) { + auto device_arch = device.value()->get_architecture(); + CHECK_EXPECTED(device_arch); + CHECK_AS_EXPECTED(HAILO_ARCH_HAILO8L != device_arch.value(), HAILO_INVALID_OPERATION, + "VDevice with multiple devices is not supported on HAILO_ARCH_HAILO8L. device {} is HAILO_ARCH_HAILO8L", device_id); + } + + auto status = device.value()->mark_as_used(); + if (!user_specific_devices && (HAILO_DEVICE_IN_USE == status)) { + // Continue only if the user didn't ask for specific devices + continue; + } + CHECK_SUCCESS_AS_EXPECTED(status); + devices.emplace_back(device.release()); + } + CHECK_AS_EXPECTED(params.device_count == devices.size(), HAILO_OUT_OF_PHYSICAL_DEVICES, + "Failed to create vdevice. there are not enough free devices. requested: {}, found: {}", + params.device_count, devices.size()); + + return devices; +} + +Expected> VDeviceBase::get_device_ids(const hailo_vdevice_params_t ¶ms) +{ + if (params.device_ids == nullptr) { + // Use device scan pool + return Device::scan(); + } + else { + std::vector device_ids; + device_ids.reserve(params.device_count); + + for (size_t i = 0; i < params.device_count; i++) { + device_ids.emplace_back(params.device_ids[i].id); + } + + return device_ids; + } +} + + } /* namespace hailort */ diff --git a/hailort/libhailort/src/vdevice_internal.hpp b/hailort/libhailort/src/vdevice_internal.hpp index d973526..c979f89 100644 --- a/hailort/libhailort/src/vdevice_internal.hpp +++ b/hailort/libhailort/src/vdevice_internal.hpp @@ -8,9 +8,14 @@ * Hence, the hiearchy is as follows: * * VDevice (External "interface") - * └── VDeviceBase (Actual implementations) + * | + * |-- VDeviceHandle (VDevice handle for a possibly shared VDeviceBase + * | when hailort is running as single process) + * |-- VDeviceClient (VDevice client for a possibly shared VDeviceBase + * | when hailort is running as a service) + * |-- VDeviceBase (Actual implementations) * | - * ├── std::vector + * |-- std::vector **/ #ifndef _HAILO_VDEVICE_INTERNAL_HPP_ @@ -18,14 +23,18 @@ #include "hailo/hailort.h" #include "hailo/vdevice.hpp" -#include "pcie_device.hpp" +#include "vdma_device.hpp" #include "context_switch/multi_context/vdma_config_manager.hpp" #include "network_group_scheduler.hpp" +#ifdef HAILO_SUPPORT_MULTI_PROCESS +#include "hailort_rpc_client.hpp" +#endif // HAILO_SUPPORT_MULTI_PROCESS namespace hailort { + class VDeviceBase : public VDevice { public: @@ -39,7 +48,7 @@ public: virtual Expected configure(Hef &hef, const NetworkGroupsParamsMap &configure_params={}) override; - virtual Expected>> get_physical_devices() override + virtual Expected>> get_physical_devices() const override { // Return Expected for future functionality std::vector> devices_refs; @@ -49,15 +58,14 @@ public: return devices_refs; } - virtual Expected> get_physical_devices_infos() override + virtual Expected> get_physical_devices_ids() const override { - // Return Expected for future functionality - std::vector devices_infos; + std::vector device_ids; + device_ids.reserve(m_devices.size()); for (auto &device : m_devices) { - devices_infos.push_back(device->get_device_info()); + device_ids.push_back(device.get()->get_dev_id()); } - - return devices_infos; + return device_ids; } const NetworkGroupSchedulerPtr &network_group_scheduler() @@ -65,14 +73,72 @@ public: return m_network_group_scheduler; } + // Currently only homogeneous vDevice is allow (= all devices are from the same type) + Expected get_device_type(); + private: - VDeviceBase(std::vector> &&devices, NetworkGroupSchedulerPtr network_group_scheduler) : + VDeviceBase(std::vector> &&devices, NetworkGroupSchedulerPtr network_group_scheduler) : m_devices(std::move(devices)), m_network_group_scheduler(network_group_scheduler) {} - std::vector> m_devices; + static Expected>> create_devices(const hailo_vdevice_params_t ¶ms); + static Expected> get_device_ids(const hailo_vdevice_params_t ¶ms); + + std::vector> m_devices; std::unique_ptr m_context_switch_manager; NetworkGroupSchedulerPtr m_network_group_scheduler; + + std::mutex m_mutex; +}; + +#ifdef HAILO_SUPPORT_MULTI_PROCESS +class VDeviceClient : public VDevice +{ +public: + static Expected> create(const hailo_vdevice_params_t ¶ms); + + VDeviceClient(VDeviceClient &&) = delete; + VDeviceClient(const VDeviceClient &) = delete; + VDeviceClient &operator=(VDeviceClient &&) = delete; + VDeviceClient &operator=(const VDeviceClient &) = delete; + virtual ~VDeviceClient(); + + Expected configure(Hef &hef, + const NetworkGroupsParamsMap &configure_params={}) override; + + Expected>> get_physical_devices() const override; + + Expected> get_physical_devices_ids() const override; + +private: + VDeviceClient(std::unique_ptr client, uint32_t handle); + + std::unique_ptr m_client; + uint32_t m_handle; +}; + +#endif // HAILO_SUPPORT_MULTI_PROCESS + +class VDeviceHandle : public VDevice +{ +public: + static Expected> create(const hailo_vdevice_params_t ¶ms); + + VDeviceHandle(VDeviceHandle &&) = delete; + VDeviceHandle(const VDeviceHandle &) = delete; + VDeviceHandle &operator=(VDeviceHandle &&) = delete; + VDeviceHandle &operator=(const VDeviceHandle &) = delete; + virtual ~VDeviceHandle(); + + Expected configure(Hef &hef, + const NetworkGroupsParamsMap &configure_params={}) override; + + Expected>> get_physical_devices() const override; + Expected> get_physical_devices_ids() const override; + +private: + VDeviceHandle(uint32_t handle); + uint32_t m_handle; }; } /* namespace hailort */ diff --git a/hailort/libhailort/src/vdevice_stream.cpp b/hailort/libhailort/src/vdevice_stream.cpp index 79eab93..ab5b982 100644 --- a/hailort/libhailort/src/vdevice_stream.cpp +++ b/hailort/libhailort/src/vdevice_stream.cpp @@ -17,8 +17,6 @@ #include "hailo/hef.hpp" #include "hailo/hailort_common.hpp" #include "vdevice_stream.hpp" -#include "pcie_stream.hpp" -#include "pcie_device.hpp" #include "context_switch/multi_context/resource_manager.hpp" namespace hailort @@ -63,11 +61,16 @@ hailo_status VDeviceInputStream::activate_stream(uint16_t dynamic_batch_size) } Expected VDeviceInputStream::sync_write_raw_buffer(const MemoryView &buffer) +{ + return sync_write_raw_buffer_impl(buffer, m_network_group_handle); +} + +Expected VDeviceInputStream::sync_write_raw_buffer_impl(const MemoryView &buffer, scheduler_ng_handle_t network_group_handle) { size_t written_bytes = 0; auto network_group_scheduler = m_network_group_scheduler.lock(); if (network_group_scheduler) { - auto status = network_group_scheduler->wait_for_write(m_network_group_handle, name()); + auto status = network_group_scheduler->wait_for_write(network_group_handle, name()); if (HAILO_STREAM_INTERNAL_ABORT == status) { LOGGER__INFO("Write to stream was aborted."); return make_unexpected(status); @@ -80,7 +83,7 @@ Expected VDeviceInputStream::sync_write_raw_buffer(const MemoryView &buf return make_unexpected(status); } - status = network_group_scheduler->signal_write_finish(m_network_group_handle, name()); + status = network_group_scheduler->signal_write_finish(network_group_handle, name()); if (HAILO_STREAM_INTERNAL_ABORT == status) { return make_unexpected(status); } @@ -114,49 +117,83 @@ hailo_status VDeviceInputStream::sync_write_all_raw_buffer_no_transform_impl(voi Expected VDeviceInputStream::send_pending_buffer() { assert(1 == m_streams.size()); - VdmaInputStream &vdma_input = dynamic_cast(*m_streams[m_next_transfer_stream_index].get()); + VdmaInputStream &vdma_input = static_cast(*m_streams[m_next_transfer_stream_index].get()); return vdma_input.send_pending_buffer(); } +Expected VDeviceInputStream::get_buffer_frames_size() const +{ + size_t total_buffers_size = 0; + for (auto &stream : m_streams) { + auto stream_buffer_size = stream->get_buffer_frames_size(); + CHECK_EXPECTED(stream_buffer_size); + total_buffers_size += stream_buffer_size.value(); + } + + return total_buffers_size; +} + +Expected VDeviceInputStream::get_pending_frames_count() const +{ + size_t total_pending_frames_count = 0; + for (auto &stream : m_streams) { + auto stream_pending_frames_count = stream->get_pending_frames_count(); + CHECK_EXPECTED(stream_pending_frames_count); + total_pending_frames_count += stream_pending_frames_count.value(); + } + + return total_pending_frames_count; +} + // TODO - HRT-6830 - make create_input/output_stream_from_net_group as virutal function -Expected> VDeviceInputStream::create_input_stream_from_net_group( +Expected> VDeviceInputStream::create_input_stream_from_net_group( std::vector> &resources_managers, - const LayerInfo &edge_layer, const std::string &stream_name, const network_group_handle_t &network_group_handle, + const LayerInfo &edge_layer, const std::string &stream_name, const scheduler_ng_handle_t &network_group_handle, EventPtr &&network_group_activated_event, NetworkGroupSchedulerWeakPtr network_group_scheduler) { hailo_status status = HAILO_UNINITIALIZED; - std::vector devices; - std::vector> streams; + std::vector devices; + std::vector> streams; for (auto &resources_manager : resources_managers) { - CHECK_AS_EXPECTED(Device::Type::PCIE == resources_manager->get_device().get_type(), HAILO_INTERNAL_FAILURE, - "vDevice stream is supported only with PCIe devices"); - PcieDevice &pcie_device = reinterpret_cast(resources_manager->get_device()); + auto vdma_channel_ptr_expected = resources_manager->get_boundary_vdma_channel_by_stream_name(stream_name); + CHECK_EXPECTED(vdma_channel_ptr_expected); + auto vdma_channel_ptr = vdma_channel_ptr_expected.release(); - auto vdma_channel_ptr = resources_manager->get_boundary_vdma_channel_by_stream_name(stream_name); - CHECK_EXPECTED(vdma_channel_ptr); + auto batch_size_expected = resources_manager->get_network_batch_size(edge_layer.network_name); + CHECK_EXPECTED(batch_size_expected); + const auto batch_size = batch_size_expected.release(); - auto batch_size = resources_manager->get_network_batch_size(edge_layer.network_name); - auto local_stream = PcieInputStream::create(pcie_device, - vdma_channel_ptr.release(), edge_layer, batch_size.value(), network_group_activated_event); - CHECK_EXPECTED(local_stream); + auto &device = resources_manager->get_device(); + devices.push_back(&device); - devices.push_back(&pcie_device); + auto local_stream = VdmaInputStream::create(device, vdma_channel_ptr, edge_layer, batch_size, + network_group_activated_event); + CHECK_EXPECTED(local_stream); streams.emplace_back(local_stream.release()); } - std::unique_ptr local_vdevice_stream(new (std::nothrow) VDeviceInputStream(devices, - std::move(streams), network_group_handle, std::move(network_group_activated_event), edge_layer, network_group_scheduler, status)); + // Validate all streams share the same interface + const auto stream_interface = streams[0]->get_interface(); + for (const auto &stream : streams) { + CHECK_AS_EXPECTED(stream_interface == stream->get_interface(), HAILO_INTERNAL_FAILURE, + "vDevice internal streams should have the same stream interface"); + } + + + std::shared_ptr local_vdevice_stream(new (std::nothrow) VDeviceInputStream(devices, + std::move(streams), network_group_handle, std::move(network_group_activated_event), edge_layer, + network_group_scheduler, stream_interface, status)); CHECK_AS_EXPECTED((nullptr != local_vdevice_stream), HAILO_OUT_OF_HOST_MEMORY); CHECK_SUCCESS_AS_EXPECTED(status); return local_vdevice_stream; } -Expected> VDeviceInputStream::create(std::vector> &resources_managers, - const LayerInfo &edge_layer, const std::string &stream_name, const network_group_handle_t &network_group_handle, EventPtr network_group_activated_event, - NetworkGroupSchedulerWeakPtr network_group_scheduler) +Expected> VDeviceInputStream::create(std::vector> &resources_managers, + const LayerInfo &edge_layer, const std::string &stream_name, const scheduler_ng_handle_t &network_group_handle, + EventPtr network_group_activated_event, NetworkGroupSchedulerWeakPtr network_group_scheduler) { assert(0 < resources_managers.size()); @@ -196,6 +233,11 @@ hailo_status VDeviceInputStream::flush() } hailo_status VDeviceInputStream::abort() +{ + return abort_impl(m_network_group_handle); +} + +hailo_status VDeviceInputStream::abort_impl(scheduler_ng_handle_t network_group_handle) { auto status = HAILO_SUCCESS; // Best effort for (auto &stream : m_streams) { @@ -208,7 +250,7 @@ hailo_status VDeviceInputStream::abort() auto network_group_scheduler = m_network_group_scheduler.lock(); if (network_group_scheduler) { - auto disable_status = network_group_scheduler->disable_stream(m_network_group_handle, name()); + auto disable_status = network_group_scheduler->disable_stream(network_group_handle, name()); if (HAILO_SUCCESS != disable_status) { LOGGER__ERROR("Failed to disable stream in the network group scheduler. (status: {})", disable_status); status = disable_status; @@ -219,6 +261,11 @@ hailo_status VDeviceInputStream::abort() } hailo_status VDeviceInputStream::clear_abort() +{ + return clear_abort_impl(m_network_group_handle); +} + +hailo_status VDeviceInputStream::clear_abort_impl(scheduler_ng_handle_t network_group_handle) { auto status = HAILO_SUCCESS; // Best effort for (auto &stream : m_streams) { @@ -231,7 +278,7 @@ hailo_status VDeviceInputStream::clear_abort() auto network_group_scheduler = m_network_group_scheduler.lock(); if (network_group_scheduler) { - auto enable_status = network_group_scheduler->enable_stream(m_network_group_handle, name()); + auto enable_status = network_group_scheduler->enable_stream(network_group_handle, name()); if (HAILO_SUCCESS != enable_status) { LOGGER__ERROR("Failed to enable stream in the network group scheduler. (status: {})", enable_status); status = enable_status; @@ -301,10 +348,15 @@ Expected VDeviceOutputStream::sync_read_raw_buffer(MemoryView &/*buffer* } hailo_status VDeviceOutputStream::read(MemoryView buffer) +{ + return read_impl(buffer, m_network_group_handle); +} + +hailo_status VDeviceOutputStream::read_impl(MemoryView buffer, scheduler_ng_handle_t network_group_handle) { auto network_group_scheduler = m_network_group_scheduler.lock(); if (network_group_scheduler) { - auto status = network_group_scheduler->wait_for_read(m_network_group_handle, name()); + auto status = network_group_scheduler->wait_for_read(network_group_handle, name()); if (HAILO_STREAM_INTERNAL_ABORT == status) { LOGGER__INFO("Read from stream was aborted."); return status; @@ -325,7 +377,7 @@ hailo_status VDeviceOutputStream::read(MemoryView buffer) } if (network_group_scheduler) { - status = network_group_scheduler->signal_read_finish(m_network_group_handle, name()); + status = network_group_scheduler->signal_read_finish(network_group_handle, name()); if (HAILO_STREAM_INTERNAL_ABORT == status) { return status; } @@ -338,33 +390,42 @@ hailo_status VDeviceOutputStream::read(MemoryView buffer) // TODO - HRT-6830 - make create_input/output_stream_from_net_group as virutal function Expected> VDeviceOutputStream::create_output_stream_from_net_group( std::vector> &resources_managers, - const LayerInfo &edge_layer, const std::string &stream_name, const network_group_handle_t &network_group_handle, + const LayerInfo &edge_layer, const std::string &stream_name, const scheduler_ng_handle_t &network_group_handle, EventPtr &&network_group_activated_event, NetworkGroupSchedulerWeakPtr network_group_scheduler) { hailo_status status = HAILO_UNINITIALIZED; - std::vector devices; - std::vector> streams; + std::vector devices; + std::vector> streams; for (auto &resources_manager : resources_managers) { - CHECK_AS_EXPECTED(Device::Type::PCIE == resources_manager->get_device().get_type(), HAILO_INTERNAL_FAILURE, - "vDevice stream is supported only with PCIe devices"); - PcieDevice &pcie_device = reinterpret_cast(resources_manager->get_device()); + auto vdma_channel_ptr_expected = resources_manager->get_boundary_vdma_channel_by_stream_name(stream_name); + CHECK_EXPECTED(vdma_channel_ptr_expected); + auto vdma_channel_ptr = vdma_channel_ptr_expected.release(); - auto vdma_channel_ptr = resources_manager->get_boundary_vdma_channel_by_stream_name(stream_name); - CHECK_EXPECTED(vdma_channel_ptr); + auto batch_size_expected = resources_manager->get_network_batch_size(edge_layer.network_name); + CHECK_EXPECTED(batch_size_expected); + const auto batch_size = batch_size_expected.release(); - auto batch_size = resources_manager->get_network_batch_size(edge_layer.network_name); - auto local_stream = PcieOutputStream::create(pcie_device, - vdma_channel_ptr.release(), edge_layer, batch_size.value(), network_group_activated_event); - CHECK_EXPECTED(local_stream); + auto &device = resources_manager->get_device(); + devices.push_back(&device); - devices.push_back(&pcie_device); + auto local_stream = VdmaOutputStream::create(device, vdma_channel_ptr, edge_layer, batch_size, + network_group_activated_event); + CHECK_EXPECTED(local_stream); streams.emplace_back(local_stream.release()); } - std::unique_ptr local_vdevice_stream(new (std::nothrow) VDeviceOutputStream(devices, std::move(streams), network_group_handle, - edge_layer, std::move(network_group_activated_event), network_group_scheduler, status)); + // Validate all streams share the same interface + const auto stream_interface = streams[0]->get_interface(); + for (const auto &stream : streams) { + CHECK_AS_EXPECTED(stream_interface == stream->get_interface(), HAILO_INTERNAL_FAILURE, + "vDevice internal streams should have the same stream interface"); + } + + std::unique_ptr local_vdevice_stream(new (std::nothrow) VDeviceOutputStream(devices, + std::move(streams), network_group_handle, edge_layer, std::move(network_group_activated_event), + network_group_scheduler, stream_interface, status)); CHECK_AS_EXPECTED((nullptr != local_vdevice_stream), HAILO_OUT_OF_HOST_MEMORY); CHECK_SUCCESS_AS_EXPECTED(status); @@ -372,7 +433,7 @@ Expected> VDeviceOutputStream::create_outpu } Expected> VDeviceOutputStream::create(std::vector> &resources_managers, - const LayerInfo &edge_layer, const std::string &stream_name, const network_group_handle_t &network_group_handle, EventPtr network_group_activated_event, + const LayerInfo &edge_layer, const std::string &stream_name, const scheduler_ng_handle_t &network_group_handle, EventPtr network_group_activated_event, NetworkGroupSchedulerWeakPtr network_group_scheduler) { assert(0 < resources_managers.size()); @@ -400,6 +461,11 @@ std::chrono::milliseconds VDeviceOutputStream::get_timeout() const } hailo_status VDeviceOutputStream::abort() +{ + return abort_impl(m_network_group_handle); +} + +hailo_status VDeviceOutputStream::abort_impl(scheduler_ng_handle_t network_group_handle) { auto status = HAILO_SUCCESS; // Best effort for (auto &stream : m_streams) { @@ -412,7 +478,7 @@ hailo_status VDeviceOutputStream::abort() auto network_group_scheduler = m_network_group_scheduler.lock(); if (network_group_scheduler) { - auto disable_status = network_group_scheduler->disable_stream(m_network_group_handle, name()); + auto disable_status = network_group_scheduler->disable_stream(network_group_handle, name()); if (HAILO_SUCCESS != disable_status) { LOGGER__ERROR("Failed to disable stream in the network group scheduler. (status: {})", disable_status); status = disable_status; @@ -423,6 +489,11 @@ hailo_status VDeviceOutputStream::abort() } hailo_status VDeviceOutputStream::clear_abort() +{ + return clear_abort_impl(m_network_group_handle); +} + +hailo_status VDeviceOutputStream::clear_abort_impl(scheduler_ng_handle_t network_group_handle) { auto status = HAILO_SUCCESS; // Best effort for (auto &stream : m_streams) { @@ -435,7 +506,7 @@ hailo_status VDeviceOutputStream::clear_abort() auto network_group_scheduler = m_network_group_scheduler.lock(); if (network_group_scheduler) { - auto enable_status = network_group_scheduler->enable_stream(m_network_group_handle, name()); + auto enable_status = network_group_scheduler->enable_stream(network_group_handle, name()); if (HAILO_SUCCESS != enable_status) { LOGGER__ERROR("Failed to enable stream in the network group scheduler. (status: {})", enable_status); status = enable_status; @@ -454,4 +525,34 @@ bool VDeviceOutputStream::is_scheduled() return (HAILO_SCHEDULING_ALGORITHM_NONE != network_group_scheduler->algorithm()); } +Expected VDeviceOutputStream::get_buffer_frames_size() const +{ + size_t total_buffers_size = 0; + for (auto &stream : m_streams) { + auto stream_buffer_size = stream->get_buffer_frames_size(); + if (HAILO_NOT_AVAILABLE == stream_buffer_size.status()) { + return make_unexpected(HAILO_NOT_AVAILABLE); + } + CHECK_EXPECTED(stream_buffer_size); + total_buffers_size += stream_buffer_size.value(); + } + + return total_buffers_size; +} + +Expected VDeviceOutputStream::get_pending_frames_count() const +{ + size_t total_pending_frames_count = 0; + for (auto &stream : m_streams) { + auto stream_pending_frames_count = stream->get_pending_frames_count(); + if (HAILO_NOT_AVAILABLE == stream_pending_frames_count.status()) { + return make_unexpected(HAILO_NOT_AVAILABLE); + } + CHECK_EXPECTED(stream_pending_frames_count); + total_pending_frames_count += stream_pending_frames_count.value(); + } + + return total_pending_frames_count; +} + } /* namespace hailort */ diff --git a/hailort/libhailort/src/vdevice_stream.hpp b/hailort/libhailort/src/vdevice_stream.hpp index 44475ff..15524f7 100644 --- a/hailort/libhailort/src/vdevice_stream.hpp +++ b/hailort/libhailort/src/vdevice_stream.hpp @@ -4,9 +4,8 @@ **/ /** * @file vdevice_stream.hpp - * @brief Internal stream implementation for VDevice - holds PcieStream per physical device + * @brief Internal stream implementation for VDevice - holds VdmaStream per physical device * - * TODO: doc **/ #ifndef HAILO_VDEVICE_STREAM_HPP_ @@ -15,8 +14,8 @@ #include "stream_internal.hpp" #include "hailo/hailort.h" #include "vdevice_internal.hpp" -#include "pcie_device.hpp" -#include "pcie_stream.hpp" +#include "vdma_device.hpp" +#include "vdma_stream.hpp" #include "hailo/expected.hpp" namespace hailort @@ -32,63 +31,74 @@ public: m_streams(std::move(other.m_streams)), m_is_stream_activated(std::exchange(other.m_is_stream_activated, false)), m_next_transfer_stream_index(other.m_next_transfer_stream_index), - m_acc_frames(other.m_acc_frames) + m_acc_frames(other.m_acc_frames), + m_stream_interface(other.m_stream_interface) {} virtual ~VDeviceInputStream(); - static Expected> create(std::vector> &resources_managers, - const LayerInfo &edge_layer, const std::string &stream_name, const network_group_handle_t &network_group_handle, EventPtr network_group_activated_event, + static Expected> create(std::vector> &resources_managers, + const LayerInfo &edge_layer, const std::string &stream_name, const scheduler_ng_handle_t &network_group_handle, EventPtr network_group_activated_event, NetworkGroupSchedulerWeakPtr network_group_scheduler); virtual hailo_status activate_stream(uint16_t dynamic_batch_size) override; virtual hailo_status deactivate_stream() override; - virtual hailo_stream_interface_t get_interface() const override { return HAILO_STREAM_INTERFACE_PCIE; } + virtual hailo_stream_interface_t get_interface() const override { return m_stream_interface; } virtual std::chrono::milliseconds get_timeout() const override; + virtual hailo_status set_timeout(std::chrono::milliseconds timeout) override; virtual hailo_status abort() override; virtual hailo_status clear_abort() override; virtual bool is_scheduled() override; - Expected send_pending_buffer(); + virtual Expected send_pending_buffer() override; + virtual Expected get_buffer_frames_size() const override; + virtual Expected get_pending_frames_count() const override; protected: virtual Expected sync_write_raw_buffer(const MemoryView &buffer) override; virtual hailo_status sync_write_all_raw_buffer_no_transform_impl(void *buffer, size_t offset, size_t size) override; private: + friend class VDeviceInputStreamWrapper; + explicit VDeviceInputStream( - std::vector devices, - std::vector> &&streams, - const network_group_handle_t &network_group_handle, + std::vector devices, + std::vector> &&streams, + const scheduler_ng_handle_t &network_group_handle, EventPtr &&network_group_activated_event, const LayerInfo &layer_info, NetworkGroupSchedulerWeakPtr network_group_scheduler, + hailo_stream_interface_t stream_interface, hailo_status &status) : - InputStreamBase(layer_info, HAILO_STREAM_INTERFACE_PCIE, std::move(network_group_activated_event), status), + InputStreamBase(layer_info, stream_interface, std::move(network_group_activated_event), status), m_network_group_handle(network_group_handle), m_network_group_scheduler(network_group_scheduler), m_devices(devices), m_streams(std::move(streams)), m_is_stream_activated(false), m_next_transfer_stream_index(0), - m_acc_frames(0) + m_acc_frames(0), + m_stream_interface(stream_interface) {} - hailo_status set_timeout(std::chrono::milliseconds timeout); + Expected sync_write_raw_buffer_impl(const MemoryView &buffer, scheduler_ng_handle_t network_group_handle); + hailo_status abort_impl(scheduler_ng_handle_t network_group_handle); + hailo_status clear_abort_impl(scheduler_ng_handle_t network_group_handle); virtual hailo_status flush() override; - static Expected> create_input_stream_from_net_group( + static Expected> create_input_stream_from_net_group( std::vector> &resources_managers, - const LayerInfo &edge_layer, const std::string &stream_name, const network_group_handle_t &network_group_handle, + const LayerInfo &edge_layer, const std::string &stream_name, const scheduler_ng_handle_t &network_group_handle, EventPtr &&network_group_activated_event, NetworkGroupSchedulerWeakPtr network_group_scheduler); - network_group_handle_t m_network_group_handle; + scheduler_ng_handle_t m_network_group_handle; NetworkGroupSchedulerWeakPtr m_network_group_scheduler; - std::vector m_devices; - std::vector> m_streams; + std::vector m_devices; + std::vector> m_streams; bool m_is_stream_activated; uint32_t m_next_transfer_stream_index; uint32_t m_acc_frames; + hailo_stream_interface_t m_stream_interface; }; class VDeviceOutputStream : public OutputStreamBase { @@ -101,34 +111,50 @@ public: m_streams(std::move(other.m_streams)), m_is_stream_activated(std::exchange(other.m_is_stream_activated, false)), m_next_transfer_stream_index(other.m_next_transfer_stream_index), - m_acc_frames(other.m_acc_frames) + m_acc_frames(other.m_acc_frames), + m_stream_interface(other.m_stream_interface) {} virtual ~VDeviceOutputStream(); static Expected> create(std::vector> &resources_managers, - const LayerInfo &edge_layer, const std::string &stream_name, const network_group_handle_t &network_group_handle, + const LayerInfo &edge_layer, const std::string &stream_name, const scheduler_ng_handle_t &network_group_handle, EventPtr network_group_activated_event, NetworkGroupSchedulerWeakPtr network_group_scheduler); virtual hailo_status activate_stream(uint16_t dynamic_batch_size) override; virtual hailo_status deactivate_stream() override; - virtual hailo_stream_interface_t get_interface() const override { return HAILO_STREAM_INTERFACE_PCIE; } + virtual hailo_stream_interface_t get_interface() const override { return m_stream_interface; } virtual std::chrono::milliseconds get_timeout() const override; + virtual hailo_status set_timeout(std::chrono::milliseconds timeout) override; virtual hailo_status abort() override; virtual hailo_status clear_abort() override; virtual bool is_scheduled() override; + virtual Expected get_buffer_frames_size() const override; + virtual Expected get_pending_frames_count() const override; + + virtual hailo_status register_for_d2h_interrupts(const std::function &callback) override + { + for (auto &stream : m_streams) { + auto status = stream->register_for_d2h_interrupts(callback); + CHECK_SUCCESS(status); + } + return HAILO_SUCCESS; + } protected: virtual Expected sync_read_raw_buffer(MemoryView &buffer) override; private: + friend class VDeviceOutputStreamWrapper; + explicit VDeviceOutputStream( - std::vector devices, - std::vector> &&streams, - const network_group_handle_t &network_group_handle, + std::vector devices, + std::vector> &&streams, + const scheduler_ng_handle_t &network_group_handle, const LayerInfo &layer_info, EventPtr &&network_group_activated_event, NetworkGroupSchedulerWeakPtr network_group_scheduler, + hailo_stream_interface_t stream_interface, hailo_status &status) : OutputStreamBase(layer_info, std::move(network_group_activated_event), status), m_network_group_handle(network_group_handle), @@ -137,25 +163,29 @@ private: m_streams(std::move(streams)), m_is_stream_activated(false), m_next_transfer_stream_index(0), - m_acc_frames(0) + m_acc_frames(0), + m_stream_interface(stream_interface) {} - hailo_status set_timeout(std::chrono::milliseconds timeout); + hailo_status abort_impl(scheduler_ng_handle_t network_group_handle); + hailo_status clear_abort_impl(scheduler_ng_handle_t network_group_handle); virtual hailo_status read_all(MemoryView &buffer) override; virtual hailo_status read(MemoryView buffer) override; + hailo_status read_impl(MemoryView buffer, scheduler_ng_handle_t network_group_handle); static Expected> create_output_stream_from_net_group( std::vector> &resources_managers, const LayerInfo &edge_layer, - const std::string &stream_name, const network_group_handle_t &network_group_handle, EventPtr &&network_group_activated_event, + const std::string &stream_name, const scheduler_ng_handle_t &network_group_handle, EventPtr &&network_group_activated_event, NetworkGroupSchedulerWeakPtr network_group_scheduler); - network_group_handle_t m_network_group_handle; + scheduler_ng_handle_t m_network_group_handle; NetworkGroupSchedulerWeakPtr m_network_group_scheduler; - std::vector m_devices; - std::vector> m_streams; + std::vector m_devices; + std::vector> m_streams; bool m_is_stream_activated; uint32_t m_next_transfer_stream_index; uint32_t m_acc_frames; + hailo_stream_interface_t m_stream_interface; }; } /* namespace hailort */ diff --git a/hailort/libhailort/src/vdevice_stream_wrapper.cpp b/hailort/libhailort/src/vdevice_stream_wrapper.cpp new file mode 100644 index 0000000..face57b --- /dev/null +++ b/hailort/libhailort/src/vdevice_stream_wrapper.cpp @@ -0,0 +1,371 @@ +#include "vdevice_stream_wrapper.hpp" + +namespace hailort +{ + +const hailo_stream_info_t &VDeviceInputStreamWrapper::get_info() const +{ + return m_vdevice_input_stream->get_info(); +} + +const CONTROL_PROTOCOL__nn_stream_config_t &VDeviceInputStreamWrapper::get_nn_stream_config() +{ + return m_vdevice_input_stream->get_nn_stream_config(); +} + +hailo_status VDeviceInputStreamWrapper::activate_stream(uint16_t dynamic_batch_size) +{ + return m_vdevice_input_stream->activate_stream(dynamic_batch_size); +} + +hailo_status VDeviceInputStreamWrapper::deactivate_stream() +{ + return m_vdevice_input_stream->deactivate_stream(); +} + +hailo_stream_interface_t VDeviceInputStreamWrapper::get_interface() const +{ + return m_vdevice_input_stream->get_interface(); +} + +std::chrono::milliseconds VDeviceInputStreamWrapper::get_timeout() const +{ + return m_vdevice_input_stream->get_timeout(); +} + +hailo_status VDeviceInputStreamWrapper::abort() +{ + if (is_scheduled()) { + auto status = m_multiplexer->disable_network_group(m_network_group_multiplexer_handle); + CHECK_SUCCESS(status); + + // TODO: HRT-7638 + status = m_multiplexer->run_once_for_stream(name(), INPUT_RUN_ONCE_HANDLE__ABORT, m_network_group_multiplexer_handle); + CHECK_SUCCESS(status); + + return HAILO_SUCCESS; + } + + auto status = m_vdevice_input_stream->abort_impl(m_network_group_scheduler_handle); + CHECK_SUCCESS(status); + + return HAILO_SUCCESS; +} + +hailo_status VDeviceInputStreamWrapper::clear_abort() +{ + if (is_scheduled()) { + auto status = m_multiplexer->enable_network_group(m_network_group_multiplexer_handle); + CHECK_SUCCESS(status); + + status = m_multiplexer->run_once_for_stream(name(), INPUT_RUN_ONCE_HANDLE__CLEAR_ABORT, m_network_group_multiplexer_handle); + CHECK_SUCCESS(status); + + return HAILO_SUCCESS; + } + + auto status = m_vdevice_input_stream->clear_abort_impl(m_network_group_scheduler_handle); + CHECK_SUCCESS(status); + + return HAILO_SUCCESS; +} + +bool VDeviceInputStreamWrapper::is_scheduled() +{ + return m_vdevice_input_stream->is_scheduled(); +} + +Expected VDeviceInputStreamWrapper::send_pending_buffer() +{ + return m_vdevice_input_stream->send_pending_buffer(); +} + +Expected VDeviceInputStreamWrapper::get_buffer_frames_size() const +{ + return m_vdevice_input_stream->get_buffer_frames_size(); +} + +Expected VDeviceInputStreamWrapper::get_pending_frames_count() const +{ + return m_vdevice_input_stream->get_pending_frames_count(); +} + +Expected VDeviceInputStreamWrapper::sync_write_raw_buffer(const MemoryView &buffer) +{ + if (is_scheduled()) { + auto status = m_multiplexer->wait_for_write(m_network_group_multiplexer_handle); + if (HAILO_STREAM_INTERNAL_ABORT == status) { + return make_unexpected(status); + } + CHECK_SUCCESS_AS_EXPECTED(status); + } + + auto exp = m_vdevice_input_stream->sync_write_raw_buffer_impl(buffer, m_network_group_scheduler_handle); + if (HAILO_STREAM_INTERNAL_ABORT == exp.status()) { + return make_unexpected(exp.status()); + } + CHECK_EXPECTED(exp); + + if (is_scheduled()) { + auto status = m_multiplexer->signal_write_finish(); + CHECK_SUCCESS_AS_EXPECTED(status); + } + + return exp; +} + +hailo_status VDeviceInputStreamWrapper::sync_write_all_raw_buffer_no_transform_impl(void *buffer, size_t offset, size_t size) +{ + ASSERT(NULL != buffer); + + return sync_write_raw_buffer(MemoryView(static_cast(buffer) + offset, size)).status(); +} + +hailo_status VDeviceInputStreamWrapper::set_timeout(std::chrono::milliseconds timeout) +{ + return m_vdevice_input_stream->set_timeout(timeout); +} + +hailo_status VDeviceInputStreamWrapper::flush() +{ + if (is_scheduled()) { + auto status = m_multiplexer->run_once_for_stream(name(), INPUT_RUN_ONCE_HANDLE__FLUSH, m_network_group_multiplexer_handle); + CHECK_SUCCESS(status); + + return HAILO_SUCCESS; + } + + return m_vdevice_input_stream->flush(); +} + +Expected> VDeviceInputStreamWrapper::create(std::shared_ptr vdevice_input_stream, + std::string network_name, std::shared_ptr multiplexer, scheduler_ng_handle_t network_group_scheduler_handle, + multiplexer_ng_handle_t network_group_multiplexer_handle) +{ + hailo_status status = HAILO_UNINITIALIZED; + std::unique_ptr wrapper(new (std::nothrow) VDeviceInputStreamWrapper(vdevice_input_stream, network_name, multiplexer, + network_group_scheduler_handle, network_group_multiplexer_handle, status)); + CHECK_NOT_NULL_AS_EXPECTED(wrapper, HAILO_OUT_OF_HOST_MEMORY); + CHECK_SUCCESS_AS_EXPECTED(status); + + return wrapper; +} + +Expected> VDeviceInputStreamWrapper::clone(multiplexer_ng_handle_t network_group_multiplexer_handle) +{ + auto wrapper = create(m_vdevice_input_stream, m_network_name, m_multiplexer, m_network_group_scheduler_handle, network_group_multiplexer_handle); + CHECK_EXPECTED(wrapper); + + return wrapper; +} + +VDeviceInputStreamWrapper::VDeviceInputStreamWrapper(std::shared_ptr &vdevice_input_stream, + std::string network_name, std::shared_ptr multiplexer, scheduler_ng_handle_t network_group_scheduler_handle, + multiplexer_ng_handle_t network_group_multiplexer_handle, hailo_status &status) : + InputStreamBase(vdevice_input_stream->get_info(), + vdevice_input_stream->m_nn_stream_config, vdevice_input_stream->get_network_group_activated_event()), + m_vdevice_input_stream(vdevice_input_stream), + m_multiplexer(multiplexer), + m_network_group_scheduler_handle(network_group_scheduler_handle), + m_network_group_multiplexer_handle(network_group_multiplexer_handle), + m_network_name(network_name) +{ + status = multiplexer->register_run_once_for_stream(vdevice_input_stream->name(), INPUT_RUN_ONCE_HANDLE__FLUSH, [this] + { + return m_vdevice_input_stream->flush(); + }); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("register_run_once_for_stream failed! status = {}", status); + return; + } + + status = multiplexer->register_run_once_for_stream(vdevice_input_stream->name(), INPUT_RUN_ONCE_HANDLE__ABORT, [this] + { + return m_vdevice_input_stream->abort_impl(m_network_group_scheduler_handle); + }); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("register_run_once_for_stream failed! status = {}", status); + return; + } + + status = multiplexer->register_run_once_for_stream(vdevice_input_stream->name(), INPUT_RUN_ONCE_HANDLE__CLEAR_ABORT, [this] + { + return m_vdevice_input_stream->clear_abort_impl(m_network_group_scheduler_handle); + }); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("register_run_once_for_stream failed! status = {}", status); + return; + } +} + +const hailo_stream_info_t &VDeviceOutputStreamWrapper::get_info() const +{ + return m_vdevice_output_stream->get_info(); +} + +const CONTROL_PROTOCOL__nn_stream_config_t &VDeviceOutputStreamWrapper::get_nn_stream_config() +{ + return m_vdevice_output_stream->get_nn_stream_config(); +} + +hailo_status VDeviceOutputStreamWrapper::activate_stream(uint16_t dynamic_batch_size) +{ + return m_vdevice_output_stream->activate_stream(dynamic_batch_size); +} + +hailo_status VDeviceOutputStreamWrapper::deactivate_stream() +{ + return m_vdevice_output_stream->deactivate_stream(); +} + +hailo_stream_interface_t VDeviceOutputStreamWrapper::get_interface() const +{ + return m_vdevice_output_stream->get_interface(); +} + +std::chrono::milliseconds VDeviceOutputStreamWrapper::get_timeout() const +{ + return m_vdevice_output_stream->get_timeout(); +} + +hailo_status VDeviceOutputStreamWrapper::abort() +{ + if (is_scheduled()) { + auto status = m_multiplexer->disable_network_group(m_network_group_multiplexer_handle); + CHECK_SUCCESS(status); + + // TODO: HRT-7638 + status = m_multiplexer->run_once_for_stream(name(), OUTPUT_RUN_ONCE_HANDLE__ABORT, m_network_group_multiplexer_handle); + CHECK_SUCCESS(status); + + return HAILO_SUCCESS; + } + + auto status = m_vdevice_output_stream->abort_impl(m_network_group_scheduler_handle); + CHECK_SUCCESS(status); + + return HAILO_SUCCESS; +} + +hailo_status VDeviceOutputStreamWrapper::clear_abort() +{ + if (is_scheduled()) { + auto status = m_multiplexer->enable_network_group(m_network_group_multiplexer_handle); + CHECK_SUCCESS(status); + + status = m_multiplexer->run_once_for_stream(name(), OUTPUT_RUN_ONCE_HANDLE__CLEAR_ABORT, m_network_group_multiplexer_handle); + CHECK_SUCCESS(status); + + return HAILO_SUCCESS; + } + + auto status = m_vdevice_output_stream->clear_abort_impl(m_network_group_scheduler_handle); + CHECK_SUCCESS(status); + + return HAILO_SUCCESS; +} + +bool VDeviceOutputStreamWrapper::is_scheduled() +{ + return m_vdevice_output_stream->is_scheduled(); +} + +Expected VDeviceOutputStreamWrapper::get_buffer_frames_size() const +{ + return m_vdevice_output_stream->get_buffer_frames_size(); +} +Expected VDeviceOutputStreamWrapper::get_pending_frames_count() const +{ + return m_vdevice_output_stream->get_pending_frames_count(); +} + +Expected VDeviceOutputStreamWrapper::sync_read_raw_buffer(MemoryView &buffer) +{ + return m_vdevice_output_stream->sync_read_raw_buffer(buffer); +} + +hailo_status VDeviceOutputStreamWrapper::read_all(MemoryView &buffer) +{ + return m_vdevice_output_stream->read_all(buffer); +} + +hailo_status VDeviceOutputStreamWrapper::read(MemoryView buffer) +{ + if (is_scheduled()) { + auto status = m_multiplexer->wait_for_read(m_network_group_multiplexer_handle, name()); + if (HAILO_STREAM_INTERNAL_ABORT == status) { + return status; + } + CHECK_SUCCESS(status); + } + + auto status = m_vdevice_output_stream->read_impl(buffer, m_network_group_scheduler_handle); + if ((HAILO_STREAM_INTERNAL_ABORT == status) || (HAILO_STREAM_NOT_ACTIVATED == status)) { + return status; + } + CHECK_SUCCESS(status); + + if (is_scheduled()) { + status = m_multiplexer->signal_read_finish(m_network_group_multiplexer_handle); + CHECK_SUCCESS(status); + } + + return HAILO_SUCCESS; +} + +hailo_status VDeviceOutputStreamWrapper::set_timeout(std::chrono::milliseconds timeout) +{ + return m_vdevice_output_stream->set_timeout(timeout); +} + +Expected> VDeviceOutputStreamWrapper::create(std::shared_ptr vdevice_output_stream, + std::string network_name, std::shared_ptr multiplexer, scheduler_ng_handle_t network_group_scheduler_handle, + multiplexer_ng_handle_t network_group_multiplexer_handle) +{ + hailo_status status = HAILO_UNINITIALIZED; + std::unique_ptr wrapper(new (std::nothrow) VDeviceOutputStreamWrapper(vdevice_output_stream, network_name, multiplexer, + network_group_scheduler_handle, network_group_multiplexer_handle, status)); + CHECK_NOT_NULL_AS_EXPECTED(wrapper, HAILO_OUT_OF_HOST_MEMORY); + + return wrapper; +} + +Expected> VDeviceOutputStreamWrapper::clone(scheduler_ng_handle_t network_group_multiplexer_handle) +{ + auto wrapper = create(m_vdevice_output_stream, m_network_name, m_multiplexer, m_network_group_scheduler_handle, network_group_multiplexer_handle); + CHECK_EXPECTED(wrapper); + + return wrapper; +} + +VDeviceOutputStreamWrapper::VDeviceOutputStreamWrapper(std::shared_ptr &vdevice_output_stream, + std::string network_name, std::shared_ptr multiplexer, scheduler_ng_handle_t network_group_scheduler_handle, + multiplexer_ng_handle_t network_group_multiplexer_handle, hailo_status &status) : + OutputStreamBase(vdevice_output_stream->get_layer_info(), vdevice_output_stream->get_info(), + vdevice_output_stream->m_nn_stream_config, vdevice_output_stream->get_network_group_activated_event()), + m_vdevice_output_stream(vdevice_output_stream), + m_multiplexer(multiplexer), + m_network_group_scheduler_handle(network_group_scheduler_handle), + m_network_group_multiplexer_handle(network_group_multiplexer_handle), + m_network_name(network_name) +{ + status = multiplexer->register_run_once_for_stream(vdevice_output_stream->name(), OUTPUT_RUN_ONCE_HANDLE__ABORT, [this] + { + return m_vdevice_output_stream->abort_impl(m_network_group_scheduler_handle); + }); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("register_run_once_for_stream failed! status = {}", status); + return; + } + + status = multiplexer->register_run_once_for_stream(vdevice_output_stream->name(), OUTPUT_RUN_ONCE_HANDLE__CLEAR_ABORT, [this] + { + return m_vdevice_output_stream->clear_abort_impl(m_network_group_scheduler_handle); + }); + if (HAILO_SUCCESS != status) { + LOGGER__ERROR("register_run_once_for_stream failed! status = {}", status); + return; + } +} + +} /* namespace hailort */ diff --git a/hailort/libhailort/src/vdevice_stream_wrapper.hpp b/hailort/libhailort/src/vdevice_stream_wrapper.hpp new file mode 100644 index 0000000..390c5ac --- /dev/null +++ b/hailort/libhailort/src/vdevice_stream_wrapper.hpp @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + **/ +/** + * @file vdevice_stream_wrapper.hpp + * @brief Wrapper classes for VDeviceInputStream and VDeviceOutputStream + **/ + +#ifndef HAILO_VDEVICE_STREAM_WRAPPER_HPP_ +#define HAILO_VDEVICE_STREAM_WRAPPER_HPP_ + +#include "vdevice_stream.hpp" +#include "stream_internal.hpp" +#include "hailo/expected.hpp" +#include "context_switch/pipeline_multiplexer.hpp" + +namespace hailort +{ + +enum input_run_once_handle_t { + INPUT_RUN_ONCE_HANDLE__FLUSH, + INPUT_RUN_ONCE_HANDLE__ABORT, + INPUT_RUN_ONCE_HANDLE__CLEAR_ABORT +}; + +enum output_run_once_handle_t { + OUTPUT_RUN_ONCE_HANDLE__ABORT, + OUTPUT_RUN_ONCE_HANDLE__CLEAR_ABORT +}; + +class VDeviceInputStreamWrapper : public InputStreamBase { +public: + virtual ~VDeviceInputStreamWrapper() = default; + VDeviceInputStreamWrapper(const VDeviceInputStreamWrapper &other) = delete; + VDeviceInputStreamWrapper &operator=(const VDeviceInputStreamWrapper &other) = delete; + VDeviceInputStreamWrapper &operator=(VDeviceInputStreamWrapper &&other) = delete; + VDeviceInputStreamWrapper(VDeviceInputStreamWrapper &&other) = default; + + static Expected> create(std::shared_ptr vdevice_input_stream, + std::string network_name, std::shared_ptr multiplexer, scheduler_ng_handle_t network_group_scheduler_handle, + multiplexer_ng_handle_t network_group_multiplexer_handle = 0); + Expected> clone(multiplexer_ng_handle_t network_group_multiplexer_handle); + + virtual const hailo_stream_info_t &get_info() const override; + virtual const CONTROL_PROTOCOL__nn_stream_config_t &get_nn_stream_config() override; + virtual hailo_status activate_stream(uint16_t dynamic_batch_size) override; + virtual hailo_status deactivate_stream() override; + virtual hailo_stream_interface_t get_interface() const override; + virtual std::chrono::milliseconds get_timeout() const override; + virtual hailo_status abort() override; + virtual hailo_status clear_abort() override; + virtual bool is_scheduled() override; + + virtual Expected send_pending_buffer() override; + virtual Expected get_buffer_frames_size() const override; + virtual Expected get_pending_frames_count() const override; + +protected: + virtual Expected sync_write_raw_buffer(const MemoryView &buffer) override; + virtual hailo_status sync_write_all_raw_buffer_no_transform_impl(void *buffer, size_t offset, size_t size) override; + +private: + VDeviceInputStreamWrapper(std::shared_ptr &vdevice_input_stream, + std::string network_name, std::shared_ptr multiplexer, scheduler_ng_handle_t network_group_scheduler_handle, + multiplexer_ng_handle_t network_group_multiplexer_handle, hailo_status &status); + + virtual hailo_status set_timeout(std::chrono::milliseconds timeout) override; + virtual hailo_status flush() override; + + std::shared_ptr m_vdevice_input_stream; + std::shared_ptr m_multiplexer; + scheduler_ng_handle_t m_network_group_scheduler_handle; + multiplexer_ng_handle_t m_network_group_multiplexer_handle; + std::string m_network_name; +}; + +class VDeviceOutputStreamWrapper : public OutputStreamBase { +public: + virtual ~VDeviceOutputStreamWrapper() noexcept = default; + VDeviceOutputStreamWrapper(const VDeviceOutputStreamWrapper &other) = delete; + VDeviceOutputStreamWrapper &operator=(const VDeviceOutputStreamWrapper &other) = delete; + VDeviceOutputStreamWrapper &operator=(VDeviceOutputStreamWrapper &&other) = delete; + VDeviceOutputStreamWrapper(VDeviceOutputStreamWrapper &&other) = default; + + static Expected> create(std::shared_ptr vdevice_output_stream, + std::string network_name, std::shared_ptr multiplexer, scheduler_ng_handle_t network_group_scheduler_handle, + multiplexer_ng_handle_t network_group_multiplexer_handle = 0); + Expected> clone(multiplexer_ng_handle_t network_group_multiplexer_handle); + + virtual const hailo_stream_info_t &get_info() const override; + virtual const CONTROL_PROTOCOL__nn_stream_config_t &get_nn_stream_config() override; + virtual hailo_status activate_stream(uint16_t dynamic_batch_size) override; + virtual hailo_status deactivate_stream() override; + virtual hailo_stream_interface_t get_interface() const override; + virtual std::chrono::milliseconds get_timeout() const override; + virtual hailo_status abort() override; + virtual hailo_status clear_abort() override; + virtual bool is_scheduled() override; + virtual Expected get_buffer_frames_size() const override; + virtual Expected get_pending_frames_count() const override; + + virtual hailo_status register_for_d2h_interrupts(const std::function &callback) override + { + return m_vdevice_output_stream->register_for_d2h_interrupts(callback); + } + +protected: + virtual Expected sync_read_raw_buffer(MemoryView &buffer) override; + +private: + VDeviceOutputStreamWrapper(std::shared_ptr &vdevice_output_stream, + std::string network_name, std::shared_ptr multiplexer, scheduler_ng_handle_t network_group_scheduler_handle, + multiplexer_ng_handle_t network_group_multiplexer_handle, hailo_status &status); + + virtual hailo_status set_timeout(std::chrono::milliseconds timeout) override; + virtual hailo_status read_all(MemoryView &buffer) override; + virtual hailo_status read(MemoryView buffer) override; + + std::shared_ptr m_vdevice_output_stream; + std::shared_ptr m_multiplexer; + scheduler_ng_handle_t m_network_group_scheduler_handle; + multiplexer_ng_handle_t m_network_group_multiplexer_handle; + std::string m_network_name; + EventPtr m_read_event; +}; + +} /* namespace hailort */ + +#endif /* HAILO_VDEVICE_STREAM_WRAPPER_HPP_ */ diff --git a/hailort/libhailort/src/vdma/channel_id.hpp b/hailort/libhailort/src/vdma/channel_id.hpp new file mode 100644 index 0000000..2934456 --- /dev/null +++ b/hailort/libhailort/src/vdma/channel_id.hpp @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) +**/ +/** + * @file channel_index.hpp + * @brief Struct used for channel identifier - (engine_index, channel_index) pair. + **/ + +#ifndef _HAILO_VDMA_CHANNEL_ID_HPP_ +#define _HAILO_VDMA_CHANNEL_ID_HPP_ + +#include "hailo/hailort.h" +#include "common/logger_macros.hpp" +#include +#include + + +namespace hailort { +namespace vdma { + +// TODO: HRT-6949 don't use default engine index. +static constexpr uint8_t DEFAULT_ENGINE_INDEX = 0; + +/** + * For each dma engine we have 16 inputs channels and 16 output channels. + * The amount of engines is determined by the driver. + */ +struct ChannelId +{ + uint8_t engine_index; + uint8_t channel_index; + + // Allow put `ChannelId` inside std::set + friend bool operator<(const ChannelId &a, const ChannelId &b) + { + return std::make_pair(a.engine_index, a.channel_index) < std::make_pair(b.engine_index, b.channel_index); + } +}; + +} /* namespace vdma */ +} /* namespace hailort */ + + +template<> +struct fmt::formatter : fmt::formatter { + template + auto format(const hailort::vdma::ChannelId &input, FormatContext& ctx) -> decltype(ctx.out()) { + std::stringstream ss; + ss << static_cast(input.engine_index) << ":" + << static_cast(input.channel_index); + return fmt::formatter::format(ss.str(), ctx); + } +}; + +#endif /* _HAILO_VDMA_CHANNEL_ID_HPP_ */ diff --git a/hailort/libhailort/src/vdma/continuous_buffer.hpp b/hailort/libhailort/src/vdma/continuous_buffer.hpp index fcaec99..481d9ce 100644 --- a/hailort/libhailort/src/vdma/continuous_buffer.hpp +++ b/hailort/libhailort/src/vdma/continuous_buffer.hpp @@ -50,13 +50,6 @@ public: virtual uint16_t desc_page_size() const override; virtual uint32_t descs_count() const override; - // Not used in this flow - virtual ExpectedRef get_desc_list() override - { - LOGGER__ERROR("Can't get descriptor list on continuous buffer"); - return make_unexpected(HAILO_INVALID_OPERATION); - } - virtual hailo_status read(void *buf_dst, size_t count, size_t offset) override; virtual hailo_status write(const void *buf_src, size_t count, size_t offset) override; diff --git a/hailort/libhailort/src/vdma/mapped_buffer.cpp b/hailort/libhailort/src/vdma/mapped_buffer.cpp index 9195178..fa1d0fc 100644 --- a/hailort/libhailort/src/vdma/mapped_buffer.cpp +++ b/hailort/libhailort/src/vdma/mapped_buffer.cpp @@ -18,34 +18,36 @@ Expected MappedBuffer::create(size_t required_size, HailoRTDriver: MappedBuffer::MappedBuffer( size_t required_size, HailoRTDriver::DmaDirection data_direction, HailoRTDriver &driver, hailo_status &status) - : m_user_address(), m_size(required_size), m_driver(driver), - m_driver_buff_handle(HailoRTDriver::INVALID_DRIVER_BUFFER_HANDLE_VALUE) + : m_size(required_size), m_driver(driver) { - auto buffer = allocate_vdma_buffer(driver, required_size, m_driver_buff_handle); + auto buffer = VdmaMappedBufferImpl::allocate_vdma_buffer(driver, required_size); if (! buffer) { status = buffer.status(); return; } - auto expected_handle = m_driver.vdma_buffer_map(buffer->get(), required_size, data_direction, m_driver_buff_handle); + auto expected_handle = m_driver.vdma_buffer_map(buffer->get(), required_size, data_direction, + buffer->get_mapped_buffer_identifier()); if (!expected_handle) { status = expected_handle.status(); return; } + + m_vdma_mapped_buffer = make_unique_nothrow(buffer.release()); + if (nullptr == m_vdma_mapped_buffer) { + m_driver.vdma_buffer_unmap(expected_handle.value()); + status = HAILO_OUT_OF_HOST_MEMORY; + return; + } m_handle = expected_handle.release(); - m_user_address = buffer.release(); status = HAILO_SUCCESS; } MappedBuffer::~MappedBuffer() { - if (m_user_address) { + if (m_vdma_mapped_buffer && *m_vdma_mapped_buffer) { m_driver.vdma_buffer_unmap(m_handle); - - if (HailoRTDriver::INVALID_DRIVER_BUFFER_HANDLE_VALUE != m_driver_buff_handle) { - m_driver.vdma_low_memory_buffer_free(m_driver_buff_handle); - } } } @@ -57,7 +59,7 @@ hailo_status MappedBuffer::write(const void *buf_src, size_t count, size_t offse } if (count > 0) { - auto dst_vdma_address = (uint8_t*)m_user_address.get() + offset; + auto dst_vdma_address = (uint8_t*)m_vdma_mapped_buffer->get() + offset; memcpy(dst_vdma_address, buf_src, count); auto status = m_driver.vdma_buffer_sync(m_handle, HailoRTDriver::DmaDirection::H2D, dst_vdma_address, count); @@ -78,7 +80,7 @@ hailo_status MappedBuffer::read(void *buf_dst, size_t count, size_t offset) } if (count > 0) { - auto dst_vdma_address = (uint8_t*)m_user_address.get() + offset; + auto dst_vdma_address = (uint8_t*)m_vdma_mapped_buffer->get() + offset; auto status = m_driver.vdma_buffer_sync(m_handle, HailoRTDriver::DmaDirection::D2H, dst_vdma_address, count); if (HAILO_SUCCESS != status) { LOGGER__ERROR("Failed synching vdma buffer on read"); @@ -143,22 +145,5 @@ hailo_status MappedBuffer::read_cyclic(void *buf_dst, size_t count, size_t offse return HAILO_SUCCESS; } -Expected> MappedBuffer::allocate_vdma_buffer(HailoRTDriver &driver, size_t required_size, - uintptr_t &driver_buff_handle) -{ - // Check if driver should be allocated from driver or from user - if (driver.allocate_driver_buffer()) { - auto driver_buffer_handle = driver.vdma_low_memory_buffer_alloc(required_size); - CHECK_EXPECTED(driver_buffer_handle); - - driver_buff_handle = driver_buffer_handle.release(); - - return MmapBuffer::create_file_map(required_size, driver.fd(), driver_buff_handle); - } - else { - return MmapBuffer::create_shared_memory(required_size); - } -} - } /* namespace vdma */ } /* namespace hailort */ diff --git a/hailort/libhailort/src/vdma/mapped_buffer.hpp b/hailort/libhailort/src/vdma/mapped_buffer.hpp index 2fc2d68..b5b4c18 100644 --- a/hailort/libhailort/src/vdma/mapped_buffer.hpp +++ b/hailort/libhailort/src/vdma/mapped_buffer.hpp @@ -24,6 +24,7 @@ #include "os/mmap_buffer.hpp" #include "os/hailort_driver.hpp" #include "hailo/expected.hpp" +#include "vdma_mapped_buffer_impl.hpp" namespace hailort { namespace vdma { @@ -43,8 +44,8 @@ public: MappedBuffer(MappedBuffer &&other) noexcept = default; MappedBuffer &operator=(MappedBuffer &&other) = delete; - void *user_address() { return m_user_address.get(); } - size_t handle() { return m_handle; } + void *user_address() { return m_vdma_mapped_buffer->get(); } + HailoRTDriver::VdmaBufferHandle handle() { return m_handle; } size_t size() const { return m_size; } /** @@ -99,14 +100,10 @@ public: private: - static Expected> allocate_vdma_buffer(HailoRTDriver &driver, size_t required_size, - uintptr_t &driver_buff_handle); - - MmapBuffer m_user_address; + std::unique_ptr m_vdma_mapped_buffer; HailoRTDriver::VdmaBufferHandle m_handle; size_t m_size; HailoRTDriver &m_driver; - uintptr_t m_driver_buff_handle; }; } /* namespace vdma */ diff --git a/hailort/libhailort/src/vdma/sg_buffer.cpp b/hailort/libhailort/src/vdma/sg_buffer.cpp index 01c3583..c02066f 100644 --- a/hailort/libhailort/src/vdma/sg_buffer.cpp +++ b/hailort/libhailort/src/vdma/sg_buffer.cpp @@ -68,6 +68,16 @@ hailo_status SgBuffer::write(const void *buf_src, size_t count, size_t offset) return m_mapped_buffer.write(buf_src, count, offset); } +hailo_status SgBuffer::read_cyclic(void *buf_dst, size_t count, size_t offset) +{ + return m_mapped_buffer.read_cyclic(buf_dst, count, offset); +} + +hailo_status SgBuffer::write_cyclic(const void *buf_src, size_t count, size_t offset) +{ + return m_mapped_buffer.write_cyclic(buf_src, count, offset); +} + Expected SgBuffer::program_descriptors(size_t transfer_size, VdmaInterruptsDomain first_desc_interrupts_domain, VdmaInterruptsDomain last_desc_interrupts_domain, size_t desc_offset, bool is_circular) { diff --git a/hailort/libhailort/src/vdma/sg_buffer.hpp b/hailort/libhailort/src/vdma/sg_buffer.hpp index c1472a1..fb79fcb 100644 --- a/hailort/libhailort/src/vdma/sg_buffer.hpp +++ b/hailort/libhailort/src/vdma/sg_buffer.hpp @@ -26,7 +26,7 @@ namespace vdma { class SgBuffer final : public VdmaBuffer { public: static Expected create(HailoRTDriver &driver, uint32_t desc_count, uint16_t desc_page_size, - HailoRTDriver::DmaDirection data_direction, uint8_t channel_index = 0); + HailoRTDriver::DmaDirection data_direction, uint8_t channel_index = HailoRTDriver::INVALID_VDMA_CHANNEL_INDEX); virtual ~SgBuffer() = default; @@ -46,13 +46,14 @@ public: virtual uint32_t descs_count() const override; uint8_t depth() const; - // Should be only used for host managed ddr buffer, in the future this function may return nullptr (on CCB - // case where there is no descriptors list) - virtual ExpectedRef get_desc_list() override; + ExpectedRef get_desc_list(); virtual hailo_status read(void *buf_dst, size_t count, size_t offset) override; virtual hailo_status write(const void *buf_src, size_t count, size_t offset) override; + hailo_status read_cyclic(void *buf_dst, size_t count, size_t offset); + hailo_status write_cyclic(const void *buf_src, size_t count, size_t offset); + virtual Expected program_descriptors(size_t transfer_size, VdmaInterruptsDomain first_desc_interrupts_domain, VdmaInterruptsDomain last_desc_interrupts_domain, size_t desc_offset, bool is_circular) override; virtual hailo_status reprogram_device_interrupts_for_end_of_batch(size_t transfer_size, uint16_t batch_size, diff --git a/hailort/libhailort/src/vdma/vdma_buffer.cpp b/hailort/libhailort/src/vdma/vdma_buffer.cpp new file mode 100644 index 0000000..500e80d --- /dev/null +++ b/hailort/libhailort/src/vdma/vdma_buffer.cpp @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + **/ +/** + * @file vdma_buffer.cpp + * @brief vdma buffer. + **/ + +#include "vdma_buffer.hpp" +#include "control_protocol.h" + +namespace hailort { +namespace vdma { + +CONTROL_PROTOCOL__host_buffer_info_t VdmaBuffer::get_host_buffer_info(uint32_t transfer_size) +{ + CONTROL_PROTOCOL__host_buffer_info_t buffer_info = {}; + + buffer_info.buffer_type = static_cast((type() == vdma::VdmaBuffer::Type::SCATTER_GATHER) ? + CONTROL_PROTOCOL__HOST_BUFFER_TYPE_EXTERNAL_DESC : + CONTROL_PROTOCOL__HOST_BUFFER_TYPE_CCB); + buffer_info.dma_address = dma_address(); + buffer_info.desc_page_size = desc_page_size(); + buffer_info.total_desc_count = descs_count(); + buffer_info.bytes_in_pattern = transfer_size; + + return buffer_info; +} + +} +} \ No newline at end of file diff --git a/hailort/libhailort/src/vdma/vdma_buffer.hpp b/hailort/libhailort/src/vdma/vdma_buffer.hpp index e28187f..2474a91 100644 --- a/hailort/libhailort/src/vdma/vdma_buffer.hpp +++ b/hailort/libhailort/src/vdma/vdma_buffer.hpp @@ -13,6 +13,7 @@ #include "os/hailort_driver.hpp" #include "vdma_descriptor_list.hpp" +#include "control_protocol.h" namespace hailort { namespace vdma { @@ -46,10 +47,6 @@ public: return static_cast(DESCRIPTORS_IN_BUFFER(buffer_size, page_size)); } - // If there's no descriptor list then Unexpected(HAILO_INVALID_OPERATION) will be returned - // (E.g. for CCB, where there is no descriptors list) - virtual ExpectedRef get_desc_list() = 0; - virtual hailo_status read(void *buf_dst, size_t count, size_t offset) = 0; virtual hailo_status write(const void *buf_src, size_t count, size_t offset) = 0; @@ -57,6 +54,8 @@ public: VdmaInterruptsDomain last_desc_interrupts_domain, size_t desc_offset, bool is_circular) = 0; virtual hailo_status reprogram_device_interrupts_for_end_of_batch(size_t transfer_size, uint16_t batch_size, VdmaInterruptsDomain new_interrupts_domain) = 0; + + CONTROL_PROTOCOL__host_buffer_info_t get_host_buffer_info(uint32_t transfer_size); }; } /* vdma */ diff --git a/hailort/libhailort/src/vdma/vdma_mapped_buffer_impl.cpp b/hailort/libhailort/src/vdma/vdma_mapped_buffer_impl.cpp new file mode 100644 index 0000000..1188b64 --- /dev/null +++ b/hailort/libhailort/src/vdma/vdma_mapped_buffer_impl.cpp @@ -0,0 +1,108 @@ +#include "vdma_mapped_buffer_impl.hpp" + +namespace hailort { +namespace vdma { + +#if defined(__linux__) || defined(_MSC_VER) + +Expected VdmaMappedBufferImpl::allocate_vdma_buffer(HailoRTDriver &driver, size_t required_size) +{ + // Check if driver should be allocated from driver or from user + if (driver.allocate_driver_buffer()) { + auto driver_buffer_handle = driver.vdma_low_memory_buffer_alloc(required_size); + CHECK_EXPECTED(driver_buffer_handle); + + uintptr_t driver_buff_handle = driver_buffer_handle.release(); + + auto mapped_buffer = MmapBuffer::create_file_map(required_size, driver.fd(), driver_buff_handle); + CHECK_EXPECTED(mapped_buffer); + + return VdmaMappedBufferImpl(mapped_buffer.release(), driver_buff_handle, driver); + } + else { + auto mapped_buffer = MmapBuffer::create_shared_memory(required_size); + CHECK_EXPECTED(mapped_buffer); + return VdmaMappedBufferImpl(mapped_buffer.release(), HailoRTDriver::INVALID_DRIVER_BUFFER_HANDLE_VALUE, driver); + } +} + +VdmaMappedBufferImpl::~VdmaMappedBufferImpl() +{ + if (m_mapped_buffer) { + if (HailoRTDriver::INVALID_DRIVER_BUFFER_HANDLE_VALUE != m_driver_mapped_buffer_identifier) { + m_driver.vdma_low_memory_buffer_free(m_driver_mapped_buffer_identifier); + } + } +} + +#elif defined(__QNX__) + +const int VdmaMappedBufferImpl::INVALID_FD = -1; +const shm_handle_t VdmaMappedBufferImpl::INVALID_HANDLE = (shm_handle_t)-1; +const char* VdmaMappedBufferImpl::VDMA_BUFFER_TYPE_MEMORY_NAME = "/memory/below4G/ram/below1G"; + +Expected VdmaMappedBufferImpl::allocate_vdma_buffer(HailoRTDriver &driver, size_t required_size) +{ + // Desctructor of type_mem_fd will close fd + FileDescriptor type_mem_fd(posix_typed_mem_open(VDMA_BUFFER_TYPE_MEMORY_NAME, O_RDWR, POSIX_TYPED_MEM_ALLOCATE)); + if (INVALID_FD == type_mem_fd) { + LOGGER__ERROR("Error getting fd from typed memory of type {}, errno {}\n", VDMA_BUFFER_TYPE_MEMORY_NAME, + errno); + return make_unexpected(HAILO_INTERNAL_FAILURE); + } + + vdma_mapped_buffer_driver_identifier driver_buff_handle; + driver_buff_handle.shm_fd = shm_open(SHM_ANON, O_RDWR | O_CREAT, 0777); + CHECK_AS_EXPECTED(INVALID_FD != driver_buff_handle.shm_fd, HAILO_INTERNAL_FAILURE, + "Error creating shm object, errno is: {}", errno); + + // backs the shared memory object with physical memory + int err = shm_ctl(driver_buff_handle.shm_fd, SHMCTL_ANON | SHMCTL_TYMEM, (uint64_t)type_mem_fd, + required_size); + if (-1 == err) { + LOGGER__ERROR("Error backing shm object in physical memory, errno is: {}", errno); + close(driver_buff_handle.shm_fd); + return make_unexpected(HAILO_INTERNAL_FAILURE); + } + + // Create shared memory handle to send to driver + err = shm_create_handle(driver_buff_handle.shm_fd, driver.resource_manager_pid(), O_RDWR, + &driver_buff_handle.shm_handle, 0); + if (0 != err) { + LOGGER__ERROR("Error creating shm object handle, errno is: {}", errno); + close(driver_buff_handle.shm_fd); + return make_unexpected(HAILO_INTERNAL_FAILURE); + } + + void *address = mmap(0, required_size, PROT_WRITE | PROT_READ | PROT_NOCACHE, MAP_SHARED, driver_buff_handle.shm_fd, 0); + if (MAP_FAILED == address) { + LOGGER__ERROR("Failed to mmap buffer with errno:{}", errno); + shm_delete_handle(driver_buff_handle.shm_handle); + close(driver_buff_handle.shm_fd); + return make_unexpected(HAILO_OUT_OF_HOST_MEMORY); + } + + return VdmaMappedBufferImpl(address, required_size, driver_buff_handle.shm_handle, driver_buff_handle.shm_fd, driver); +} + +VdmaMappedBufferImpl::~VdmaMappedBufferImpl() +{ + if (nullptr != m_address) { + if (0 != munmap(m_address, m_length)) { + LOGGER__ERROR("Error unmapping memory at address {}, Errno: {}", m_address, errno); + } + + if (INVALID_FD != m_driver_mapped_buffer_identifier.shm_fd) { + if (0 != close(m_driver_mapped_buffer_identifier.shm_fd)) { + LOGGER__ERROR("Error closing shared memory fd, Errno: {}", errno); + } + } + } +} + +#else +#error "unsupported platform!" +#endif // defined(__linux__) || defined(_MSC_VER) + +} /* namespace vdma */ +} /* namespace hailort */ \ No newline at end of file diff --git a/hailort/libhailort/src/vdma/vdma_mapped_buffer_impl.hpp b/hailort/libhailort/src/vdma/vdma_mapped_buffer_impl.hpp new file mode 100644 index 0000000..28186c3 --- /dev/null +++ b/hailort/libhailort/src/vdma/vdma_mapped_buffer_impl.hpp @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + **/ + +#ifndef _HAILO_VDMA_MAPPED_BUFFER_IMPL_HPP_ +#define _HAILO_VDMA_MAPPED_BUFFER_IMPL_HPP_ + +#include "os/mmap_buffer.hpp" +#include "os/hailort_driver.hpp" +#include "hailo/expected.hpp" + +namespace hailort { +namespace vdma { + +#if defined(__linux__) || defined(_MSC_VER) + +class VdmaMappedBufferImpl final { +public: + VdmaMappedBufferImpl(HailoRTDriver &driver) : m_mapped_buffer(), + m_driver_mapped_buffer_identifier(HailoRTDriver::INVALID_DRIVER_BUFFER_HANDLE_VALUE), m_driver(driver) {} + + ~VdmaMappedBufferImpl(); + + VdmaMappedBufferImpl(VdmaMappedBufferImpl &&other) noexcept : + m_mapped_buffer(std::move(other.m_mapped_buffer)), + m_driver_mapped_buffer_identifier(std::exchange(other.m_driver_mapped_buffer_identifier, HailoRTDriver::INVALID_DRIVER_BUFFER_HANDLE_VALUE)), + m_driver(other.m_driver) + {} + + VdmaMappedBufferImpl(const VdmaMappedBufferImpl &other) = delete; + VdmaMappedBufferImpl &operator=(const VdmaMappedBufferImpl &other) = delete; + VdmaMappedBufferImpl &operator=(VdmaMappedBufferImpl &&other) = delete; + + void* get() { return m_mapped_buffer.get(); } + + vdma_mapped_buffer_driver_identifier& get_mapped_buffer_identifier() { return m_driver_mapped_buffer_identifier; } + + explicit operator bool() + { + if (m_mapped_buffer) + return true; + return false; + } + + static Expected allocate_vdma_buffer(HailoRTDriver &driver, size_t required_size); + +private: + VdmaMappedBufferImpl(MmapBuffer&& mapped_buffer, vdma_mapped_buffer_driver_identifier driver_handle, HailoRTDriver &driver) : + m_mapped_buffer(std::move(mapped_buffer)), m_driver_mapped_buffer_identifier(driver_handle), m_driver(driver) {} + + MmapBuffer m_mapped_buffer; + vdma_mapped_buffer_driver_identifier m_driver_mapped_buffer_identifier; + HailoRTDriver &m_driver; +}; + +#elif defined(__QNX__) + +class VdmaMappedBufferImpl final { +public: + VdmaMappedBufferImpl(HailoRTDriver &driver): m_address(nullptr), m_length(0), m_driver(driver) { + m_driver_mapped_buffer_identifier.shm_handle = INVALID_HANDLE; + m_driver_mapped_buffer_identifier.shm_fd = INVALID_FD; + } + + ~VdmaMappedBufferImpl(); + + VdmaMappedBufferImpl(VdmaMappedBufferImpl &&other) noexcept : m_address(std::exchange(other.m_address, nullptr)), + m_length(std::exchange(other.m_length, 0)), m_driver(other.m_driver) + { + m_driver_mapped_buffer_identifier.shm_handle = std::exchange(other.m_driver_mapped_buffer_identifier.shm_handle, INVALID_HANDLE); + m_driver_mapped_buffer_identifier.shm_fd = std::exchange(other.m_driver_mapped_buffer_identifier.shm_fd, INVALID_FD); + + } + + VdmaMappedBufferImpl(const VdmaMappedBufferImpl &other) = delete; + VdmaMappedBufferImpl &operator=(const VdmaMappedBufferImpl &other) = delete; + VdmaMappedBufferImpl &operator=(VdmaMappedBufferImpl &&other) = delete; + + void* get() { return m_address; } + + vdma_mapped_buffer_driver_identifier& get_mapped_buffer_identifier() { return m_driver_mapped_buffer_identifier; } + + explicit operator bool() + { + return (nullptr != m_address); + } + + static Expected allocate_vdma_buffer(HailoRTDriver &driver, size_t required_size); + +private: + VdmaMappedBufferImpl(void *addr, size_t length, shm_handle_t shm_handle, int shm_fd, HailoRTDriver &driver) : + m_address(addr), m_length(length), m_driver(driver) + { + m_driver_mapped_buffer_identifier.shm_handle = shm_handle; + m_driver_mapped_buffer_identifier.shm_fd = shm_fd; + } + + static const int INVALID_FD; + static const shm_handle_t INVALID_HANDLE; + static const char* VDMA_BUFFER_TYPE_MEMORY_NAME; + + void *m_address; + size_t m_length; + vdma_mapped_buffer_driver_identifier m_driver_mapped_buffer_identifier; + HailoRTDriver &m_driver; +}; + +#else +#error "unsupported platform!" +#endif // defined(__linux__) || defined(_MSC_VER) + +} /* namespace vdma */ +} /* namespace hailort */ + +#endif /* _HAILO_VDMA_MAPPED_BUFFER_IMPL_HPP_ */ \ No newline at end of file diff --git a/hailort/libhailort/src/vdma_channel.cpp b/hailort/libhailort/src/vdma_channel.cpp index 79f4fbf..1dad777 100644 --- a/hailort/libhailort/src/vdma_channel.cpp +++ b/hailort/libhailort/src/vdma_channel.cpp @@ -4,11 +4,17 @@ #include "common/logger_macros.hpp" #include "common/utils.hpp" #include "microprofile.h" +#include "vdma/sg_buffer.hpp" +#include "vdma_descriptor_list.hpp" + +#include "hailo/hailort_common.hpp" #include #include #include +#include + namespace hailort { @@ -52,7 +58,7 @@ void VdmaChannel::State::unlock() #endif } -Expected VdmaChannel::create(uint8_t channel_index, Direction direction, HailoRTDriver &driver, +Expected VdmaChannel::create(vdma::ChannelId channel_id, Direction direction, HailoRTDriver &driver, uint16_t requested_desc_page_size, uint32_t stream_index, LatencyMeterPtr latency_meter, uint16_t transfers_per_axi_intr) { CHECK_AS_EXPECTED(Direction::BOTH != direction, HAILO_INVALID_ARGUMENT); @@ -61,8 +67,12 @@ Expected VdmaChannel::create(uint8_t channel_index, Direction direc auto desc_page_size_value = driver.calc_desc_page_size(requested_desc_page_size); CHECK_AS_EXPECTED(is_powerof2(desc_page_size_value), HAILO_INVALID_ARGUMENT, "Descriptor page_size must be a power of two."); + CHECK_AS_EXPECTED(channel_id.channel_index < VDMA_CHANNELS_PER_ENGINE, HAILO_INVALID_ARGUMENT, + "Invalid DMA channel index {}", channel_id.channel_index); + CHECK_AS_EXPECTED(channel_id.engine_index < driver.dma_engines_count(), HAILO_INVALID_ARGUMENT, + "Invalid DMA engine index {}, max {}", channel_id.engine_index, driver.dma_engines_count()); - VdmaChannel object(channel_index, direction, driver, stream_index, latency_meter, desc_page_size_value, + VdmaChannel object(channel_id, direction, driver, stream_index, latency_meter, desc_page_size_value, transfers_per_axi_intr, status); if (HAILO_SUCCESS != status) { LOGGER__ERROR("Failed creating VdmaChannel"); @@ -71,23 +81,17 @@ Expected VdmaChannel::create(uint8_t channel_index, Direction direc return object; } -VdmaChannel::VdmaChannel(uint8_t channel_index, Direction direction, HailoRTDriver &driver, +VdmaChannel::VdmaChannel(vdma::ChannelId channel_id, Direction direction, HailoRTDriver &driver, uint32_t stream_index, LatencyMeterPtr latency_meter, uint16_t desc_page_size, uint16_t transfers_per_axi_intr, hailo_status &status) - : m_channel_index(channel_index), m_direction(direction), m_driver(driver), - m_host_registers(driver, channel_index, direction), - m_device_registers(driver, channel_index, other_direction(direction)), m_desc_page_size(desc_page_size), - m_stream_index(stream_index), m_latency_meter(latency_meter), m_channel_enabled(false), + : m_d2h_callback_thread(nullptr), m_channel_id(channel_id), + m_direction(direction), m_driver(driver), + m_host_registers(driver, channel_id, direction), + m_device_registers(driver, channel_id, other_direction(direction)), m_desc_page_size(desc_page_size), + m_stream_index(stream_index), m_latency_meter(latency_meter), m_channel_enabled(false), m_channel_is_active(false), m_transfers_per_axi_intr(transfers_per_axi_intr), m_pending_buffers_sizes(0), m_pending_num_avail_offset(0), m_is_waiting_for_channel_completion(false), - m_is_aborted(false) + m_is_aborted_by_internal_source(false) { - // channel index invalid - if (channel_index >= MAX_HOST_CHANNELS_COUNT) { - LOGGER__ERROR("Invalid DMA index"); - status = HAILO_INVALID_ARGUMENT; - return; - } - if (m_transfers_per_axi_intr == 0) { LOGGER__ERROR("Invalid transfers per axi interrupt"); status = HAILO_INVALID_ARGUMENT; @@ -115,6 +119,11 @@ VdmaChannel::~VdmaChannel() if (m_channel_enabled) { stop_channel(); m_channel_enabled = false; + if (Direction::H2D == m_direction) { + m_can_write_buffer_cv.notify_all(); + } else { + m_can_read_buffer_cv.notify_all(); + } } if (m_state) { @@ -130,40 +139,49 @@ VdmaChannel::~VdmaChannel() } VdmaChannel::VdmaChannel(VdmaChannel &&other) noexcept: -m_channel_index(other.m_channel_index), + m_d2h_callback_thread(std::move(other.m_d2h_callback_thread)), + m_channel_id(std::move(other.m_channel_id)), m_direction(other.m_direction), m_driver(other.m_driver), m_host_registers(std::move(other.m_host_registers)), m_device_registers(std::move(other.m_device_registers)), m_desc_page_size(other.m_desc_page_size), - m_mapped_user_buffer(std::move(other.m_mapped_user_buffer)), - m_descriptors_buffer(std::move(other.m_descriptors_buffer)), + m_buffer(std::move(other.m_buffer)), m_stream_index(std::move(other.m_stream_index)), m_latency_meter(std::move(other.m_latency_meter)), m_state(std::move(other.m_state)), m_channel_handle(std::move(other.m_channel_handle)), m_channel_enabled(std::exchange(other.m_channel_enabled, false)), + m_channel_is_active(std::exchange(other.m_channel_is_active, false)), m_transfers_per_axi_intr(std::move(other.m_transfers_per_axi_intr)), m_pending_buffers_sizes(std::move(other.m_pending_buffers_sizes)), m_pending_num_avail_offset(std::move(other.m_pending_num_avail_offset)), - m_is_waiting_for_channel_completion(other.m_is_waiting_for_channel_completion.load()), - m_is_aborted(std::move(other.m_is_aborted)) + m_is_waiting_for_channel_completion(other.m_is_waiting_for_channel_completion.exchange(false)), + m_is_aborted_by_internal_source(other.m_is_aborted_by_internal_source.exchange(false)) {} hailo_status VdmaChannel::stop_channel() { - assert(m_channel_handle); - if (HailoRTDriver::INVALID_VDMA_CHANNEL_HANDLE != *m_channel_handle) { - // The driver also stops the channels - const auto status = unregister_fw_controlled_channel(); - CHECK_SUCCESS(status, "Failed to disable channel {}", m_channel_index); + { + std::unique_lock lock(m_is_active_flag_mutex); + m_channel_is_active = false; } - if (m_state) { - std::lock_guard state_guard(*m_state); - reset_internal_counters(); + if (!m_state) { + const auto status = unregister_fw_controlled_channel(); + CHECK_SUCCESS(status, "Failed to disable channel {}", m_channel_id); + } else { + std::unique_lock state_guard(*m_state); + const auto status = unregister_fw_controlled_channel(); + CHECK_SUCCESS(status, "Failed to disable channel {}", m_channel_id); + + if (Direction::D2H == m_direction) { + unregister_for_d2h_interrupts(state_guard); + } else { + // For H2D channels we reset counters as we want to allow writes to the start of the buffer while the channel is stopped + reset_internal_counters(); + } } - return HAILO_SUCCESS; } @@ -172,25 +190,67 @@ uint16_t VdmaChannel::get_page_size() return m_desc_page_size; } +Expected VdmaChannel::get_boundary_buffer_info(uint32_t transfer_size) +{ + CHECK_AS_EXPECTED(m_buffer, HAILO_INVALID_OPERATION, "Cannot get host buffer before buffer is allocated"); + return m_buffer->get_host_buffer_info(transfer_size); +} hailo_status VdmaChannel::abort() { - m_is_aborted = true; - m_can_write_buffer_cv.notify_one(); - return m_driver.vdma_channel_abort(m_channel_index, *m_channel_handle); + m_is_aborted_by_internal_source = true; + if (Direction::H2D == m_direction) { + m_can_write_buffer_cv.notify_all(); + } else { + m_can_read_buffer_cv.notify_all(); + } + return m_driver.vdma_channel_abort(m_channel_id, *m_channel_handle); } hailo_status VdmaChannel::clear_abort() { - m_is_aborted = false; - return m_driver.vdma_channel_clear_abort(m_channel_index, *m_channel_handle); + auto status = m_driver.vdma_channel_clear_abort(m_channel_id, *m_channel_handle); + m_is_aborted_by_internal_source = false; + return status; +} + +size_t VdmaChannel::get_transfers_count_in_buffer(size_t transfer_size) +{ + const auto descs_in_transfer = m_buffer->descriptors_in_buffer(transfer_size); + const auto descs_count = CB_SIZE(m_state->m_descs); + return (descs_count - 1) / descs_in_transfer; +} + +size_t VdmaChannel::get_buffer_size() const +{ + assert(m_buffer); + return m_buffer->size(); +} + +Expected VdmaChannel::get_h2d_pending_frames_count() +{ + return m_pending_buffers_sizes.size(); } -hailo_status VdmaChannel::prepare_d2h_pending_descriptors(uint32_t descs_count, uint32_t transfer_size) +Expected VdmaChannel::get_d2h_pending_descs_count() { - uint32_t descs_in_transfer = m_descriptors_buffer->descriptors_in_buffer(transfer_size); - uint32_t transfers_count = std::min(((descs_count - 1) / descs_in_transfer), - static_cast(CB_SIZE(m_state->m_buffers) - 1)); + assert(m_state); + + std::lock_guard state_guard(*m_state); + + int num_proc = CB_TAIL(m_state->m_descs); + int desc_num_ready = CB_PROG(m_state->m_descs, num_proc, m_state->m_d2h_read_desc_index); + + return desc_num_ready; +} + +hailo_status VdmaChannel::prepare_d2h_pending_descriptors(uint32_t transfer_size) +{ + assert(m_buffer); + + auto transfers_count_in_buffer = get_transfers_count_in_buffer(transfer_size); + auto transfers_count = std::min(transfers_count_in_buffer, + static_cast(CB_SIZE(m_state->m_buffers) - 1)); // on D2H no need for interrupt of first descriptor const auto first_desc_interrupts_domain = VdmaInterruptsDomain::NONE; @@ -200,6 +260,10 @@ hailo_status VdmaChannel::prepare_d2h_pending_descriptors(uint32_t descs_count, (static_cast(m_transfers_per_axi_intr - 1) == (i % m_transfers_per_axi_intr)) ? VdmaInterruptsDomain::BOTH : VdmaInterruptsDomain::HOST; auto status = prepare_descriptors(transfer_size, first_desc_interrupts_domain, last_desc_interrutps_domain); + if (HAILO_STREAM_NOT_ACTIVATED == status) { + LOGGER__INFO("preparing descriptors failed because channel is not activated"); + return status; + } CHECK_SUCCESS(status, "Failed prepare desc status={}", status); } @@ -275,22 +339,22 @@ void VdmaChannel::reset_internal_counters() hailo_status VdmaChannel::start_allocated_channel(uint32_t transfer_size) { /* descriptor buffer must be allocated */ - assert(m_descriptors_buffer); + assert(m_buffer); assert(m_state); std::lock_guard state_guard(*m_state); reset_internal_counters(); - auto status = start_channel(*m_descriptors_buffer); - CHECK_SUCCESS(status, "failed to start channel {}", m_channel_index); + auto status = start_channel(); + CHECK_SUCCESS(status, "failed to start channel {}", m_channel_id); if ((Direction::D2H == m_direction) && (transfer_size != 0)) { - auto descs_count = CB_SIZE(m_state->m_descs); - status = prepare_d2h_pending_descriptors(descs_count, transfer_size); + status = prepare_d2h_pending_descriptors(transfer_size); if (HAILO_SUCCESS != status) { stop_channel(); } return status; } + m_channel_is_active = true; return HAILO_SUCCESS; } @@ -300,27 +364,89 @@ hailo_status VdmaChannel::register_fw_controlled_channel() return register_channel_to_driver(HailoRTDriver::INVALID_DRIVER_BUFFER_HANDLE_VALUE); } -hailo_status VdmaChannel::wait(size_t buffer_size, const std::chrono::milliseconds &timeout) +hailo_status VdmaChannel::register_for_d2h_interrupts(const std::function &callback) +{ + // This function has to be called after channel is started + assert(!((m_d2h_callback_thread) && m_d2h_callback_thread->joinable())); + m_d2h_callback_thread = make_unique_nothrow([this, callback]() { + wait_d2h_callback(callback); + }); + CHECK_NOT_NULL(m_d2h_callback_thread, HAILO_OUT_OF_HOST_MEMORY); + + return HAILO_SUCCESS; +} + +hailo_status VdmaChannel::unregister_for_d2h_interrupts(std::unique_lock &lock) { - if (!m_mapped_user_buffer) { + // This function has to be called after channel is stopped (after unregister_fw_controlled_channel is called) + if ((m_d2h_callback_thread) && (m_d2h_callback_thread->joinable())) { + // Let the channel finish processing interrupts + lock.unlock(); + m_d2h_callback_thread->join(); + lock.lock(); + } + return HAILO_SUCCESS; +} + +void VdmaChannel::wait_d2h_callback(const std::function &callback) +{ + assert(Direction::D2H == m_direction); + if(!m_buffer) { LOGGER__ERROR("Wait called without allocating buffers"); - return HAILO_INVALID_OPERATION; + return; } + while (true) { + auto status = wait_for_channel_completion(HAILO_INFINITE_TIMEOUT, callback); + if (HAILO_SUCCESS == status || (HAILO_STREAM_INTERNAL_ABORT == status)) { + // Ignore HAILO_STREAM_INTERNAL_ABORT as we want to keep waiting for interrupts until channel is stopped + continue; + } else if (HAILO_STREAM_NOT_ACTIVATED == status) { + // Finish gracefully + return; + } else { + LOGGER__ERROR("wait_d2h_callback failed with status={}", status); + return; + } + } +} - CHECK(buffer_size < m_mapped_user_buffer->size(), HAILO_INVALID_ARGUMENT, - "Requested transfer size ({}) must be smaller than ({})", buffer_size, m_mapped_user_buffer->size()); +hailo_status VdmaChannel::wait(size_t buffer_size, std::chrono::milliseconds timeout) +{ + if (!m_buffer) { + LOGGER__ERROR("Wait called without allocating buffers"); + return HAILO_INVALID_OPERATION; + } - auto is_ready_for_transfer = (Direction::H2D == m_direction) - ? std::bind(&VdmaChannel::is_ready_for_transfer_h2d, this, buffer_size) - : std::bind(&VdmaChannel::is_ready_for_transfer_d2h, this, buffer_size); + CHECK(buffer_size < m_buffer->size(), HAILO_INVALID_ARGUMENT, + "Requested transfer size ({}) must be smaller than ({})", buffer_size, m_buffer->size()); + if ((Direction::D2H == m_direction) && ((m_d2h_callback_thread) && (m_d2h_callback_thread->joinable()))) { + std::unique_lock state_guard(*m_state); + hailo_status status = HAILO_SUCCESS; // Best effort + bool was_successful = m_can_read_buffer_cv.wait_for(state_guard, timeout, [this, buffer_size, &status] () { + if ((!m_channel_enabled) || (m_is_aborted_by_internal_source)) { + status = HAILO_STREAM_INTERNAL_ABORT; + return true; // return true so that the wait will finish + } + return is_ready_for_transfer_d2h(buffer_size); + }); + if (HAILO_STREAM_INTERNAL_ABORT == status) { + LOGGER__INFO("wait_for in d2h wait was aborted!"); + return HAILO_STREAM_INTERNAL_ABORT; + } + CHECK(was_successful, HAILO_TIMEOUT); + return HAILO_SUCCESS; + } + auto is_ready_for_transfer = (Direction::H2D == m_direction) ? + std::bind(&VdmaChannel::is_ready_for_transfer_h2d, this, buffer_size) : + std::bind(&VdmaChannel::is_ready_for_transfer_d2h, this, buffer_size); return wait_for_condition(is_ready_for_transfer, timeout); } hailo_status VdmaChannel::transfer(void *buf, size_t count) { CHECK((nullptr != buf) && (0 < count), HAILO_INVALID_ARGUMENT); - CHECK(nullptr != m_mapped_user_buffer, HAILO_INVALID_OPERATION, "Transfer called without allocating buffers"); + CHECK(nullptr != m_buffer, HAILO_INVALID_OPERATION, "Transfer called without allocating buffers"); hailo_status status = HAILO_UNINITIALIZED; assert(m_state); @@ -328,15 +454,23 @@ hailo_status VdmaChannel::transfer(void *buf, size_t count) if (Direction::H2D == m_direction) { status = transfer_h2d(buf, count); + if (HAILO_STREAM_NOT_ACTIVATED == status) { + LOGGER__INFO("Transfer failed because Channel {} is not activated", m_channel_id); + return HAILO_STREAM_NOT_ACTIVATED; + } if (HAILO_SUCCESS != status) { - LOGGER__ERROR("Transfer failed for channel {}", m_channel_index); + LOGGER__ERROR("Transfer failed for channel {}", m_channel_id); return status; } return HAILO_SUCCESS; } else { status = transfer_d2h(buf, count); + if (HAILO_STREAM_NOT_ACTIVATED == status) { + LOGGER__INFO("Transfer failed because Channel {} is not activated", m_channel_id); + return HAILO_STREAM_NOT_ACTIVATED; + } if (HAILO_SUCCESS != status) { - LOGGER__ERROR("Transfer failed for channel {} status {}", m_channel_index, status); + LOGGER__ERROR("Transfer failed for channel {} status {}", m_channel_id, status); return status; } return HAILO_SUCCESS; @@ -347,19 +481,19 @@ hailo_status VdmaChannel::transfer(void *buf, size_t count) hailo_status VdmaChannel::write_buffer_impl(const MemoryView &buffer) { - CHECK(nullptr != m_mapped_user_buffer, HAILO_INVALID_OPERATION, "Transfer called without allocating buffers"); + CHECK(nullptr != m_buffer, HAILO_INVALID_OPERATION, "Transfer called without allocating buffers"); - size_t desired_desc_num = m_descriptors_buffer->descriptors_in_buffer(buffer.size()); + size_t desired_desc_num = m_buffer->descriptors_in_buffer(buffer.size()); uint32_t desc_avail = (get_num_available() + m_pending_num_avail_offset) & m_state->m_descs.size_mask; assert(CB_AVAIL(m_state->m_descs, desc_avail, CB_TAIL(m_state->m_descs)) >= static_cast(desired_desc_num)); /* Copy buffer into the PLDA data struct */ auto offset = desc_avail * m_desc_page_size; - auto status = m_mapped_user_buffer->write_cyclic(buffer.data(), buffer.size(), offset); + auto status = m_buffer->write_cyclic(buffer.data(), buffer.size(), offset); CHECK_SUCCESS(status); - m_pending_num_avail_offset = static_cast(m_pending_num_avail_offset + desired_desc_num); + m_pending_num_avail_offset = static_cast(m_pending_num_avail_offset + desired_desc_num); CHECK(!m_pending_buffers_sizes.full(), HAILO_INVALID_OPERATION, "Cannot add more pending buffers!"); m_pending_buffers_sizes.push_back(buffer.size()); @@ -371,23 +505,45 @@ hailo_status VdmaChannel::write_buffer(const MemoryView &buffer, std::chrono::mi assert(m_state); std::unique_lock state_guard(*m_state); - hailo_status status = HAILO_UNINITIALIZED; - size_t desired_desc_num = m_descriptors_buffer->descriptors_in_buffer(buffer.size()); - bool was_successful = m_can_write_buffer_cv.wait_for(state_guard, timeout, [this, &status, desired_desc_num] () { - if ((!m_channel_enabled) || (m_is_aborted)) { - status = HAILO_STREAM_INTERNAL_ABORT; + size_t desired_desc_num = m_buffer->descriptors_in_buffer(buffer.size()); + hailo_status channel_completion_status = HAILO_SUCCESS; + bool was_successful = m_can_write_buffer_cv.wait_for(state_guard, timeout, [this, desired_desc_num, timeout, &state_guard, + &channel_completion_status] () { + if ((!m_channel_enabled) || (m_is_aborted_by_internal_source)) { return true; } + // TODO (HRT-7252): Clean this code + while (true) { + int buffers_head = CB_HEAD(m_state->m_buffers); + int buffers_tail = CB_TAIL(m_state->m_buffers); + if (CB_AVAIL(m_state->m_buffers, buffers_head, buffers_tail)) { + break; + } + + if (HailoRTDriver::INVALID_VDMA_CHANNEL_HANDLE == *m_channel_handle) { + return false; + } + + state_guard.unlock(); + channel_completion_status = wait_for_channel_completion(timeout); + if (HAILO_SUCCESS != channel_completion_status) { + LOGGER__INFO("wait_for_channel_completion failed with status={}", channel_completion_status); + return true; + } + state_guard.lock(); + } + uint32_t desc_avail = (get_num_available() + m_pending_num_avail_offset) & m_state->m_descs.size_mask; int num_free = CB_AVAIL(m_state->m_descs, desc_avail, CB_TAIL(m_state->m_descs)); return (num_free >= static_cast(desired_desc_num)); }); - if (HAILO_STREAM_INTERNAL_ABORT == status) { + if ((!m_channel_enabled) || (m_is_aborted_by_internal_source) || (HAILO_STREAM_INTERNAL_ABORT == channel_completion_status)) { LOGGER__INFO("wait_for in write_buffer was aborted!"); - return status; + return HAILO_STREAM_INTERNAL_ABORT; } CHECK(was_successful, HAILO_TIMEOUT, "Waiting for descriptors in write_buffer has reached a timeout!"); + CHECK_SUCCESS(channel_completion_status); return write_buffer_impl(buffer); } @@ -395,6 +551,7 @@ hailo_status VdmaChannel::write_buffer(const MemoryView &buffer, std::chrono::mi hailo_status VdmaChannel::send_pending_buffer_impl() { CHECK(!m_pending_buffers_sizes.empty(), HAILO_INVALID_OPERATION, "There are no pending buffers to send!"); + assert(m_buffer); // For h2d, only the host need to get transfer done interrupts VdmaInterruptsDomain last_desc_interrupts_domain = VdmaInterruptsDomain::HOST; @@ -403,11 +560,20 @@ hailo_status VdmaChannel::send_pending_buffer_impl() VdmaInterruptsDomain::HOST : VdmaInterruptsDomain::NONE; auto status = prepare_descriptors(m_pending_buffers_sizes.front(), first_desc_interrupts_domain, last_desc_interrupts_domain); + if (HAILO_STREAM_NOT_ACTIVATED == status) { + LOGGER__INFO("sending pending buffer failed because stream is not activated"); + // Stream was aborted during transfer - reset pending buffers + m_pending_num_avail_offset = 0; + while (m_pending_buffers_sizes.size() > 0) { + m_pending_buffers_sizes.pop_front(); + } + return status; + } CHECK_SUCCESS(status); m_state->m_accumulated_transfers = (m_state->m_accumulated_transfers + 1) % m_transfers_per_axi_intr; - size_t desired_desc_num = m_descriptors_buffer->descriptors_in_buffer(m_pending_buffers_sizes.front()); + size_t desired_desc_num = m_buffer->descriptors_in_buffer(m_pending_buffers_sizes.front()); m_pending_num_avail_offset = static_cast(m_pending_num_avail_offset - desired_desc_num); m_pending_buffers_sizes.pop_front(); @@ -420,13 +586,19 @@ Expected VdmaChannel::send_pending_buffer() size_t next_buffer_desc_num = 0; { assert(m_state); + assert(m_buffer); std::lock_guard state_guard(*m_state); // Save before calling send_pending_buffer_impl because we pop from m_pending_buffers_sizes there - next_buffer_desc_num = m_descriptors_buffer->descriptors_in_buffer(m_pending_buffers_sizes.front()); + next_buffer_desc_num = m_buffer->descriptors_in_buffer(m_pending_buffers_sizes.front()); auto status = send_pending_buffer_impl(); - CHECK_SUCCESS_AS_EXPECTED(status); + if (HAILO_STREAM_NOT_ACTIVATED == status) { + LOGGER__INFO("stream is not activated"); + return make_unexpected(HAILO_STREAM_NOT_ACTIVATED); + } else { + CHECK_SUCCESS_AS_EXPECTED(status); + } } m_can_write_buffer_cv.notify_one(); @@ -479,7 +651,7 @@ hailo_status VdmaChannel::flush(const std::chrono::milliseconds &timeout) return HAILO_SUCCESS; } - if (!m_mapped_user_buffer) { + if (!m_buffer) { LOGGER__ERROR("VdmaChannel::flush is called on a channel without allocated resources"); return HAILO_INVALID_OPERATION; } @@ -493,7 +665,11 @@ hailo_status VdmaChannel::transfer_h2d(void *buf, size_t count) CHECK_SUCCESS(status); status = send_pending_buffer_impl(); - CHECK_SUCCESS(status); + if (HAILO_STREAM_NOT_ACTIVATED == status) { + return status; + } else { + CHECK_SUCCESS(status); + } return HAILO_SUCCESS; } @@ -507,8 +683,9 @@ hailo_status VdmaChannel::transfer_d2h(void *buf, size_t count) VdmaInterruptsDomain::BOTH : VdmaInterruptsDomain::HOST; assert(m_state); + assert(m_buffer); - auto desired_desc_num = m_descriptors_buffer->descriptors_in_buffer(count); + auto desired_desc_num = m_buffer->descriptors_in_buffer(count); assert(desired_desc_num <= MAX_DESCS_COUNT); int desc_num = static_cast(desired_desc_num); @@ -519,7 +696,7 @@ hailo_status VdmaChannel::transfer_d2h(void *buf, size_t count) } size_t offset = m_state->m_d2h_read_desc_index * m_desc_page_size; - status = m_mapped_user_buffer->read_cyclic(buf, count, offset); + status = m_buffer->read_cyclic(buf, count, offset); if (status != HAILO_SUCCESS) { return status; } @@ -527,8 +704,14 @@ hailo_status VdmaChannel::transfer_d2h(void *buf, size_t count) m_state->m_d2h_read_desc_index = (m_state->m_d2h_read_desc_index + desc_num) & m_state->m_descs.size_mask; // prepare descriptors for next recv - status = prepare_descriptors(count, first_desc_interrupts_domain, last_desc_interrupts_domain); - CHECK_SUCCESS(status); + if (*m_channel_handle != HailoRTDriver::INVALID_VDMA_CHANNEL_HANDLE) { + status = prepare_descriptors(count, first_desc_interrupts_domain, last_desc_interrupts_domain); + if (HAILO_STREAM_NOT_ACTIVATED == status) { + LOGGER__INFO("transfer d2h failed because stream is not activated"); + return status; + } + CHECK_SUCCESS(status); + } m_state->m_accumulated_transfers = (m_state->m_accumulated_transfers + 1) % m_transfers_per_axi_intr; @@ -551,7 +734,7 @@ uint16_t VdmaChannel::get_num_available() assert(is_aborted_exp); if ((HailoRTDriver::INVALID_VDMA_CHANNEL_HANDLE != *m_channel_handle) && !is_aborted_exp.value()) { - assert(hw_num_avail.value() == num_available); + assert(hw_num_avail.value() == num_available); } #endif return num_available; @@ -573,6 +756,10 @@ Expected VdmaChannel::get_hw_num_processed() hailo_status VdmaChannel::set_num_avail_value(uint16_t new_value) { + // TODO - HRT-7885 : add check in driver + CHECK(*m_channel_handle != HailoRTDriver::INVALID_VDMA_CHANNEL_HANDLE, HAILO_STREAM_NOT_ACTIVATED, + "Error, can't set num available when stream is not activated"); + auto status = m_host_registers.set_num_available(new_value); CHECK_SUCCESS(status, "Fail to write vdma num available register"); @@ -630,10 +817,12 @@ VdmaChannel::Direction VdmaChannel::other_direction(Direction direction) hailo_status VdmaChannel::unregister_fw_controlled_channel() { - auto status = m_driver.vdma_channel_disable(m_channel_index, *m_channel_handle); - *m_channel_handle = HailoRTDriver::INVALID_VDMA_CHANNEL_HANDLE; - CHECK_SUCCESS(status, "Failed to disable channel {}", m_channel_index); - + assert(m_channel_handle); + if (HailoRTDriver::INVALID_VDMA_CHANNEL_HANDLE != *m_channel_handle) { + auto status = m_driver.vdma_channel_disable(m_channel_id, *m_channel_handle); + *m_channel_handle = HailoRTDriver::INVALID_VDMA_CHANNEL_HANDLE; + CHECK_SUCCESS(status, "Failed to disable channel {}", m_channel_id); + } return HAILO_SUCCESS; } @@ -641,33 +830,39 @@ hailo_status VdmaChannel::register_channel_to_driver(uintptr_t desc_list_handle) { const bool measure_latency = (nullptr != m_latency_meter); const uintptr_t desc_handle = desc_list_handle; - auto channel_handle = m_driver.vdma_channel_enable(m_channel_index, m_direction, desc_handle, measure_latency); - CHECK_EXPECTED_AS_STATUS(channel_handle, "Failed to enable channel {}", m_channel_index); + auto channel_handle = m_driver.vdma_channel_enable(m_channel_id, m_direction, desc_handle, measure_latency); + CHECK_EXPECTED_AS_STATUS(channel_handle, "Failed to enable channel {}", m_channel_id); *m_channel_handle = channel_handle.release(); return HAILO_SUCCESS; } -hailo_status VdmaChannel::start_channel(VdmaDescriptorList &desc_list) +hailo_status VdmaChannel::start_channel() { - assert(is_aborted()); + auto is_aborted_exp = is_aborted(); + assert(is_aborted_exp); + assert(is_aborted_exp.value()); - auto status = register_channel_to_driver(desc_list.handle()); - CHECK_SUCCESS(status, "Failed to enable channel {}", m_channel_index); + auto status = register_channel_to_driver(m_buffer->get_desc_list()->get().handle()); + CHECK_SUCCESS(status, "Failed to enable channel {}", m_channel_id); + + m_channel_is_active = true; return HAILO_SUCCESS; } +// TODO - HRT-6984 - move function inside desc list class as part of the ctor void VdmaChannel::clear_descriptor_list() { - assert(m_descriptors_buffer); + assert(m_buffer); - size_t desc_number = m_descriptors_buffer->count(); - size_t page_size = m_mapped_user_buffer->size() / desc_number; + size_t desc_number = m_buffer->descs_count(); + size_t page_size = m_buffer->desc_page_size(); + auto desc_list = m_buffer->get_desc_list(); // Config Descriptors value in SG-List Host side for (uint32_t j = 0; j < desc_number; j++) { - VdmaDescriptor &descInfo = (*m_descriptors_buffer)[j]; + VdmaDescriptor &descInfo = (desc_list->get())[j]; descInfo.PageSize_DescControl = static_cast((page_size << 8) + 0x2); descInfo.RemainingPageSize_Status = 0x0; } @@ -678,34 +873,17 @@ hailo_status VdmaChannel::allocate_buffer(const uint32_t buffer_size) assert((buffer_size % m_desc_page_size) == 0); uint32_t desc_count = buffer_size / m_desc_page_size; - if (m_mapped_user_buffer) { - LOGGER__ERROR("m_mapped_user_buffer is not NULL"); + if (m_buffer) { + LOGGER__ERROR("m_buffer is not NULL"); return HAILO_INVALID_OPERATION; } - auto mapped_buffer = vdma::MappedBuffer::create(buffer_size, m_direction, m_driver); - if(!mapped_buffer) { - LOGGER__ERROR("create mapped buffer failed"); - return mapped_buffer.status(); - } - - auto descriptors = VdmaDescriptorList::create(desc_count, m_desc_page_size, m_driver); - if (!descriptors) { - LOGGER__ERROR("create descriptor list failed"); - return descriptors.status(); - } - - auto status = descriptors->configure_to_use_buffer(mapped_buffer.value(), m_channel_index); - if (status != HAILO_SUCCESS) { - LOGGER__ERROR("connect descriptor list to buffer failed"); - return status; - } - - m_mapped_user_buffer = make_unique_nothrow(mapped_buffer.release()); - CHECK_NOT_NULL(m_mapped_user_buffer, HAILO_OUT_OF_HOST_MEMORY); + auto buffer = vdma::SgBuffer::create(m_driver, desc_count, m_desc_page_size, m_direction, + m_channel_id.channel_index); + CHECK_EXPECTED_AS_STATUS(buffer); - m_descriptors_buffer = make_unique_nothrow(descriptors.release()); - CHECK_NOT_NULL(m_descriptors_buffer, HAILO_OUT_OF_HOST_MEMORY); + m_buffer = make_unique_nothrow(buffer.release()); + CHECK_NOT_NULL(m_buffer, HAILO_OUT_OF_HOST_MEMORY); return HAILO_SUCCESS; } @@ -726,7 +904,7 @@ uint32_t VdmaChannel::calculate_buffer_size(const HailoRTDriver &driver, uint32_ return descs_count * desc_page_size; } -hailo_status VdmaChannel::trigger_channel_completion(uint16_t hw_num_processed) +hailo_status VdmaChannel::trigger_channel_completion(uint16_t hw_num_processed, const std::function &callback) { // NOTE: right now, we can retake the 'completion' descriptor for a new transfer before handling the interrupt. // we should have our own pointers indicating whats free instead of reading from HW. @@ -736,6 +914,7 @@ hailo_status VdmaChannel::trigger_channel_completion(uint16_t hw_num_processed) // situation correctly. assert(m_state); + assert(m_buffer); std::lock_guard state_guard(*m_state); int processed_no = 0; @@ -746,7 +925,8 @@ hailo_status VdmaChannel::trigger_channel_completion(uint16_t hw_num_processed) auto channel_error = m_host_registers.get_channel_error(); CHECK_EXPECTED_AS_STATUS(channel_error, "Fail to read vdma channel error register"); - CHECK(0 == channel_error.value(), HAILO_INTERNAL_FAILURE, "Vdma channel {} in error state {}", m_channel_index, channel_error.value()); + CHECK(0 == channel_error.value(), HAILO_INTERNAL_FAILURE, "Vdma channel {} in error state {}", m_channel_id, + channel_error.value()); uint16_t last_num_processed = static_cast(CB_TAIL(m_state->m_descs)); @@ -757,10 +937,10 @@ hailo_status VdmaChannel::trigger_channel_completion(uint16_t hw_num_processed) bool is_complete = is_desc_between(last_num_processed, hw_num_processed, last_desc_index) || (hw_num_processed == get_num_available()); #ifndef NDEBUG - auto status = (*m_descriptors_buffer)[last_desc_index].RemainingPageSize_Status & 0xFF; + auto status = (m_buffer->get_desc_list()->get())[last_desc_index].RemainingPageSize_Status & 0xFF; // Verify if a DMA Descriptor error occurred. if (status & 0x2) { - LOGGER__ERROR("Error while processing descriptor {} of DMA {} on board {}.", last_desc_index, m_channel_index, + LOGGER__ERROR("Error while processing descriptor {} of DMA {} on board {}.", last_desc_index, m_channel_id, m_driver.dev_path()); return HAILO_INTERNAL_FAILURE; } @@ -785,7 +965,10 @@ hailo_status VdmaChannel::trigger_channel_completion(uint16_t hw_num_processed) if (Direction::H2D == m_direction) { m_can_write_buffer_cv.notify_one(); + } else { + m_can_read_buffer_cv.notify_one(); } + callback(processed_no); } m_is_waiting_for_channel_completion = false; @@ -795,8 +978,9 @@ hailo_status VdmaChannel::trigger_channel_completion(uint16_t hw_num_processed) bool VdmaChannel::is_ready_for_transfer_h2d(size_t buffer_size) { assert(m_state); + assert(m_buffer); - size_t desired_desc_num = m_descriptors_buffer->descriptors_in_buffer(buffer_size); + size_t desired_desc_num = m_buffer->descriptors_in_buffer(buffer_size); assert(desired_desc_num <= MAX_DESCS_COUNT); int desc_num = static_cast(desired_desc_num); @@ -825,8 +1009,9 @@ bool VdmaChannel::is_ready_for_transfer_h2d(size_t buffer_size) bool VdmaChannel::is_ready_for_transfer_d2h(size_t buffer_size) { assert(m_state); + assert(m_buffer); - size_t desired_desc_num = m_descriptors_buffer->descriptors_in_buffer(buffer_size); + size_t desired_desc_num = m_buffer->descriptors_in_buffer(buffer_size); assert(desired_desc_num <= MAX_DESCS_COUNT); int desc_num = static_cast(desired_desc_num); @@ -841,7 +1026,6 @@ bool VdmaChannel::is_ready_for_transfer_d2h(size_t buffer_size) if (num_ready < desc_num) { return false; } - return true; } @@ -850,12 +1034,12 @@ hailo_status VdmaChannel::prepare_descriptors(size_t transfer_size, VdmaInterrup { MICROPROFILE_SCOPEI("vDMA Channel", "Trigger vDMA", 0); - assert(m_descriptors_buffer); + assert(m_buffer); assert(m_state); - auto &desc_info = *m_descriptors_buffer; + auto desc_info = m_buffer->get_desc_list(); /* calculate desired descriptors for the buffer */ - size_t desired_desc_num = m_descriptors_buffer->descriptors_in_buffer(transfer_size); + size_t desired_desc_num = m_buffer->descriptors_in_buffer(transfer_size); assert(desired_desc_num <= MAX_DESCS_COUNT); uint16_t desc_num = static_cast(desired_desc_num); @@ -866,10 +1050,10 @@ hailo_status VdmaChannel::prepare_descriptors(size_t transfer_size, VdmaInterrup return HAILO_OUT_OF_DESCRIPTORS; } - auto actual_desc_count = desc_info.program_descriptors(transfer_size, first_desc_interrupts_domain, + auto actual_desc_count = desc_info->get().program_descriptors(transfer_size, first_desc_interrupts_domain, last_desc_interrupts_domain, num_available, true); if (!actual_desc_count) { - LOGGER__ERROR("Failed to program desc_list for channel {}", m_channel_index); + LOGGER__ERROR("Failed to program desc_list for channel {}", m_channel_id); return actual_desc_count.status(); } assert (actual_desc_count.value() == desc_num); @@ -940,7 +1124,7 @@ hailo_status VdmaChannel::wait_for_condition(std::function condition, st return condition() ? HAILO_SUCCESS : HAILO_TIMEOUT; } -hailo_status VdmaChannel::wait_for_channel_completion(std::chrono::milliseconds timeout) +hailo_status VdmaChannel::wait_for_channel_completion(std::chrono::milliseconds timeout, const std::function &callback) { auto hw_num_processed = wait_interrupts(timeout); if ((hw_num_processed.status() == HAILO_TIMEOUT) || @@ -952,7 +1136,12 @@ hailo_status VdmaChannel::wait_for_channel_completion(std::chrono::milliseconds auto is_aborted_exp = is_aborted(); CHECK_EXPECTED_AS_STATUS(is_aborted_exp); if (is_aborted_exp.value()) { - LOGGER__CRITICAL("Channel {} was aborted by an external source!", m_channel_index); + std::unique_lock lock(m_is_active_flag_mutex); + if (!m_channel_is_active) { + return HAILO_STREAM_NOT_ACTIVATED; + } + + LOGGER__CRITICAL("Channel {} was aborted by an external source!", m_channel_id); return HAILO_STREAM_ABORTED; } } @@ -962,7 +1151,7 @@ hailo_status VdmaChannel::wait_for_channel_completion(std::chrono::milliseconds } CHECK_EXPECTED_AS_STATUS(hw_num_processed); - auto status = trigger_channel_completion(hw_num_processed.value()); + auto status = trigger_channel_completion(hw_num_processed.value(), callback); CHECK_SUCCESS(status); return HAILO_SUCCESS; @@ -972,7 +1161,7 @@ Expected VdmaChannel::wait_interrupts(std::chrono::milliseconds timeou { assert(m_state); - auto irq_data = m_driver.wait_channel_interrupts(m_channel_index, *m_channel_handle, timeout); + auto irq_data = m_driver.wait_channel_interrupts(m_channel_id, *m_channel_handle, timeout); if ((HAILO_STREAM_INTERNAL_ABORT == irq_data.status()) || (HAILO_STREAM_NOT_ACTIVATED == irq_data.status())) { LOGGER__INFO("Wait channel interrupts was aborted!"); diff --git a/hailort/libhailort/src/vdma_channel.hpp b/hailort/libhailort/src/vdma_channel.hpp index 0ceb03f..1388ae0 100644 --- a/hailort/libhailort/src/vdma_channel.hpp +++ b/hailort/libhailort/src/vdma_channel.hpp @@ -18,8 +18,9 @@ #include "vdma_channel_regs.hpp" #include "hailo/expected.hpp" #include "os/hailort_driver.hpp" -#include "vdma/mapped_buffer.hpp" +#include "vdma/sg_buffer.hpp" #include "vdma_descriptor_list.hpp" +#include "vdma/channel_id.hpp" #include "hailo/buffer.hpp" #include @@ -47,7 +48,7 @@ class VdmaChannel final public: using Direction = HailoRTDriver::DmaDirection; - static Expected create(uint8_t channel_index, Direction direction, HailoRTDriver &driver, + static Expected create(vdma::ChannelId channel_id, Direction direction, HailoRTDriver &driver, uint16_t requested_desc_page_size, uint32_t stream_index = 0, LatencyMeterPtr latency_meter = nullptr, uint16_t transfers_per_axi_intr = 1); ~VdmaChannel(); @@ -60,12 +61,12 @@ public: * @param[in] buffer_size * @param[in] timeout */ - hailo_status wait(size_t buffer_size, const std::chrono::milliseconds &timeout); + hailo_status wait(size_t buffer_size, std::chrono::milliseconds timeout); hailo_status transfer(void *buf, size_t count); hailo_status write_buffer(const MemoryView &buffer, std::chrono::milliseconds timeout); Expected send_pending_buffer(); - hailo_status trigger_channel_completion(uint16_t hw_num_processed); + hailo_status trigger_channel_completion(uint16_t hw_num_processed, const std::function &callback); hailo_status allocate_resources(uint32_t descs_count); /* For channels controlled by the HailoRT, the HailoRT needs to use this function to start the channel (it registers the channel to driver and starts the vDMA channel. Used for boundary channels */ @@ -77,22 +78,26 @@ public: hailo_status set_transfers_per_axi_intr(uint16_t transfers_per_axi_intr); hailo_status inc_num_available_for_ddr(uint16_t value, uint32_t size_mask); Expected get_hw_num_processed_ddr(uint32_t size_mask); - /*Used for DDR channels only. TODO - remove */ - hailo_status start_channel(VdmaDescriptorList &desc_list); /* For channels controlled by the FW (inter context and cfg channels), the hailort needs only to register the channel to the driver. The FW would be responsible to open and close the channel */ hailo_status register_channel_to_driver(uintptr_t desc_list_handle); hailo_status stop_channel(); uint16_t get_page_size(); + Expected get_boundary_buffer_info(uint32_t transfer_size); hailo_status abort(); hailo_status clear_abort(); - uint8_t get_channel_index() + vdma::ChannelId get_channel_id() const { - return m_channel_index; + return m_channel_id; } + size_t get_transfers_count_in_buffer(size_t transfer_size); + size_t get_buffer_size() const; + Expected get_h2d_pending_frames_count(); + Expected get_d2h_pending_descs_count(); + VdmaChannel(const VdmaChannel &other) = delete; VdmaChannel &operator=(const VdmaChannel &other) = delete; VdmaChannel(VdmaChannel &&other) noexcept; @@ -101,8 +106,7 @@ public: static uint32_t calculate_buffer_size(const HailoRTDriver &driver, uint32_t transfer_size, uint32_t transfers_count, uint16_t requested_desc_page_size); - - const uint8_t m_channel_index; + hailo_status register_for_d2h_interrupts(const std::function &callback); friend class PendingBufferState; @@ -134,9 +138,12 @@ private: // Contains the last num_processed of the last interrupt (only used on latency measurement) uint16_t m_last_timestamp_num_processed; size_t m_accumulated_transfers; + bool pending_reads; }; - VdmaChannel(uint8_t channel_index, Direction direction, HailoRTDriver &driver, uint32_t stream_index, + hailo_status unregister_for_d2h_interrupts(std::unique_lock &lock); + + VdmaChannel(vdma::ChannelId channel_id, Direction direction, HailoRTDriver &driver, uint32_t stream_index, LatencyMeterPtr latency_meter, uint16_t desc_page_size, uint16_t transfers_per_axi_intr, hailo_status &status); hailo_status allocate_buffer(const uint32_t buffer_size); @@ -155,14 +162,17 @@ private: bool is_ready_for_transfer_d2h(size_t buffer_size); hailo_status prepare_descriptors(size_t transfer_size, VdmaInterruptsDomain first_desc_interrupts_domain, VdmaInterruptsDomain last_desc_interrupts_domain); - hailo_status prepare_d2h_pending_descriptors(uint32_t descs_count, uint32_t transfer_size); + hailo_status prepare_d2h_pending_descriptors(uint32_t transfer_size); void reset_internal_counters(); - hailo_status wait_for_channel_completion(std::chrono::milliseconds timeout); + hailo_status wait_for_channel_completion(std::chrono::milliseconds timeout, const std::function &callback = [](uint32_t) { return; }); uint32_t calculate_descriptors_count(uint32_t buffer_size); hailo_status wait_for_condition(std::function condition, std::chrono::milliseconds timeout); + void wait_d2h_callback(const std::function &callback); + std::unique_ptr m_d2h_callback_thread; + /** * Returns the new hw num_processed of the irq */ @@ -174,7 +184,9 @@ private: Expected update_latency_meter(const ChannelInterruptTimestampList ×tamp_list); static bool is_desc_between(uint16_t begin, uint16_t end, uint16_t desc); Expected is_aborted(); + hailo_status start_channel(); + const vdma::ChannelId m_channel_id; Direction m_direction; HailoRTDriver &m_driver; VdmaChannelRegs m_host_registers; @@ -185,8 +197,7 @@ private: // TODO: remove the unique_ptr, instead allocate the buffer in the ctor (needs to move ddr channel to // other class) - std::unique_ptr m_mapped_user_buffer; - std::unique_ptr m_descriptors_buffer; + std::unique_ptr m_buffer; uint32_t m_stream_index; LatencyMeterPtr m_latency_meter; @@ -196,14 +207,17 @@ private: MmapBuffer m_channel_handle; bool m_channel_enabled; + bool m_channel_is_active; + std::mutex m_is_active_flag_mutex; uint16_t m_transfers_per_axi_intr; // Using CircularArray because it won't allocate or free memory wile pushing and poping. The fact that it is circural is not relevant here CircularArray m_pending_buffers_sizes; uint16_t m_pending_num_avail_offset; std::condition_variable_any m_can_write_buffer_cv; + std::condition_variable_any m_can_read_buffer_cv; std::atomic_bool m_is_waiting_for_channel_completion; - bool m_is_aborted; + std::atomic_bool m_is_aborted_by_internal_source; }; } /* namespace hailort */ diff --git a/hailort/libhailort/src/vdma_channel_regs.hpp b/hailort/libhailort/src/vdma_channel_regs.hpp index f035e2e..dc0bb7c 100644 --- a/hailort/libhailort/src/vdma_channel_regs.hpp +++ b/hailort/libhailort/src/vdma_channel_regs.hpp @@ -33,9 +33,10 @@ inline bool vdma_channel_control_is_paused(uint8_t control_reg) class VdmaChannelRegs final { public: - VdmaChannelRegs(HailoRTDriver &driver, uint32_t channel_index, HailoRTDriver::DmaDirection direction) : + VdmaChannelRegs(HailoRTDriver &driver, vdma::ChannelId channel_id, HailoRTDriver::DmaDirection direction) : m_driver(driver), - m_channel_offset(VDMA_CHANNEL_OFFSET(channel_index, (HailoRTDriver::DmaDirection::H2D == direction))) + m_channel_id(channel_id), + m_direction(direction) {} Expected get_control() @@ -86,7 +87,7 @@ private: template Expected read_integer(uint32_t offset) { - auto value = m_driver.read_vdma_channel_registers(m_channel_offset + offset, sizeof(IntegerType)); + auto value = m_driver.read_vdma_channel_register(m_channel_id, m_direction, offset, sizeof(IntegerType)); CHECK_EXPECTED(value); return static_cast(value.release()); } @@ -99,11 +100,12 @@ private: template hailo_status write_integer(uint32_t offset, IntegerType value) { - return m_driver.write_vdma_channel_registers(m_channel_offset + offset, sizeof(value), value); + return m_driver.write_vdma_channel_register(m_channel_id, m_direction, offset, sizeof(value), value); } HailoRTDriver &m_driver; - uint32_t m_channel_offset; + const vdma::ChannelId m_channel_id; + const HailoRTDriver::DmaDirection m_direction; }; } /* namespace hailort */ diff --git a/hailort/libhailort/src/vdma_descriptor_list.hpp b/hailort/libhailort/src/vdma_descriptor_list.hpp index 28cd03d..5958216 100644 --- a/hailort/libhailort/src/vdma_descriptor_list.hpp +++ b/hailort/libhailort/src/vdma_descriptor_list.hpp @@ -20,8 +20,6 @@ namespace hailort { -// HW doens't support more than 32 channels -#define MAX_HOST_CHANNELS_COUNT (32) #define MAX_DESCS_COUNT (64 * 1024u) #define MIN_DESCS_COUNT (2u) diff --git a/hailort/libhailort/src/vdma_device.cpp b/hailort/libhailort/src/vdma_device.cpp index 22d7374..f252d91 100644 --- a/hailort/libhailort/src/vdma_device.cpp +++ b/hailort/libhailort/src/vdma_device.cpp @@ -12,6 +12,8 @@ #include "vdma_device.hpp" #include "vdma_descriptor_list.hpp" #include "context_switch/multi_context/vdma_config_manager.hpp" +#include "pcie_device.hpp" +#include "core_device.hpp" #include @@ -22,11 +24,29 @@ namespace hailort VdmaDevice::VdmaDevice(HailoRTDriver &&driver, Device::Type type) : DeviceBase::DeviceBase(type), - m_driver(std::move(driver)), - m_context_switch_manager(nullptr) + m_driver(std::move(driver)) { } +Expected> VdmaDevice::create(const std::string &device_id) +{ + const bool DONT_LOG_ON_FAILURE = false; + if (CoreDevice::DEVICE_ID == device_id) { + auto device = CoreDevice::create(); + CHECK_EXPECTED(device);; + return std::unique_ptr(device.release()); + } + else if (auto pcie_info = PcieDevice::parse_pcie_device_info(device_id, DONT_LOG_ON_FAILURE)) { + auto device = PcieDevice::create(pcie_info.release()); + CHECK_EXPECTED(device);; + return std::unique_ptr(device.release()); + } + else { + LOGGER__ERROR("Invalid device id {}", device_id); + return make_unexpected(HAILO_INVALID_ARGUMENT); + } +} + hailo_status VdmaDevice::wait_for_wakeup() { return HAILO_SUCCESS; @@ -42,22 +62,6 @@ hailo_status VdmaDevice::disable_notifications() return m_driver.disable_notifications(); } -ExpectedRef VdmaDevice::get_config_manager() -{ - auto status = mark_as_used(); - CHECK_SUCCESS_AS_EXPECTED(status); - - if (!m_context_switch_manager) { - auto local_context_switch_manager = VdmaConfigManager::create(*this); - CHECK_EXPECTED(local_context_switch_manager); - - m_context_switch_manager = make_unique_nothrow(local_context_switch_manager.release()); - CHECK_AS_EXPECTED(nullptr != m_context_switch_manager, HAILO_OUT_OF_HOST_MEMORY); - } - - return std::ref(*m_context_switch_manager); -} - Expected VdmaDevice::read_log(MemoryView &buffer, hailo_cpu_id_t cpu_id) { size_t read_bytes = 0; diff --git a/hailort/libhailort/src/vdma_device.hpp b/hailort/libhailort/src/vdma_device.hpp index 0809828..c084f77 100644 --- a/hailort/libhailort/src/vdma_device.hpp +++ b/hailort/libhailort/src/vdma_device.hpp @@ -22,6 +22,8 @@ namespace hailort class VdmaDevice : public DeviceBase { public: + static Expected> create(const std::string &device_id); + virtual ~VdmaDevice() = default; virtual hailo_status wait_for_wakeup() override; @@ -41,10 +43,8 @@ protected: virtual Expected read_notification() override; virtual hailo_status disable_notifications() override; - virtual ExpectedRef get_config_manager() override; HailoRTDriver m_driver; - std::unique_ptr m_context_switch_manager; }; } /* namespace hailort */ diff --git a/hailort/libhailort/src/vdma_stream.cpp b/hailort/libhailort/src/vdma_stream.cpp index 60af946..72e1a2b 100644 --- a/hailort/libhailort/src/vdma_stream.cpp +++ b/hailort/libhailort/src/vdma_stream.cpp @@ -7,10 +7,38 @@ **/ #include "vdma_stream.hpp" +#include "pcie_stream.hpp" +#include "core_stream.hpp" namespace hailort { +Expected> VdmaInputStream::create(VdmaDevice &device, + std::shared_ptr channel, const LayerInfo &edge_layer, uint16_t batch_size, + EventPtr network_group_activated_event) +{ + switch (device.get_type()) { + case Device::Type::PCIE: + { + auto local_stream = PcieInputStream::create(device, channel, edge_layer, batch_size, + network_group_activated_event); + CHECK_EXPECTED(local_stream); + return std::unique_ptr(local_stream.release()); + } + case Device::Type::CORE: + { + auto local_stream = CoreInputStream::create(device, channel, edge_layer, batch_size, + network_group_activated_event); + CHECK_EXPECTED(local_stream); + return std::unique_ptr(local_stream.release()); + } + default: + assert(false); + LOGGER__ERROR("Invalid device type {}", static_cast(device.get_type())); + return make_unexpected(HAILO_INTERNAL_FAILURE); + } +} + VdmaInputStream::VdmaInputStream(VdmaDevice &device, std::shared_ptr channel, const LayerInfo &edge_layer, EventPtr network_group_activated_event, uint16_t batch_size, std::chrono::milliseconds transfer_timeout, @@ -101,10 +129,10 @@ hailo_status VdmaInputStream::deactivate_stream() /* Flush is best effort */ auto status = m_channel->flush(VDMA_FLUSH_TIMEOUT); if (HAILO_STREAM_INTERNAL_ABORT == status) { - LOGGER__INFO("Flush input_channel is not needed because channel was aborted. (channel {})", m_channel->get_channel_index()); + LOGGER__INFO("Flush input_channel is not needed because channel was aborted. (channel {})", m_channel->get_channel_id()); status = HAILO_SUCCESS; } else if (HAILO_SUCCESS != status) { - LOGGER__ERROR("Failed to flush input_channel. (status {} channel {})", status, m_channel->get_channel_index()); + LOGGER__ERROR("Failed to flush input_channel. (status {} channel {})", status, m_channel->get_channel_id()); } /* Close channel is best effort. */ @@ -123,12 +151,15 @@ Expected VdmaInputStream::sync_write_raw_buffer(const MemoryView &buffer hailo_status status = HAILO_UNINITIALIZED; status = m_channel->wait(buffer.size(), m_channel_timeout); - if ((HAILO_STREAM_INTERNAL_ABORT == status) || (HAILO_STREAM_NOT_ACTIVATED == status)) { + if ((status == HAILO_STREAM_INTERNAL_ABORT) || (status == HAILO_STREAM_NOT_ACTIVATED)) { return make_unexpected(status); } CHECK_SUCCESS_AS_EXPECTED(status); status = m_channel->transfer((void*)buffer.data(), buffer.size()); + if ((status == HAILO_STREAM_INTERNAL_ABORT) || (status == HAILO_STREAM_NOT_ACTIVATED)) { + return make_unexpected(status); + } CHECK_SUCCESS_AS_EXPECTED(status); return buffer.size(); @@ -136,11 +167,13 @@ Expected VdmaInputStream::sync_write_raw_buffer(const MemoryView &buffer hailo_status VdmaInputStream::write_buffer_only(const MemoryView &buffer) { + std::unique_lock lock(m_write_only_mutex); return m_channel->write_buffer(buffer, m_channel_timeout); } Expected VdmaInputStream::send_pending_buffer() { + std::unique_lock lock(m_send_pending_mutex); hailo_status status = m_channel->wait(get_frame_size(), m_channel_timeout); if ((HAILO_STREAM_INTERNAL_ABORT == status) || (HAILO_STREAM_NOT_ACTIVATED == status)) { return make_unexpected(status); @@ -160,6 +193,16 @@ const char* VdmaInputStream::get_dev_id() const return m_device->get_dev_id(); } +Expected VdmaInputStream::get_buffer_frames_size() const +{ + return m_channel->get_transfers_count_in_buffer(m_stream_info.hw_frame_size); +} + +Expected VdmaInputStream::get_pending_frames_count() const +{ + return m_channel->get_h2d_pending_frames_count(); +} + hailo_status VdmaInputStream::sync_write_all_raw_buffer_no_transform_impl(void *buffer, size_t offset, size_t size) { ASSERT(NULL != buffer); @@ -188,6 +231,32 @@ hailo_status VdmaInputStream::set_dynamic_batch_size(uint16_t dynamic_batch_size } /** Output stream **/ +Expected> VdmaOutputStream::create(VdmaDevice &device, + std::shared_ptr channel, const LayerInfo &edge_layer, uint16_t batch_size, + EventPtr network_group_activated_event) +{ + switch (device.get_type()) { + case Device::Type::PCIE: + { + auto local_stream = PcieOutputStream::create(device, channel, edge_layer, batch_size, + network_group_activated_event); + CHECK_EXPECTED(local_stream); + return std::unique_ptr(local_stream.release()); + } + case Device::Type::CORE: + { + auto local_stream = CoreOutputStream::create(device, channel, edge_layer, batch_size, + network_group_activated_event); + CHECK_EXPECTED(local_stream); + return std::unique_ptr(local_stream.release()); + } + default: + assert(false); + LOGGER__ERROR("Invalid device type {}", static_cast(device.get_type())); + return make_unexpected(HAILO_INTERNAL_FAILURE); + } +} + VdmaOutputStream::VdmaOutputStream(VdmaDevice &device, std::shared_ptr channel, const LayerInfo &edge_layer, EventPtr network_group_activated_event, uint16_t batch_size, std::chrono::milliseconds transfer_timeout, hailo_status &status) : @@ -277,6 +346,11 @@ hailo_status VdmaOutputStream::activate_stream(uint16_t dynamic_batch_size) return HAILO_SUCCESS; } +hailo_status VdmaOutputStream::register_for_d2h_interrupts(const std::function &callback) +{ + return m_channel->register_for_d2h_interrupts(callback); +} + hailo_status VdmaOutputStream::deactivate_stream() { if (!is_stream_activated) { @@ -295,12 +369,15 @@ Expected VdmaOutputStream::sync_read_raw_buffer(MemoryView &buffer) hailo_status status = HAILO_UNINITIALIZED; status = m_channel->wait(buffer.size(), m_transfer_timeout); - if ((HAILO_STREAM_INTERNAL_ABORT == status) || (HAILO_STREAM_NOT_ACTIVATED == status)) { + if ((status == HAILO_STREAM_INTERNAL_ABORT) || (status == HAILO_STREAM_NOT_ACTIVATED)) { return make_unexpected(status); } CHECK_SUCCESS_AS_EXPECTED(status); status = m_channel->transfer(buffer.data(), buffer.size()); + if ((status == HAILO_STREAM_NOT_ACTIVATED) || (status == HAILO_STREAM_INTERNAL_ABORT)) { + return make_unexpected(status); + } CHECK_SUCCESS_AS_EXPECTED(status); return buffer.size(); @@ -308,6 +385,7 @@ Expected VdmaOutputStream::sync_read_raw_buffer(MemoryView &buffer) hailo_status VdmaOutputStream::read_all(MemoryView &buffer) { + std::unique_lock lock(m_read_mutex); CHECK((buffer.size() % HailoRTCommon::HW_DATA_ALIGNMENT) == 0, HAILO_INVALID_ARGUMENT, "Size must be aligned to {} (got {})", HailoRTCommon::HW_DATA_ALIGNMENT, buffer.size()); @@ -341,4 +419,37 @@ hailo_status VdmaOutputStream::set_dynamic_batch_size(uint16_t dynamic_batch_siz return HAILO_SUCCESS; } +Expected VdmaOutputStream::get_buffer_frames_size() const +{ + if (HAILO_FORMAT_ORDER_HAILO_NMS == m_stream_info.format.order) { + // In NMS, each output frame has different size depending on the number of bboxes found for each class + // and m_stream_info.hw_frame_size is the max frame size. To know the actual frame size and + // calculate the number of frames we need to read the content of the buffer (and finding the delimiter for each class in each frame). + LOGGER__INFO("NMS is not supported in function get_buffer_frames_size()"); + return make_unexpected(HAILO_NOT_AVAILABLE); + } + + return m_channel->get_transfers_count_in_buffer(m_stream_info.hw_frame_size); +} + +Expected VdmaOutputStream::get_pending_frames_count() const +{ + if (HAILO_FORMAT_ORDER_HAILO_NMS == m_stream_info.format.order) { + // In NMS, each output frame has different size depending on the number of bboxes found for each class + // and m_stream_info.hw_frame_size is the max frame size. To know the actual frame size and + // calculate the number of frames we need to read the content of the buffer (and finding the delimiter for each class in each frame). + LOGGER__INFO("NMS is not supported in function get_pending_frames_count()"); + return make_unexpected(HAILO_NOT_AVAILABLE); + } + + auto pending_descs_count = m_channel->get_d2h_pending_descs_count(); + CHECK_EXPECTED(pending_descs_count); + + auto channel_page_size = m_channel->get_page_size(); + uint32_t descs_per_frame = (0 == (m_stream_info.hw_frame_size % channel_page_size)) ? (m_stream_info.hw_frame_size / channel_page_size) : + ((m_stream_info.hw_frame_size / channel_page_size) + 1); + + return static_cast(std::floor(pending_descs_count.value() / descs_per_frame)); +} + } /* namespace hailort */ diff --git a/hailort/libhailort/src/vdma_stream.hpp b/hailort/libhailort/src/vdma_stream.hpp index 8eb9faf..8e4664b 100644 --- a/hailort/libhailort/src/vdma_stream.hpp +++ b/hailort/libhailort/src/vdma_stream.hpp @@ -22,6 +22,10 @@ constexpr std::chrono::seconds VDMA_FLUSH_TIMEOUT(10); class VdmaInputStream : public InputStreamBase { public: + static Expected> create(VdmaDevice &device, + std::shared_ptr channel, const LayerInfo &edge_layer, uint16_t batch_size, + EventPtr network_group_activated_event); + VdmaInputStream(VdmaInputStream &&other); virtual ~VdmaInputStream(); @@ -34,6 +38,8 @@ public: Expected send_pending_buffer(); uint16_t get_dynamic_batch_size() const; const char* get_dev_id() const; + virtual Expected get_buffer_frames_size() const override; + virtual Expected get_pending_frames_count() const override; protected: VdmaInputStream(VdmaDevice &device, std::shared_ptr channel, const LayerInfo &edge_layer, @@ -56,10 +62,18 @@ private: std::chrono::milliseconds m_channel_timeout; const uint16_t m_max_batch_size; uint16_t m_dynamic_batch_size; + std::mutex m_write_only_mutex; + std::mutex m_send_pending_mutex; + + friend class VDeviceInputStream; }; class VdmaOutputStream : public OutputStreamBase { public: + static Expected> create(VdmaDevice &device, + std::shared_ptr channel, const LayerInfo &edge_layer, uint16_t batch_size, + EventPtr network_group_activated_event); + VdmaOutputStream(VdmaOutputStream &&other); virtual ~VdmaOutputStream(); @@ -69,6 +83,10 @@ public: virtual hailo_status clear_abort() override; uint16_t get_dynamic_batch_size() const; const char* get_dev_id() const; + virtual Expected get_buffer_frames_size() const override; + virtual Expected get_pending_frames_count() const override; + + virtual hailo_status register_for_d2h_interrupts(const std::function &callback); protected: VdmaOutputStream(VdmaDevice &device, std::shared_ptr channel, const LayerInfo &edge_layer, @@ -92,6 +110,9 @@ private: const uint16_t m_max_batch_size; uint16_t m_dynamic_batch_size; const uint32_t m_transfer_size; + std::mutex m_read_mutex; + + friend class VDeviceOutputStream; }; diff --git a/hailort/libhailort/src/vstream.cpp b/hailort/libhailort/src/vstream.cpp index ffd14e5..4b6859b 100644 --- a/hailort/libhailort/src/vstream.cpp +++ b/hailort/libhailort/src/vstream.cpp @@ -4,7 +4,7 @@ **/ /** * @file vstream.cpp - * @brief Implemention of the virtual stream + * @brief Implementation of the virtual stream **/ #include "hailo/vstream.hpp" @@ -12,6 +12,10 @@ #include "vstream_internal.hpp" #include "common/runtime_statistics_internal.hpp" +#ifdef HAILO_SUPPORT_MULTI_PROCESS +#include "rpc/rpc_definitions.hpp" +#endif // HAILO_SUPPORT_MULTI_PROCESS + #include namespace hailort @@ -357,6 +361,7 @@ BaseVStream::BaseVStream(const hailo_vstream_info_t &vstream_info, const hailo_v m_entry_element(pipeline_entry), m_pipeline(std::move(pipeline)), m_is_activated(false), + m_is_aborted(false), m_pipeline_status(std::move(pipeline_status)), m_shutdown_event(shutdown_event), m_network_group_activated_event(std::move(network_group_activated_event)), @@ -375,6 +380,7 @@ BaseVStream::BaseVStream(BaseVStream &&other) noexcept : m_entry_element(std::move(other.m_entry_element)), m_pipeline(std::move(other.m_pipeline)), m_is_activated(std::exchange(other.m_is_activated, false)), + m_is_aborted(std::exchange(other.m_is_aborted, false)), m_pipeline_status(std::move(other.m_pipeline_status)), m_shutdown_event(std::move(other.m_shutdown_event)), m_network_group_activated_event(std::move(other.m_network_group_activated_event)), @@ -395,6 +401,7 @@ BaseVStream& BaseVStream::operator=(BaseVStream &&other) noexcept m_entry_element = std::move(other.m_entry_element); m_pipeline = std::move(other.m_pipeline); m_is_activated = std::exchange(other.m_is_activated, false); + m_is_aborted = std::exchange(other.m_is_aborted, false); m_pipeline_status = std::move(other.m_pipeline_status); m_shutdown_event = std::move(other.m_shutdown_event); m_network_group_activated_event = std::move(other.m_network_group_activated_event); @@ -406,11 +413,6 @@ BaseVStream& BaseVStream::operator=(BaseVStream &&other) noexcept return *this; } -BaseVStream::~BaseVStream() -{ - (void)stop_vstream(); -} - hailo_status BaseVStream::start_vstream() { auto status = m_shutdown_event->reset(); @@ -420,10 +422,26 @@ hailo_status BaseVStream::start_vstream() status = m_entry_element->activate(); CHECK_SUCCESS(status); + status = resume(); + CHECK(((status == HAILO_SUCCESS) || (status == HAILO_STREAM_NOT_ACTIVATED)), status, + "Failed to resume stream in {}", name()); + m_is_activated = true; return HAILO_SUCCESS; } +hailo_status BaseVStream::abort() +{ + m_is_aborted = true; + return m_entry_element->abort(); +} + +hailo_status BaseVStream::resume() +{ + m_is_aborted = false; + return m_entry_element->resume(); +} + hailo_status BaseVStream::stop_vstream() { hailo_status status = HAILO_SUCCESS; @@ -511,6 +529,258 @@ const std::vector> &BaseVStream::get_pipeline() return m_pipeline; } +Expected InputVStream::create(const hailo_vstream_info_t &vstream_info, + const hailo_vstream_params_t &vstream_params, std::shared_ptr pipeline_entry, + std::shared_ptr pipeline_exit, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, EventPtr network_group_activated_event, + AccumulatorPtr pipeline_latency_accumulator) +{ + auto vstream_internal = InputVStreamInternal::create(vstream_info, vstream_params, pipeline_entry, pipeline_exit, + std::move(pipeline), std::move(pipeline_status), shutdown_event, network_group_activated_event, pipeline_latency_accumulator); + CHECK_EXPECTED(vstream_internal); + + InputVStream vstream(vstream_internal.release()); + return vstream; +} + +hailo_status InputVStream::write(const MemoryView &buffer) +{ + return m_vstream->write(std::move(buffer)); +} + +hailo_status InputVStream::flush() +{ + return m_vstream->flush(); +} + +hailo_status InputVStream::clear(std::vector &vstreams) +{ + for (auto &vstream : vstreams) { + auto status = vstream.stop_and_clear(); + CHECK_SUCCESS(status); + } + for (auto &vstream : vstreams) { + auto status = vstream.start_vstream(); + CHECK_SUCCESS(status); + } + + return HAILO_SUCCESS; +} + +hailo_status InputVStream::clear(std::vector> &vstreams) +{ + for (auto &vstream : vstreams) { + auto status = vstream.get().stop_and_clear(); + CHECK_SUCCESS(status); + } + for (auto &vstream : vstreams) { + auto status = vstream.get().start_vstream(); + CHECK_SUCCESS(status); + } + + return HAILO_SUCCESS; +} + +hailo_status InputVStream::abort() +{ + return m_vstream->abort(); +} + +size_t InputVStream::get_frame_size() const +{ + return m_vstream->get_frame_size(); +} + +const hailo_vstream_info_t &InputVStream::get_info() const +{ + return m_vstream->get_info(); +} + +const hailo_format_t &InputVStream::get_user_buffer_format() const +{ + return m_vstream->get_user_buffer_format(); +} + +std::string InputVStream::name() const +{ + return m_vstream->name(); +} + +std::string InputVStream::network_name() const +{ + return m_vstream->network_name(); +} + +const std::map &InputVStream::get_fps_accumulators() const +{ + return m_vstream->get_fps_accumulators(); +} + +const std::map &InputVStream::get_latency_accumulators() const +{ + return m_vstream->get_latency_accumulators(); +} + +const std::map> &InputVStream::get_queue_size_accumulators() const +{ + return m_vstream->get_queue_size_accumulators(); +} + +AccumulatorPtr InputVStream::get_pipeline_latency_accumulator() const +{ + return m_vstream->get_pipeline_latency_accumulator(); +} + +const std::vector> &InputVStream::get_pipeline() const +{ + return m_vstream->get_pipeline(); +} + +hailo_status InputVStream::start_vstream() +{ + return m_vstream->start_vstream(); +} + +hailo_status InputVStream::stop_vstream() +{ + return m_vstream->stop_vstream(); +} + +hailo_status InputVStream::stop_and_clear() +{ + return m_vstream->stop_and_clear(); +} + +std::string InputVStream::get_pipeline_description() const +{ + return m_vstream->get_pipeline_description(); +} + +InputVStream::InputVStream(std::shared_ptr vstream) : m_vstream(std::move(vstream)) {} + +Expected OutputVStream::create( + const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, + std::shared_ptr pipeline_entry, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, + EventPtr network_group_activated_event, AccumulatorPtr pipeline_latency_accumulator) +{ + auto vstream_internal = OutputVStreamInternal::create(vstream_info, vstream_params, pipeline_entry, + std::move(pipeline), std::move(pipeline_status), shutdown_event, network_group_activated_event, pipeline_latency_accumulator); + CHECK_EXPECTED(vstream_internal); + + OutputVStream vstream(vstream_internal.release()); + return vstream; +} + +hailo_status OutputVStream::read(MemoryView buffer) +{ + return m_vstream->read(std::move(buffer)); +} + +hailo_status OutputVStream::clear(std::vector &vstreams) +{ + for (auto &vstream : vstreams) { + auto status = vstream.stop_and_clear(); + CHECK_SUCCESS(status); + } + for (auto &vstream : vstreams) { + auto status = vstream.start_vstream(); + CHECK_SUCCESS(status); + } + + return HAILO_SUCCESS; +} + +hailo_status OutputVStream::abort() +{ + return m_vstream->abort(); +} + +hailo_status OutputVStream::clear(std::vector> &vstreams) +{ + for (auto &vstream : vstreams) { + auto status = vstream.get().stop_and_clear(); + CHECK_SUCCESS(status); + } + for (auto &vstream : vstreams) { + auto status = vstream.get().start_vstream(); + CHECK_SUCCESS(status); + } + + return HAILO_SUCCESS; +} + +size_t OutputVStream::get_frame_size() const +{ + return m_vstream->get_frame_size(); +} + +const hailo_vstream_info_t &OutputVStream::get_info() const +{ + return m_vstream->get_info(); +} + +const hailo_format_t &OutputVStream::get_user_buffer_format() const +{ + return m_vstream->get_user_buffer_format(); +} + +std::string OutputVStream::name() const +{ + return m_vstream->name(); +} + +std::string OutputVStream::network_name() const +{ + return m_vstream->network_name(); +} + +const std::map &OutputVStream::get_fps_accumulators() const +{ + return m_vstream->get_fps_accumulators(); +} + +const std::map &OutputVStream::get_latency_accumulators() const +{ + return m_vstream->get_latency_accumulators(); +} + +const std::map> &OutputVStream::get_queue_size_accumulators() const +{ + return m_vstream->get_queue_size_accumulators(); +} + +AccumulatorPtr OutputVStream::get_pipeline_latency_accumulator() const +{ + return m_vstream->get_pipeline_latency_accumulator(); +} + +const std::vector> &OutputVStream::get_pipeline() const +{ + return m_vstream->get_pipeline(); +} + +hailo_status OutputVStream::start_vstream() +{ + return m_vstream->start_vstream(); +} + +hailo_status OutputVStream::stop_vstream() +{ + return m_vstream->stop_vstream(); +} + +hailo_status OutputVStream::stop_and_clear() +{ + return m_vstream->stop_and_clear(); +} + +std::string OutputVStream::get_pipeline_description() const +{ + return m_vstream->get_pipeline_description(); +} + +OutputVStream::OutputVStream(std::shared_ptr vstream) : m_vstream(std::move(vstream)) {} std::map get_pipeline_accumulators_by_type( const std::vector> &pipeline, AccumulatorType accumulator_type) @@ -556,7 +826,28 @@ std::map> get_pipeline_queue_size_accum return result; } -Expected InputVStream::create(const hailo_vstream_info_t &vstream_info, +Expected> InputVStreamInternal::create(const hailo_vstream_info_t &vstream_info, + const hailo_vstream_params_t &vstream_params, std::shared_ptr pipeline_entry, + std::shared_ptr pipeline_exit, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, EventPtr network_group_activated_event, + AccumulatorPtr pipeline_latency_accumulator) +{ + auto vstream = InputVStreamImpl::create(vstream_info, vstream_params, pipeline_entry, pipeline_exit, + std::move(pipeline), std::move(pipeline_status), shutdown_event, network_group_activated_event, pipeline_latency_accumulator); + CHECK_EXPECTED(vstream); + auto vstream_ptr = std::shared_ptr(vstream.release()); + return vstream_ptr; +} + +InputVStreamInternal::InputVStreamInternal(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, + std::shared_ptr pipeline_entry, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, + EventPtr shutdown_event, AccumulatorPtr pipeline_latency_accumulator, EventPtr &&network_group_activated_event, + hailo_status &output_status) : + BaseVStream(vstream_info, vstream_params, pipeline_entry, std::move(pipeline), std::move(pipeline_status), + shutdown_event, pipeline_latency_accumulator, std::move(network_group_activated_event), output_status){} + +Expected> InputVStreamImpl::create(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, std::shared_ptr pipeline_entry, std::shared_ptr pipeline_exit, std::vector> &&pipeline, std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, EventPtr network_group_activated_event, @@ -572,21 +863,19 @@ Expected InputVStream::create(const hailo_vstream_info_t &vstream_ }); } - InputVStream vstream(vstream_info, vstream_params, std::move(pipeline_entry), std::move(pipeline), - std::move(pipeline_status), shutdown_event, pipeline_latency_accumulator, std::move(network_group_activated_event), status); + auto vstream_ptr = std::shared_ptr(new InputVStreamImpl(vstream_info, vstream_params, std::move(pipeline_entry), std::move(pipeline), + std::move(pipeline_status), shutdown_event, pipeline_latency_accumulator, std::move(network_group_activated_event), status)); CHECK_SUCCESS_AS_EXPECTED(status, "Failed to create virtual stream"); - return vstream; + return vstream_ptr; } -InputVStream::InputVStream(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, - std::shared_ptr pipeline_entry, - std::vector> &&pipeline, - std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, - AccumulatorPtr pipeline_latency_accumulator, - EventPtr network_group_activated_event, hailo_status &output_status) : - BaseVStream(vstream_info, vstream_params, pipeline_entry, std::move(pipeline), std::move(pipeline_status), - shutdown_event, pipeline_latency_accumulator, std::move(network_group_activated_event), output_status) +InputVStreamImpl::InputVStreamImpl(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, + std::shared_ptr pipeline_entry, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, AccumulatorPtr pipeline_latency_accumulator, + EventPtr network_group_activated_event, hailo_status &output_status) : + InputVStreamInternal(vstream_info, vstream_params, pipeline_entry, std::move(pipeline), std::move(pipeline_status), + shutdown_event, pipeline_latency_accumulator, std::move(network_group_activated_event), output_status) { if (HAILO_SUCCESS != output_status) { return; @@ -594,7 +883,17 @@ InputVStream::InputVStream(const hailo_vstream_info_t &vstream_info, const hailo LOGGER__INFO("Creating {}...", name()); } -hailo_status InputVStream::write(const MemoryView &buffer) +InputVStreamImpl::~InputVStreamImpl() +{ + (void)stop_vstream(); + if (m_is_aborted) { + // If VStream was aborted, do not clear low-level stream abortion, + // otherwise flush would be called on low-level stream d-tor when there is no receiver. + (void)abort(); + } +} + +hailo_status InputVStreamImpl::write(const MemoryView &buffer) { if (nullptr != m_network_group_activated_event) { CHECK(m_is_activated, HAILO_VSTREAM_PIPELINE_NOT_ACTIVATED, "Failed to write buffer! Virtual stream {} is not activated!", name()); @@ -615,46 +914,150 @@ hailo_status InputVStream::write(const MemoryView &buffer) return status; } -hailo_status InputVStream::flush() +hailo_status InputVStreamImpl::flush() { auto status = m_entry_element->run_push(PipelineBuffer(PipelineBuffer::Type::FLUSH)); CHECK_SUCCESS(status); - + status = m_entry_element->flush(); CHECK_SUCCESS(status); return HAILO_SUCCESS; } -hailo_status InputVStream::clear(std::vector &vstreams) +#ifdef HAILO_SUPPORT_MULTI_PROCESS +// TODO: HRT-6606 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wreturn-type" +Expected> InputVStreamClient::create(uint32_t input_vstream_handle) { - for (auto &vstream : vstreams) { - auto status = vstream.stop_and_clear(); - CHECK_SUCCESS(status); - } - for (auto &vstream : vstreams) { - auto status = vstream.start_vstream(); - CHECK_SUCCESS(status); + grpc::ChannelArguments ch_args; + ch_args.SetMaxReceiveMessageSize(-1); + auto channel = grpc::CreateCustomChannel(HAILO_DEFAULT_UDS_ADDR, grpc::InsecureChannelCredentials(), ch_args); + CHECK_AS_EXPECTED(channel != nullptr, HAILO_INTERNAL_FAILURE); + auto client = std::unique_ptr(new HailoRtRpcClient(channel)); + CHECK_AS_EXPECTED(client != nullptr, HAILO_OUT_OF_HOST_MEMORY); + return std::shared_ptr(new InputVStreamClient(std::move(client), std::move(input_vstream_handle))); +} + +// TODO: HRT-6606 +InputVStreamClient::InputVStreamClient(std::unique_ptr client, uint32_t input_vstream_handle) + : m_client(std::move(client)), m_handle(std::move(input_vstream_handle)) {} + +InputVStreamClient::~InputVStreamClient() +{ + auto reply = m_client->InputVStream_release(m_handle); + if (reply != HAILO_SUCCESS) { + LOGGER__CRITICAL("InputVStream_release failed!"); } +} - return HAILO_SUCCESS; +hailo_status InputVStreamClient::write(const MemoryView &buffer) +{ + return m_client->InputVStream_write(m_handle, buffer); } -hailo_status InputVStream::clear(std::vector> &vstreams) +hailo_status InputVStreamClient::flush() { - for (auto &vstream : vstreams) { - auto status = vstream.get().stop_and_clear(); - CHECK_SUCCESS(status); + return m_client->InputVStream_flush(m_handle); +} + +hailo_status InputVStreamClient::abort() +{ + auto channel = grpc::CreateChannel(HAILO_DEFAULT_UDS_ADDR, grpc::InsecureChannelCredentials()); + CHECK(channel != nullptr, HAILO_INTERNAL_FAILURE); + auto abort_client = std::unique_ptr(new HailoRtRpcClient(channel)); + CHECK(abort_client != nullptr, HAILO_OUT_OF_HOST_MEMORY); + return abort_client->InputVStream_abort(m_handle); +} + +hailo_status InputVStreamClient::resume() +{ + return m_client->InputVStream_resume(m_handle); +} + +size_t InputVStreamClient::get_frame_size() const +{ + auto frame_size = m_client->InputVStream_get_frame_size(m_handle); + if (!frame_size) { + LOGGER__CRITICAL("InputVStream_get_frame_size failed with status={}", frame_size.status()); + return 0; } - for (auto &vstream : vstreams) { - auto status = vstream.get().start_vstream(); - CHECK_SUCCESS(status); + return frame_size.release(); +} + +const hailo_vstream_info_t &InputVStreamClient::get_info() const +{ + // TODO: HRT-6606 + assert(false); +} + +const hailo_format_t &InputVStreamClient::get_user_buffer_format() const +{ + // TODO: HRT-6606 + assert(false); +} + +std::string InputVStreamClient::name() const +{ + auto expected_name = m_client->InputVStream_name(m_handle); + if (!expected_name) { + LOGGER__CRITICAL("InputVStream_name failed with status={}", expected_name.status()); + return ""; } + return expected_name.release(); +} - return HAILO_SUCCESS; +std::string InputVStreamClient::network_name() const +{ + // TODO: HRT-6606 + assert(false); } -std::string InputVStream::get_pipeline_description() const +const std::map &InputVStreamClient::get_fps_accumulators() const +{ + // TODO: HRT-6606 + assert(false); +} +const std::map &InputVStreamClient::get_latency_accumulators() const +{ + // TODO: HRT-6606 + assert(false); +} + +const std::map> &InputVStreamClient::get_queue_size_accumulators() const +{ + // TODO: HRT-6606 + assert(false); +} +AccumulatorPtr InputVStreamClient::get_pipeline_latency_accumulator() const +{ + // TODO: HRT-6606 + assert(false); +} +const std::vector> &InputVStreamClient::get_pipeline() const +{ + // TODO: HRT-6606 + assert(false); +} + +hailo_status InputVStreamClient::start_vstream() +{ + return HAILO_NOT_IMPLEMENTED; +} +hailo_status InputVStreamClient::stop_vstream() +{ + return HAILO_NOT_IMPLEMENTED; +} +hailo_status InputVStreamClient::stop_and_clear() +{ + return HAILO_NOT_IMPLEMENTED; +} + +#pragma GCC diagnostic pop +#endif // HAILO_SUPPORT_MULTI_PROCESS + +std::string InputVStreamInternal::get_pipeline_description() const { std::stringstream pipeline_str; pipeline_str << "Input pipeline '" << name() << "': "; @@ -665,48 +1068,103 @@ std::string InputVStream::get_pipeline_description() const return pipeline_str.str(); } -Expected OutputVStream::create(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, +Expected> OutputVStreamInternal::create( + const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, + std::shared_ptr pipeline_entry, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, + EventPtr network_group_activated_event, AccumulatorPtr pipeline_latency_accumulator) +{ + auto vstream = OutputVStreamImpl::create(vstream_info, vstream_params, pipeline_entry, + std::move(pipeline), std::move(pipeline_status), shutdown_event, network_group_activated_event, pipeline_latency_accumulator); + CHECK_EXPECTED(vstream); + auto vstream_ptr = std::shared_ptr(vstream.release()); + return vstream_ptr; +} + +OutputVStreamInternal::OutputVStreamInternal(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, + std::shared_ptr pipeline_entry, + std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, + AccumulatorPtr pipeline_latency_accumulator, + EventPtr network_group_activated_event, hailo_status &output_status) : + BaseVStream(vstream_info, vstream_params, pipeline_entry, std::move(pipeline), std::move(pipeline_status), + shutdown_event, pipeline_latency_accumulator, std::move(network_group_activated_event), output_status){} + +Expected> OutputVStreamImpl::create(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, std::shared_ptr pipeline_entry, std::vector> &&pipeline, std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, EventPtr network_group_activated_event, AccumulatorPtr pipeline_latency_accumulator) { hailo_status status = HAILO_UNINITIALIZED; - CHECK_AS_EXPECTED(1 == pipeline_entry->sources().size(), HAILO_INVALID_ARGUMENT, + CHECK_AS_EXPECTED(1 == pipeline_entry->sources().size(), HAILO_INVALID_ARGUMENT, "OutputVStream's entry element is expected to have one source"); if (nullptr != pipeline_latency_accumulator) { - pipeline_entry->sources()[0].set_pull_complete_callback([pipeline_latency_accumulator](const PipelineBuffer::Metadata& metadata) { + pipeline_entry->sources()[0].set_pull_complete_callback([pipeline_latency_accumulator](const PipelineBuffer::Metadata& metadata) { const auto duration_sec = std::chrono::duration_cast>( std::chrono::steady_clock::now() - metadata.get_start_time()).count(); pipeline_latency_accumulator->add_data_point(duration_sec); }); } - OutputVStream vstream(vstream_info, vstream_params, std::move(pipeline_entry), std::move(pipeline), - std::move(pipeline_status), shutdown_event, pipeline_latency_accumulator, std::move(network_group_activated_event), status); + auto vstream_ptr = std::shared_ptr(new OutputVStreamImpl(vstream_info, vstream_params, std::move(pipeline_entry), std::move(pipeline), + std::move(pipeline_status), shutdown_event, pipeline_latency_accumulator, std::move(network_group_activated_event), status)); CHECK_SUCCESS_AS_EXPECTED(status, "Failed to create virtual stream"); - return vstream; + return vstream_ptr; } -OutputVStream::OutputVStream(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, - std::shared_ptr pipeline_entry, - std::vector> &&pipeline, - std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, - AccumulatorPtr pipeline_latency_accumulator, - EventPtr network_group_activated_event, hailo_status &output_status) : - BaseVStream(vstream_info, vstream_params, pipeline_entry, std::move(pipeline), std::move(pipeline_status), +std::string OutputVStreamInternal::get_pipeline_description() const +{ + std::stringstream pipeline_str; + pipeline_str << "Output pipeline '" << name() << "': HW"; + for (const auto &element : m_pipeline) { + pipeline_str << " >> " << element->description(); + } + return pipeline_str.str(); +} + +OutputVStreamImpl::OutputVStreamImpl(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, + std::shared_ptr pipeline_entry, + std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, + AccumulatorPtr pipeline_latency_accumulator, + EventPtr network_group_activated_event, hailo_status &output_status) : + OutputVStreamInternal(vstream_info, vstream_params, pipeline_entry, std::move(pipeline), std::move(pipeline_status), shutdown_event, pipeline_latency_accumulator, std::move(network_group_activated_event), output_status) { if (HAILO_SUCCESS != output_status) { return; } + for (auto &element : m_pipeline) { + element->set_on_cant_pull_callback([this] () { + if (m_cant_read_callback) { + m_cant_read_callback(); + } + }); + element->set_on_can_pull_callback([this] () { + if (m_can_read_callback) { + m_can_read_callback(); + } + }); + } + LOGGER__INFO("Creating {}...", name()); } -hailo_status OutputVStream::read(MemoryView buffer) +OutputVStreamImpl::~OutputVStreamImpl() +{ + (void)stop_vstream(); + if (m_is_aborted) { + // If VStream was aborted, do not clear low-level stream abortion, + // otherwise flush would be called on low-level stream d-tor when there is no receiver. + (void)abort(); + } +} + +hailo_status OutputVStreamImpl::read(MemoryView buffer) { if (nullptr != m_network_group_activated_event) { CHECK(m_is_activated, HAILO_VSTREAM_PIPELINE_NOT_ACTIVATED, "read() failed! Virtual stream {} is not activated!", name()); @@ -727,51 +1185,137 @@ hailo_status OutputVStream::read(MemoryView buffer) } if (HAILO_STREAM_INTERNAL_ABORT == status) { LOGGER__INFO("Receiving to VStream was aborted!"); + m_entry_element->wait_for_finish(); return HAILO_STREAM_INTERNAL_ABORT; } return status; } -hailo_status OutputVStream::clear(std::vector &vstreams) +#ifdef HAILO_SUPPORT_MULTI_PROCESS +// TODO: HRT-6606 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wreturn-type" +Expected> OutputVStreamClient::create(uint32_t outputs_vstream_handle) { - for (auto &vstream : vstreams) { - auto status = vstream.stop_and_clear(); - CHECK_SUCCESS(status); - } + grpc::ChannelArguments ch_args; + ch_args.SetMaxReceiveMessageSize(-1); + auto channel = grpc::CreateCustomChannel(HAILO_DEFAULT_UDS_ADDR, grpc::InsecureChannelCredentials(), ch_args); + CHECK_AS_EXPECTED(channel != nullptr, HAILO_INTERNAL_FAILURE); + auto client = std::unique_ptr(new HailoRtRpcClient(channel)); + CHECK_AS_EXPECTED(client != nullptr, HAILO_OUT_OF_HOST_MEMORY); + return std::shared_ptr(new OutputVStreamClient(std::move(client), std::move(outputs_vstream_handle))); +} - for (auto &vstream : vstreams) { - auto status = vstream.start_vstream(); - CHECK_SUCCESS(status); +OutputVStreamClient::OutputVStreamClient(std::unique_ptr client, uint32_t outputs_vstream_handle) + : m_client(std::move(client)), m_handle(std::move(outputs_vstream_handle)) {} + +OutputVStreamClient::~OutputVStreamClient() +{ + auto reply = m_client->OutputVStream_release(m_handle); + if (reply != HAILO_SUCCESS) { + LOGGER__CRITICAL("OutputVStream_release failed!"); } +} - return HAILO_SUCCESS; +hailo_status OutputVStreamClient::read(MemoryView buffer) +{ + return m_client->OutputVStream_read(m_handle, buffer); } -hailo_status OutputVStream::clear(std::vector> &vstreams) +hailo_status OutputVStreamClient::abort() { - for (auto &vstream : vstreams) { - auto status = vstream.get().stop_and_clear(); - CHECK_SUCCESS(status); - } + auto channel = grpc::CreateChannel(HAILO_DEFAULT_UDS_ADDR, grpc::InsecureChannelCredentials()); + CHECK(channel != nullptr, HAILO_INTERNAL_FAILURE); + auto abort_client = std::unique_ptr(new HailoRtRpcClient(channel)); + CHECK(abort_client != nullptr, HAILO_OUT_OF_HOST_MEMORY); + return abort_client->OutputVStream_abort(m_handle); +} - for (auto &vstream : vstreams) { - auto status = vstream.get().start_vstream(); - CHECK_SUCCESS(status); +hailo_status OutputVStreamClient::resume() +{ + return m_client->OutputVStream_resume(m_handle); +} + +size_t OutputVStreamClient::get_frame_size() const +{ + auto frame_size = m_client->OutputVStream_get_frame_size(m_handle); + if (!frame_size) { + LOGGER__CRITICAL("OutputVStream_get_frame_size failed with status={}", frame_size.status()); + return 0; } + return frame_size.release(); +} - return HAILO_SUCCESS; +const hailo_vstream_info_t &OutputVStreamClient::get_info() const +{ + // TODO: HRT-6606 + assert(false); } -std::string OutputVStream::get_pipeline_description() const +const hailo_format_t &OutputVStreamClient::get_user_buffer_format() const { - std::stringstream pipeline_str; - pipeline_str << "Output pipeline '" << name() << "': HW"; - for (const auto &element : m_pipeline) { - pipeline_str << " >> " << element->description(); + // TODO: HRT-6606 + assert(false); +} + +std::string OutputVStreamClient::name() const +{ + auto expected_name = m_client->OutputVStream_name(m_handle); + if (!expected_name) { + LOGGER__CRITICAL("InputVStream_name failed with status={}", expected_name.status()); + return ""; } - return pipeline_str.str(); + return expected_name.release(); +} + +std::string OutputVStreamClient::network_name() const +{ + // TODO: HRT-6606 + assert(false); +} + +const std::map &OutputVStreamClient::get_fps_accumulators() const +{ + // TODO: HRT-6606 + assert(false); +} +const std::map &OutputVStreamClient::get_latency_accumulators() const +{ + // TODO: HRT-6606 + assert(false); +} + +const std::map> &OutputVStreamClient::get_queue_size_accumulators() const +{ + // TODO: HRT-6606 + assert(false); +} +AccumulatorPtr OutputVStreamClient::get_pipeline_latency_accumulator() const +{ + // TODO: HRT-6606 + assert(false); +} +const std::vector> &OutputVStreamClient::get_pipeline() const +{ + // TODO: HRT-6606 + assert(false); } +hailo_status OutputVStreamClient::start_vstream() +{ + return HAILO_NOT_IMPLEMENTED; +} +hailo_status OutputVStreamClient::stop_vstream() +{ + return HAILO_NOT_IMPLEMENTED; +} +hailo_status OutputVStreamClient::stop_and_clear() +{ + return HAILO_NOT_IMPLEMENTED; +} +#pragma GCC diagnostic pop +#endif // HAILO_SUPPORT_MULTI_PROCESS + Expected> HwReadElement::create(OutputStream &stream, const std::string &name, std::chrono::milliseconds timeout, size_t buffer_pool_size, hailo_pipeline_elem_stats_flags_t elem_flags, hailo_vstream_stats_flags_t vstream_flags, EventPtr shutdown_event, std::shared_ptr> pipeline_status) @@ -833,6 +1377,20 @@ hailo_status HwReadElement::flush() return HAILO_INVALID_OPERATION; } +hailo_status HwReadElement::abort() +{ + return m_stream.abort(); +} + +void HwReadElement::wait_for_finish() +{ +} + +hailo_status HwReadElement::resume() +{ + return m_stream.clear_abort(); +} + std::vector HwReadElement::get_queue_size_accumulators() { if (nullptr == m_pool->get_queue_size_accumulator()) { @@ -984,7 +1542,7 @@ hailo_status HwWriteElement::deactivate() } auto abort_status = m_stream.abort(); - CHECK(((HAILO_SUCCESS == abort_status) || (HAILO_STREAM_NOT_ACTIVATED == abort_status)), abort_status, + CHECK(((abort_status == HAILO_SUCCESS) || (abort_status == HAILO_STREAM_NOT_ACTIVATED)), abort_status, "Failed to abort stream in {}", name()); return HAILO_SUCCESS; } @@ -992,7 +1550,7 @@ hailo_status HwWriteElement::deactivate() hailo_status HwWriteElement::post_deactivate() { auto status = m_stream.clear_abort(); - CHECK(((HAILO_SUCCESS == status) || (HAILO_STREAM_NOT_ACTIVATED == status)), status, + CHECK(((status == HAILO_SUCCESS) || (status == HAILO_STREAM_NOT_ACTIVATED)), status, "Failed to clear abort stream in {}", name()); return HAILO_SUCCESS; } @@ -1013,6 +1571,20 @@ hailo_status HwWriteElement::flush() return HAILO_SUCCESS; } +hailo_status HwWriteElement::abort() +{ + return m_stream.abort(); +} + +void HwWriteElement::wait_for_finish() +{ +} + +hailo_status HwWriteElement::resume() +{ + return m_stream.clear_abort(); +} + std::string HwWriteElement::description() const { std::stringstream element_description; @@ -1095,106 +1667,25 @@ Expected, std::vector>> VStre expected_all_inputs.release(), expected_all_outputs.release()); } -static std::map vstream_infos_vector_to_map(std::vector &&vstream_info_vector) -{ - std::map vstream_infos_map; - for (const auto &vstream_info : vstream_info_vector) { - vstream_infos_map.emplace(std::string(vstream_info.name), vstream_info); - } - - return vstream_infos_map; -} - -static Expected> get_input_vstream_infos_map(ConfiguredNetworkGroup &net_group) -{ - auto input_vstream_infos = net_group.get_input_vstream_infos(); - CHECK_EXPECTED(input_vstream_infos); - - return vstream_infos_vector_to_map(input_vstream_infos.release()); -} - -static Expected> get_output_vstream_infos_map(ConfiguredNetworkGroup &net_group) -{ - auto output_vstream_infos = net_group.get_output_vstream_infos(); - CHECK_EXPECTED(output_vstream_infos); - - return vstream_infos_vector_to_map(output_vstream_infos.release()); -} - -static hailo_vstream_params_t expand_vstream_params_autos(const hailo_vstream_info_t &vstream_info, +static hailo_vstream_params_t expand_vstream_params_autos(const hailo_stream_info_t &stream_info, const hailo_vstream_params_t &vstream_params) { auto local_vstream_params = vstream_params; local_vstream_params.user_buffer_format = HailoRTDefaults::expand_auto_format(vstream_params.user_buffer_format, - vstream_info.format); + stream_info.format); return local_vstream_params; } Expected> VStreamsBuilder::create_input_vstreams(ConfiguredNetworkGroup &net_group, const std::map &inputs_params) { - auto input_vstream_infos = get_input_vstream_infos_map(net_group); - CHECK_EXPECTED(input_vstream_infos); - - std::vector vstreams; - vstreams.reserve(inputs_params.size()); - for (const auto &name_params_pair : inputs_params) { - auto input_stream_ref = net_group.get_input_stream_by_name(name_params_pair.first); - CHECK_EXPECTED(input_stream_ref); - - const auto vstream_info = input_vstream_infos->find(name_params_pair.first); - CHECK_AS_EXPECTED(vstream_info != input_vstream_infos->end(), HAILO_NOT_FOUND, - "Failed to find vstream info of {}", name_params_pair.first); - - const auto vstream_params = expand_vstream_params_autos(vstream_info->second, name_params_pair.second); - auto inputs = VStreamsBuilderUtils::create_inputs(input_stream_ref->get(), vstream_info->second, vstream_params); - CHECK_EXPECTED(inputs); - - vstreams.insert(vstreams.end(), std::make_move_iterator(inputs->begin()), std::make_move_iterator(inputs->end())); - } - return vstreams; -} - -static bool is_defused_nms(const hailo_stream_info_t &info) -{ - return (HAILO_FORMAT_ORDER_HAILO_NMS == info.format.order && info.nms_info.is_defused); + return net_group.create_input_vstreams(inputs_params); } Expected> VStreamsBuilder::create_output_vstreams(ConfiguredNetworkGroup &net_group, const std::map &outputs_params) { - std::vector vstreams; - vstreams.reserve(outputs_params.size()); - auto output_streams = net_group.get_output_streams_from_vstream_names(outputs_params); - CHECK_EXPECTED(output_streams); - - auto output_vstream_infos = get_output_vstream_infos_map(net_group); - CHECK_EXPECTED(output_vstream_infos); - - // We iterate through all output streams, and if they are nms, we collect them together by their original stream name. - // We need this step because all nms output streams of the same original stream need to be fused together - std::map> nms_output_streams; - for (auto &stream_params_pair: output_streams.value()) { - auto &out_stream = stream_params_pair.first.get(); - if (is_defused_nms(out_stream.get_info()) && - (outputs_params.end() != outputs_params.find(out_stream.get_info().nms_info.defuse_info.original_name))) { - auto original_name = stream_params_pair.first.get().get_info().nms_info.defuse_info.original_name; - nms_output_streams.emplace(original_name, std::pair( - OutputStreamRefVector(), outputs_params.at(original_name))); - nms_output_streams[original_name].first.push_back(stream_params_pair.first.get()); - } else { - auto outputs = VStreamsBuilderUtils::create_outputs(stream_params_pair.first.get(), stream_params_pair.second, output_vstream_infos.value()); - CHECK_EXPECTED(outputs); - vstreams.insert(vstreams.end(), std::make_move_iterator(outputs->begin()), std::make_move_iterator(outputs->end())); - } - } - for (auto &nms_output_stream_pair : nms_output_streams) { - auto outputs = VStreamsBuilderUtils::create_output_nms(nms_output_stream_pair.second.first, nms_output_stream_pair.second.second, - output_vstream_infos.value()); - CHECK_EXPECTED(outputs); - vstreams.insert(vstreams.end(), std::make_move_iterator(outputs->begin()), std::make_move_iterator(outputs->end())); - } - return vstreams; + return net_group.create_output_vstreams(outputs_params); } Expected> VStreamsBuilderUtils::create_inputs(InputStream &input_stream, const hailo_vstream_info_t &vstream_info, @@ -1264,7 +1755,7 @@ Expected> VStreamsBuilderUtils::create_inputs(InputStr } for (const auto &vstream : vstreams) { - LOGGER__INFO("{}", vstream.get_pipeline_description()); + LOGGER__INFO("{}", vstream.get_pipeline_description()); } return vstreams; @@ -1320,7 +1811,7 @@ Expected> VStreamsBuilderUtils::create_outputs(Output "Failed to find vstream info of {}", output_stream.name()); assert(1 == vstreams_params_map.size()); - auto vstream_params = expand_vstream_params_autos(vstream_info->second, vstreams_params_map.begin()->second); + auto vstream_params = expand_vstream_params_autos(output_stream.get_info(), vstreams_params_map.begin()->second); auto pipeline_latency_accumulator = create_pipeline_latency_accumulator(vstream_params); CHECK_EXPECTED(pipeline_latency_accumulator); @@ -1374,6 +1865,16 @@ Expected> VStreamsBuilderUtils::create_outputs(Output return vstreams; } +InputVStream VStreamsBuilderUtils::create_input(std::shared_ptr input_vstream) +{ + return InputVStream(std::move(input_vstream)); +} + +OutputVStream VStreamsBuilderUtils::create_output(std::shared_ptr output_vstream) +{ + return OutputVStream(std::move(output_vstream)); +} + static bool are_formats_equal(const hailo_format_t &format1, const hailo_format_t &format2) { return ((format1.order == format2.order) && (format1.flags == format2.flags) && (format1.type == format2.type)); } @@ -1454,7 +1955,7 @@ hailo_status VStreamsBuilderUtils::add_demux(OutputStream &output_stream, NameTo CHECK(vstream_info != output_vstream_infos.end(), HAILO_NOT_FOUND, "Failed to find vstream info of {}", edge_info.name); - const auto vstream_params = expand_vstream_params_autos(vstream_info->second, name_params_pair->second); + const auto vstream_params = expand_vstream_params_autos(output_stream.get_info(), name_params_pair->second); // For each mux vstream, we create a copy of the previous elements auto current_vstream_elements = base_elements; @@ -1537,7 +2038,7 @@ hailo_status VStreamsBuilderUtils::add_nms_fuse(OutputStreamRefVector &output_st CHECK(vstream_info != output_vstream_infos.end(), HAILO_NOT_FOUND, "Failed to find vstream info of {}", fused_layer_name); - vstreams_params = expand_vstream_params_autos(vstream_info->second, vstreams_params); + vstreams_params = expand_vstream_params_autos(first_defused_stream_info, vstreams_params); auto nms_elem = NmsMuxElement::create(nms_infos, PipelineObject::create_element_name("NmsMuxElement", fused_layer_name, 0), vstreams_params, shutdown_event, pipeline_status); diff --git a/hailort/libhailort/src/vstream_internal.hpp b/hailort/libhailort/src/vstream_internal.hpp index 64d7544..875cbfe 100644 --- a/hailort/libhailort/src/vstream_internal.hpp +++ b/hailort/libhailort/src/vstream_internal.hpp @@ -4,7 +4,23 @@ **/ /** * @file vstream.hpp - * @brief Virtual Stream + * @brief Virtual Stream. + * Hence, the hierarchy is as follows: + * ------------------------------------------------------------------------------------------------ + * | BaseVStream | (Internal "interface") + * | ___________________________|___________________________ | + * | / \ | + * | InputVStreamInternal OutputVStreamInternal | (Base classes) + * | / \ / \ | + * | InputVStreamImpl InputVStreamClient OuputVStreamImpl OutputVStreamClient | (Actual implementations) + * ------------------------------------------------------------------------------------------------ + * -- InputVStream (External 'interface') + * | + * |__ std::share_ptr + * + * -- OutputVStream (External 'interface') + * | + * |__ std::share_ptr **/ #ifndef _HAILO_VSTREAM_INTERNAL_HPP_ @@ -15,9 +31,250 @@ #include "hailo/stream.hpp" #include "hailo/network_group.hpp" +#ifdef HAILO_SUPPORT_MULTI_PROCESS +#include "hailort_rpc_client.hpp" +#endif // HAILO_SUPPORT_MULTI_PROCESS + namespace hailort { +/*! Virtual stream base class */ +class BaseVStream +{ +public: + BaseVStream(BaseVStream &&other) noexcept; + BaseVStream& operator=(BaseVStream &&other) noexcept; + virtual ~BaseVStream() = default; + + virtual size_t get_frame_size() const; + virtual const hailo_vstream_info_t &get_info() const; + virtual const hailo_format_t &get_user_buffer_format() const; + virtual std::string name() const; + virtual std::string network_name() const; + virtual const std::map &get_fps_accumulators() const; + virtual const std::map &get_latency_accumulators() const; + virtual const std::map> &get_queue_size_accumulators() const; + virtual AccumulatorPtr get_pipeline_latency_accumulator() const; + virtual const std::vector> &get_pipeline() const; + + virtual hailo_status abort(); + virtual hailo_status resume(); + virtual hailo_status start_vstream(); + virtual hailo_status stop_vstream(); + virtual hailo_status stop_and_clear(); + +protected: + BaseVStream(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, + std::shared_ptr pipeline_entry, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, AccumulatorPtr pipeline_latency_accumulator, + EventPtr &&network_group_activated_event, hailo_status &output_status); + BaseVStream() = default; + + virtual std::string get_pipeline_description() const = 0; + + hailo_vstream_info_t m_vstream_info; + hailo_vstream_params_t m_vstream_params; + bool m_measure_pipeline_latency; + std::shared_ptr m_entry_element; + std::vector> m_pipeline; + volatile bool m_is_activated; + volatile bool m_is_aborted; + std::shared_ptr> m_pipeline_status; + EventPtr m_shutdown_event; + EventPtr m_network_group_activated_event; + std::map m_fps_accumulators; + std::map m_latency_accumulators; + std::map> m_queue_size_accumulators; + AccumulatorPtr m_pipeline_latency_accumulator; +}; + +/*! Input virtual stream, used to stream data to device */ +class InputVStreamInternal : public BaseVStream +{ +public: + static Expected> create(const hailo_vstream_info_t &vstream_info, + const hailo_vstream_params_t &vstream_params, std::shared_ptr pipeline_entry, + std::shared_ptr pipeline_exit, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, EventPtr network_group_activated_event, + AccumulatorPtr pipeline_latency_accumulator); + InputVStreamInternal(InputVStreamInternal &&other) noexcept = default; + InputVStreamInternal &operator=(InputVStreamInternal &&other) noexcept = default; + virtual ~InputVStreamInternal() = default; + + virtual hailo_status write(const MemoryView &buffer) = 0; + virtual hailo_status flush() = 0; + + virtual std::string get_pipeline_description() const override; + +protected: + InputVStreamInternal(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, + std::shared_ptr pipeline_entry, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, AccumulatorPtr pipeline_latency_accumulator, + EventPtr &&network_group_activated_event, hailo_status &output_status); + InputVStreamInternal() = default; +}; + +/*! Output virtual stream, used to read data from device */ +class OutputVStreamInternal : public BaseVStream +{ +public: + static Expected> create( + const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, + std::shared_ptr pipeline_entry, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, + EventPtr network_group_activated_event, AccumulatorPtr pipeline_latency_accumulator); + OutputVStreamInternal(OutputVStreamInternal &&other) noexcept = default; + OutputVStreamInternal &operator=(OutputVStreamInternal &&other) noexcept = default; + virtual ~OutputVStreamInternal() = default; + + + virtual hailo_status read(MemoryView buffer) = 0; + virtual std::string get_pipeline_description() const override; + +protected: + OutputVStreamInternal(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, + std::shared_ptr pipeline_entry, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, AccumulatorPtr pipeline_latency_accumulator, + EventPtr network_group_activated_event, hailo_status &output_status); + OutputVStreamInternal() = default; +}; + +class InputVStreamImpl : public InputVStreamInternal +{ +public: + static Expected> create(const hailo_vstream_info_t &vstream_info, + const hailo_vstream_params_t &vstream_params, std::shared_ptr pipeline_entry, + std::shared_ptr pipeline_exit, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, EventPtr network_group_activated_event, + AccumulatorPtr pipeline_latency_accumulator); + InputVStreamImpl(InputVStreamImpl &&) noexcept = default; + InputVStreamImpl(const InputVStreamImpl &) = delete; + InputVStreamImpl &operator=(InputVStreamImpl &&) noexcept = default; + InputVStreamImpl &operator=(const InputVStreamImpl &) = delete; + virtual ~InputVStreamImpl(); + + virtual hailo_status write(const MemoryView &buffer) override; + virtual hailo_status flush() override; +private: + InputVStreamImpl(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, + std::shared_ptr pipeline_entry, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, AccumulatorPtr pipeline_latency_accumulator, + EventPtr network_group_activated_event, hailo_status &output_status); +}; + +class OutputVStreamImpl : public OutputVStreamInternal +{ +public: + static Expected> create( + const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, + std::shared_ptr pipeline_entry, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, + EventPtr network_group_activated_event, AccumulatorPtr pipeline_latency_accumulator); + OutputVStreamImpl(OutputVStreamImpl &&) noexcept = default; + OutputVStreamImpl(const OutputVStreamImpl &) = delete; + OutputVStreamImpl &operator=(OutputVStreamImpl &&) noexcept = default; + OutputVStreamImpl &operator=(const OutputVStreamImpl &) = delete; + virtual ~OutputVStreamImpl(); + + virtual hailo_status read(MemoryView buffer); + + void set_on_vstream_cant_read_callback(std::function callback) + { + m_cant_read_callback = callback; + } + + void set_on_vstream_can_read_callback(std::function callback) + { + m_can_read_callback = callback; + } + +private: + OutputVStreamImpl(const hailo_vstream_info_t &vstream_info, const hailo_vstream_params_t &vstream_params, + std::shared_ptr pipeline_entry, std::vector> &&pipeline, + std::shared_ptr> &&pipeline_status, EventPtr shutdown_event, AccumulatorPtr pipeline_latency_accumulator, + EventPtr network_group_activated_event, hailo_status &output_status); + + std::function m_cant_read_callback; + std::function m_can_read_callback; +}; + +#ifdef HAILO_SUPPORT_MULTI_PROCESS +class InputVStreamClient : public InputVStreamInternal +{ +public: + static Expected> create(uint32_t input_vstream_handle); + InputVStreamClient(InputVStreamClient &&) noexcept = default; + InputVStreamClient(const InputVStreamClient &) = delete; + InputVStreamClient &operator=(InputVStreamClient &&) noexcept = default; + InputVStreamClient &operator=(const InputVStreamClient &) = delete; + virtual ~InputVStreamClient(); + + virtual hailo_status write(const MemoryView &buffer) override; + virtual hailo_status flush() override; + + virtual hailo_status abort() override; + virtual hailo_status resume() override; + virtual size_t get_frame_size() const override; + virtual const hailo_vstream_info_t &get_info() const override; + virtual const hailo_format_t &get_user_buffer_format() const override; + virtual std::string name() const override; + virtual std::string network_name() const override; + virtual const std::map &get_fps_accumulators() const override; + virtual const std::map &get_latency_accumulators() const override; + virtual const std::map> &get_queue_size_accumulators() const override; + virtual AccumulatorPtr get_pipeline_latency_accumulator() const override; + virtual const std::vector> &get_pipeline() const override; + +protected: + virtual hailo_status start_vstream() override; + virtual hailo_status stop_vstream() override; + virtual hailo_status stop_and_clear() override; + +private: + InputVStreamClient(std::unique_ptr client, uint32_t input_vstream_handle); + + std::unique_ptr m_client; + uint32_t m_handle; +}; + +class OutputVStreamClient : public OutputVStreamInternal +{ +public: + static Expected> create(uint32_t outputs_vstream_handle); + OutputVStreamClient(OutputVStreamClient &&) noexcept = default; + OutputVStreamClient(const OutputVStreamClient &) = delete; + OutputVStreamClient &operator=(OutputVStreamClient &&) noexcept = default; + OutputVStreamClient &operator=(const OutputVStreamClient &) = delete; + virtual ~OutputVStreamClient(); + + virtual hailo_status read(MemoryView buffer); + + virtual hailo_status abort() override; + virtual hailo_status resume() override; + virtual size_t get_frame_size() const override; + virtual const hailo_vstream_info_t &get_info() const override; + virtual const hailo_format_t &get_user_buffer_format() const override; + virtual std::string name() const override; + virtual std::string network_name() const override; + virtual const std::map &get_fps_accumulators() const override; + virtual const std::map &get_latency_accumulators() const override; + virtual const std::map> &get_queue_size_accumulators() const override; + virtual AccumulatorPtr get_pipeline_latency_accumulator() const override; + virtual const std::vector> &get_pipeline() const override; + +protected: + virtual hailo_status start_vstream() override; + virtual hailo_status stop_vstream() override; + virtual hailo_status stop_and_clear() override; + +private: + OutputVStreamClient(std::unique_ptr client, uint32_t outputs_vstream_handle); + + std::unique_ptr m_client; + uint32_t m_handle; +}; +#endif // HAILO_SUPPORT_MULTI_PROCESS + class PreInferElement : public FilterElement { public: @@ -132,6 +389,9 @@ public: virtual hailo_status post_deactivate() override; virtual hailo_status clear() override; virtual hailo_status flush() override; + virtual hailo_status abort() override; + virtual void wait_for_finish() override; + virtual hailo_status resume() override; uint32_t get_invalid_frames_count(); virtual std::string description() const override; @@ -159,6 +419,9 @@ public: virtual hailo_status post_deactivate() override; virtual hailo_status clear() override; virtual hailo_status flush() override; + virtual hailo_status abort() override; + virtual void wait_for_finish() override; + virtual hailo_status resume() override; virtual std::string description() const override; private: @@ -185,6 +448,8 @@ public: const hailo_vstream_params_t &vstreams_params); static Expected> create_outputs(OutputStream &output_stream, NameToVStreamParamsMap &vstreams_params_map, const std::map &output_vstream_infos); + static InputVStream create_input(std::shared_ptr input_vstream); + static OutputVStream create_output(std::shared_ptr output_vstream); static Expected> create_output_nms(OutputStreamRefVector &output_streams, hailo_vstream_params_t vstreams_params, const std::map &output_vstream_infos); diff --git a/hailort/pre_build/CMakeLists.txt b/hailort/pre_build/CMakeLists.txt index 47a43c5..9c32415 100644 --- a/hailort/pre_build/CMakeLists.txt +++ b/hailort/pre_build/CMakeLists.txt @@ -3,12 +3,14 @@ project(hailort_prebuild) if(NOT HAILO_OFFLINE_COMPILATION) set(HAILO_PRE_BUILD_EXTERNAL_DIR ${CMAKE_CURRENT_LIST_DIR}/external) - include(../libhailort/cmake/execute_cmake.cmake) + include(../cmake/execute_cmake.cmake) message("Downloading dependencies to ${HAILO_EXTERNAL_DIR} ...") execute_cmake( - SOURCE_DIR "${HAILO_PRE_BUILD_EXTERNAL_DIR}" - BUILD_DIR "${HAILO_PRE_BUILD_EXTERNAL_DIR}/build" - CONFIGURE_ARGS "-DHAILO_EXTERNAL_DIR=${HAILO_EXTERNAL_DIR}" + SOURCE_DIR ${HAILO_PRE_BUILD_EXTERNAL_DIR} + BUILD_DIR ${HAILO_PRE_BUILD_EXTERNAL_DIR}/build + CONFIGURE_ARGS + -DHAILO_EXTERNAL_DIR=${HAILO_EXTERNAL_DIR} + -DHAILO_BUILD_SERVICE=${HAILO_BUILD_SERVICE} ) message("Finished downloading dependencies") else() diff --git a/hailort/pre_build/external/CMakeLists.txt b/hailort/pre_build/external/CMakeLists.txt index c807e11..373775c 100644 --- a/hailort/pre_build/external/CMakeLists.txt +++ b/hailort/pre_build/external/CMakeLists.txt @@ -18,12 +18,16 @@ endfunction() git_clone(pybind11 https://github.com/pybind/pybind11.git 8de7772cc72daca8e947b79b83fea46214931604) git_clone(Catch2 https://github.com/catchorg/Catch2.git c4e3767e265808590986d5db6ca1b5532a7f3d13) -git_clone(CLI11 https://github.com/CLIUtils/CLI11.git 706b14fb14444e68ace232eb9a0ef106bf99343b) +git_clone(CLI11 https://github.com/hailo-ai/CLI11.git 635773b0a1d76a1744c122b98eda6702c909edb2) git_clone(spdlog https://github.com/gabime/spdlog.git e2789531912a5c6ab28a90387f97c52963eec08a) -git_clone(protobuf https://github.com/protocolbuffers/protobuf.git d0bfd5221182da1a7cc280f3337b5e41a89539cf) +git_clone(protobuf https://github.com/protocolbuffers/protobuf.git 22d0e265de7d2b3d2e9a00d071313502e7d4cccf) git_clone(readerwriterqueue https://github.com/cameron314/readerwriterqueue.git 435e36540e306cac40fcfeab8cc0a22d48464509) git_clone(json https://github.com/ArthurSonzogni/nlohmann_json_cmake_fetchcontent.git 391786c6c3abdd3eeb993a3154f1f2a4cfe137a0) git_clone(DotWriter https://github.com/hailo-ai/DotWriter.git e5fa8f281adca10dd342b1d32e981499b8681daf) git_clone(benchmark https://github.com/google/benchmark.git f91b6b42b1b9854772a90ae9501464a161707d1e) git_clone(pevents https://github.com/neosmart/pevents.git 1209b1fd1bd2e75daab4380cf43d280b90b45366) git_clone(microprofile https://github.com/jonasmr/microprofile 03c34f96840defe0f4c196309628815d02b98059) + +if(HAILO_BUILD_SERVICE) + git_clone(grpc https://github.com/grpc/grpc 53d69cc581c5b7305708587f4f1939278477c28a) +endif() \ No newline at end of file diff --git a/hailort/pre_build/tools/CMakeLists.txt b/hailort/pre_build/tools/CMakeLists.txt index 1bb7f3f..2254251 100644 --- a/hailort/pre_build/tools/CMakeLists.txt +++ b/hailort/pre_build/tools/CMakeLists.txt @@ -7,4 +7,22 @@ endif() if(MSVC AND NOT protobuf_MSVC_STATIC_RUNTIME) set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Protobuf MSVC static runtime") endif() -add_subdirectory(${HAILO_EXTERNAL_DIR}/protobuf/cmake build_protoc) \ No newline at end of file +if(NOT protobuf_WITH_ZLIB) + set(protobuf_WITH_ZLIB OFF CACHE BOOL "Compile protobuf with zlib") +endif() +add_subdirectory(${HAILO_EXTERNAL_DIR}/protobuf/cmake build_protoc) + +if(HAILO_BUILD_SERVICE) + message(STATUS "Building grpc...") + # The following is an awful hack needed in order to force grpc to use our libprotobuf+liborotoc targets + # ('formal' options are to let grpc recompile it which causes a name conflict, + # or let it use find_package and take the risk it will use a different installed lib) + set(gRPC_PROTOBUF_PROVIDER "hack" CACHE STRING "Provider of protobuf library") + # Avoiding "formal" gRPC_PROTOBUF_PROVIDER option, the following variables should be set independently + set(_gRPC_PROTOBUF_LIBRARIES protobuf::libprotobuf) + set(_gRPC_PROTOBUF_PROTOC_LIBRARIES protobuf::libprotoc) + # Build grpc_cpp_plugin target only + add_subdirectory(${HAILO_EXTERNAL_DIR}/grpc build_grpc EXCLUDE_FROM_ALL) + add_custom_target(grpc_cpp_plugin_target ALL) + add_dependencies(grpc_cpp_plugin_target grpc_cpp_plugin) +endif() \ No newline at end of file diff --git a/hailort/rpc/CMakeLists.txt b/hailort/rpc/CMakeLists.txt new file mode 100644 index 0000000..7cf3b64 --- /dev/null +++ b/hailort/rpc/CMakeLists.txt @@ -0,0 +1,33 @@ +include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/common_compiler_options.cmake) + +# Proto file +get_filename_component(hailort_rpc_proto "hailort_rpc.proto" ABSOLUTE) +get_filename_component(hailort_rpc_proto_path "${hailort_rpc_proto}" PATH) +# Generated sources +set(hailort_rpc_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/hailort_rpc.pb.cc") +set(hailort_rpc_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/hailort_rpc.pb.h") +set(hailort_rpc_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/hailort_rpc.grpc.pb.cc") +set(hailort_rpc_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/hailort_rpc.grpc.pb.h") + +add_custom_command( + OUTPUT "${hailort_rpc_proto_srcs}" "${hailort_rpc_proto_hdrs}" "${hailort_rpc_grpc_srcs}" "${hailort_rpc_grpc_hdrs}" + COMMAND ${HAILO_PROTOBUF_PROTOC} + ARGS + --grpc_out "${CMAKE_CURRENT_BINARY_DIR}" + --cpp_out "${CMAKE_CURRENT_BINARY_DIR}" + -I "${hailort_rpc_proto_path}" + --plugin=protoc-gen-grpc="${HAILO_GRPC_CPP_PLUGIN_EXECUTABLE}" + "${hailort_rpc_proto}" + DEPENDS "${hailort_rpc_proto}") + +add_library(hailort_rpc_grpc_proto STATIC EXCLUDE_FROM_ALL + ${hailort_rpc_grpc_srcs} + ${hailort_rpc_grpc_hdrs} + ${hailort_rpc_proto_srcs} + ${hailort_rpc_proto_hdrs}) + +set_target_properties(hailort_rpc_grpc_proto PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_link_libraries(hailort_rpc_grpc_proto libprotobuf-lite grpc++_unsecure) +# Include generated *.pb.h files +target_include_directories(hailort_rpc_grpc_proto PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") +disable_exceptions(hailort_rpc_grpc_proto) \ No newline at end of file diff --git a/hailort/rpc/hailort_rpc.proto b/hailort/rpc/hailort_rpc.proto new file mode 100644 index 0000000..7873cb4 --- /dev/null +++ b/hailort/rpc/hailort_rpc.proto @@ -0,0 +1,410 @@ +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +service HailoRtRpc { + rpc client_keep_alive (keepalive_Request) returns (empty) {} + rpc get_service_version (get_service_version_Request) returns (get_service_version_Reply) {} + rpc VDevice_create (VDevice_create_Request) returns (VDevice_create_Reply) {} + rpc VDevice_release (Release_Request) returns (Release_Reply) {} + rpc VDevice_configure (VDevice_configure_Request) returns (VDevice_configure_Reply) {} + rpc VDevice_get_physical_devices_ids (VDevice_get_physical_devices_ids_Request) returns (VDevice_get_physical_devices_ids_Reply) {} + + rpc ConfiguredNetworkGroup_release (Release_Request) returns (Release_Reply) {} + rpc ConfiguredNetworkGroup_make_input_vstream_params (ConfiguredNetworkGroup_make_input_vstream_params_Request) returns (ConfiguredNetworkGroup_make_input_vstream_params_Reply) {} + rpc ConfiguredNetworkGroup_make_output_vstream_params (ConfiguredNetworkGroup_make_output_vstream_params_Request) returns (ConfiguredNetworkGroup_make_output_vstream_params_Reply) {} + rpc ConfiguredNetworkGroup_get_name (ConfiguredNetworkGroup_get_name_Request) returns (ConfiguredNetworkGroup_get_name_Reply) {} + rpc ConfiguredNetworkGroup_get_network_infos (ConfiguredNetworkGroup_get_network_infos_Request) returns (ConfiguredNetworkGroup_get_network_infos_Reply) {} + rpc ConfiguredNetworkGroup_get_all_stream_infos (ConfiguredNetworkGroup_get_all_stream_infos_Request) returns (ConfiguredNetworkGroup_get_all_stream_infos_Reply) {} + rpc ConfiguredNetworkGroup_get_default_stream_interface (ConfiguredNetworkGroup_get_default_stream_interface_Request) returns (ConfiguredNetworkGroup_get_default_stream_interface_Reply) {} + rpc ConfiguredNetworkGroup_get_output_vstream_groups (ConfiguredNetworkGroup_get_output_vstream_groups_Request) returns (ConfiguredNetworkGroup_get_output_vstream_groups_Reply) {} + rpc ConfiguredNetworkGroup_get_input_vstream_infos (ConfiguredNetworkGroup_get_vstream_infos_Request) returns (ConfiguredNetworkGroup_get_vstream_infos_Reply) {} + rpc ConfiguredNetworkGroup_get_output_vstream_infos (ConfiguredNetworkGroup_get_vstream_infos_Request) returns (ConfiguredNetworkGroup_get_vstream_infos_Reply) {} + rpc ConfiguredNetworkGroup_get_all_vstream_infos (ConfiguredNetworkGroup_get_vstream_infos_Request) returns (ConfiguredNetworkGroup_get_vstream_infos_Reply) {} + rpc ConfiguredNetworkGroup_set_scheduler_timeout (ConfiguredNetworkGroup_set_scheduler_timeout_Request) returns (ConfiguredNetworkGroup_set_scheduler_timeout_Reply) {} + rpc ConfiguredNetworkGroup_set_scheduler_threshold (ConfiguredNetworkGroup_set_scheduler_threshold_Request) returns (ConfiguredNetworkGroup_set_scheduler_threshold_Reply) {} + rpc ConfiguredNetworkGroup_get_latency_measurement (ConfiguredNetworkGroup_get_latency_measurement_Request) returns (ConfiguredNetworkGroup_get_latency_measurement_Reply) {} + + rpc InputVStreams_create (VStream_create_Request) returns (VStreams_create_Reply) {} + rpc InputVStream_release (Release_Request) returns (Release_Reply) {} + rpc OutputVStreams_create (VStream_create_Request) returns (VStreams_create_Reply) {} + rpc OutputVStream_release (Release_Request) returns (Release_Reply) {} + rpc InputVStream_write (InputVStream_write_Request) returns (InputVStream_write_Reply) {} + rpc OutputVStream_read (OutputVStream_read_Request) returns (OutputVStream_read_Reply) {} + rpc InputVStream_get_frame_size (VStream_get_frame_size_Request) returns (VStream_get_frame_size_Reply) {} + rpc OutputVStream_get_frame_size (VStream_get_frame_size_Request) returns (VStream_get_frame_size_Reply) {} + rpc InputVStream_flush (InputVStream_flush_Request) returns (InputVStream_flush_Reply) {} + rpc InputVStream_name (VStream_name_Request) returns (VStream_name_Reply) {} + rpc OutputVStream_name (VStream_name_Request) returns (VStream_name_Reply) {} + rpc InputVStream_abort (VStream_abort_Request) returns (VStream_abort_Reply) {} + rpc OutputVStream_abort (VStream_abort_Request) returns (VStream_abort_Reply) {} + rpc InputVStream_resume (VStream_resume_Request) returns (VStream_resume_Reply) {} + rpc OutputVStream_resume (VStream_resume_Request) returns (VStream_resume_Reply) {} +} + +message empty {} + +message keepalive_Request { + uint32 process_id = 1; +} + +message HailoVDeviceParams { + uint32 device_count = 1; + repeated string device_ids = 2; + uint32 scheduling_algorithm = 3; + string group_id = 4; +} + +message HailoVersion { + uint32 major_version = 1; + uint32 minor_version = 2; + uint32 revision_version = 3; +} + +message get_service_version_Request { +} + +message get_service_version_Reply { + uint32 status = 1; + HailoVersion hailo_version = 2; +} + +message VDevice_create_Request { + HailoVDeviceParams hailo_vdevice_params = 1; + uint32 pid = 2; +} + +message VDevice_create_Reply { + uint32 status = 1; + uint32 handle = 2; +} + +message Release_Request { + uint32 handle = 1; +} + +message Release_Reply { + uint32 status = 1; +} + +message VStreams_create_Reply { + uint32 status = 1; + repeated uint32 handles = 2; +} + +message VStream_create_Request { + uint32 net_group = 1; + repeated NamedVStreamParams vstreams_params = 2; + uint32 pid = 3; +} + +message HailoFormat { + uint32 type = 1; + uint32 order = 2; + uint32 flags = 3; +} + +message VStreamParams { + HailoFormat user_buffer_format = 1; + uint32 timeout_ms = 2; + uint32 queue_size = 3; + uint32 vstream_stats_flags = 4; + uint32 pipeline_elements_stats_flags = 5; +} + +message NamedVStreamParams { + string name = 1; + VStreamParams params = 2; +} + +message ThreeDImageShape { + uint32 height = 1; + uint32 width = 2; + uint32 features = 3; +} + +message StreamShape { + ThreeDImageShape shape = 1; + ThreeDImageShape hw_shape = 2; +} + +message NmsDefuseInfo { + uint32 class_group_index = 1; + string original_name = 2; +} + +message NmsInfo { + uint32 number_of_classes = 1; + uint32 max_bboxes_per_class = 2; + uint32 bbox_size = 3; + uint32 chunks_per_frame = 4; + bool is_defused = 5; + NmsDefuseInfo defuse_info = 6; +} + +message QuantInfo { + float qp_zp = 1; + float qp_scale = 2; + float limvals_min = 3; + float limvals_max = 4; +} + +message StreamInfo { + StreamShape stream_shape = 1; + NmsInfo nms_info = 2; + uint32 hw_data_bytes = 3; + uint32 hw_frame_size = 4; + HailoFormat format = 5; + uint32 direction = 6; + uint32 index = 7; + string name = 8; + QuantInfo quant_info = 9; + bool is_mux = 10; +} + +message StreamsParams { + uint32 stream_interface = 1; + uint32 direction = 2; +} + +message NamedStreamParams { + string name = 1; + StreamsParams params = 2; +} + +message NetworkParams { + uint32 batch_size = 1; +} + +message NamedNetworkParams { + string name = 1; + NetworkParams params = 2; +} + +message NmsShape { + uint32 number_of_classes = 1; + uint32 max_bbox_per_class = 2; +} + +message VStreamInfo { + string name = 1; + string network_name = 2; + uint32 direction = 3; + HailoFormat format = 4; + ThreeDImageShape shape = 5; + NmsShape nms_shape = 6; + QuantInfo quant_info = 7; +} + +message HailoConfigureNetworkParams { + uint32 batch_size = 1; + uint32 power_mode = 2; + uint32 latency = 3; + repeated NamedStreamParams stream_params_map = 4; + repeated NamedNetworkParams network_params_map = 5; +} + +message NamedConfigureNetworkParams { + string name = 1; + HailoConfigureNetworkParams params = 2; +} + +message VStreamGroup { + repeated string vstream_group = 1; +} + +message VDevice_configure_Request { + uint32 handle = 1; + bytes hef = 2; + repeated NamedConfigureNetworkParams configure_params_map = 3; + uint32 pid = 4; +} + +message VDevice_configure_Reply { + uint32 status = 1; + repeated uint32 networks_handles = 2; +} + +message VDevice_get_physical_devices_ids_Request { + uint32 handle = 1; +} + +message VDevice_get_physical_devices_ids_Reply { + uint32 status = 1; + repeated string devices_ids = 2; +} + +message ConfiguredNetworkGroup_make_input_vstream_params_Request { + uint32 handle = 1; + bool quantized = 2; + uint32 format_type = 3; + uint32 timeout_ms = 4; + uint32 queue_size = 5; + string network_name = 6; +} + +message ConfiguredNetworkGroup_make_input_vstream_params_Reply { + uint32 status = 1; + repeated NamedVStreamParams vstream_params_map = 2; +} + +message ConfiguredNetworkGroup_make_output_vstream_params_Request { + uint32 handle = 1; + bool quantized = 2; + uint32 format_type = 3; + uint32 timeout_ms = 4; + uint32 queue_size = 5; + string network_name = 6; +} + +message ConfiguredNetworkGroup_make_output_vstream_params_Reply { + uint32 status = 1; + repeated NamedVStreamParams vstream_params_map = 2; +} + +message ConfiguredNetworkGroup_get_name_Request { + uint32 handle = 1; +} + +message ConfiguredNetworkGroup_get_name_Reply { + uint32 status = 1; + string network_group_name = 2; +} + +message ConfiguredNetworkGroup_get_network_infos_Request { + uint32 handle = 1; +} + +message ConfiguredNetworkGroup_get_network_infos_Reply { + uint32 status = 1; + repeated string network_infos = 2; +} + +message ConfiguredNetworkGroup_get_all_stream_infos_Request { + uint32 handle = 1; + string network_name = 2; +} + +message ConfiguredNetworkGroup_get_all_stream_infos_Reply { + uint32 status = 1; + repeated StreamInfo stream_infos = 2; +} + +message ConfiguredNetworkGroup_get_default_stream_interface_Request { + uint32 handle = 1; +} + +message ConfiguredNetworkGroup_get_default_stream_interface_Reply { + uint32 status = 1; + uint32 stream_interface = 2; +} + +message ConfiguredNetworkGroup_get_output_vstream_groups_Request { + uint32 handle = 1; +} + +message ConfiguredNetworkGroup_get_output_vstream_groups_Reply { + uint32 status = 1; + repeated VStreamGroup output_vstream_groups = 2; +} + +message ConfiguredNetworkGroup_get_vstream_infos_Request { + uint32 handle = 1; + string network_name = 2; +} + +message ConfiguredNetworkGroup_get_latency_measurement_Request { + uint32 handle = 1; + string network_name = 2; +} + +message ConfiguredNetworkGroup_get_vstream_infos_Reply { + uint32 status = 1; + repeated VStreamInfo vstream_infos = 2; +} + +message ConfiguredNetworkGroup_set_scheduler_timeout_Request { + uint32 handle = 1; + uint32 timeout_ms = 2; + string network_name = 3; +} + +message ConfiguredNetworkGroup_set_scheduler_timeout_Reply { + uint32 status = 1; +} + +message ConfiguredNetworkGroup_set_scheduler_threshold_Request { + uint32 handle = 1; + uint32 threshold = 2; + string network_name = 3; +} + +message ConfiguredNetworkGroup_set_scheduler_threshold_Reply { + uint32 status = 1; +} + +message ConfiguredNetworkGroup_get_latency_measurement_Reply { + uint32 status = 1; + uint32 avg_hw_latency = 2; +} + +message InputVStream_write_Request { + uint32 handle = 1; + bytes data = 2; +} + +message InputVStream_write_Reply { + uint32 status = 1; +} + +message OutputVStream_read_Request { + uint32 handle = 1; + uint32 size = 2; +} + +message OutputVStream_read_Reply { + uint32 status = 1; + bytes data = 2; +} + +message VStream_get_frame_size_Request { + uint32 handle = 1; +} + +message VStream_get_frame_size_Reply { + uint32 status = 1; + uint32 frame_size = 2; +} + +message InputVStream_flush_Request { + uint32 handle = 1; +} + +message InputVStream_flush_Reply { + uint32 status = 1; +} + +message VStream_name_Request { + uint32 handle = 1; +} + +message VStream_name_Reply { + uint32 status = 1; + string name = 2; +} + +message VStream_abort_Request { + uint32 handle = 1; +} + +message VStream_abort_Reply { + uint32 status = 1; +} + +message VStream_resume_Request { + uint32 handle = 1; +} + +message VStream_resume_Reply { + uint32 status = 1; +} \ No newline at end of file diff --git a/hailort/rpc/rpc_definitions.hpp b/hailort/rpc/rpc_definitions.hpp new file mode 100644 index 0000000..7d18326 --- /dev/null +++ b/hailort/rpc/rpc_definitions.hpp @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2020-2022 Hailo Technologies Ltd. All rights reserved. + * Distributed under the MIT license (https://opensource.org/licenses/MIT) + **/ +/** + * @file rpc_definitions.hpp + * @brief Common defines used by hailort service and libhailort + **/ + +#ifndef _HAILO_RPC_DEFINITIONS_HPP_ +#define _HAILO_RPC_DEFINITIONS_HPP_ + +namespace hailort +{ + +static const std::string HAILO_UDS_PREFIX = "unix://"; +static const std::string HAILO_DEFAULT_SERVICE_ADDR = "/tmp/hailort_uds.sock"; +static const std::string HAILO_DEFAULT_UDS_ADDR = HAILO_UDS_PREFIX + HAILO_DEFAULT_SERVICE_ADDR; +static const uint32_t HAILO_KEEPALIVE_INTERVAL_SEC = 2; + +} + +#endif \ No newline at end of file diff --git a/hailort/scripts/download_hefs.cmd b/hailort/scripts/download_hefs.cmd index 98c8fb6..2be01f1 100644 --- a/hailort/scripts/download_hefs.cmd +++ b/hailort/scripts/download_hefs.cmd @@ -1,7 +1,7 @@ :: cmd @ECHO OFF set BASE_URI=https://hailo-hailort.s3.eu-west-2.amazonaws.com -set HRT_VERSION=4.8.0 +set HRT_VERSION=4.10.0 set REMOTE_HEF_DIR=Hailo8/%HRT_VERSION%/HEFS set LOCAL_EXAMPLES_HEF_DIR=..\libhailort\examples\hefs set LOCAL_TUTORIALS_HEF_DIR=..\libhailort\bindings\python\platform\tutorials\hefs diff --git a/hailort/scripts/download_hefs.sh b/hailort/scripts/download_hefs.sh index 28d8479..6295d04 100755 --- a/hailort/scripts/download_hefs.sh +++ b/hailort/scripts/download_hefs.sh @@ -2,7 +2,7 @@ set -e readonly BASE_URI="https://hailo-hailort.s3.eu-west-2.amazonaws.com" -readonly HRT_VERSION=4.8.0 +readonly HRT_VERSION=4.10.0 readonly REMOTE_HEF_DIR="Hailo8/${HRT_VERSION}/HEFS" readonly LOCAL_EXAMPLES_HEF_DIR="../libhailort/examples/hefs" readonly LOCAL_TUTORIALS_HEF_DIR="../libhailort/bindings/python/platform/tutorials/hefs/" @@ -17,7 +17,7 @@ readonly TUTORIALS_HEFS=( function create_hef_dir(){ for d in $LOCAL_EXAMPLES_HEF_DIR $LOCAL_TUTORIALS_HEF_DIR; do if ! [ -d ${d} ]; then - mkdir ${d} + mkdir -p ${d} fi done } -- 2.34.1

  • P= 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