From 240508667319144f1b3a2b6bdcaa0b0e024178d6 Mon Sep 17 00:00:00 2001 From: Nesterov Alexander Date: Tue, 21 Apr 2026 08:02:15 +0200 Subject: [PATCH 001/545] [core] Recover from model cache write failures (#35132) ## Summary Make file-based model cache write failures recoverable and keep cache entries consistent. Before this change, a cache write failure during model export could leave an invalid cache entry and break subsequent cache use. After this change: - file-based cache writes either complete successfully or the failed cache entry is removed - recoverable cache export failures no longer fail model compilation - the cache write path stays simple and writes directly to the final blob path ### Details: - File-based cache writes directly to the final `.blob` path. - Repeated writes to the same cache entry overwrite the existing blob. - Existing blob permissions are temporarily relaxed before overwrite, then restored to read-only after a successful store. - Cache export failures are handled in `CoreImpl`, which removes the failed cache entry on caching errors. - Stream write failures remain recoverable and do not fail model compilation. - GPU export failures that surface as `ov::Exception` are also treated as recoverable cache export failures. - Other unexpected exceptions are still propagated. ### Result: - before: a cache write failure could leave a broken cache entry and break subsequent cache use - after: failed cache writes are discarded, cache state remains usable, and subsequent runs continue normally ### Tickets: - CVS-104958 ### AI Assistance: - *AI assistance used: yes* --- src/inference/src/cache_manager.hpp | 17 ++- src/inference/src/dev/core_impl.cpp | 4 + .../tests/functional/caching_test.cpp | 62 ++++++++- src/inference/tests/unit/cache_manager.cpp | 124 ++++++++++++++++++ 4 files changed, 201 insertions(+), 6 deletions(-) create mode 100644 src/inference/tests/unit/cache_manager.cpp diff --git a/src/inference/src/cache_manager.hpp b/src/inference/src/cache_manager.hpp index 199952486df66a..54f8d0e8d4bcae 100644 --- a/src/inference/src/cache_manager.hpp +++ b/src/inference/src/cache_manager.hpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include @@ -51,7 +50,16 @@ class FileStorageCacheManager final : public ICacheManager { // Fix the bug caused by pugixml, which may return unexpected results if the locale is different from "C". ScopedLocale plocal_C(LC_ALL, "C"); const auto blob_path = get_blob_file(id); - std::ofstream stream(blob_path, std::ios_base::binary); + + if (ov::util::file_exists(blob_path)) { + std::filesystem::permissions(blob_path, + std::filesystem::perms::owner_write, + std::filesystem::perm_options::add); + } + + std::ofstream stream; + stream.exceptions(std::ios_base::failbit | std::ios_base::badbit); + stream.open(blob_path, std::ios_base::binary); writer(stream); stream.close(); std::filesystem::permissions(blob_path, @@ -62,7 +70,7 @@ class FileStorageCacheManager final : public ICacheManager { // Fix the bug caused by pugixml, which may return unexpected results if the locale is different from "C". ScopedLocale plocal_C(LC_ALL, "C"); const auto blob_path = get_blob_file(id); - if (std::filesystem::exists(blob_path)) { + if (ov::util::file_exists(blob_path)) { if (enable_mmap) { CompiledBlobVariant compiled_blob{std::in_place_index<0>, ov::read_tensor_data(blob_path)}; reader(compiled_blob); @@ -76,8 +84,7 @@ class FileStorageCacheManager final : public ICacheManager { void remove_cache_entry(const std::string& id) override { const auto blob_path = get_blob_file(id); - - if (std::filesystem::exists(blob_path)) { + if (ov::util::file_exists(blob_path)) { std::ignore = std::filesystem::remove(blob_path); } } diff --git a/src/inference/src/dev/core_impl.cpp b/src/inference/src/dev/core_impl.cpp index 5c3af4886f4bb5..a280a0c60e2940 100644 --- a/src/inference/src/dev/core_impl.cpp +++ b/src/inference/src/dev/core_impl.cpp @@ -1546,6 +1546,10 @@ ov::SoPtr ov::CoreImpl::compile_model_and_cache(ov::Plugin& header_size_alignment); compiled_model->export_model(stream); }); + } catch (const std::ios_base::failure&) { + cache_content.m_cache_manager->remove_cache_entry(cache_content.m_blob_id); + } catch (const ov::Exception&) { + cache_content.m_cache_manager->remove_cache_entry(cache_content.m_blob_id); } catch (...) { cache_content.m_cache_manager->remove_cache_entry(cache_content.m_blob_id); throw; diff --git a/src/inference/tests/functional/caching_test.cpp b/src/inference/tests/functional/caching_test.cpp index 47747245ff9517..4325abbc8ad86b 100644 --- a/src/inference/tests/functional/caching_test.cpp +++ b/src/inference/tests/functional/caching_test.cpp @@ -1638,7 +1638,7 @@ TEST_P(CachingTest, TestNoCachingProperties) { } } -TEST_P(CachingTest, TestThrowOnExport) { +TEST_P(CachingTest, TestThrowOnExportFailure) { EXPECT_CALL(*mockPlugin, get_property(ov::supported_properties.name(), _)).Times(AnyNumber()); EXPECT_CALL(*mockPlugin, get_property(ov::device::capability::EXPORT_IMPORT, _)).Times(AnyNumber()); EXPECT_CALL(*mockPlugin, get_property(ov::device::architecture.name(), _)).Times(AnyNumber()); @@ -1657,6 +1657,66 @@ TEST_P(CachingTest, TestThrowOnExport) { testLoad([&](ov::Core& core) { core.set_property(ov::cache_dir(m_cacheDir)); EXPECT_ANY_THROW(m_testFunction(core)); + EXPECT_FALSE(std::filesystem::exists(ov::util::make_path(m_cacheDir)) && + std::filesystem::directory_iterator(ov::util::make_path(m_cacheDir)) != + std::filesystem::directory_iterator{}); + }); + } +} + +TEST_P(CachingTest, TestIgnoreOvExceptionExportFailure) { + EXPECT_CALL(*mockPlugin, get_property(ov::supported_properties.name(), _)).Times(AnyNumber()); + EXPECT_CALL(*mockPlugin, get_property(ov::device::capability::EXPORT_IMPORT, _)).Times(AnyNumber()); + EXPECT_CALL(*mockPlugin, get_property(ov::device::architecture.name(), _)).Times(AnyNumber()); + EXPECT_CALL(*mockPlugin, get_property(ov::internal::supported_properties.name(), _)).Times(AnyNumber()); + EXPECT_CALL(*mockPlugin, get_property(ov::internal::caching_properties.name(), _)).Times(AnyNumber()); + EXPECT_CALL(*mockPlugin, get_property(ov::device::capabilities.name(), _)).Times(AnyNumber()); + { + EXPECT_CALL(*mockPlugin, compile_model(_, _, _)).Times(m_remoteContext ? 1 : 0); + EXPECT_CALL(*mockPlugin, compile_model(A&>(), _)) + .Times(!m_remoteContext ? 1 : 0); + EXPECT_CALL(*mockPlugin, import_model(A(), _, _)).Times(0); + EXPECT_CALL(*mockPlugin, import_model(A(), _)).Times(0); + m_post_mock_net_callbacks.emplace_back([&](MockICompiledModelImpl& net) { + EXPECT_CALL(net, export_model(_)).Times(1).WillOnce(Invoke([&](std::ostream&) { + OPENVINO_THROW("export failed"); + })); + }); + testLoad([&](ov::Core& core) { + core.set_property(ov::cache_dir(m_cacheDir)); + OV_ASSERT_NO_THROW(m_testFunction(core)); + EXPECT_FALSE(std::filesystem::exists(ov::util::make_path(m_cacheDir)) && + std::filesystem::directory_iterator(ov::util::make_path(m_cacheDir)) != + std::filesystem::directory_iterator{}); + }); + } +} + +TEST_P(CachingTest, TestIgnoreStreamExportFailure) { + EXPECT_CALL(*mockPlugin, get_property(ov::supported_properties.name(), _)).Times(AnyNumber()); + EXPECT_CALL(*mockPlugin, get_property(ov::device::capability::EXPORT_IMPORT, _)).Times(AnyNumber()); + EXPECT_CALL(*mockPlugin, get_property(ov::device::architecture.name(), _)).Times(AnyNumber()); + EXPECT_CALL(*mockPlugin, get_property(ov::internal::supported_properties.name(), _)).Times(AnyNumber()); + EXPECT_CALL(*mockPlugin, get_property(ov::internal::caching_properties.name(), _)).Times(AnyNumber()); + EXPECT_CALL(*mockPlugin, get_property(ov::device::capabilities.name(), _)).Times(AnyNumber()); + { + EXPECT_CALL(*mockPlugin, compile_model(_, _, _)).Times(m_remoteContext ? 1 : 0); + EXPECT_CALL(*mockPlugin, compile_model(A&>(), _)) + .Times(!m_remoteContext ? 1 : 0); + EXPECT_CALL(*mockPlugin, import_model(A(), _, _)).Times(0); + EXPECT_CALL(*mockPlugin, import_model(A(), _)).Times(0); + m_post_mock_net_callbacks.emplace_back([&](MockICompiledModelImpl& net) { + EXPECT_CALL(net, export_model(_)).Times(1).WillOnce(Invoke([&](std::ostream& stream) { + stream << "partial"; + stream.setstate(std::ios_base::badbit); + })); + }); + testLoad([&](ov::Core& core) { + core.set_property(ov::cache_dir(m_cacheDir)); + OV_ASSERT_NO_THROW(m_testFunction(core)); + EXPECT_FALSE(std::filesystem::exists(ov::util::make_path(m_cacheDir)) && + std::filesystem::directory_iterator(ov::util::make_path(m_cacheDir)) != + std::filesystem::directory_iterator{}); }); } } diff --git a/src/inference/tests/unit/cache_manager.cpp b/src/inference/tests/unit/cache_manager.cpp new file mode 100644 index 00000000000000..4d816ed9ba7b38 --- /dev/null +++ b/src/inference/tests/unit/cache_manager.cpp @@ -0,0 +1,124 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "cache_manager.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include "common_test_utils/common_utils.hpp" + +namespace ov::test { +namespace { + +std::string read_blob(const std::filesystem::path& path) { + std::ifstream stream(path, std::ios::binary); + return {std::istreambuf_iterator(stream), std::istreambuf_iterator()}; +} + +class FlushFailingStreambuf final : public std::streambuf { +public: + explicit FlushFailingStreambuf(std::streambuf* delegate) : m_delegate(delegate) {} + +protected: + std::streamsize xsputn(const char* s, std::streamsize count) override { + return m_delegate->sputn(s, count); + } + + int_type overflow(int_type ch) override { + if (traits_type::eq_int_type(ch, traits_type::eof())) { + return traits_type::not_eof(ch); + } + return m_delegate->sputc(traits_type::to_char_type(ch)); + } + + int sync() override { + return -1; + } + +private: + std::streambuf* m_delegate; +}; + +class FileStorageCacheManagerTest : public ::testing::Test { +protected: + std::filesystem::path m_cache_dir; + std::unique_ptr m_cache_manager; + + void SetUp() override { + m_cache_dir = ov::test::utils::generateTestFilePrefix(); + m_cache_manager = std::make_unique(m_cache_dir); + } + + void TearDown() override { + m_cache_manager.reset(); + std::filesystem::remove_all(m_cache_dir); + } + + std::filesystem::path blob_path(const std::string& id) const { + return m_cache_dir / (id + ".blob"); + } +}; + +TEST_F(FileStorageCacheManagerTest, RemovesBlobAfterWriteFailureWhenCallerCleansUp) { + EXPECT_THROW(m_cache_manager->write_cache_entry("1", + [&](std::ostream& stream) { + stream << "partial"; + throw std::runtime_error("write failed"); + }), + std::runtime_error); + + EXPECT_NO_THROW(m_cache_manager->remove_cache_entry("1")); + EXPECT_FALSE(std::filesystem::exists(blob_path("1"))); +} + +TEST_F(FileStorageCacheManagerTest, RemovesBlobAfterStreamFailureWhenCallerCleansUp) { + EXPECT_THROW(m_cache_manager->write_cache_entry("2", + [&](std::ostream& stream) { + stream << "partial"; + stream.setstate(std::ios_base::badbit); + }), + std::ios_base::failure); + + EXPECT_NO_THROW(m_cache_manager->remove_cache_entry("2")); + EXPECT_FALSE(std::filesystem::exists(blob_path("2"))); +} + +TEST_F(FileStorageCacheManagerTest, OverwritesExistingBlobWhenEntryAlreadyExists) { + m_cache_manager->write_cache_entry("7", [&](std::ostream& stream) { + stream << "cached"; + }); + + bool writer_called = false; + EXPECT_NO_THROW(m_cache_manager->write_cache_entry("7", [&](std::ostream& stream) { + writer_called = true; + stream << "new"; + })); + + EXPECT_TRUE(writer_called); + EXPECT_EQ(read_blob(blob_path("7")), "new"); +} + +TEST_F(FileStorageCacheManagerTest, DoesNotLeaveAuxiliaryFilesInCacheDirectory) { + m_cache_manager->write_cache_entry("8", [&](std::ostream& stream) { + stream << "cached"; + }); + + std::vector entries; + for (const auto& entry : std::filesystem::directory_iterator(m_cache_dir)) { + entries.push_back(entry.path().filename()); + } + + ASSERT_EQ(entries.size(), 1); + EXPECT_EQ(entries.front(), std::filesystem::path{"8.blob"}); +} + +} // namespace +} // namespace ov::test From c0bfee7023318764fca546887d905a9dc0e78e51 Mon Sep 17 00:00:00 2001 From: Mikolaj Roszczyk Date: Tue, 21 Apr 2026 08:49:53 +0200 Subject: [PATCH 002/545] [GPU] L0-oriented tests fixing (#35111) ### Details: - fixing segmentation fault for convolution tests in Level Zero runtime - changing ```ASSERT_EQ(1,1); return;``` to ```GTEST_SKIP()``` for skipping in unit tests - adding ```get_context()``` method to ```ze_stream``` - ```GPU_DEBUG_INFO``` for ```ze_stream::flush()``` method in Level Zero - avoiding program termination while stack unwinding (in ```OV_ZE_EXPECT```) ### Tickets: - CVS-179218 ### AI Assistance: - *AI assistance used: yes* --- .../intel_gpu/src/runtime/ze/ze_common.hpp | 24 ++-- .../intel_gpu/src/runtime/ze/ze_stream.cpp | 6 +- .../intel_gpu/src/runtime/ze/ze_stream.hpp | 1 + .../unit/fusions/convolution_fusion_test.cpp | 4 +- .../unit/test_cases/convolution_gpu_test.cpp | 112 +++++------------- .../unit/test_cases/eltwise_gpu_test.cpp | 3 +- .../test_cases/fully_connected_gpu_test.cpp | 10 +- .../unit/test_cases/reorder_gpu_test.cpp | 19 +-- 8 files changed, 60 insertions(+), 119 deletions(-) diff --git a/src/plugins/intel_gpu/src/runtime/ze/ze_common.hpp b/src/plugins/intel_gpu/src/runtime/ze/ze_common.hpp index e15ea385a4f9cd..ffdfc9580ea80e 100644 --- a/src/plugins/intel_gpu/src/runtime/ze/ze_common.hpp +++ b/src/plugins/intel_gpu/src/runtime/ze/ze_common.hpp @@ -12,16 +12,22 @@ #include #include #include +#include -// Expect success of level zero command, throw runtime error otherwise -#define OV_ZE_EXPECT(f) \ - do { \ - ze_result_t res_ = (f); \ - if (res_ != ZE_RESULT_SUCCESS) { \ - std::stringstream s; \ - s << std::hex << res_; \ - throw std::runtime_error(#f " command failed with code " + s.str()); \ - } \ +// Expect success of level zero command, throw runtime error otherwise. +// if already handling an exception (stack unwinding), logging a warning instead to avoid termination. +#define OV_ZE_EXPECT(f) \ + do { \ + ze_result_t res_ = (f); \ + if (res_ != ZE_RESULT_SUCCESS) { \ + std::stringstream s; \ + s << std::hex << res_; \ + if (std::uncaught_exceptions() > 0) { \ + GPU_DEBUG_INFO << ("[GPU] " #f " command failed with code " + s.str() + " (during stack unwinding)"); \ + } else { \ + OPENVINO_THROW(#f " command failed with code " + s.str()); \ + } \ + } \ } while (false) // Prints warning if level zero command does not return success result diff --git a/src/plugins/intel_gpu/src/runtime/ze/ze_stream.cpp b/src/plugins/intel_gpu/src/runtime/ze/ze_stream.cpp index 5aa45c2502ab9e..9602c480d54902 100644 --- a/src/plugins/intel_gpu/src/runtime/ze/ze_stream.cpp +++ b/src/plugins/intel_gpu/src/runtime/ze/ze_stream.cpp @@ -348,7 +348,7 @@ std::unique_ptr ze_stream::create_surfaces_lock(const std::vector } void ze_stream::flush() const { - // Immediate Command List submits commands immediately - no flush impl + GPU_DEBUG_INFO << "Immediate Command List submits commands immediately - no flush impl"; } void ze_stream::finish() const { @@ -400,6 +400,10 @@ void ze_stream::sync_events(std::vector const& deps, bool is_output) } } +ze_context_handle_t ze_stream::get_context() const { + return _engine.get_context(); +} + #ifdef ENABLE_ONEDNN_FOR_GPU dnnl::stream& ze_stream::get_onednn_stream() { OPENVINO_ASSERT(m_queue_type == QueueTypes::in_order, "[GPU] Can't create onednn stream handle as onednn doesn't support out-of-order queue"); diff --git a/src/plugins/intel_gpu/src/runtime/ze/ze_stream.hpp b/src/plugins/intel_gpu/src/runtime/ze/ze_stream.hpp index 0b34c81bedccb6..b7d14967849070 100644 --- a/src/plugins/intel_gpu/src/runtime/ze/ze_stream.hpp +++ b/src/plugins/intel_gpu/src/runtime/ze/ze_stream.hpp @@ -50,6 +50,7 @@ class ze_stream : public stream { event::ptr create_user_event(bool set) override; event::ptr create_base_event() override; std::unique_ptr create_surfaces_lock(const std::vector &mem) const override; + ze_context_handle_t get_context() const; #ifdef ENABLE_ONEDNN_FOR_GPU dnnl::stream& get_onednn_stream() override; diff --git a/src/plugins/intel_gpu/tests/unit/fusions/convolution_fusion_test.cpp b/src/plugins/intel_gpu/tests/unit/fusions/convolution_fusion_test.cpp index 8983a17a98ad62..614f80a23e3486 100644 --- a/src/plugins/intel_gpu/tests/unit/fusions/convolution_fusion_test.cpp +++ b/src/plugins/intel_gpu/tests/unit/fusions/convolution_fusion_test.cpp @@ -2444,9 +2444,7 @@ INSTANTIATE_TEST_SUITE_P(fusings_gpu, conv_int8_scale_quantize_i8, ::testing::Va class conv_int8_scale_quantize_i8_conv_b_fs_yx_fsv4_int8 : public ConvFusingTest {}; TEST_P(conv_int8_scale_quantize_i8_conv_b_fs_yx_fsv4_int8, basic) { if (engine.get_device_info().supports_immad) { - std::cout << "[ SKIPPED ] The test is skipped (not targeting for onednn path)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (not targeting for onednn path)."; } auto p = GetParam(); diff --git a/src/plugins/intel_gpu/tests/unit/test_cases/convolution_gpu_test.cpp b/src/plugins/intel_gpu/tests/unit/test_cases/convolution_gpu_test.cpp index 4b855b6bc6f4d2..34cb1b8eb97850 100644 --- a/src/plugins/intel_gpu/tests/unit/test_cases/convolution_gpu_test.cpp +++ b/src/plugins/intel_gpu/tests/unit/test_cases/convolution_gpu_test.cpp @@ -5002,9 +5002,7 @@ TEST(convolution_gpu, basic_yxfb_4_4_yxfb_2_2_b16_if2_of16_st2_2_p0_sp1_fp16) auto& engine = get_test_engine(); if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const auto input_format = format::yxfb; @@ -5398,9 +5396,7 @@ TEST_P(convolution_gpu_fs_byx_fsv32, fs_byx_fsv32) auto& engine = get_test_engine(); if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } if (engine.get_device_info().supports_immad) { @@ -5554,9 +5550,7 @@ TEST(convolution_f16_fsv_gpu, convolution_f16_fsv_gpu_padding) { auto& engine = get_test_engine(); if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const int batch_num = 2; @@ -5663,9 +5657,7 @@ TEST(convolution_f16_fsv16, preload_groups_when_groups_is_not_greater_than_one) tests::random_generator rg(GET_SUITE_NAME); auto& engine = get_test_engine(); if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const int batch_num = 1; const int input_xy = 5; @@ -5772,9 +5764,7 @@ TEST_P(convolution_gpu_fs_byx_fsv32_crop, fs_byx_fsv32_crop) auto& engine = get_test_engine(); if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const int batch_num = 4; @@ -6071,9 +6061,7 @@ TEST(convolution_gpu, bfyx_iyxo_5x5_fp16) auto& engine = get_test_engine(); if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const int batch_num = 1; @@ -6447,9 +6435,7 @@ TEST_P(convolution_gpu_block_layout3D, bfzyx_bsv16_fsv16_fp16) auto& engine = get_test_engine(); if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const int batch_num = testing::get<0>(GetParam()); @@ -6762,9 +6748,7 @@ TEST_P(convolution_gpu_block_layout, bfyx_bsv16_fsv16_fp32) if (batch_num <= 16) { - std::cout << "[ SKIPPED ] The test is skipped (for bs_fs_yx_bsv16_fsv16 batch should be greater than 16)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (for bs_fs_yx_bsv16_fsv16 batch should be greater than 16)."; } auto input_size = tensor(batch_num, input_f, input_xy, input_xy); @@ -6884,9 +6868,7 @@ TEST_P(convolution_gpu_block_layout, bfyx_bsv16_fsv16_fp16) auto& engine = get_test_engine(); if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const int batch_num = testing::get<0>(GetParam()); @@ -6901,9 +6883,7 @@ TEST_P(convolution_gpu_block_layout, bfyx_bsv16_fsv16_fp16) if (batch_num % 32 != 0) { - std::cout << "[ SKIPPED ] The test is skipped (for fp16 batch should be multiple of 32)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (for fp16 batch should be multiple of 32)."; } tests::random_generator rg(GET_SUITE_NAME); @@ -7025,9 +7005,7 @@ TEST_P(convolution_gpu_block_layout, bfyx_bsv16_fsv16_fp32_fused_ops) auto& engine = get_test_engine(); if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const int batch_num = testing::get<0>(GetParam()) * 2; @@ -7200,9 +7178,7 @@ TEST_P(convolution_depthwise_gpu, depthwise_conv_fs_b_yx_fsv32) auto& engine = get_test_engine(); if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } if (engine.get_device_info().supports_immad) { // This test is not targeting for onednn case @@ -7347,9 +7323,7 @@ TEST_P(convolution_depthwise_gpu_fsv16, depthwise_conv_b_fs_yx_fsv16) auto& engine = get_test_engine(); if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const int batch_num = 2; @@ -7478,9 +7452,7 @@ TEST_P(convolution_depthwise_gpu_fsv16_xy, depthwise_conv_b_fs_yx_fsv16) auto& engine = get_test_engine(); if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const int batch_num = 2; @@ -7695,9 +7667,7 @@ TEST_P(convolution_depthwise_gpu_bfyx, depthwise_conv_bfyx) auto& engine = get_test_engine(); if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const int batch_num = 2; @@ -8148,14 +8118,10 @@ TEST_P(convolution_general_gpu, conv_fp16_cases) { auto& engine = get_test_engine(); if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } if (engine.get_device_info().supports_immad) { - std::cout << "[ SKIPPED ] The test is skipped (not targeting for onednn path)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (not targeting for onednn path)."; } const int input_x = testing::get<0>(GetParam()), @@ -8317,9 +8283,7 @@ TEST_P(convolution_gpu_fsv16_to_bfyx, conv_b_fs_yx_fsv16_to_bfyx_padding) if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const int input_b = testing::get<10>(GetParam()); @@ -8417,9 +8381,7 @@ TEST_P(convolution_gpu_fsv16_to_bfyx, conv_b_fs_yx_fsv16_to_bfyx_different_type) if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const int input_b = testing::get<10>(GetParam()); @@ -9866,9 +9828,7 @@ TEST_P(convolution_gpu_onednn, conv_onednn_cases) { if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const int input_x = testing::get<0>(GetParam()), @@ -11228,9 +11188,7 @@ TEST(convolution_gpu_onednn, dyn_conv4d_reshape6d_pattern) { if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } const int input_x = 8, input_y = 8, input_f = 32, output_f = 32, filter_x = 3, filter_y = 3, groups = 1, batch_num = 1; @@ -11801,9 +11759,7 @@ TEST_P(conv_dyn_test, convolution_gpu_bfyx_os_iyx_osv32_no_bias) { auto p = GetParam(); if (p.groups > 1) { - std::cout << "[ SKIPPED ] The test is skipped (group convolution is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (group convolution is not supported)."; } auto groups_num = 1; @@ -11904,9 +11860,7 @@ TEST_P(conv_dyn_test, convolution_gpu_fsv16_1x1_no_bias) { auto is_grouped = p.wei_shape.size() == 5; if (is_grouped) { - std::cout << "[ SKIPPED ] The test is skipped (group convolution is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (group convolution is not supported)."; } auto groups_num = 1; @@ -11917,16 +11871,12 @@ TEST_P(conv_dyn_test, convolution_gpu_fsv16_1x1_no_bias) { && std::all_of(p.pad_end.begin(), p.pad_end.end(), [](int i) { return i == 0; }); if (!is_weight_1x1 || !is_valid_output || !is_valid_strid || !is_valid_padding) { - std::cout << "[ SKIPPED ] The test is skipped (is_weight_1x1: " << is_weight_1x1 << ", is_valid_output: " << is_valid_output - << ", is_valid_strid: " << is_valid_strid << ", is_valid_padding: " << is_valid_padding << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (is_weight_1x1: " << is_weight_1x1 << ", is_valid_output: " << is_valid_output + << ", is_valid_strid: " << is_valid_strid << ", is_valid_padding: " << is_valid_padding; } if (!engine.get_device_info().supports_immad && p.in_shape[1] > 16) { - std::cout << "[ SKIPPED ] The test is skipped (convolution_fsv16_1x1 static kernel has accuracy issue with input feature > 16 in igpu)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (convolution_fsv16_1x1 static kernel has accuracy issue with input feature > 16 in igpu)."; } auto calculate_ref = [&](memory::ptr input, memory::ptr weights, ExecutionConfig config) { @@ -12033,9 +11983,7 @@ TEST_P(conv_dyn_test, convolution_gpu_fsv16_depthwise_quantized) { auto is_depthwise = p.in_shape[1] == p.groups; if (!is_depthwise) { - std::cout << "[ SKIPPED ] The test is skipped (convolution is not depthwise)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (convolution is not depthwise)."; } auto is_valid_output = p.wei_shape[0] % 16 == 0; @@ -12043,12 +11991,10 @@ TEST_P(conv_dyn_test, convolution_gpu_fsv16_depthwise_quantized) { auto is_valid_padding = all_zeroes(p.pad_begin) && all_zeroes(p.pad_end); auto is_valid_ranks = p.in_shape.size() == p.wei_shape.size() - 1; if (!is_valid_output || !is_valid_strid || !is_valid_padding || !is_valid_ranks) { - std::cout << "[ SKIPPED ] The test is skipped (is_valid_output: " << is_valid_output + GTEST_SKIP() << "The test is skipped (is_valid_output: " << is_valid_output << ", is_valid_strid: " << is_valid_strid << ", is_valid_padding: " << is_valid_padding - << ", is_valid_ranks: " << is_valid_ranks << std::endl; - ASSERT_EQ(1, 1); - return; + << ", is_valid_ranks: " << is_valid_ranks; } auto calculate_ref = [&](memory::ptr input, memory::ptr weights, diff --git a/src/plugins/intel_gpu/tests/unit/test_cases/eltwise_gpu_test.cpp b/src/plugins/intel_gpu/tests/unit/test_cases/eltwise_gpu_test.cpp index 92dc429cf89f6b..5521115a807271 100644 --- a/src/plugins/intel_gpu/tests/unit/test_cases/eltwise_gpu_test.cpp +++ b/src/plugins/intel_gpu/tests/unit/test_cases/eltwise_gpu_test.cpp @@ -3598,8 +3598,7 @@ TEST(eltwise_gpu_f16, fs_b_yx_fsv32_broadcast_bfyx) auto& engine = get_test_engine(); bool f16_supported = engine.get_device_info().supports_fp16; if (!f16_supported) { - std::cout << "[ SKIPPED ] float16 combinations are skipped (cl_khr_fp16 is not supported)." << std::endl; - return; + GTEST_SKIP() << "float16 combinations are skipped (cl_khr_fp16 is not supported)."; } tensor::value_type input_b = 2; diff --git a/src/plugins/intel_gpu/tests/unit/test_cases/fully_connected_gpu_test.cpp b/src/plugins/intel_gpu/tests/unit/test_cases/fully_connected_gpu_test.cpp index 44f4befc817376..8f14f3c1b5fa45 100644 --- a/src/plugins/intel_gpu/tests/unit/test_cases/fully_connected_gpu_test.cpp +++ b/src/plugins/intel_gpu/tests/unit/test_cases/fully_connected_gpu_test.cpp @@ -175,7 +175,7 @@ TEST(DISABLED_fully_connected_gpu, generic_random_short) { auto& engine = get_test_engine(); bool f16_supported = !!engine.get_device_info().supports_fp16; if (!f16_supported) { - std::cout << "[ SKIPPED ] float16 combinations are skipped (cl_khr_fp16 is not supported)." << std::endl; + GTEST_SKIP() << "float16 combinations are skipped (cl_khr_fp16 is not supported)."; } for (cldnn::format test_input_fmt : test_input_fmts) { @@ -1084,9 +1084,7 @@ TEST(fully_connected_gpu, DISABLED_fs_byx_fsv32_b12) { if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } // Test parameters const int batch_num = 12; @@ -1288,9 +1286,7 @@ TEST(fully_connected_gpu, DISABLED_fs_byx_fsv32_b34) if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } // Test parameters const int batch_num = 34; diff --git a/src/plugins/intel_gpu/tests/unit/test_cases/reorder_gpu_test.cpp b/src/plugins/intel_gpu/tests/unit/test_cases/reorder_gpu_test.cpp index f38162dbb6f875..3b76944f51c089 100644 --- a/src/plugins/intel_gpu/tests/unit/test_cases/reorder_gpu_test.cpp +++ b/src/plugins/intel_gpu/tests/unit/test_cases/reorder_gpu_test.cpp @@ -666,9 +666,7 @@ TEST(reorder_gpu_f16, basic_subtract_f32_output_f32) { if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } auto input = engine.allocate_memory({ data_types::f16, format::yxfb, { 2, 2, 2, 2 } }); @@ -758,11 +756,8 @@ TEST(reorder_gpu_f16, basic_subtract_value) { // auto& engine = get_test_engine(); - if (!engine.get_device_info().supports_fp16) - { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + if (!engine.get_device_info().supports_fp16) { + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } auto input = engine.allocate_memory({ data_types::f16, format::yxfb, { 2, 2, 2, 2 } }); @@ -830,9 +825,7 @@ TEST(reorder_gpu, basic_convert_f16_f32_f16) { if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } std::vector expected_values; @@ -1015,9 +1008,7 @@ TEST(reorder_gpu, basic_convert_uint8rgbabyxf_to_fp32_bfyx) { if (!engine.get_device_info().supports_fp16) { - std::cout << "[ SKIPPED ] The test is skipped (cl_khr_fp16 is not supported)." << std::endl; - ASSERT_EQ(1, 1); - return; + GTEST_SKIP() << "The test is skipped (cl_khr_fp16 is not supported)."; } std::initializer_list input_i8 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, From 1ad63191f103059ea6e5f532a975a9a9c6d955a1 Mon Sep 17 00:00:00 2001 From: Paul Youngsoo Ahn Date: Tue, 21 Apr 2026 16:33:27 +0900 Subject: [PATCH 003/545] [GPU] Fix implicit narrowing conversion warnings (C4244) in GPU plugin (#35419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Details: - Fix implicit narrowing conversions (C4244) in GPU plugin to prepare for /wd4244 removal (BinSkim BA2007 compliance) - loop.hpp: Change constructor param from int64_t to int32_t with OPENVINO_ASSERT range check at caller - memory_pool.cpp/hpp: Change debug accumulators from float to size_t (float loses precision above 16MB) - swiglu_with_clamp.hpp / swiglu_kernel_base.h: Correct setter and fuse_params types to float to match member types - paged_attention_gen.cpp: Use float sqrt path instead of double for scale_factor - custom_gpu_primitive.hpp: Add static_cast in GetDim macro to prevent int64_t→int narrowing ### Tickets: - CVS-185007 ### AI Assistance: - AI assistance used: yes - GitHub Copilot used for code analysis (identifying root causes of each warning, evaluating fix options for type safety vs. serialization compatibility), generating fix code, and verifying build results. All changes were human-reviewed for correctness, validated via local /WX incremental build, and confirmed to have no performance or accuracy impact. --- .../intel_gpu/op/swiglu_with_clamp.hpp | 4 ++-- .../primitives/custom_gpu_primitive.hpp | 10 ++++----- .../include/intel_gpu/primitives/loop.hpp | 2 +- .../include/intel_gpu/runtime/memory_pool.hpp | 12 +++++----- .../intel_gpu/src/graph/debug_helper.cpp | 2 +- .../graph/impls/cm/paged_attention_gen.cpp | 4 ++-- .../kernels/swiglu/swiglu_kernel_base.h | 2 +- src/plugins/intel_gpu/src/plugin/ops/loop.cpp | 7 +++++- .../intel_gpu/src/runtime/memory_pool.cpp | 22 +++++++++---------- .../tests/unit/fusions/loop_fusion_test.cpp | 2 +- .../tests/unit/test_cases/loop_gpu_test.cpp | 2 +- 11 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/plugins/intel_gpu/include/intel_gpu/op/swiglu_with_clamp.hpp b/src/plugins/intel_gpu/include/intel_gpu/op/swiglu_with_clamp.hpp index a8d876632052bf..e4fd2b223acc70 100644 --- a/src/plugins/intel_gpu/include/intel_gpu/op/swiglu_with_clamp.hpp +++ b/src/plugins/intel_gpu/include/intel_gpu/op/swiglu_with_clamp.hpp @@ -82,10 +82,10 @@ class SwiGluWithClamp : public ov::op::Op { void set_gate_idx(size_t gate_idx) { m_gate_idx = gate_idx; } - void set_clamp_min(int64_t min) { + void set_clamp_min(float min) { m_clamp_min = min; } - void set_clamp_max(int64_t max) { + void set_clamp_max(float max) { m_clamp_max = max; } void set_swiglu_beta(float beta) { diff --git a/src/plugins/intel_gpu/include/intel_gpu/primitives/custom_gpu_primitive.hpp b/src/plugins/intel_gpu/include/intel_gpu/primitives/custom_gpu_primitive.hpp index b2ac166dd0a4c4..0ebe2ebc75ba86 100644 --- a/src/plugins/intel_gpu/include/intel_gpu/primitives/custom_gpu_primitive.hpp +++ b/src/plugins/intel_gpu/include/intel_gpu/primitives/custom_gpu_primitive.hpp @@ -55,7 +55,7 @@ struct custom_gpu_primitive : public primitive_base { const std::vector& localSizeRules, std::vector& gws, std::vector& lws) { -#define GetDim(DIM) DIM.is_dynamic() ? -1 : DIM.get_length() +#define GetDim(DIM) static_cast(DIM.is_dynamic() ? -1 : DIM.get_length()) gws.clear(); lws.clear(); @@ -63,10 +63,10 @@ struct custom_gpu_primitive : public primitive_base { int batchDim = 0, featureDim = 0, yDim = 0, xDim = 0; // if calcWgDimInputIdx is greater than -1, take dimension from input if (calcWgDimInputIdx >= 0) { - xDim = static_cast(GetDim(inputDims[inputDims.size() - 1])); - yDim = dims.size() > 1 ? static_cast(GetDim(inputDims[inputDims.size() - 2])) : 0; - featureDim = dims.size() > 2 ? static_cast(GetDim(inputDims[inputDims.size() - 3])) : 0; - batchDim = dims.size() > 3 ? static_cast(GetDim(inputDims[inputDims.size() - 4])) : 0; + xDim = GetDim(inputDims[inputDims.size() - 1]); + yDim = dims.size() > 1 ? GetDim(inputDims[inputDims.size() - 2]) : 0; + featureDim = dims.size() > 2 ? GetDim(inputDims[inputDims.size() - 3]) : 0; + batchDim = dims.size() > 3 ? GetDim(inputDims[inputDims.size() - 4]) : 0; } else { batchDim = (dims.size() > 0) ? GetDim(dims[0]) : 1; featureDim = (dims.size() > 1) ? GetDim(dims[1]) : 1; diff --git a/src/plugins/intel_gpu/include/intel_gpu/primitives/loop.hpp b/src/plugins/intel_gpu/include/intel_gpu/primitives/loop.hpp index 7e1f23d0ae8c83..9a7da0bd804270 100644 --- a/src/plugins/intel_gpu/include/intel_gpu/primitives/loop.hpp +++ b/src/plugins/intel_gpu/include/intel_gpu/primitives/loop.hpp @@ -192,7 +192,7 @@ struct loop : public primitive_base { const std::vector& input_primitive_maps, const std::vector& output_primitive_maps, const std::vector& back_edges, - int64_t max_num_iterations = -1, + int32_t max_num_iterations = -1, const primitive_id& body_current_iteration_id = primitive_id(), const primitive_id& body_execution_condition_id = primitive_id(), const size_t num_outputs = 1) diff --git a/src/plugins/intel_gpu/include/intel_gpu/runtime/memory_pool.hpp b/src/plugins/intel_gpu/include/intel_gpu/runtime/memory_pool.hpp index 607200f7dd7aea..f6e42f716d1a6d 100644 --- a/src/plugins/intel_gpu/include/intel_gpu/runtime/memory_pool.hpp +++ b/src/plugins/intel_gpu/include/intel_gpu/runtime/memory_pool.hpp @@ -205,12 +205,12 @@ class memory_pool { #ifdef GPU_DEBUG_CONFIG std::vector _no_reusable_mems; - float total_mem_size_non_padded_pool = 0.f; - float total_mem_size_padded_pool = 0.f; - float total_mem_size_no_reusable = 0.f; - float mem_size_non_padded_pool_host = 0.f; - float mem_size_padded_pool_host = 0.f; - float mem_size_no_reusable_host = 0.f; + size_t total_mem_size_non_padded_pool = 0; + size_t total_mem_size_padded_pool = 0; + size_t total_mem_size_no_reusable = 0; + size_t mem_size_non_padded_pool_host = 0; + size_t mem_size_padded_pool_host = 0; + size_t mem_size_no_reusable_host = 0; #endif }; diff --git a/src/plugins/intel_gpu/src/graph/debug_helper.cpp b/src/plugins/intel_gpu/src/graph/debug_helper.cpp index 56c0fb4a4b5a72..129ad9469bf80e 100644 --- a/src/plugins/intel_gpu/src/graph/debug_helper.cpp +++ b/src/plugins/intel_gpu/src/graph/debug_helper.cpp @@ -691,7 +691,7 @@ NetworkDebugHelper::~NetworkDebugHelper() { } if (!config.get_dump_graphs_path().empty() && is_target_iteration(m_iter, config.get_dump_iterations())) { - auto get_fixed_str = [](int value, int length = 2) -> std::string { + auto get_fixed_str = [](size_t value, int length = 2) -> std::string { std::ostringstream ss; ss << std::setw(length) << std::setfill('0') << std::to_string(value); return ss.str(); diff --git a/src/plugins/intel_gpu/src/graph/impls/cm/paged_attention_gen.cpp b/src/plugins/intel_gpu/src/graph/impls/cm/paged_attention_gen.cpp index 95b8fb61a15da2..1b195f566fec4b 100644 --- a/src/plugins/intel_gpu/src/graph/impls/cm/paged_attention_gen.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/cm/paged_attention_gen.cpp @@ -321,7 +321,7 @@ Arguments PagedAttentionGeneratorMultiToken::get_arguments_desc(const kernel_imp JitConstants PagedAttentionGeneratorMultiToken::get_jit_constants(const kernel_impl_params& params) const { auto jit = PagedAttentionGeneratorBase::get_jit_constants(params); const auto desc = params.typed_desc(); - const float scale_factor = 1.0 / std::sqrt(static_cast(desc->k_head_size)); + const float scale_factor = 1.0f / std::sqrt(static_cast(desc->k_head_size)); OPENVINO_ASSERT(_xattn_block_size == 1 || _xattn_block_size == 128 || _xattn_block_size == 256, "Unsupported xattention block size for multi token kernel: ", _xattn_block_size); @@ -396,7 +396,7 @@ JitConstants PagedAttentionGeneratorSingleToken::get_jit_constants(const kernel_ auto jit = PagedAttentionGeneratorBase::get_jit_constants(params); // jit.add(make_jit_constant("KERNEL_NAME", get_entry_point(params))); auto desc = params.typed_desc(); - const float scale_factor = 1.0 / std::sqrt(static_cast(desc->k_head_size)); + const float scale_factor = 1.0f / std::sqrt(static_cast(desc->k_head_size)); const size_t kv_partition_size = get_partition_size(desc->has_xattention); jit.make("KV_PARTITION_SIZE", kv_partition_size); if (desc->has_xattention) { diff --git a/src/plugins/intel_gpu/src/kernel_selector/kernels/swiglu/swiglu_kernel_base.h b/src/plugins/intel_gpu/src/kernel_selector/kernels/swiglu/swiglu_kernel_base.h index b58808db9f3f26..2e240717f92ee7 100644 --- a/src/plugins/intel_gpu/src/kernel_selector/kernels/swiglu/swiglu_kernel_base.h +++ b/src/plugins/intel_gpu/src/kernel_selector/kernels/swiglu/swiglu_kernel_base.h @@ -34,7 +34,7 @@ struct swiglu_params : public base_params { }; struct swiglu_fuse_params : fuse_params { - explicit swiglu_fuse_params(int32_t axis, size_t glu_stride, size_t gate_idx, size_t clamp_min, size_t clamp_max, float swish_beta, float up_add_val) + explicit swiglu_fuse_params(int32_t axis, size_t glu_stride, size_t gate_idx, float clamp_min, float clamp_max, float swish_beta, float up_add_val) : fuse_params(KernelType::SWIGLU), axis(axis), glu_stride(glu_stride), diff --git a/src/plugins/intel_gpu/src/plugin/ops/loop.cpp b/src/plugins/intel_gpu/src/plugin/ops/loop.cpp index c1a58b4e7b8609..e16012cf04c5ee 100644 --- a/src/plugins/intel_gpu/src/plugin/ops/loop.cpp +++ b/src/plugins/intel_gpu/src/plugin/ops/loop.cpp @@ -20,6 +20,7 @@ #include #include +#include using Loop = ov::op::v5::Loop; using TensorIterator = ov::op::v0::TensorIterator; @@ -209,7 +210,11 @@ static void CreateCommonLoopOp(ProgramBuilder& p, const std::shared_ptris_dynamic(); - int64_t num_iterations = op->get_num_iterations(); + OPENVINO_ASSERT(op->get_num_iterations() >= std::numeric_limits::min() && + op->get_num_iterations() <= std::numeric_limits::max(), + "num_iterations (", op->get_num_iterations(), ") exceeds int32_t range"); + + int32_t num_iterations = static_cast(op->get_num_iterations()); auto num_outputs = is_dynamic? op->get_output_size() : 1; auto ov_model = op->get_function(); diff --git a/src/plugins/intel_gpu/src/runtime/memory_pool.cpp b/src/plugins/intel_gpu/src/runtime/memory_pool.cpp index 5350815b3bf80a..f57c6f4a1c46f0 100644 --- a/src/plugins/intel_gpu/src/runtime/memory_pool.cpp +++ b/src/plugins/intel_gpu/src/runtime/memory_pool.cpp @@ -419,11 +419,11 @@ memory_pool::memory_pool(engine& engine, const ExecutionConfig& config) : _engin inline std::string get_mb_size(size_t size) { if (size == 0) return "0 MB"; - return std::to_string(static_cast(size) / (1024 * 1024)) + " MB"; + return std::to_string(static_cast(size) / (1024.f * 1024.f)) + " MB"; } inline float get_utilization(size_t size, size_t total_size) { - return (static_cast(size) * 100.0f / total_size); + return (static_cast(size) * 100.0f / static_cast(total_size)); } #endif @@ -485,8 +485,8 @@ void memory_pool::dump_to_file(uint32_t net_id, uint32_t iter, std::string dump_ void memory_pool::dump_to_screen(uint32_t net_id, uint32_t iter) { #ifdef GPU_DEBUG_CONFIG GPU_DEBUG_COUT << "Dump memory pool of network (net_id : " << net_id << ", iter : " << iter << ")" << std::endl; - float total_requested_mem_non_padded_pool = 0.f; - float total_requested_mem_padded_pool = 0.f; + size_t total_requested_mem_non_padded_pool = 0; + size_t total_requested_mem_padded_pool = 0; { GPU_DEBUG_COUT << "========== non-padded pool ( " << _non_padded_pool.size() << " records) ==========" << std::endl; @@ -499,7 +499,7 @@ void memory_pool::dump_to_screen(uint32_t net_id, uint32_t iter) { float utilization = get_utilization(user._mem_size, mem.first); min_utilization = std::min(utilization, min_utilization); max_utilization = std::max(utilization, max_utilization); - total_requested_mem_non_padded_pool += static_cast(user._mem_size); + total_requested_mem_non_padded_pool += user._mem_size; GPU_DEBUG_COUT << " --- " << user._prim_id << " (" << user._unique_id << "), " << get_mb_size(user._mem_size) << ", " << utilization << "%" << std::endl; } @@ -522,7 +522,7 @@ void memory_pool::dump_to_screen(uint32_t net_id, uint32_t iter) { float utilization = get_utilization(user._mem_size, mem_size); min_utilization = std::min(utilization, min_utilization); max_utilization = std::max(utilization, max_utilization); - total_requested_mem_padded_pool += static_cast(user._mem_size); + total_requested_mem_padded_pool += user._mem_size; GPU_DEBUG_COUT << " --- " << user._prim_id << " (" << user._unique_id << "), " << get_mb_size(user._mem_size) << ", " << utilization << "%" << std::endl; } @@ -546,9 +546,9 @@ void memory_pool::dump_to_screen(uint32_t net_id, uint32_t iter) { GPU_DEBUG_COUT << "************************************************************************" << std::endl; GPU_DEBUG_COUT << "Memory pool footprint of the network (net_id : " << net_id << ", iter : " << iter << ")" << std::endl; GPU_DEBUG_COUT << "Total memory size of non_padded_pool : " << get_mb_size(total_mem_size_non_padded_pool) << std::endl; - if (total_mem_size_non_padded_pool > 0.f) { + if (total_mem_size_non_padded_pool > 0) { GPU_DEBUG_COUT << " * Efficiency : " - << std::to_string(static_cast(total_requested_mem_non_padded_pool / total_mem_size_non_padded_pool)) + << std::to_string(static_cast(total_requested_mem_non_padded_pool) / static_cast(total_mem_size_non_padded_pool)) << " (total mem requested : " << get_mb_size(total_requested_mem_non_padded_pool) << " / total mem pool size : " << get_mb_size(total_mem_size_non_padded_pool) << ")" << std::endl; GPU_DEBUG_COUT << " * host mem size : " << get_mb_size(mem_size_non_padded_pool_host) << std::endl; @@ -556,16 +556,16 @@ void memory_pool::dump_to_screen(uint32_t net_id, uint32_t iter) { << get_mb_size(total_mem_size_non_padded_pool - mem_size_non_padded_pool_host) << std::endl; } GPU_DEBUG_COUT << "Total memory size of padded_pool memory : " << get_mb_size(total_mem_size_padded_pool) << std::endl; - if (total_mem_size_padded_pool > 0.f) { + if (total_mem_size_padded_pool > 0) { GPU_DEBUG_COUT << " * Efficiency : " - << std::to_string(static_cast(total_requested_mem_padded_pool / total_mem_size_padded_pool)) + << std::to_string(static_cast(total_requested_mem_padded_pool) / static_cast(total_mem_size_padded_pool)) << " (total mem requested : " << get_mb_size(total_requested_mem_padded_pool) << " / total mem pool size : " << get_mb_size(total_mem_size_padded_pool) << ")" << std::endl; GPU_DEBUG_COUT << " * host mem size : " << get_mb_size(mem_size_padded_pool_host) << std::endl; GPU_DEBUG_COUT << " * device mem size : " << get_mb_size((total_mem_size_padded_pool - mem_size_padded_pool_host)) << std::endl; } GPU_DEBUG_COUT << "Total memory size of no reusable memory : " << get_mb_size(total_mem_size_no_reusable) << std::endl; - if (total_mem_size_no_reusable > 0.f) { + if (total_mem_size_no_reusable > 0) { GPU_DEBUG_COUT << " * host mem size : " << get_mb_size(mem_size_no_reusable_host) << std::endl; GPU_DEBUG_COUT << " * device mem size : " << get_mb_size((total_mem_size_no_reusable - mem_size_no_reusable_host)) << std::endl; } diff --git a/src/plugins/intel_gpu/tests/unit/fusions/loop_fusion_test.cpp b/src/plugins/intel_gpu/tests/unit/fusions/loop_fusion_test.cpp index e039e82c5a5503..30ec9f8d52ff68 100644 --- a/src/plugins/intel_gpu/tests/unit/fusions/loop_fusion_test.cpp +++ b/src/plugins/intel_gpu/tests/unit/fusions/loop_fusion_test.cpp @@ -19,7 +19,7 @@ using namespace ::tests; namespace { struct loop_params { - size_t loop_trip_count; + int32_t loop_trip_count; tensor in_shape; tensor loop_input_shape; tensor loop_eltwise_shape; diff --git a/src/plugins/intel_gpu/tests/unit/test_cases/loop_gpu_test.cpp b/src/plugins/intel_gpu/tests/unit/test_cases/loop_gpu_test.cpp index f68d3c07e25c36..e293d4a75dc320 100644 --- a/src/plugins/intel_gpu/tests/unit/test_cases/loop_gpu_test.cpp +++ b/src/plugins/intel_gpu/tests/unit/test_cases/loop_gpu_test.cpp @@ -288,7 +288,7 @@ void test_loop_gpu_basic_concat_nested(bool is_caching_test) 1.f, -2.f, 3.f, -4.f }; - size_t inner_trip_count = input_data.size() / inner_eltwise_operand.size(); + int32_t inner_trip_count = static_cast(input_data.size() / inner_eltwise_operand.size()); int inner_initial_condition = 1; int outer_trip_count = 8; int outer_initial_condition = 1; From fabf42007401e8403a8f3f9c526189ff024c69d8 Mon Sep 17 00:00:00 2001 From: Alicja Miloszewska Date: Tue, 21 Apr 2026 09:36:39 +0200 Subject: [PATCH 004/545] [TF Lite FE] Migrate GraphIteratorFlatBuffer to use std::filesystem::path (#35408) ### Details: - _tensorflow_lite/src/frontend.cpp_ methods pass `std::filesystem::path` directly instead of extracting `.native()` - Replaces `std::string/std::wstring` path parameters with `std::filesystem::path` in `GraphIteratorFlatBuffer` constructor and `is_supported()` method ### Tickets: - part of [CVS-146661](https://jira.devtools.intel.com/browse/CVS-146661) ### AI Assistance: - yes, codebase analysis --- .../tensorflow_lite/src/frontend.cpp | 11 +++--- .../src/graph_iterator_flatbuffer.cpp | 25 ++----------- .../src/graph_iterator_flatbuffer.hpp | 36 ++++--------------- 3 files changed, 13 insertions(+), 59 deletions(-) diff --git a/src/frontends/tensorflow_lite/src/frontend.cpp b/src/frontends/tensorflow_lite/src/frontend.cpp index 8741f380f3ecd8..8108df4e5ffbb7 100644 --- a/src/frontends/tensorflow_lite/src/frontend.cpp +++ b/src/frontends/tensorflow_lite/src/frontend.cpp @@ -58,8 +58,7 @@ bool FrontEnd::supported_impl(const std::vector& variants) const { return false; if (const auto path = ov::frontend::get_path_from_any(variants[0])) { - const auto model_path = path.value().native(); - if (GraphIteratorFlatBuffer::is_supported(model_path)) { + if (GraphIteratorFlatBuffer::is_supported(*path)) { return true; } } else if (variants[0].is()) { @@ -73,11 +72,9 @@ ov::frontend::InputModel::Ptr FrontEnd::load_impl(const std::vector& va size_t extra_variants_num = variants.size() > 0 && variants[variants.size() - 1].is() ? 1 : 0; if (variants.size() == 1 + extra_variants_num) { if (const auto path = ov::frontend::get_path_from_any(variants[0])) { - const auto model_path = path.value().native(); - if (GraphIteratorFlatBuffer::is_supported(model_path)) { - return std::make_shared( - std::make_shared(model_path), - m_telemetry); + if (GraphIteratorFlatBuffer::is_supported(*path)) { + return std::make_shared(std::make_shared(*path), + m_telemetry); } } else if (variants[0].is()) { auto graph_iterator = variants[0].as(); diff --git a/src/frontends/tensorflow_lite/src/graph_iterator_flatbuffer.cpp b/src/frontends/tensorflow_lite/src/graph_iterator_flatbuffer.cpp index b92e8e480b9242..e409ca7b95d7cc 100644 --- a/src/frontends/tensorflow_lite/src/graph_iterator_flatbuffer.cpp +++ b/src/frontends/tensorflow_lite/src/graph_iterator_flatbuffer.cpp @@ -10,14 +10,7 @@ using namespace ov::frontend::tensorflow_lite; -#ifdef OPENVINO_ENABLE_UNICODE_PATH_SUPPORT - -GraphIteratorFlatBuffer::GraphIteratorFlatBuffer(const std::wstring& path) - : GraphIteratorFlatBuffer(ov::util::wstring_to_string(path)) {} - -#endif // OPENVINO_ENABLE_UNICODE_PATH_SUPPORT - -GraphIteratorFlatBuffer::GraphIteratorFlatBuffer(const std::string& path) { +GraphIteratorFlatBuffer::GraphIteratorFlatBuffer(const std::filesystem::path& path) { std::ifstream model_file(path, std::ios::binary | std::ios::in); FRONT_END_GENERAL_CHECK(model_file && model_file.is_open(), "Model file does not exist: ", path); @@ -26,9 +19,9 @@ GraphIteratorFlatBuffer::GraphIteratorFlatBuffer(const std::string& path) { flatbuffers::Verifier verifier(m_data.data(), m_data.size()); FRONT_END_GENERAL_CHECK(tflite::VerifyModelBuffer(verifier), - "TensorFlow Lite Frontend: the model file \"", + "TensorFlow Lite Frontend: the model file ", path, - "\" is corrupted or malformed (FlatBuffer verification failed)."); + " is corrupted or malformed (FlatBuffer verification failed)."); m_model = tflite::GetModel(m_data.data()); FRONT_END_GENERAL_CHECK(m_model != nullptr, "Failed to parse TFLite model from file: ", path); @@ -176,15 +169,3 @@ std::shared_ptr GraphIteratorFlatBuffer::get_decoder() const { return std::make_shared(info, input_idx, output_idx); } } - -template <> -std::basic_string ov::frontend::tensorflow_lite::get_model_extension() { - return ::tflite::ModelExtension(); -} - -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -template <> -std::basic_string ov::frontend::tensorflow_lite::get_model_extension() { - return util::string_to_wstring(::tflite::ModelExtension()); -} -#endif diff --git a/src/frontends/tensorflow_lite/src/graph_iterator_flatbuffer.hpp b/src/frontends/tensorflow_lite/src/graph_iterator_flatbuffer.hpp index 591219a8bcf81a..18996b02c721fd 100644 --- a/src/frontends/tensorflow_lite/src/graph_iterator_flatbuffer.hpp +++ b/src/frontends/tensorflow_lite/src/graph_iterator_flatbuffer.hpp @@ -4,16 +4,13 @@ #pragma once +#include #include -#if defined(__MINGW32__) || defined(__MINGW64__) -# include -#endif #include "openvino/core/any.hpp" #include "openvino/frontend/exception.hpp" #include "openvino/frontend/tensorflow_lite/decoder.hpp" #include "openvino/frontend/tensorflow_lite/graph_iterator.hpp" -#include "openvino/util/common_util.hpp" #include "openvino/util/file_util.hpp" #include "schema_generated.h" @@ -26,16 +23,6 @@ struct TensorInfo { const tflite::Buffer* buffer; }; -template -std::basic_string get_model_extension() {} -template <> -std::basic_string get_model_extension(); - -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -template <> -std::basic_string get_model_extension(); -#endif - class GraphIteratorFlatBuffer : public GraphIterator { size_t node_index = 0; std::vector m_data; @@ -46,25 +33,18 @@ class GraphIteratorFlatBuffer : public GraphIterator { public: GraphIteratorFlatBuffer() = default; - explicit GraphIteratorFlatBuffer(const std::string& path); - -#ifdef OPENVINO_ENABLE_UNICODE_PATH_SUPPORT - explicit GraphIteratorFlatBuffer(const std::wstring& path); -#endif + explicit GraphIteratorFlatBuffer(const std::filesystem::path& path); using Ptr = std::shared_ptr; ~GraphIteratorFlatBuffer() = default; /// Verifies file is supported - template - static bool is_supported(const std::basic_string& path) { - FRONT_END_GENERAL_CHECK(util::file_exists(path), - "Could not open the file: \"", - util::path_to_string(path), - '"'); + static bool is_supported(const std::filesystem::path& path) { + FRONT_END_GENERAL_CHECK(util::file_exists(path), "Could not open the file: ", path); + try { - if (!ov::util::ends_with(path, get_model_extension())) { + if (path.extension() != std::filesystem::path("." + std::string(::tflite::ModelExtension()))) { return false; } const std::streamsize offset_size = static_cast(sizeof(::flatbuffers::uoffset_t)); @@ -73,11 +53,7 @@ class GraphIteratorFlatBuffer : public GraphIterator { if (file_size < offset_size) { return false; } -#if defined(__MINGW32__) || defined(__MINGW64__) - std::ifstream tflite_stream(std::filesystem::path(path), std::ios::in | std::ifstream::binary); -#else std::ifstream tflite_stream(path, std::ios::in | std::ifstream::binary); -#endif char buf[offset_size * 2 + 1] = {}; // +1 is used to overcome gcc's -Wstringop-overread warning tflite_stream.read(buf, offset_size * 2); // If we have enough read bytes - try to detect prefixed identifier, else try without size prefix From 1df9c4ca9f095d6a98f01042bfe34d03bc9f6dcb Mon Sep 17 00:00:00 2001 From: pravin25 Date: Tue, 21 Apr 2026 16:52:25 +0800 Subject: [PATCH 005/545] [GPU] Optimize ArgMaxMin buffer allocation for TopK=1 (#35324) ### Details: - Use minimal reduction workspace for TopK=1 (avoid full sort buffers) - Add unit tests with large random inputs and dynamic shapes - No behavior change for TopK > 1 - Improves memory usage and reduces overhead for argmax/argmin. ### Tickets: - *CVS-184749* ### AI Assistance: - *AI assistance used: no * Co-authored-by: Chon Ming Lee --- .../arg_max_min/arg_max_min_kernel_axis.cpp | 6 +- .../unit/test_cases/arg_max_gpu_test.cpp | 70 +++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/plugins/intel_gpu/src/kernel_selector/kernels/arg_max_min/arg_max_min_kernel_axis.cpp b/src/plugins/intel_gpu/src/kernel_selector/kernels/arg_max_min/arg_max_min_kernel_axis.cpp index 6d4a247cdb8098..be5c19a2fdb99f 100644 --- a/src/plugins/intel_gpu/src/kernel_selector/kernels/arg_max_min/arg_max_min_kernel_axis.cpp +++ b/src/plugins/intel_gpu/src/kernel_selector/kernels/arg_max_min/arg_max_min_kernel_axis.cpp @@ -131,7 +131,11 @@ void ArgMaxMinKernelAxis::GetUpdateDispatchDataFunc(KernelData& kd) const { const size_t group_num = ((sort_size - 1) / group_size) + 1; kd.internalBuffers.clear(); - kd.internalBuffers.push_back(iav_type_size * sort_size * ops_size * 2); + if (prim_params.topK == 1) { + kd.internalBuffers.push_back(iav_type_size * sort_size * 2); + } else { + kd.internalBuffers.push_back(iav_type_size * sort_size * ops_size * 2); + } kd.internalBuffers.push_back(4 * group_num * ops_size * 2); kd.internalBuffers.push_back(ops_size * elem_size); kd.internalBufferDataType = prim_params.inputs[0].GetDType(); diff --git a/src/plugins/intel_gpu/tests/unit/test_cases/arg_max_gpu_test.cpp b/src/plugins/intel_gpu/tests/unit/test_cases/arg_max_gpu_test.cpp index c56822b98f060e..7c8d293f4a6b4c 100644 --- a/src/plugins/intel_gpu/tests/unit/test_cases/arg_max_gpu_test.cpp +++ b/src/plugins/intel_gpu/tests/unit/test_cases/arg_max_gpu_test.cpp @@ -1343,3 +1343,73 @@ TEST(arg_max_gpu_topk_radix, fallback_to_axis_for_small_sort_size_and_topk1) { cldnn::mem_lock out_ptr(output, get_test_stream()); ASSERT_NEAR(static_cast(out_ptr[0]), 9.0f, 0.01f); } + +TEST(arg_max_gpu_dynamic_large_input, f32) { + const int b = 1; + const int f = 1024; + const int y = 2048; + const int x = 1; + const int top_k = 1; + + auto& engine = get_test_engine(); + + auto input_layout_dynamic = layout{ + ov::PartialShape::dynamic(4), + data_types::f32, + format::bfyx + }; + + auto input_layout_static = layout{ + ov::PartialShape{b, f, y, x}, + data_types::f32, + format::bfyx + }; + + auto input = engine.allocate_memory(input_layout_static); + + std::vector input_vec(b * f * y * x); + std::mt19937 gen(0); + std::uniform_real_distribution dist(-10.f, 10.f); + for (auto& v : input_vec) + v = dist(gen); + + set_values(input, input_vec); + + topology topology; + topology.add(input_layout("input", input_layout_dynamic)); + + topology.add(arg_max_min("arg_max", + { input_info("input") }, + ov::op::TopKMode::MAX, + top_k, + 2, + ov::op::TopKSortType::SORT_VALUES, + false, + false, + data_types::i32)); + + ExecutionConfig config = get_test_default_config(engine); + config.set_property(ov::intel_gpu::allow_new_shape_infer(true)); + + network network(engine, topology, config); + network.set_input_data("input", input); + + auto inst = network.get_primitive("arg_max"); + ASSERT_TRUE(inst->get_impl()->is_dynamic()); + + auto outputs = network.execute(); + auto output = outputs.at("arg_max").get_memory(); + cldnn::mem_lock out_ptr(output, get_test_stream()); + + for (int fi = 0; fi < f; fi++) { + std::vector row( + input_vec.begin() + fi * y, + input_vec.begin() + (fi + 1) * y + ); + int expected = std::distance( + row.begin(), + std::max_element(row.begin(), row.end()) + ); + ASSERT_EQ(out_ptr[fi], expected); + } +} From 173cb433d3352d709c3c2bbe8d904244470d6396 Mon Sep 17 00:00:00 2001 From: Reese Liao Date: Tue, 21 Apr 2026 17:12:56 +0800 Subject: [PATCH 006/545] docs: fix broken links in GPU and CPU plugin READMEs (#34566) ### Details: - Removed broken maintainer links and outdated file links in `src/plugins/intel_gpu/README.md` and `src/plugins/intel_cpu/README.md`. - I noticed the `.take` bot didn't assign the issue to me, possibly due to the label. Since this is a quick and straightforward documentation fix, I went ahead and prepared the PR to save the maintainers' time. ### Tickets: - Resolves #34556 ### AI Assistance: - AI assistance used: no --------- Co-authored-by: Pavel Durandin --- src/plugins/intel_cpu/README.md | 2 +- src/plugins/intel_gpu/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/intel_cpu/README.md b/src/plugins/intel_cpu/README.md index d27014159ca095..a311f0378348b7 100644 --- a/src/plugins/intel_cpu/README.md +++ b/src/plugins/intel_cpu/README.md @@ -2,7 +2,7 @@ ## Key Contacts -For assistance regarding CPU, contact a member of [openvino-ie-cpu-maintainers](https://github.com/orgs/openvinotoolkit/teams/openvino-ie-cpu-maintainers) group. +For assistance regarding CPU, contact a member of openvino-ie-cpu-maintainers group. ## Components diff --git a/src/plugins/intel_gpu/README.md b/src/plugins/intel_gpu/README.md index be33e6cb2ebea5..3759a1eb1d08ea 100644 --- a/src/plugins/intel_gpu/README.md +++ b/src/plugins/intel_gpu/README.md @@ -4,7 +4,7 @@ GPU plugin in [OpenVINO toolkit](https://github.com/openvinotoolkit/openvino) su ## Key Contacts -For assistance regarding GPU, contact a member of [openvino-ie-gpu-maintainers](https://github.com/orgs/openvinotoolkit/teams/openvino-ie-gpu-maintainers) group. +For assistance regarding GPU, contact a member of openvino-ie-gpu-maintainers group. ## Components From ad5d8e0f99b329a0f9bee67e530e7e2ae42f538d Mon Sep 17 00:00:00 2001 From: Bogdan Pereanu Date: Tue, 21 Apr 2026 10:14:18 +0100 Subject: [PATCH 007/545] [NPU] Fix dynamic bounds (#35402) ### Details: - *Fix dynamic bounds by forcing to create a new level zero tensor with max size and do a memcpy all the time* - *Throw if the dimension of the dynamic rank is different than the expected one for the indexes that aren't dynamic.* ### Tickets: - *N/A* ### AI Assistance: - *AI assistance used: no / yes* - *If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).* --------- Signed-off-by: Bogdan Pereanu --- .../src/backend/src/zero_infer_request.cpp | 82 +++++- .../ov_infer_request/infer_request_run.cpp | 6 + .../ov_infer_request/infer_request_run.hpp | 235 ++++++++++++++++++ .../tests/functional/common/utils.hpp | 15 +- 4 files changed, 325 insertions(+), 13 deletions(-) diff --git a/src/plugins/intel_npu/src/backend/src/zero_infer_request.cpp b/src/plugins/intel_npu/src/backend/src/zero_infer_request.cpp index 8987914d4e9d3d..f1490d060bf9cd 100644 --- a/src/plugins/intel_npu/src/backend/src/zero_infer_request.cpp +++ b/src/plugins/intel_npu/src/backend/src/zero_infer_request.cpp @@ -19,8 +19,6 @@ #include "transformations/utils/utils.hpp" #include "zero_variable_state.hpp" -namespace intel_npu { - namespace { std::shared_ptr validate_compiled_model( @@ -29,8 +27,47 @@ std::shared_ptr validate_compiled_model( return compiledModel; } +bool has_related_shape_tensor(const std::vector& metadata, size_t descriptorIdx) { + const auto& relatedDescriptor = metadata.at(descriptorIdx).relatedDescriptorIndex; + return relatedDescriptor.has_value() && metadata.at(relatedDescriptor.value()).isShapeTensor; +} + +void copy_tensor_with_optional_shape_view(const std::shared_ptr& srcTensor, + const std::shared_ptr& dstTensor, + bool srcTensorIsUserTensor, + bool hasRelatedShapeTensor) { + if (hasRelatedShapeTensor && srcTensor->get_shape() != dstTensor->get_shape()) { + // With dynamic bounds (shape tensor present), Level Zero tensor is allocated for max shape. + // User tensor can be smaller for the current inference, so we create a smaller view over + // the same Level Zero buffer and copy using the user-visible shape. + if (srcTensorIsUserTensor) { + OPENVINO_ASSERT(srcTensor->get_byte_size() <= dstTensor->get_byte_size(), + "Source byte size exceeds destination tensor allocation for dynamic-shape view copy"); + + auto viewTensor = ov::make_tensor(dstTensor->get_element_type(), + srcTensor->get_shape(), + static_cast(dstTensor->data())); + srcTensor->copy_to(viewTensor); + return; + } + + OPENVINO_ASSERT(dstTensor->get_byte_size() <= srcTensor->get_byte_size(), + "Source byte size exceeds destination tensor allocation for dynamic-shape view copy"); + + auto viewTensor = ov::make_tensor(srcTensor->get_element_type(), + dstTensor->get_shape(), + static_cast(srcTensor->data())); + viewTensor->copy_to(dstTensor); + return; + } + + srcTensor->copy_to(dstTensor); +} + } // namespace +namespace intel_npu { + std::optional determine_dynamic_batch_size(const IODescriptor& desc, const ov::PartialShape& ioShape, const std::shared_ptr& tensor, @@ -467,10 +504,14 @@ void ZeroInferRequest::sync_zero_tensor_with_graph(const ZeroInferRequest::Found itt::domains::LevelZeroBackend, "ZeroInferRequest", "sync_zero_tensor_with_graph"); + const auto& metadata = foundPort.is_input() ? _metadata.inputs : _metadata.outputs; auto& levelZeroTensor = foundPort.is_input() ? get_level_zero_input(foundPort.idx) : _levelZeroOutputTensors.at(foundPort.idx); - if (_initStructs->getMutableCommandListExtVersion() >= ZE_MAKE_VERSION(1, 0)) { + // For dynamic bounds (related shape tensor present), we must keep Level Zero allocation at max shape. + // Therefore user-memory import is allowed only when there is no related shape tensor. + if (_initStructs->getMutableCommandListExtVersion() >= ZE_MAKE_VERSION(1, 0) && + !has_related_shape_tensor(metadata, foundPort.idx)) { bool updateCommandListArg = false; try { _logger.debug("sync_zero_tensor_with_graph - create zero tensor"); @@ -586,7 +627,10 @@ void ZeroInferRequest::sync_zero_tensors_with_graph(const ZeroInferRequest::Foun "ZeroInferRequest", "sync_zero_tensors_with_graph"); - if (_initStructs->getMutableCommandListExtVersion() >= ZE_MAKE_VERSION(1, 0)) { + // For dynamic bounds (related shape tensor present), we must keep Level Zero allocation at max shape. + // Therefore user-memory import is allowed only when there is no related shape tensor. + if (_initStructs->getMutableCommandListExtVersion() >= ZE_MAKE_VERSION(1, 0) && + !has_related_shape_tensor(_metadata.inputs, foundPort.idx)) { if (batchSize.has_value()) { get_level_zero_inputs(foundPort.idx).resize(tensors.size()); for (size_t i = 0; i < tensors.size(); i++) { @@ -903,6 +947,11 @@ void ZeroInferRequest::prepare_inputs() { OPENVINO_ASSERT(!inputDescriptor.isInitInputWeights, "This path should not be used for running inferences for the \"init\" model"); + if (inputDescriptor.isMainInputWeights) { + // These values were set while running the "WeightlessGraph::init" method + continue; + } + if (inputDescriptor.isShapeTensor) { OPENVINO_ASSERT(inputDescriptor.relatedDescriptorIndex.has_value(), "The link between the dynamic tensor and its shape tensor is missing, entry name: ", @@ -978,11 +1027,6 @@ void ZeroInferRequest::prepare_inputs() { continue; } - if (inputDescriptor.isMainInputWeights) { - // These values were set while running the "WeightlessGraph::init" method - continue; - } - const auto& levelZeroTensor = get_level_zero_input(inputIndex); OPENVINO_ASSERT(levelZeroTensor, "Input zero tensor is not allocated."); @@ -1000,7 +1044,10 @@ void ZeroInferRequest::prepare_inputs() { _logger.info("prepare_inputs - tensor is not allocated in the current Level Zero context"); OV_ITT_TASK_NEXT(ZERO_INFER, "memcpy"); - userTensor.at(SINGLE_TENSOR)->copy_to(levelZeroTensor); + copy_tensor_with_optional_shape_view(userTensor.at(SINGLE_TENSOR)._ptr, + levelZeroTensor, + true, + has_related_shape_tensor(_metadata.inputs, inputIndex)); } ++inputIndex; @@ -1057,7 +1104,10 @@ void ZeroInferRequest::get_result() { _logger.info("get_result - output tensor by index: %zu is not allocated in the current Level Zero context", outputIndex); OV_ITT_TASK_NEXT(ZERO_RESULT, "memcpy"); - levelZeroTensor->copy_to(userTensor._ptr); + copy_tensor_with_optional_shape_view(levelZeroTensor, + userTensor._ptr, + false, + has_related_shape_tensor(_metadata.outputs, outputIndex)); } levelZeroTensor->detach_imported_allocation_for_custom_tensor(); @@ -1130,13 +1180,21 @@ void ZeroInferRequest::check_tensor(const ov::Output& port, if (port_length > 0) { const auto& port_max_shape = port_partial_shape.get_max_shape(); + const auto& port_min_shape = port_partial_shape.get_min_shape(); for (auto i = 0; i < port_length; ++i) { - if (tensor_shape[i] > port_max_shape[i]) { + if (port_min_shape[i] != port_max_shape[i] && tensor_shape[i] > port_max_shape[i]) { OPENVINO_THROW("The tensor shape is not compatible with the model input/output max shape: got ", tensor_shape, " expecting max shape ", port_max_shape); } + + if (port_min_shape[i] == port_max_shape[i] && tensor_shape[i] != port_min_shape[i]) { + OPENVINO_THROW("The tensor shape is not compatible with the model input/output shape: got ", + tensor_shape, + " expecting shape ", + port_min_shape); + } } } } diff --git a/src/plugins/intel_npu/tests/functional/behavior/ov_infer_request/infer_request_run.cpp b/src/plugins/intel_npu/tests/functional/behavior/ov_infer_request/infer_request_run.cpp index d679b2e0e8d1f8..6b6c89593bee41 100644 --- a/src/plugins/intel_npu/tests/functional/behavior/ov_infer_request/infer_request_run.cpp +++ b/src/plugins/intel_npu/tests/functional/behavior/ov_infer_request/infer_request_run.cpp @@ -76,6 +76,12 @@ INSTANTIATE_TEST_SUITE_P(smoke_BehaviorTest, ::testing::ValuesIn(configsInferRequestRunTests)), InferRequestRunTests::getTestCaseName); +INSTANTIATE_TEST_SUITE_P(smoke_BehaviorTest, + DynamicBoundsTests, + ::testing::Combine(::testing::Values(ov::test::utils::DEVICE_NPU), + ::testing::ValuesIn(configsInferRequestRunTests)), + InferRequestRunTests::getTestCaseName); + const std::vector batchingConfigs = {{ov::intel_npu::batch_mode(ov::intel_npu::BatchMode::PLUGIN)}, {ov::intel_npu::batch_mode(ov::intel_npu::BatchMode::COMPILER)}, {ov::intel_npu::batch_mode(ov::intel_npu::BatchMode::AUTO)}}; diff --git a/src/plugins/intel_npu/tests/functional/behavior/ov_infer_request/infer_request_run.hpp b/src/plugins/intel_npu/tests/functional/behavior/ov_infer_request/infer_request_run.hpp index 3c4d5053795a96..ad3a649c1fa340 100644 --- a/src/plugins/intel_npu/tests/functional/behavior/ov_infer_request/infer_request_run.hpp +++ b/src/plugins/intel_npu/tests/functional/behavior/ov_infer_request/infer_request_run.hpp @@ -2595,6 +2595,241 @@ TEST_P(CpuVaTensorsTests, checkResultsAfterRunningWithSameRawMemoryMultipleTimes ::operator delete(output_data, std::align_val_t(4096)); } +using DynamicBoundsTests = InferRequestRunTests; + +TEST_P(DynamicBoundsTests, ChangeShapeAfterTensorIsSet) { + SKIP_IF_CURRENT_TEST_IS_DISABLED(); + + auto model_shape = PartialShape{1, ov::Dimension(1, 10), 2, 2}; + auto model = createModel(element::f32, model_shape, "N..."); + + auto compiled_model = core->compile_model(model, target_device, configuration); + // Create InferRequest + ov::InferRequest req; + req = compiled_model.create_infer_request(); + + auto shape = Shape{1, 4, 2, 2}; + auto shape_size = ov::shape_size(shape); + auto input_tensor = ov::Tensor{ov::element::f32, shape}; + auto output_tensor = ov::Tensor{ov::element::f32, shape}; + + ASSERT_EQ(shape_size * sizeof(float), input_tensor.get_byte_size()); + ASSERT_EQ(shape_size * sizeof(float), output_tensor.get_byte_size()); + ASSERT_EQ(shape, input_tensor.get_shape()); + ASSERT_EQ(shape, output_tensor.get_shape()); + + req.set_input_tensor(input_tensor); + req.set_output_tensor(output_tensor); + + auto input_data = input_tensor.data(); + auto output_data = output_tensor.data(); + for (size_t i = 0; i < shape_size; ++i) { + input_data[i] = 21.f; + } + + req.infer(); // Adds '1' to each element + + auto expected = 22.f; + for (size_t i = 0; i < shape_size; ++i) { + ASSERT_EQ(output_data[i], expected) + << ": Expected=" << expected << ", actual=" << output_data[i] << " for index " << i; + } + + auto input_data_after_run = input_tensor.data(); + auto output_data_after_run = output_tensor.data(); + + ASSERT_EQ(input_data, input_data_after_run); + ASSERT_EQ(output_data, output_data_after_run); + + auto new_shape = Shape{1, 6, 2, 2}; + auto new_shape_size = ov::shape_size(new_shape); + input_tensor.set_shape(new_shape); + output_tensor.set_shape(new_shape); + + ASSERT_EQ(new_shape_size * sizeof(float), input_tensor.get_byte_size()); + ASSERT_EQ(new_shape_size * sizeof(float), output_tensor.get_byte_size()); + ASSERT_EQ(new_shape, input_tensor.get_shape()); + ASSERT_EQ(new_shape, output_tensor.get_shape()); + + input_data = input_tensor.data(); + output_data = output_tensor.data(); + + for (size_t i = 0; i < new_shape_size; ++i) { + input_data[i] = 25.f; + } + + req.infer(); // Adds '1' to each element + + expected = 26.f; + for (size_t i = 0; i < new_shape_size; ++i) { + ASSERT_EQ(output_data[i], expected) + << ": Expected=" << expected << ", actual=" << output_data[i] << " for index " << i; + } + + ASSERT_EQ(new_shape_size * sizeof(float), input_tensor.get_byte_size()); + ASSERT_EQ(new_shape_size * sizeof(float), output_tensor.get_byte_size()); + ASSERT_EQ(new_shape, input_tensor.get_shape()); + ASSERT_EQ(new_shape, output_tensor.get_shape()); + + input_data_after_run = input_tensor.data(); + output_data_after_run = output_tensor.data(); + + ASSERT_EQ(input_data, input_data_after_run); + ASSERT_EQ(output_data, output_data_after_run); +} + +TEST_P(DynamicBoundsTests, ChangeShapeAfterTensorIsSetUsingAlignedExternalMemory) { + SKIP_IF_CURRENT_TEST_IS_DISABLED(); + + auto model_shape = PartialShape{1, ov::Dimension(1, 10), 4, 64}; + auto model = createModel(element::f32, model_shape, "N..."); + + auto compiled_model = core->compile_model(model, target_device, configuration); + // Create InferRequest + ov::InferRequest req; + req = compiled_model.create_infer_request(); + + ov::Allocator allocator{ov::test::utils::DefaultAllocatorAligned{}}; + + auto shape = Shape{1, 8, 4, 64}; + auto shape_size = ov::shape_size(shape); + auto input_tensor = ov::Tensor{ov::element::f32, shape, allocator}; + auto output_tensor = ov::Tensor{ov::element::f32, shape, allocator}; + + ASSERT_EQ(shape_size * sizeof(float), input_tensor.get_byte_size()); + ASSERT_EQ(shape_size * sizeof(float), output_tensor.get_byte_size()); + ASSERT_EQ(shape, input_tensor.get_shape()); + ASSERT_EQ(shape, output_tensor.get_shape()); + + req.set_input_tensor(input_tensor); + req.set_output_tensor(output_tensor); + + auto* input_data = input_tensor.data(); + for (size_t i = 0; i < shape_size; ++i) { + input_data[i] = 21.f; + } + + req.infer(); // Adds '1' to each element + + auto expected = 22.f; + auto* output_data = output_tensor.data(); + for (size_t i = 0; i < shape_size; ++i) { + ASSERT_EQ(output_data[i], expected) + << ": Expected=" << expected << ", actual=" << output_data[i] << " for index " << i; + } + + auto new_shape = Shape{1, 6, 4, 64}; + auto new_shape_size = ov::shape_size(new_shape); + input_tensor.set_shape(new_shape); + output_tensor.set_shape(new_shape); + + ASSERT_EQ(new_shape_size * sizeof(float), input_tensor.get_byte_size()); + ASSERT_EQ(new_shape_size * sizeof(float), output_tensor.get_byte_size()); + ASSERT_EQ(new_shape, input_tensor.get_shape()); + ASSERT_EQ(new_shape, output_tensor.get_shape()); + + input_data = input_tensor.data(); + output_data = output_tensor.data(); + + for (size_t i = 0; i < new_shape_size; ++i) { + input_data[i] = 25.f; + } + + req.infer(); // Adds '1' to each element + + expected = 26.f; + for (size_t i = 0; i < new_shape_size; ++i) { + ASSERT_EQ(output_data[i], expected) + << ": Expected=" << expected << ", actual=" << output_data[i] << " for index " << i; + } + + ASSERT_EQ(new_shape_size * sizeof(float), input_tensor.get_byte_size()); + ASSERT_EQ(new_shape_size * sizeof(float), output_tensor.get_byte_size()); + ASSERT_EQ(new_shape, input_tensor.get_shape()); + ASSERT_EQ(new_shape, output_tensor.get_shape()); +} + +TEST_P(DynamicBoundsTests, RunningTwiceWithRemoteTensor) { + SKIP_IF_CURRENT_TEST_IS_DISABLED(); + + auto model_shape = PartialShape{1, ov::Dimension(1, 10), 4, 64}; + auto model = createModel(element::f32, model_shape, "N..."); + + auto compiled_model = core->compile_model(model, target_device, configuration); + // Create InferRequest + ov::InferRequest req; + req = compiled_model.create_infer_request(); + + auto context = core->get_default_context(target_device); + + auto shape = Shape{1, 8, 4, 64}; + auto shape_size = ov::shape_size(shape); + auto input_tensor = context.create_host_tensor(ov::element::f32, shape); + auto output_tensor = context.create_host_tensor(ov::element::f32, shape); + + ASSERT_EQ(shape_size * sizeof(float), input_tensor.get_byte_size()); + ASSERT_EQ(shape_size * sizeof(float), output_tensor.get_byte_size()); + ASSERT_EQ(shape, input_tensor.get_shape()); + ASSERT_EQ(shape, output_tensor.get_shape()); + + req.set_input_tensor(input_tensor); + req.set_output_tensor(output_tensor); + + auto* input_data = input_tensor.data(); + for (size_t i = 0; i < shape_size; ++i) { + input_data[i] = 21.f; + } + + req.infer(); // Adds '1' to each element + + auto expected = 22.f; + auto* output_data = output_tensor.data(); + for (size_t i = 0; i < shape_size; ++i) { + ASSERT_EQ(output_data[i], expected) + << ": Expected=" << expected << ", actual=" << output_data[i] << " for index " << i; + } + + for (size_t i = 0; i < shape_size; ++i) { + input_data[i] = 25.f; + } + + req.infer(); // Adds '1' to each element + + expected = 26.f; + for (size_t i = 0; i < shape_size; ++i) { + ASSERT_EQ(output_data[i], expected) + << ": Expected=" << expected << ", actual=" << output_data[i] << " for index " << i; + } + + ASSERT_EQ(shape_size * sizeof(float), input_tensor.get_byte_size()); + ASSERT_EQ(shape_size * sizeof(float), output_tensor.get_byte_size()); + ASSERT_EQ(shape, input_tensor.get_shape()); + ASSERT_EQ(shape, output_tensor.get_shape()); +} + +TEST_P(DynamicBoundsTests, ExpectErrorFromWrongTensorShape) { + SKIP_IF_CURRENT_TEST_IS_DISABLED(); + + auto model_shape = PartialShape{1, ov::Dimension(1, 10), 4, 64}; + auto model = createModel(element::f32, model_shape, "N..."); + + auto compiled_model = core->compile_model(model, target_device, configuration); + // Create InferRequest + ov::InferRequest req; + req = compiled_model.create_infer_request(); + + auto context = core->get_default_context(target_device); + auto input_tensor = context.create_host_tensor(ov::element::f32, Shape{1, 8, 2, 64}); + OV_EXPECT_THROW(req.set_input_tensor(input_tensor), + ov::Exception, + HasSubstr("The tensor shape is not compatible with the model input/output shape")); + + auto output_tensor = context.create_host_tensor(ov::element::f32, Shape{1, 8, 12, 64}); + OV_EXPECT_THROW(req.set_output_tensor(output_tensor), + ov::Exception, + HasSubstr("The tensor shape is not compatible with the model input/output shape")); +} + } // namespace behavior } // namespace test } // namespace ov diff --git a/src/plugins/intel_npu/tests/functional/common/utils.hpp b/src/plugins/intel_npu/tests/functional/common/utils.hpp index 5199d41f11e777..441a0c7b79cb1a 100644 --- a/src/plugins/intel_npu/tests/functional/common/utils.hpp +++ b/src/plugins/intel_npu/tests/functional/common/utils.hpp @@ -134,7 +134,7 @@ class DefaultAllocatorNotAligned final { void deallocate(void* handle, const size_t bytes, size_t alignment = 4096) noexcept { ::operator delete(static_cast(handle) - _offset, std::align_val_t(alignment)); } - bool is_equal(const DefaultAllocatorNotAligned& other) const { + bool is_equal(const DefaultAllocatorNotAligned&) const { return false; } @@ -142,6 +142,19 @@ class DefaultAllocatorNotAligned final { size_t _offset = 16; }; +class DefaultAllocatorAligned final { +public: + void* allocate(const size_t bytes, const size_t) { + return ::operator new(bytes, std::align_val_t(4096)); + } + void deallocate(void* handle, const size_t, size_t) noexcept { + ::operator delete(static_cast(handle), std::align_val_t(4096)); + } + bool is_equal(const DefaultAllocatorAligned&) const { + return false; + } +}; + std::tuple Date: Tue, 21 Apr 2026 13:42:29 +0200 Subject: [PATCH 008/545] [FE] Migrate tensorflow frontend to use std::fs::path (#35356) ### Details: - Removed from _frontend.cpp_ (`supported_impl`, `load_impl`) most usages of `path::native()` and `ov::util::path_to_string()` - Replaced template-based and `std::string` path parameters with `std::filesystem::path` in `CheckpointV1Reader`, `GraphIteratorProto`, `GraphIteratorProtoTxt`, and `GraphIteratorMeta`. - `ov::util::path_to_string(`) is used for `GraphIteratorSavedModel` second argument "tags", which originally can be passed into `.load()` method as `std::string` or `std::wstring` - `m_shard_names` is used for error messages - not a path for I/O operations so I kept it as `std::string`. These changes make the codebase more robust, easier to maintain, and better aligned with modern C++ practices. ### Tickets: - [CVS-184167](https://jira.devtools.intel.com/browse/CVS-184167) ### AI Assistance: - AI assistance used: yes - Codebase analysis --- .../tensorflow/src/checkpoint_v1_reader.cpp | 26 ++++++------ .../tensorflow/src/checkpoint_v1_reader.hpp | 6 +-- src/frontends/tensorflow/src/frontend.cpp | 40 +++++++++---------- .../tensorflow/src/graph_iterator_meta.cpp | 12 +----- .../tensorflow/src/graph_iterator_meta.hpp | 37 ++++++----------- .../tensorflow/src/graph_iterator_proto.hpp | 29 ++++---------- .../src/graph_iterator_proto_txt.hpp | 24 ++++------- .../src/graph_iterator_saved_model.cpp | 35 ++++------------ .../src/graph_iterator_saved_model.hpp | 38 +++++------------- .../tensorflow/src/variables_index.cpp | 40 ++++--------------- .../tensorflow/src/variables_index.hpp | 11 +---- 11 files changed, 91 insertions(+), 207 deletions(-) diff --git a/src/frontends/tensorflow/src/checkpoint_v1_reader.cpp b/src/frontends/tensorflow/src/checkpoint_v1_reader.cpp index d8ac5892f65069..fe1e8704d7659d 100644 --- a/src/frontends/tensorflow/src/checkpoint_v1_reader.cpp +++ b/src/frontends/tensorflow/src/checkpoint_v1_reader.cpp @@ -17,13 +17,13 @@ using namespace ov::frontend::tensorflow; namespace { -std::vector list_files_in_dir(const std::string& directory_path) { - std::vector res; +std::vector list_files_in_dir(const std::filesystem::path& directory_path) { + std::vector res; try { ov::util::iterate_files( - ov::util::make_path(directory_path), + directory_path, [&res](const std::filesystem::path& file_path) { - res.push_back(ov::util::path_to_string(file_path)); + res.push_back(file_path); }, true); } catch (...) { @@ -33,11 +33,11 @@ std::vector list_files_in_dir(const std::string& directory_path) { } } // namespace -CheckpointV1Reader::CheckpointV1Reader(const std::string& checkpoints) : m_checkpoints(checkpoints) {} +CheckpointV1Reader::CheckpointV1Reader(const std::filesystem::path& checkpoints) : m_checkpoints(checkpoints) {} void CheckpointV1Reader::initialize() { // figure out if the input is a file or a directory of checkpoints - std::vector checkpoints_paths; + std::vector checkpoints_paths; if (ov::util::directory_exists(m_checkpoints)) { checkpoints_paths = list_files_in_dir(m_checkpoints); } else if (ov::util::file_exists(m_checkpoints)) { @@ -48,18 +48,20 @@ void CheckpointV1Reader::initialize() { m_variables_info_map.clear(); - for (auto checkpoint_path : checkpoints_paths) { + for (const auto& checkpoint_path : checkpoints_paths) { // create ifstream for each shard std::shared_ptr shard_stream = std::make_shared(checkpoint_path, std::ifstream::in | std::ifstream::binary); - FRONT_END_GENERAL_CHECK( - shard_stream && shard_stream->is_open(), - "[TensorFlow Frontend] incorrect model: checkpoint file " + checkpoint_path + "does not exist"); + FRONT_END_GENERAL_CHECK(shard_stream && shard_stream->is_open(), + "[TensorFlow Frontend] incorrect model: checkpoint file ", + checkpoint_path, + " does not exist"); const int32_t shard_ind = static_cast(m_shards.size()); m_shards.push_back(shard_stream); - m_shard_names.push_back(checkpoint_path); + const std::string shard_name = ov::util::path_to_string(checkpoint_path); + m_shard_names.push_back(shard_name); std::string value; - find_entry(shard_stream, checkpoint_path, SAVED_TENSOR_SLICES_KEY, value); + find_entry(shard_stream, shard_name, SAVED_TENSOR_SLICES_KEY, value); // parse empty index block // This is only present at the first item of each checkpoint file and serves diff --git a/src/frontends/tensorflow/src/checkpoint_v1_reader.hpp b/src/frontends/tensorflow/src/checkpoint_v1_reader.hpp index 168185ab29967a..70d2606135f9fc 100644 --- a/src/frontends/tensorflow/src/checkpoint_v1_reader.hpp +++ b/src/frontends/tensorflow/src/checkpoint_v1_reader.hpp @@ -6,6 +6,7 @@ #include +#include #include #include @@ -31,7 +32,7 @@ struct VariableInfo { // reads checkpoints of v1 version // it parses value, shape and type for Variable nodes class CheckpointV1Reader { - const std::string m_checkpoints; + const std::filesystem::path m_checkpoints; // a map from Variable name to its information std::unordered_map m_variables_info_map; // a vector of streams for shards, where shard is one checkpoint file @@ -41,8 +42,7 @@ class CheckpointV1Reader { public: /// \brief constructs CheckpointV1Reader for a given directory of checkpoint files - // CheckpointV1Reader(const std::string& checkpoints_dir); - CheckpointV1Reader(const std::string& checkpoints); + CheckpointV1Reader(const std::filesystem::path& checkpoints); /// \brief initialize Checkpoint V1 reader void initialize(); diff --git a/src/frontends/tensorflow/src/frontend.cpp b/src/frontends/tensorflow/src/frontend.cpp index 20762bd9857596..65ebc0c0c46ea6 100644 --- a/src/frontends/tensorflow/src/frontend.cpp +++ b/src/frontends/tensorflow/src/frontend.cpp @@ -142,21 +142,20 @@ bool FrontEnd::supported_impl(const std::vector& variants) const { if (variants.size() != 1 + extra_variants_num) return false; - std::filesystem::path model_fs_path, checkpoints_dir; - if (const auto path = ov::frontend::get_path_from_any(variants[0])) { - model_fs_path = path.value(); - } else if (const auto paths = ov::frontend::get_path_vec_from_any(variants[0])) { + std::filesystem::path model_path, checkpoints_or_tags; + if (auto path = ov::frontend::get_path_from_any(variants[0])) { + model_path = std::move(*path); + } else if (auto paths = ov::frontend::get_path_vec_from_any(variants[0])) { if (paths->size() == 2) { - model_fs_path = paths.value()[0]; - checkpoints_dir = paths.value()[1]; + model_path = std::move((*paths)[0]); + checkpoints_or_tags = std::move((*paths)[1]); } } // to figure out if the model with v1 checkpoints is supported, // it is sufficient to check only the input model format // avoid parsing of checkpoints here - if (!model_fs_path.empty() && checkpoints_dir.empty()) { - auto model_path = model_fs_path.native(); + if (!model_path.empty() && checkpoints_or_tags.empty()) { if (GraphIteratorProto::is_supported(model_path)) { // handle binary protobuf format // for automatic deduction of the frontend to convert the model @@ -170,8 +169,7 @@ bool FrontEnd::supported_impl(const std::vector& variants) const { // handle text protobuf format return true; } - } else if (!model_fs_path.empty() && !checkpoints_dir.empty()) { - auto model_path = model_fs_path.native(); + } else if (!model_path.empty() && !checkpoints_or_tags.empty()) { // here, we assume to get the input model path and checkpoints directory if (GraphIteratorProto::is_supported(model_path)) { // binary protobuf format with checkpoints @@ -202,17 +200,16 @@ ov::frontend::InputModel::Ptr FrontEnd::load_impl(const std::vector& va "frozen formats (.pb and .pbtxt), SavedModel and MetaGraph (.meta) formats, and v1 checkpoints."; FRONT_END_GENERAL_CHECK(variants.size() == 1 + extra_variants_num, err_msg); - std::filesystem::path model_fs_path, checkpoints_fs_dir; - if (const auto path = ov::frontend::get_path_from_any(variants[0])) { - model_fs_path = path.value(); - } else if (const auto paths = ov::frontend::get_path_vec_from_any(variants[0])) { + std::filesystem::path model_path, checkpoints_or_tags; + if (auto path = ov::frontend::get_path_from_any(variants[0])) { + model_path = std::move(*path); + } else if (auto paths = ov::frontend::get_path_vec_from_any(variants[0])) { FRONT_END_GENERAL_CHECK(paths->size() == 2, err_msg); - model_fs_path = paths.value()[0]; - checkpoints_fs_dir = paths.value()[1]; + model_path = std::move((*paths)[0]); + checkpoints_or_tags = std::move((*paths)[1]); } - if (!model_fs_path.empty() && checkpoints_fs_dir.empty()) { - const auto model_path = model_fs_path.native(); + if (!model_path.empty() && checkpoints_or_tags.empty()) { if (GraphIteratorProto::is_supported(model_path)) { // handle binary protobuf format return std::make_shared(std::make_shared(model_path), m_telemetry); @@ -243,10 +240,9 @@ ov::frontend::InputModel::Ptr FrontEnd::load_impl(const std::vector& va // handle text protobuf format return std::make_shared(std::make_shared(model_path), m_telemetry); } - } else if (!model_fs_path.empty() && !checkpoints_fs_dir.empty()) { + } else if (!model_path.empty() && !checkpoints_or_tags.empty()) { // here, we assume to get the input model path and checkpoints directory - const auto model_path = ov::util::path_to_string(model_fs_path); - const auto checkpoints_dir = ov::util::path_to_string(checkpoints_fs_dir); + const auto checkpoints_dir = checkpoints_or_tags; if (GraphIteratorProto::is_supported(model_path)) { auto graph_iterator = std::make_shared(model_path, checkpoints_dir); // handle binary protobuf format with checkpoints @@ -272,7 +268,7 @@ ov::frontend::InputModel::Ptr FrontEnd::load_impl(const std::vector& va graph_iterator->get_checkpoint_v1_reader(), false); } else if (GraphIteratorSavedModel::is_supported(model_path)) { - auto saved_model_tags = checkpoints_dir; + const auto saved_model_tags = ov::util::path_to_string(checkpoints_or_tags); std::shared_ptr graph_iterator; graph_iterator = std::make_shared(model_path, saved_model_tags, mmap_enabled); return std::make_shared(graph_iterator, diff --git a/src/frontends/tensorflow/src/graph_iterator_meta.cpp b/src/frontends/tensorflow/src/graph_iterator_meta.cpp index 8f069c56376af6..d71b2771af8627 100644 --- a/src/frontends/tensorflow/src/graph_iterator_meta.cpp +++ b/src/frontends/tensorflow/src/graph_iterator_meta.cpp @@ -29,18 +29,10 @@ bool GraphIteratorMeta::is_valid_signature(const ::tensorflow::SignatureDef& sig return true; } -template <> -std::basic_string get_variables_index_name(const std::string name) { - return name + ".index"; +std::filesystem::path get_variables_index_name(const std::filesystem::path& name) { + return std::filesystem::path(name) += ".index"; } -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -template <> -std::basic_string get_variables_index_name(const std::wstring name) { - return name + L".index"; -} -#endif - } // namespace tensorflow } // namespace frontend } // namespace ov diff --git a/src/frontends/tensorflow/src/graph_iterator_meta.hpp b/src/frontends/tensorflow/src/graph_iterator_meta.hpp index 3c4e3174f08c10..f409b009e848dd 100644 --- a/src/frontends/tensorflow/src/graph_iterator_meta.hpp +++ b/src/frontends/tensorflow/src/graph_iterator_meta.hpp @@ -4,6 +4,7 @@ #pragma once +#include #include #include "graph_iterator_proto.hpp" @@ -14,16 +15,7 @@ namespace ov { namespace frontend { namespace tensorflow { -template -std::basic_string get_variables_index_name(const std::basic_string name) {} - -template <> -std::basic_string get_variables_index_name(const std::string name); - -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -template <> -std::basic_string get_variables_index_name(const std::wstring name); -#endif +std::filesystem::path get_variables_index_name(const std::filesystem::path& name); // Loads graph from Tensorflow MetaGraph file (*.meta) class GraphIteratorMeta : public GraphIteratorProto { @@ -36,21 +28,18 @@ class GraphIteratorMeta : public GraphIteratorProto { bool m_mmap_enabled; public: - template - GraphIteratorMeta(const std::basic_string& path, const bool mmap_enabled) + GraphIteratorMeta(const std::filesystem::path& path, const bool mmap_enabled) : m_metagraph_def(std::make_shared<::tensorflow::MetaGraphDef>()), m_mmap_enabled(mmap_enabled) { this->read_meta(path); } - template - static bool is_supported(const std::basic_string& path) { + static bool is_supported(const std::filesystem::path& path) { FRONT_END_GENERAL_CHECK(util::directory_exists(path) || util::file_exists(path), - "Could not open the file: \"", - util::path_to_string(path), - '"'); + "Could not open the file: ", + path); try { - std::ifstream mg_stream(path.c_str(), std::ios::in | std::ifstream::binary); + std::ifstream mg_stream(path, std::ios::in | std::ifstream::binary); auto metagraph_def = std::make_shared<::tensorflow::MetaGraphDef>(); return mg_stream && mg_stream.is_open() && metagraph_def->ParsePartialFromIstream(&mg_stream) && metagraph_def->has_graph_def() && metagraph_def->graph_def().node_size() > 0; @@ -82,17 +71,17 @@ class GraphIteratorMeta : public GraphIteratorProto { private: bool is_valid_signature(const ::tensorflow::SignatureDef& signature) const; - template - bool read_meta(const std::basic_string& path) { - std::basic_string model_path = path.substr(0, path.find_last_of('.')); + bool read_meta(const std::filesystem::path& path) { + auto model_path = path; + model_path.replace_extension(); - std::ifstream mg_stream{path.c_str(), std::ifstream::in | std::ifstream::binary}; + std::ifstream mg_stream{path, std::ifstream::in | std::ifstream::binary}; FRONT_END_GENERAL_CHECK(mg_stream && mg_stream.is_open(), "Model file does not exist"); - std::basic_string varIndexPath = get_variables_index_name(model_path); + auto varIndexPath = get_variables_index_name(model_path); if (ov::util::file_exists(varIndexPath)) { m_variables_index = std::make_shared(m_mmap_enabled); - std::ifstream vi_stream{varIndexPath.c_str(), std::ifstream::in | std::ifstream::binary}; + std::ifstream vi_stream{varIndexPath, std::ifstream::in | std::ifstream::binary}; FRONT_END_GENERAL_CHECK(vi_stream && vi_stream.is_open(), "MetaGraph's variable index file does not exist"); FRONT_END_GENERAL_CHECK(m_variables_index->read_variables(vi_stream, model_path, false), "MetaGraph's variable index file cannot be parsed"); diff --git a/src/frontends/tensorflow/src/graph_iterator_proto.hpp b/src/frontends/tensorflow/src/graph_iterator_proto.hpp index 966fe8e631a879..37ce202f095dcf 100644 --- a/src/frontends/tensorflow/src/graph_iterator_proto.hpp +++ b/src/frontends/tensorflow/src/graph_iterator_proto.hpp @@ -4,10 +4,8 @@ #pragma once +#include #include -#if defined(__MINGW32__) || defined(__MINGW64__) -# include -#endif #include #include "checkpoint_v1_reader.hpp" @@ -59,8 +57,7 @@ class GraphIteratorProto : public GraphIterator { } } - template - void initialize_v1_checkpoints(const std::basic_string& checkpoint_directory) { + void initialize_v1_checkpoints(const std::filesystem::path& checkpoint_directory) { m_checkpoint_v1_reader = std::make_shared(checkpoint_directory); m_checkpoint_v1_reader->initialize(); } @@ -107,16 +104,11 @@ class GraphIteratorProto : public GraphIterator { } /// \brief Construct GraphIterator for the frozen model without v1 checkpoints - template - GraphIteratorProto(const std::basic_string& model_path) + GraphIteratorProto(const std::filesystem::path& model_path) : m_graph_def(std::make_shared<::tensorflow::GraphDef>()), m_func_def(nullptr), m_checkpoint_v1_reader(nullptr) { -#if defined(__MINGW32__) || defined(__MINGW64__) - std::ifstream pb_stream(std::filesystem::path(model_path), std::ios::in | std::ifstream::binary); -#else std::ifstream pb_stream(model_path, std::ios::in | std::ifstream::binary); -#endif FRONT_END_GENERAL_CHECK(pb_stream && pb_stream.is_open(), "Model file does not exist"); FRONT_END_GENERAL_CHECK(m_graph_def->ParseFromIstream(&pb_stream), "Model cannot be parsed"); @@ -125,8 +117,7 @@ class GraphIteratorProto : public GraphIterator { } /// \brief Construct GraphIterator for the frozen model with v1 checkpoints - template - GraphIteratorProto(const std::basic_string& model_path, const std::basic_string& checkpoint_directory) + GraphIteratorProto(const std::filesystem::path& model_path, const std::filesystem::path& checkpoint_directory) : m_graph_def(std::make_shared<::tensorflow::GraphDef>()), m_func_def(nullptr), m_checkpoint_v1_reader(nullptr) { @@ -140,18 +131,12 @@ class GraphIteratorProto : public GraphIterator { } /// \brief Check if the input file is supported - template - static bool is_supported(const std::basic_string& path) { + static bool is_supported(const std::filesystem::path& path) { FRONT_END_GENERAL_CHECK(util::directory_exists(path) || util::file_exists(path), - "Could not open the file: \"", - util::path_to_string(path), - '"'); + "Could not open the file: ", + path); try { -#if defined(__MINGW32__) || defined(__MINGW64__) - std::ifstream pb_stream(std::filesystem::path(path), std::ios::in | std::ifstream::binary); -#else std::ifstream pb_stream(path, std::ios::in | std::ifstream::binary); -#endif auto graph_def = std::make_shared<::tensorflow::GraphDef>(); return pb_stream && pb_stream.is_open() && graph_def->ParsePartialFromIstream(&pb_stream) && graph_def->node_size() > 0; diff --git a/src/frontends/tensorflow/src/graph_iterator_proto_txt.hpp b/src/frontends/tensorflow/src/graph_iterator_proto_txt.hpp index 7922939116f535..7c65970d21b319 100644 --- a/src/frontends/tensorflow/src/graph_iterator_proto_txt.hpp +++ b/src/frontends/tensorflow/src/graph_iterator_proto_txt.hpp @@ -4,10 +4,8 @@ #pragma once +#include #include -#if defined(__MINGW32__) || defined(__MINGW64__) -# include -#endif #include "google/protobuf/io/zero_copy_stream_impl.h" #include "google/protobuf/text_format.h" @@ -22,13 +20,8 @@ namespace tensorflow { class GraphIteratorProtoTxt : public GraphIteratorProto { public: /// \brief Construct GraphIterator for the frozen model in text format without v1 checkpoints - template - GraphIteratorProtoTxt(const std::basic_string& path) : GraphIteratorProto() { -#if defined(__MINGW32__) || defined(__MINGW64__) - std::ifstream pbtxt_stream(std::filesystem::path(path), std::ios::in); -#else + GraphIteratorProtoTxt(const std::filesystem::path& path) : GraphIteratorProto() { std::ifstream pbtxt_stream(path, std::ios::in); -#endif FRONT_END_GENERAL_CHECK(pbtxt_stream && pbtxt_stream.is_open(), "Model file does not exist"); auto input_stream = std::make_shared<::google::protobuf::io::IstreamInputStream>(&pbtxt_stream); FRONT_END_GENERAL_CHECK(input_stream, "Model cannot be read"); @@ -41,8 +34,7 @@ class GraphIteratorProtoTxt : public GraphIteratorProto { } /// \brief Construct GraphIterator for the frozen model in text format with v1 checkpoints - template - GraphIteratorProtoTxt(const std::basic_string& path, const std::basic_string& checkpoint_directory) + GraphIteratorProtoTxt(const std::filesystem::path& path, const std::filesystem::path& checkpoint_directory) : GraphIteratorProto() { std::ifstream pbtxt_stream(path, std::ios::in); FRONT_END_GENERAL_CHECK(pbtxt_stream && pbtxt_stream.is_open(), "Model file does not exist"); @@ -58,14 +50,12 @@ class GraphIteratorProtoTxt : public GraphIteratorProto { } /// \brief Check if the input file is supported - template - static bool is_supported(const std::basic_string& path) { + static bool is_supported(const std::filesystem::path& path) { FRONT_END_GENERAL_CHECK(util::directory_exists(path) || util::file_exists(path), - "Could not open the file: \"", - util::path_to_string(path), - '"'); + "Could not open the file: ", + path); try { - std::ifstream pbtxt_stream(path.c_str(), std::ios::in); + std::ifstream pbtxt_stream(path, std::ios::in); bool model_exists = (pbtxt_stream && pbtxt_stream.is_open()); if (!model_exists) { return false; diff --git a/src/frontends/tensorflow/src/graph_iterator_saved_model.cpp b/src/frontends/tensorflow/src/graph_iterator_saved_model.cpp index 717ae5c5a0296e..37715b98a58c40 100644 --- a/src/frontends/tensorflow/src/graph_iterator_saved_model.cpp +++ b/src/frontends/tensorflow/src/graph_iterator_saved_model.cpp @@ -29,43 +29,24 @@ bool GraphIteratorSavedModel::is_valid_signature(const ::tensorflow::SignatureDe return true; } -bool GraphIteratorSavedModel::is_supported(const std::string& path) { +bool GraphIteratorSavedModel::is_supported(const std::filesystem::path& path) { if (ov::util::directory_exists(path)) { - FRONT_END_GENERAL_CHECK(util::file_exists(ov::util::path_join({path, "saved_model.pb"})), - "Could not open the file: \"", - ov::util::path_join({path, "saved_model.pb"}), - '"'); + FRONT_END_GENERAL_CHECK(util::file_exists(path / "saved_model.pb"), + "Could not open the file: ", + path / "saved_model.pb"); return true; } else { return false; } } -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -bool GraphIteratorSavedModel::is_supported(const std::wstring& path) { - return ov::util::directory_exists(path) && ov::util::file_exists(ov::util::path_join_w({path, L"saved_model.pb"})); +std::filesystem::path get_saved_model_name() { + return "saved_model.pb"; } -#endif -template <> -std::basic_string get_saved_model_name() { - return "/saved_model.pb"; +std::filesystem::path get_variables_index_name() { + return std::filesystem::path("variables") / "variables.index"; } -template <> -std::basic_string get_variables_index_name() { - return "/variables/variables.index"; -} - -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -template <> -std::basic_string get_saved_model_name() { - return L"/saved_model.pb"; -} -template <> -std::basic_string get_variables_index_name() { - return L"/variables/variables.index"; -} -#endif std::vector GraphIteratorSavedModel::split_tags(const std::string tags) const { std::vector tag_list = {}; diff --git a/src/frontends/tensorflow/src/graph_iterator_saved_model.hpp b/src/frontends/tensorflow/src/graph_iterator_saved_model.hpp index 0a2ee5ac1a13ce..16a264a06f12ac 100644 --- a/src/frontends/tensorflow/src/graph_iterator_saved_model.hpp +++ b/src/frontends/tensorflow/src/graph_iterator_saved_model.hpp @@ -4,6 +4,7 @@ #pragma once +#include #include #include "graph_iterator_proto.hpp" @@ -15,22 +16,8 @@ namespace ov { namespace frontend { namespace tensorflow { -template -std::basic_string get_saved_model_name() {} -template -std::basic_string get_variables_index_name() {} - -template <> -std::basic_string get_saved_model_name(); -template <> -std::basic_string get_variables_index_name(); - -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -template <> -std::basic_string get_saved_model_name(); -template <> -std::basic_string get_variables_index_name(); -#endif +std::filesystem::path get_saved_model_name(); +std::filesystem::path get_variables_index_name(); // Loads graph from Tensorflow Saved Model file (saved_model.pb) class GraphIteratorSavedModel : public GraphIteratorProto { @@ -43,17 +30,13 @@ class GraphIteratorSavedModel : public GraphIteratorProto { bool m_mmap_enabled; public: - template - GraphIteratorSavedModel(const std::basic_string& path, const std::string& tags, const bool mmap_enabled) + GraphIteratorSavedModel(const std::filesystem::path& path, const std::string& tags, const bool mmap_enabled) : m_saved_model(std::make_shared<::tensorflow::SavedModel>()), m_mmap_enabled(mmap_enabled) { this->read_saved_model(path, tags); } - static bool is_supported(const std::string& path); -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) - static bool is_supported(const std::wstring& path); -#endif + static bool is_supported(const std::filesystem::path& path); std::shared_ptr get_variables_index() { return m_variables_index; @@ -78,16 +61,15 @@ class GraphIteratorSavedModel : public GraphIteratorProto { private: bool is_valid_signature(const ::tensorflow::SignatureDef& signature) const; - template - bool read_saved_model(const std::basic_string& path, const std::string& tags) { - std::basic_string save_model_path = path + get_saved_model_name(); - std::ifstream sm_stream{save_model_path.c_str(), std::ifstream::in | std::ifstream::binary}; + bool read_saved_model(const std::filesystem::path& path, const std::string& tags) { + const auto save_model_path = path / get_saved_model_name(); + std::ifstream sm_stream{save_model_path, std::ifstream::in | std::ifstream::binary}; FRONT_END_GENERAL_CHECK(sm_stream && sm_stream.is_open(), "[TensorFlow Frontend] Model file does not exist"); - std::basic_string varIndexPath = path + get_variables_index_name(); + const auto varIndexPath = path / get_variables_index_name(); if (ov::util::file_exists(varIndexPath)) { m_variables_index = std::make_shared(m_mmap_enabled); - std::ifstream vi_stream{varIndexPath.c_str(), std::ifstream::in | std::ifstream::binary}; + std::ifstream vi_stream{varIndexPath, std::ifstream::in | std::ifstream::binary}; FRONT_END_GENERAL_CHECK(vi_stream && vi_stream.is_open(), "[TensorFlow Frontend] Saved Model's variable index file does not exist"); FRONT_END_GENERAL_CHECK(m_variables_index->read_variables(vi_stream, path), diff --git a/src/frontends/tensorflow/src/variables_index.cpp b/src/frontends/tensorflow/src/variables_index.cpp index d269b91ef907fd..87d5bf87b3da45 100644 --- a/src/frontends/tensorflow/src/variables_index.cpp +++ b/src/frontends/tensorflow/src/variables_index.cpp @@ -206,7 +206,9 @@ void VariablesIndex::read_checkpointable_object_graph() { } } -bool VariablesIndex::read_variables(std::ifstream& vi_stream, const std::string& path, const bool is_saved_model) { +bool VariablesIndex::read_variables(std::ifstream& vi_stream, + const std::filesystem::path& path, + const bool is_saved_model) { m_variables_index.clear(); read_variables_index(vi_stream, m_variables_index); read_bundle_header(); @@ -216,9 +218,11 @@ bool VariablesIndex::read_variables(std::ifstream& vi_stream, const std::string& std::snprintf(suffix.data(), suffix.size(), "data-%05d-of-%05d", shard, m_total_shards); std::filesystem::path fullPath; if (is_saved_model) { - fullPath = ov::util::path_join({path, "variables", std::string("variables.") + suffix.data()}); + fullPath = path / "variables" / (std::string("variables.") + suffix.data()); } else { - fullPath = ov::util::make_path(path + "." + suffix.data()); + fullPath = path; + fullPath += "."; + fullPath += suffix.data(); } if (m_mmap_enabled) { m_data_files[shard].mmap = load_mmap_object(fullPath); @@ -234,36 +238,6 @@ bool VariablesIndex::read_variables(std::ifstream& vi_stream, const std::string& return true; } -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -bool VariablesIndex::read_variables(std::ifstream& vi_stream, const std::wstring& path, const bool is_saved_model) { - m_variables_index.clear(); - read_variables_index(vi_stream, m_variables_index); - read_bundle_header(); - - std::vector suffix(20); - for (int32_t shard = 0; shard < m_total_shards; ++shard) { - swprintf_s(suffix.data(), suffix.size(), L"data-%05d-of-%05d", shard, m_total_shards); - std::filesystem::path fullPath; - if (is_saved_model) { - fullPath = ov::util::path_join_w({path, L"variables", std::wstring(L"variables.") + suffix.data()}); - } else { - fullPath = path + L"." + suffix.data(); - } - if (m_mmap_enabled) { - m_data_files[shard].mmap = load_mmap_object(fullPath); - FRONT_END_GENERAL_CHECK(m_data_files[shard].mmap->data(), "Variable index data cannot be mapped"); - } else { - m_data_files[shard].stream = - std::shared_ptr(new std::ifstream(fullPath, std::ifstream::in | std::ifstream::binary)); - FRONT_END_GENERAL_CHECK(m_data_files[shard].stream->is_open(), "Variable index data file does not exist"); - } - } - - read_checkpointable_object_graph(); - return true; -} -#endif - struct PtrNode { using SharedPtrNode = std::shared_ptr; diff --git a/src/frontends/tensorflow/src/variables_index.hpp b/src/frontends/tensorflow/src/variables_index.hpp index b120beba255c62..a61c8b0a467c2f 100644 --- a/src/frontends/tensorflow/src/variables_index.hpp +++ b/src/frontends/tensorflow/src/variables_index.hpp @@ -4,6 +4,7 @@ #pragma once +#include #include #include "graph_iterator_proto.hpp" @@ -52,15 +53,7 @@ class VariablesIndex { /// \param path A path to file with variables data /// \param is_saved_model Flag shows variables index is a part of Saved Model format /// \returns Returns true in case of everything loads successfully, false otherwise - bool read_variables(std::ifstream& vi_stream, const std::string& path, const bool is_saved_model = true); -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) - /// \brief Reads variables from opened variable index file. Can cause an asserts in case of issues. - /// \param vi_stream Opened stream file, file pointer doesn't matter, it will be rewind internally. - /// \param path A path to file with variables data - /// \param is_saved_model Flag shows variables index is a part of Saved Model format - /// \returns Returns true in case of everything loads successfully, false otherwise - bool read_variables(std::ifstream& vi_stream, const std::wstring& path, const bool is_saved_model = true); -#endif + bool read_variables(std::ifstream& vi_stream, const std::filesystem::path& path, const bool is_saved_model = true); /// \brief Returns data and size of data of stored variable /// \param name Name of variable From 0ad04eb8f7457d3fa9140c20e27240c327a0f26c Mon Sep 17 00:00:00 2001 From: Chenhu Wang Date: Tue, 21 Apr 2026 20:37:16 +0800 Subject: [PATCH 009/545] [CPU]Coverity scan issue fix (#35397) ### Details: - *coverity scan issue of underflow and overflow fix* ### Tickets: - *CVS-185067* ### AI Assistance: - *AI assistance used: no / yes* - *If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).* --- src/plugins/intel_cpu/src/nodes/mvn.cpp | 38 +++++++++++++++++++------ 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/plugins/intel_cpu/src/nodes/mvn.cpp b/src/plugins/intel_cpu/src/nodes/mvn.cpp index 3a3b895eb86e83..b91d9abd9df3a2 100644 --- a/src/plugins/intel_cpu/src/nodes/mvn.cpp +++ b/src/plugins/intel_cpu/src/nodes/mvn.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -2796,6 +2797,7 @@ void MVN::MVNJitExecutor::mvn_blk(const uint8_t* src_data, aux_buffer_size += blk_size; std::vector mean_buffer(aux_buffer_size * threads_num); std::vector variance_buffer(aux_buffer_size * threads_num); + const size_t overflow_thread_id = std::numeric_limits::max() / aux_buffer_size; for (size_t b = 0LU; b < N; b++) { size_t b_offset = b * C3; @@ -2816,13 +2818,19 @@ void MVN::MVNJitExecutor::mvn_blk(const uint8_t* src_data, // // | // // \|/ ///////////////////////////////// - auto thread_idx = static_cast(parallel_get_thread_num()); - if (thread_idx >= threads_num) { - return mean_internal; - } - // coverity[INTEGER_OVERFLOW] thread_idx < threads_num and - // aux_buffer_size * num_threads <= mean_buffer_size guaranteed - auto* mean_buffer_ptr = &mean_buffer[aux_buffer_size * thread_idx]; + auto raw_tid = parallel_get_thread_num(); + OPENVINO_ASSERT(raw_tid >= 0, "parallel_get_thread_num() returns negative value in MVNJitExecutor"); + const auto thread_idx = static_cast(raw_tid); + OPENVINO_ASSERT( + thread_idx < threads_num, + "parallel_get_thread_num() returns value greater than or equal to max threads in MVNJitExecutor"); + // Prevent overflow in aux_buffer_size * thread_idx (for static analyzers and safety) + OPENVINO_ASSERT(thread_idx <= overflow_thread_id, + "Calculated offset for mean_buffer is too large in MVNJitExecutor"); + const size_t offset = aux_buffer_size * thread_idx; + OPENVINO_ASSERT(offset <= mean_buffer.size(), + "Calculated offset for mean_buffer is out of range in MVNJitExecutor"); + auto* mean_buffer_ptr = mean_buffer.data() + offset; for (size_t i = 0; i < blk_size; i++) { mean_buffer_ptr[i] = 0.F; } @@ -2852,8 +2860,20 @@ void MVN::MVNJitExecutor::mvn_blk(const uint8_t* src_data, size_t src_offset = b_offset + cb * C2 + d * C1 + h * C0; float variance_internal = 0.0F; - auto* variance_buffer_ptr = - &variance_buffer[aux_buffer_size * static_cast(parallel_get_thread_num())]; + auto raw_tid = parallel_get_thread_num(); + OPENVINO_ASSERT(raw_tid >= 0, + "parallel_get_thread_num() returns negative value in MVNJitExecutor"); + const auto thread_idx = static_cast(raw_tid); + OPENVINO_ASSERT(thread_idx < threads_num, + "parallel_get_thread_num() returns value greater than or equal to max threads " + "in MVNJitExecutor"); + // Prevent overflow in aux_buffer_size * thread_idx (for static analyzers and safety) + OPENVINO_ASSERT(thread_idx <= overflow_thread_id, + "Calculated offset for mean_buffer is too large in MVNJitExecutor"); + const size_t offset = aux_buffer_size * thread_idx; + OPENVINO_ASSERT(offset <= variance_buffer.size(), + "Calculated offset for variance_buffer is out of range in MVNJitExecutor"); + auto* variance_buffer_ptr = variance_buffer.data() + offset; for (size_t i = 0; i < blk_size; i++) { variance_buffer_ptr[i] = 0.F; } From e06433993388d1a60ce348ce9c7a24fcf24170e6 Mon Sep 17 00:00:00 2001 From: Wanming Lin Date: Tue, 21 Apr 2026 22:02:40 +0800 Subject: [PATCH 010/545] [TRANSFORMATIONS][GPU] Support 4D InstanceNorm MVN pattern in GroupNormalizationFusion (#35195) ### Details: Some frameworks decompose GroupNormalization via InstanceNormalization and reshape the grouped tensor directly to 4D {N,G,-1,1} with a trailing unit dimension. MVN then reduces over axes {2,3} instead of axis {2} as in the existing 3D pattern {N,G,-1}. This is mathematically equivalent since the trailing dimension is always 1. Changes: - Extend `pre_mvn_reshape_m` predicate from `rank_equals(3)` to `is_rank_3_or_4` so the pattern matcher accepts both 3D and 4D reshapes before MVN. - Validate the trailing dimension is 1 for the 4D variant. - Handle MVN axes {2,3} validation for the 4D variant, including negative axis normalization. - Allow concrete values (not just -1) in the pre-MVN reshape's third dimension, since some frameworks emit explicit flattened sizes after constant folding. - Add positive tests for both special-marker ({0,G,-1,1}) and concrete-value 4D shapes, covering f32 and f16. - Add negative edge-case tests: wrong MVN axes, single axis with 4D reshape, trailing dim != 1, axes over batch/groups, and three MVN axes. ### Tickets: - CVS-184129 ### AI Assistance: - *AI assistance used: yes* - *VS Code Copilot(Claude Opus 4.6) was used to create code changes, to execute unit tests.* --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Pavel Durandin --- .../group_normalization_fusion.cpp | 117 +++- .../group_normalization_fusion_tests.cpp | 573 +++++++++++++++--- 2 files changed, 584 insertions(+), 106 deletions(-) diff --git a/src/common/transformations/src/transformations/common_optimizations/group_normalization_fusion.cpp b/src/common/transformations/src/transformations/common_optimizations/group_normalization_fusion.cpp index 6e736a591c7e76..3545347c7ebbd1 100644 --- a/src/common/transformations/src/transformations/common_optimizations/group_normalization_fusion.cpp +++ b/src/common/transformations/src/transformations/common_optimizations/group_normalization_fusion.cpp @@ -4,6 +4,8 @@ #include "transformations/common_optimizations/group_normalization_fusion.hpp" +#include + #include "itt.hpp" #include "openvino/core/graph_util.hpp" #include "openvino/core/rt_info.hpp" @@ -39,14 +41,41 @@ GroupNormalizationFusion::GroupNormalizationFusion() { return (output_ps.rank().is_static()) && (output_ps.rank().get_length() >= 2); }; + // This pattern matches GroupNormalization decomposed as InstanceNormalization. + // Two variants are supported: + // + // 1. 3D MVN pattern: + // Input -> Reshape{N,G,-1} -> MVN(axes={2}) -> [Mul] -> [Add] + // -> Reshape(original) -> Mul(gamma) -> Add(beta) + // + // 2. 4D MVN pattern: + // Input -> Reshape{N,G,-1,1} -> MVN(axes={2,3}) -> [Mul] -> [Add] + // -> Reshape(original) -> Mul(gamma) -> Add(beta) + // + // Some frameworks decompose GroupNormalization via InstanceNormalization + // and reshape directly to 4D {N,G,-1,1} with a trailing unit dimension. + // MVN then reduces over axes {2,3}, which is mathematically equivalent + // to the 3D variant since the trailing dimension is always 1. + // + // In both variants, optional instance-norm scale [Mul] and bias [Add] may + // appear immediately after MVN, before the reshape back to original shape. + auto input_m = pattern::any_input( pattern::all_of({has_real_not_quantized_type, has_at_least_2d_shape, pattern::has_static_dim(1)})); + auto is_rank_3_or_4 = [](const ov::Output& output) -> bool { + const auto& ps = output.get_partial_shape(); + if (ps.rank().is_dynamic()) + return false; + const auto r = ps.rank().get_length(); + return r == 3 || r == 4; + }; + auto pre_mvn_shape_const_m = pattern::wrap_type(pattern::all_of({pattern::rank_equals(1), pattern::has_static_dim(0)})); auto pre_mvn_reshape_m = pattern::wrap_type( {input_m, pre_mvn_shape_const_m}, - pattern::all_of({has_real_not_quantized_type, pattern::rank_equals(3), pattern::has_static_dim(1)})); + pattern::all_of({has_real_not_quantized_type, is_rank_3_or_4, pattern::has_static_dim(1)})); auto mvn_reduction_axes_const_m = pattern::wrap_type(pattern::all_of({pattern::rank_equals(1), pattern::has_static_dim(0)})); @@ -95,26 +124,62 @@ GroupNormalizationFusion::GroupNormalizationFusion() { const auto& pre_mvn_shape_const = ov::as_type_ptr(pattern_map.at(pre_mvn_shape_const_m).get_node_shared_ptr()); const auto& pre_mvn_shape_out_ps = pre_mvn_shape.get_shape(); - if (pre_mvn_shape_out_ps[0] != 3) + const auto pre_mvn_rank = pre_mvn_reshape_out_ps.rank().get_length(); + if (pre_mvn_shape_out_ps[0] != static_cast(pre_mvn_rank)) return false; + // For 4D pattern, the trailing dimension must be 1 + const bool is_4d_pattern = (pre_mvn_rank == 4); + if (is_4d_pattern) { + if (!pre_mvn_reshape_out_ps[3].is_static() || pre_mvn_reshape_out_ps[3].get_length() != 1) + return false; + } + auto pre_mvn_shape_vals_correct = [](const std::vector& pre_mvn_shape_vals, const ov::PartialShape& input_ps, const ov::Dimension::value_type num_groups) -> bool { - bool res = true; + // Validate first dimension (batch): must be 0 (special value) or match input batch size if (input_ps[0].is_dynamic()) { if (pre_mvn_shape_vals[0] != 0ll) - res = false; + return false; } else { if ((pre_mvn_shape_vals[0] != 0ll) && (pre_mvn_shape_vals[0] != static_cast(input_ps[0].get_max_length()))) - res = false; + return false; + } + // Validate second dimension (groups): allow either an explicit num_groups value + // or special-zero copy when it provably copies the input channel dimension and + // that channel dimension is equal to num_groups. + if (pre_mvn_shape_vals[1] == 0ll) { + if (!input_ps[1].is_static() || input_ps[1].get_length() != static_cast(num_groups)) + return false; + } else if (pre_mvn_shape_vals[1] != static_cast(num_groups)) { + return false; + } + // Validate third dimension: can be -1 (infer) or the actual flattened size. + // When a concrete value is given, we must verify it against the expected + // merged spatial size computed from the input shape, not from the reshape + // output (which is derived from the same constant and would be tautological). + if (pre_mvn_shape_vals[2] != -1ll) { + // Compute expected merged spatial: (C / G) * product(spatial dims) + // All relevant input dimensions must be static to verify. + if (!input_ps[1].is_static()) + return false; + int64_t expected_merged = input_ps[1].get_length() / num_groups; + for (int64_t d = 2; d < input_ps.rank().get_length(); d++) { + if (input_ps[d].is_dynamic()) + return false; + expected_merged *= input_ps[d].get_length(); + } + if (pre_mvn_shape_vals[2] != expected_merged) + return false; } - if ((pre_mvn_shape_vals[1] != 0ll) && (pre_mvn_shape_vals[1] != static_cast(num_groups))) - res = false; - if (pre_mvn_shape_vals[2] != -1ll) - res = false; - return res; + // For 4D pattern, validate the trailing dimension value is 1 + if (pre_mvn_shape_vals.size() == 4) { + if (pre_mvn_shape_vals[3] != 1ll) + return false; + } + return true; }; if (!pre_mvn_shape_vals_correct(pre_mvn_shape_const->cast_vector(), input_ps, num_groups)) @@ -129,22 +194,32 @@ GroupNormalizationFusion::GroupNormalizationFusion() { if (input_ps[0].get_max_length() != pre_mvn_reshape_out_ps[0].get_max_length()) return false; - // we expect to execute normalization over last dimension of MVN input + // we expect to execute normalization over last dimension(s) of MVN input const auto& mvn_reduction_axes = pattern_map.at(mvn_reduction_axes_const_m); const auto& mvn_reduction_axes_const = ov::as_type_ptr(mvn_reduction_axes.get_node_shared_ptr()); const auto& mvn_reduction_axes_out_shape = mvn_reduction_axes.get_shape(); - if (mvn_reduction_axes_out_shape[0] != 1) - return false; - auto mvn_reduction_axes_correct = [](const std::vector& mvn_reduction_axes) -> bool { - bool res = true; - if ((mvn_reduction_axes[0] != 2ll) && (mvn_reduction_axes[0] != -1ll)) + auto axes = mvn_reduction_axes_const->cast_vector(); + if (is_4d_pattern) { + // 4D pattern: MVN axes must be {2, 3} + if (mvn_reduction_axes_out_shape[0] != 2) return false; - return res; - }; - - if (!mvn_reduction_axes_correct(mvn_reduction_axes_const->cast_vector())) - return false; + for (auto& ax : axes) { + if (ax < -4 || ax >= 4) + return false; + if (ax < 0) + ax += 4; // normalize negative axes + } + std::sort(axes.begin(), axes.end()); + if (axes.size() != 2 || axes[0] != 2 || axes[1] != 3) + return false; + } else { + // 3D pattern: MVN axis must be 2 (or -1 which equals 2 for rank-3) + if (mvn_reduction_axes_out_shape[0] != 1) + return false; + if (axes[0] != 2 && axes[0] != -1) + return false; + } const auto& post_instance_norm_reshape_out_ps = pattern_map.at(post_instance_norm_reshape_m).get_partial_shape(); diff --git a/src/common/transformations/tests/common_optimizations/group_normalization_fusion_tests.cpp b/src/common/transformations/tests/common_optimizations/group_normalization_fusion_tests.cpp index 882ddb56166bfd..453f402f9d46d0 100644 --- a/src/common/transformations/tests/common_optimizations/group_normalization_fusion_tests.cpp +++ b/src/common/transformations/tests/common_optimizations/group_normalization_fusion_tests.cpp @@ -47,6 +47,7 @@ class GroupNormalizationFusionTestBase { protected: element::Type elem_type; int64_t num_channels; + bool positive_test; bool instance_norm_gamma_present; bool instance_norm_beta_present; @@ -65,70 +66,7 @@ class GroupNormalizationFusionTestBase { virtual void read_test_parameters() = 0; - void generate_weights_init_values() { - if (instance_norm_gamma_present) { - auto instanceNormGammaTensor = utils::create_and_fill_tensor(elem_type, - instance_norm_gamma_shape, - utils::InputGenerateData(1, 10, 1, 2)); - instance_norm_gamma_const = std::make_shared(instanceNormGammaTensor); - } - if (instance_norm_beta_present) { - auto instanceNormBetaTensor = utils::create_and_fill_tensor(elem_type, - instance_norm_beta_shape, - utils::InputGenerateData(1, 10, 1, 3)); - instance_norm_beta_const = std::make_shared(instanceNormBetaTensor); - } - - auto groupNormGammaTensor = - utils::create_and_fill_tensor(elem_type, group_norm_gamma_shape, utils::InputGenerateData(1, 10, 1, 1)); - group_norm_gamma_const = std::make_shared(groupNormGammaTensor); - - auto groupNormBetaTensor = - utils::create_and_fill_tensor(elem_type, group_norm_beta_shape, utils::InputGenerateData(1, 10, 1, 11)); - group_norm_beta_const = std::make_shared(groupNormBetaTensor); - } - - std::shared_ptr create_model() { - auto input = std::make_shared(elem_type, data_shape); - auto pre_mvn_shape_const = op::v0::Constant::create(element::i64, Shape{3}, {0, num_groups, -1}); - auto pre_mvn_reshape = std::make_shared(input, pre_mvn_shape_const, true); - - auto mvn_axes_const = op::v0::Constant::create(element::i64, Shape{1}, {2}); - auto mvn = std::make_shared(pre_mvn_reshape, - mvn_axes_const, - true, - static_cast(epsilon), - op::MVNEpsMode::INSIDE_SQRT); - - std::shared_ptr opt_instance_norm_gamma_multiply = mvn; - if (instance_norm_gamma_present) - opt_instance_norm_gamma_multiply = std::make_shared(mvn, instance_norm_gamma_const); - - std::shared_ptr opt_instance_norm_beta_add = opt_instance_norm_gamma_multiply; - if (instance_norm_beta_present) - opt_instance_norm_beta_add = - std::make_shared(opt_instance_norm_gamma_multiply, instance_norm_beta_const); - - auto post_instance_norm_shape = std::make_shared(input); - auto post_instance_norm_reshape = - std::make_shared(opt_instance_norm_beta_add, post_instance_norm_shape, true); - auto group_norm_gamma_multiply = - std::make_shared(post_instance_norm_reshape, group_norm_gamma_const); - auto group_norm_beta_add = std::make_shared(group_norm_gamma_multiply, group_norm_beta_const); - - return std::make_shared(OutputVector{group_norm_beta_add}, ParameterVector{input}); - } -}; - -class GroupNormalizationFusionTransformationTestsF - : public GroupNormalizationFusionTestBase, - public TransformationTestsF, - public testing::WithParamInterface { -public: - static std::string getTestCaseName( - const testing::TestParamInfo& obj) { - const auto& params = obj.param; - + static std::string get_test_case_name(const GroupNormalizationFusionTransformationTestValues& params) { const auto& data_shape = std::get<0>(params); const auto& instance_norm_gamma_shape = std::get<1>(params); const auto& instance_norm_beta_shape = std::get<2>(params); @@ -154,26 +92,7 @@ class GroupNormalizationFusionTransformationTestsF return results.str(); } - void run() { - read_test_parameters(); - generate_weights_init_values(); - model = create_model(); - manager.register_pass(); - - if (positive_test) { - model_ref = create_ref_model(); - } else { - ASSERT_EQ(count_ops_of_type(model), 0); - test_skipped = true; - } - } - -protected: - bool positive_test; - - void read_test_parameters() override { - const auto& params = GetParam(); - + void read_test_parameters_impl(const GroupNormalizationFusionTransformationTestValues& params) { data_shape = std::get<0>(params); if (!data_shape.rank().is_static()) throw std::runtime_error("Rank of input tensor has to be static!"); @@ -217,7 +136,61 @@ class GroupNormalizationFusionTransformationTestsF } } - std::shared_ptr create_ref_model() { + void generate_weights_init_values() { + if (instance_norm_gamma_present) { + auto instanceNormGammaTensor = utils::create_and_fill_tensor(elem_type, + instance_norm_gamma_shape, + utils::InputGenerateData(1, 10, 1, 2)); + instance_norm_gamma_const = std::make_shared(instanceNormGammaTensor); + } + if (instance_norm_beta_present) { + auto instanceNormBetaTensor = utils::create_and_fill_tensor(elem_type, + instance_norm_beta_shape, + utils::InputGenerateData(1, 10, 1, 3)); + instance_norm_beta_const = std::make_shared(instanceNormBetaTensor); + } + + auto groupNormGammaTensor = + utils::create_and_fill_tensor(elem_type, group_norm_gamma_shape, utils::InputGenerateData(1, 10, 1, 1)); + group_norm_gamma_const = std::make_shared(groupNormGammaTensor); + + auto groupNormBetaTensor = + utils::create_and_fill_tensor(elem_type, group_norm_beta_shape, utils::InputGenerateData(1, 10, 1, 11)); + group_norm_beta_const = std::make_shared(groupNormBetaTensor); + } + + std::shared_ptr create_model() { + auto input = std::make_shared(elem_type, data_shape); + auto pre_mvn_shape_const = op::v0::Constant::create(element::i64, Shape{3}, {0, num_groups, -1}); + auto pre_mvn_reshape = std::make_shared(input, pre_mvn_shape_const, true); + + auto mvn_axes_const = op::v0::Constant::create(element::i64, Shape{1}, {2}); + auto mvn = std::make_shared(pre_mvn_reshape, + mvn_axes_const, + true, + static_cast(epsilon), + op::MVNEpsMode::INSIDE_SQRT); + + std::shared_ptr opt_instance_norm_gamma_multiply = mvn; + if (instance_norm_gamma_present) + opt_instance_norm_gamma_multiply = std::make_shared(mvn, instance_norm_gamma_const); + + std::shared_ptr opt_instance_norm_beta_add = opt_instance_norm_gamma_multiply; + if (instance_norm_beta_present) + opt_instance_norm_beta_add = + std::make_shared(opt_instance_norm_gamma_multiply, instance_norm_beta_const); + + auto post_instance_norm_shape = std::make_shared(input); + auto post_instance_norm_reshape = + std::make_shared(opt_instance_norm_beta_add, post_instance_norm_shape, true); + auto group_norm_gamma_multiply = + std::make_shared(post_instance_norm_reshape, group_norm_gamma_const); + auto group_norm_beta_add = std::make_shared(group_norm_gamma_multiply, group_norm_beta_const); + + return std::make_shared(OutputVector{group_norm_beta_add}, ParameterVector{input}); + } + + std::shared_ptr create_ref_model_impl() { auto input = std::make_shared(elem_type, data_shape); auto shape_1d_const = op::v0::Constant::create(element::i64, Shape{1}, {1}); @@ -272,6 +245,45 @@ class GroupNormalizationFusionTransformationTestsF return std::make_shared(OutputVector{group_norm}, ParameterVector{input}); } + + void run_transformation_test(const std::shared_ptr& model_under_test, + pass::Manager& test_manager, + std::shared_ptr& transformed_model, + std::shared_ptr& reference_model) { + transformed_model = model_under_test; + test_manager.register_pass(); + + if (positive_test) { + reference_model = create_ref_model_impl(); + } else { + // Verify no GroupNormalization exists before the pass runs. + // Leave reference_model unset so TransformationTestsF::TearDown + // clones the original model and verifies the pass leaves it unchanged. + ASSERT_EQ(count_ops_of_type(transformed_model), 0); + } + } +}; + +class GroupNormalizationFusionTransformationTestsF + : public GroupNormalizationFusionTestBase, + public TransformationTestsF, + public testing::WithParamInterface { +public: + static std::string getTestCaseName( + const testing::TestParamInfo& obj) { + return get_test_case_name(obj.param); + } + + void run() { + read_test_parameters(); + generate_weights_init_values(); + run_transformation_test(create_model(), manager, model, model_ref); + } + +protected: + void read_test_parameters() override { + read_test_parameters_impl(GetParam()); + } }; TEST_P(GroupNormalizationFusionTransformationTestsF, GroupNormalizationFusionTransformationTests) { @@ -551,5 +563,396 @@ INSTANTIATE_TEST_SUITE_P( GroupNormalizationFusionTransformationTestAdditionalValues(element::f8e8m0, false))), GroupNormalizationFusionTransformationTestsF::getTestCaseName); +// 4D InstanceNormalization MVN Pattern Tests +// Pattern: Input -> Reshape{N,G,-1,1} -> MVN(axes={2,3}) -> [Mul] -> [Add] -> Reshape(original) +// This pattern appears when GroupNormalization is decomposed via InstanceNormalization +// and reshaped directly to 4D with a trailing unit dimension before MVN. + +class GroupNormalizationFusion4DTestsF + : public GroupNormalizationFusionTestBase, + public TransformationTestsF, + public testing::WithParamInterface { +public: + static std::string getTestCaseName( + const testing::TestParamInfo& obj) { + return get_test_case_name(obj.param); + } + + void run() { + read_test_parameters(); + generate_weights_init_values(); + run_transformation_test(create_model_4d(), manager, model, model_ref); + } + +protected: + void read_test_parameters() override { + read_test_parameters_impl(GetParam()); + } + + // Creates the 4D InstanceNorm pattern model + std::shared_ptr create_model_4d() { + auto input = std::make_shared(elem_type, data_shape); + + // Reshape directly to 4D: {0, G, -1, 1} + auto pre_mvn_shape_const_4d = + op::v0::Constant::create(element::i64, Shape{4}, {0, num_groups, -1, 1}); + auto pre_mvn_reshape_4d = std::make_shared(input, pre_mvn_shape_const_4d, true); + + // MVN with axes={2,3} + auto mvn_axes_const = op::v0::Constant::create(element::i64, Shape{2}, {2, 3}); + auto mvn = std::make_shared(pre_mvn_reshape_4d, + mvn_axes_const, + true, + static_cast(epsilon), + op::MVNEpsMode::INSIDE_SQRT); + + std::shared_ptr opt_instance_norm_gamma_multiply = mvn; + if (instance_norm_gamma_present) { + opt_instance_norm_gamma_multiply = std::make_shared(mvn, instance_norm_gamma_const); + } + + std::shared_ptr opt_instance_norm_beta_add = opt_instance_norm_gamma_multiply; + if (instance_norm_beta_present) { + opt_instance_norm_beta_add = + std::make_shared(opt_instance_norm_gamma_multiply, instance_norm_beta_const); + } + + // Reshape back to original shape + auto post_instance_norm_shape = std::make_shared(input); + auto post_instance_norm_reshape = + std::make_shared(opt_instance_norm_beta_add, post_instance_norm_shape, true); + + auto group_norm_gamma_multiply = + std::make_shared(post_instance_norm_reshape, group_norm_gamma_const); + auto group_norm_beta_add = std::make_shared(group_norm_gamma_multiply, group_norm_beta_const); + + return std::make_shared(OutputVector{group_norm_beta_add}, ParameterVector{input}); + } +}; + +TEST_P(GroupNormalizationFusion4DTestsF, GroupNormalizationFusion4DTests) { + GroupNormalizationFusion4DTestsF::run(); +} + +const std::vector valid_vals_4d = { + std::make_tuple(PartialShape{1, 320}, Shape{}, Shape{}, Shape{320}, Shape{320}, 1, 1e-5), + std::make_tuple(PartialShape{1, 320}, Shape{1, 32, 1, 1}, Shape{1, 32, 1, 1}, Shape{320}, Shape{320}, 32, 1e-5), + std::make_tuple(PartialShape{1, 64, 8, 8}, + Shape{1, 8, 1, 1}, + Shape{1, 8, 1, 1}, + Shape{64, 1, 1}, + Shape{64, 1, 1}, + 8, + 1e-5), + std::make_tuple(PartialShape{2, 128, 16, 16}, Shape{}, Shape{}, Shape{128, 1, 1}, Shape{128, 1, 1}, 32, 1e-5), + std::make_tuple(PartialShape{1, 320, 64, 64}, Shape{}, Shape{}, Shape{320, 1, 1}, Shape{1, 320, 1, 1}, 32, 1e-5), + std::make_tuple(PartialShape{1, 512, 32, 32}, Shape{}, Shape{}, Shape{512, 1, 1}, Shape{1, 512, 1, 1}, 32, 1e-6), + std::make_tuple(PartialShape{4, 512, 64, 64}, + Shape{1, 32, 1, 1}, + Shape{1, 32, 1, 1}, + Shape{512, 1, 1}, + Shape{512, 1, 1}, + 32, + 1e-6), +}; + +INSTANTIATE_TEST_SUITE_P(GroupNormalizationFusion4DPositiveTests_f32, + GroupNormalizationFusion4DTestsF, + ValuesIn(expand_vals(valid_vals_4d, + GroupNormalizationFusionTransformationTestAdditionalValues(element::f32, + true))), + GroupNormalizationFusion4DTestsF::getTestCaseName); + +INSTANTIATE_TEST_SUITE_P(GroupNormalizationFusion4DPositiveTests_f16, + GroupNormalizationFusion4DTestsF, + ValuesIn(expand_vals(valid_vals_4d, + GroupNormalizationFusionTransformationTestAdditionalValues(element::f16, + true))), + GroupNormalizationFusion4DTestsF::getTestCaseName); + +INSTANTIATE_TEST_SUITE_P(GroupNormalizationFusion4DPositiveTests_bf16, + GroupNormalizationFusion4DTestsF, + ValuesIn(expand_vals(valid_vals_4d, + GroupNormalizationFusionTransformationTestAdditionalValues(element::bf16, + true))), + GroupNormalizationFusion4DTestsF::getTestCaseName); + +// 4D InstanceNorm pattern with concrete shape values (vs special markers like {0, G, -1, 1}) +// Some frameworks resolve shapes during optimization and emit concrete dimension values. +class GroupNormalizationFusion4DConcreteValuesTestsF + : public GroupNormalizationFusionTestBase, + public TransformationTestsF, + public testing::WithParamInterface { +public: + static std::string getTestCaseName( + const testing::TestParamInfo& obj) { + return get_test_case_name(obj.param); + } + + void run() { + read_test_parameters(); + generate_weights_init_values(); + run_transformation_test(create_model_4d_concrete(), manager, model, model_ref); + } + +protected: + void read_test_parameters() override { + read_test_parameters_impl(GetParam()); + } + + // Creates the 4D InstanceNorm pattern model with concrete shape values + std::shared_ptr create_model_4d_concrete() { + if (!data_shape.is_static()) + throw std::runtime_error("Data shape must be static for concrete values test!"); + + auto static_shape = data_shape.to_shape(); + auto input = std::make_shared(elem_type, static_shape); + + // Compute concrete shape values + int64_t batch = static_cast(static_shape[0]); + int64_t merged_spatial = 1; + for (size_t i = 1; i < static_shape.size(); i++) { + if (i == 1) { + const auto channels = static_cast(static_shape[i]); + if (channels % num_groups != 0) { + throw std::runtime_error( + "Channel dimension must be divisible by num_groups for concrete values test!"); + } + merged_spatial = channels / num_groups; + } else { + merged_spatial *= static_cast(static_shape[i]); + } + } + + // Reshape directly to 4D with concrete values: {batch, num_groups, merged_spatial, 1} + auto pre_mvn_shape_const_4d = + op::v0::Constant::create(element::i64, Shape{4}, {batch, num_groups, merged_spatial, 1}); + auto pre_mvn_reshape_4d = std::make_shared(input, pre_mvn_shape_const_4d, false); + + // MVN with axes={2,3} + auto mvn_axes_const = op::v0::Constant::create(element::i64, Shape{2}, {2, 3}); + auto mvn = std::make_shared(pre_mvn_reshape_4d, + mvn_axes_const, + true, + static_cast(epsilon), + op::MVNEpsMode::INSIDE_SQRT); + + std::shared_ptr opt_instance_norm_gamma_multiply = mvn; + if (instance_norm_gamma_present) { + opt_instance_norm_gamma_multiply = std::make_shared(mvn, instance_norm_gamma_const); + } + + std::shared_ptr opt_instance_norm_beta_add = opt_instance_norm_gamma_multiply; + if (instance_norm_beta_present) { + opt_instance_norm_beta_add = + std::make_shared(opt_instance_norm_gamma_multiply, instance_norm_beta_const); + } + + // Reshape back to original shape with concrete values + std::vector orig_shape_vals; + for (auto dim : static_shape) + orig_shape_vals.push_back(static_cast(dim)); + auto original_shape_const = + op::v0::Constant::create(element::i64, Shape{static_shape.size()}, orig_shape_vals); + auto post_instance_norm_reshape = + std::make_shared(opt_instance_norm_beta_add, original_shape_const, false); + + auto group_norm_gamma_multiply = + std::make_shared(post_instance_norm_reshape, group_norm_gamma_const); + auto group_norm_beta_add = std::make_shared(group_norm_gamma_multiply, group_norm_beta_const); + + return std::make_shared(OutputVector{group_norm_beta_add}, ParameterVector{input}); + } +}; + +TEST_P(GroupNormalizationFusion4DConcreteValuesTestsF, GroupNormalizationFusion4DConcreteValuesTests) { + GroupNormalizationFusion4DConcreteValuesTestsF::run(); +} + +const std::vector valid_vals_4d_concrete = { + std::make_tuple(PartialShape{2, 64, 8, 8}, Shape{}, Shape{}, Shape{64, 1, 1}, Shape{64, 1, 1}, 8, 1e-5), + std::make_tuple(PartialShape{1, 320, 64, 64}, Shape{}, Shape{}, Shape{320, 1, 1}, Shape{1, 320, 1, 1}, 32, 1e-5), + std::make_tuple(PartialShape{4, 512, 64, 64}, Shape{}, Shape{}, Shape{512, 1, 1}, Shape{1, 512, 1, 1}, 32, 1e-6), +}; + +INSTANTIATE_TEST_SUITE_P(GroupNormalizationFusion4DConcreteValuesPositiveTests_f32, + GroupNormalizationFusion4DConcreteValuesTestsF, + ValuesIn(expand_vals(valid_vals_4d_concrete, + GroupNormalizationFusionTransformationTestAdditionalValues(element::f32, + true))), + GroupNormalizationFusion4DConcreteValuesTestsF::getTestCaseName); + +INSTANTIATE_TEST_SUITE_P(GroupNormalizationFusion4DConcreteValuesPositiveTests_f16, + GroupNormalizationFusion4DConcreteValuesTestsF, + ValuesIn(expand_vals(valid_vals_4d_concrete, + GroupNormalizationFusionTransformationTestAdditionalValues(element::f16, + true))), + GroupNormalizationFusion4DConcreteValuesTestsF::getTestCaseName); + +INSTANTIATE_TEST_SUITE_P(GroupNormalizationFusion4DConcreteValuesPositiveTests_bf16, + GroupNormalizationFusion4DConcreteValuesTestsF, + ValuesIn(expand_vals(valid_vals_4d_concrete, + GroupNormalizationFusionTransformationTestAdditionalValues(element::bf16, + true))), + GroupNormalizationFusion4DConcreteValuesTestsF::getTestCaseName); + +// Helper: builds a 4D InstanceNorm-style pattern with customizable MVN axes and 4D trailing dimension. +// Returns a Model whose output is the final Add node (beta_add). +static std::shared_ptr build_4d_pattern_model(const PartialShape& data_shape, + int64_t num_groups, + const std::vector& mvn_axes_vals, + long long trailing_dim, + float epsilon = 1e-5f, + element::Type elem_type = element::f32) { + OPENVINO_ASSERT(data_shape[1].is_static() && data_shape[1].get_length() > 0, + "Channel dimension must be static and positive for build_4d_pattern_model"); + const auto num_channels = data_shape[1].get_max_length(); + + auto input = std::make_shared(elem_type, data_shape); + + // Reshape directly to 4D: {0, G, -1, trailing_dim} + auto pre_mvn_shape_4d = + op::v0::Constant::create(element::i64, Shape{4}, {0, num_groups, -1, trailing_dim}); + auto reshape_4d = std::make_shared(input, pre_mvn_shape_4d, true); + + // MVN with custom axes + auto mvn_axes = op::v0::Constant::create(element::i64, Shape{mvn_axes_vals.size()}, mvn_axes_vals); + auto mvn = std::make_shared(reshape_4d, mvn_axes, true, epsilon, op::MVNEpsMode::INSIDE_SQRT); + + // Reshape back to original shape + auto post_shape = std::make_shared(input); + auto post_reshape = std::make_shared(mvn, post_shape, true); + + // Group norm gamma/beta + auto gamma_const = op::v0::Constant::create(elem_type, + Shape{static_cast(num_channels), 1, 1}, + std::vector(num_channels, 1.0f)); + auto gamma_mul = std::make_shared(post_reshape, gamma_const); + auto beta_const = op::v0::Constant::create(elem_type, + Shape{1, static_cast(num_channels), 1, 1}, + std::vector(num_channels, 0.0f)); + auto beta_add = std::make_shared(gamma_mul, beta_const); + + return std::make_shared(OutputVector{beta_add}, ParameterVector{input}); +} + +// 4D pattern with negative MVN axes {-2, -1}; these should normalize to {2, 3} +// and still fuse successfully. +TEST(GroupNormalizationFusion4DAdditionalTests, NegativeMVNAxesNormalizedToPositive) { + auto model = build_4d_pattern_model(PartialShape{1, 320, 64, 64}, + /*num_groups=*/32, + /*mvn_axes_vals=*/{-2, -1}, + /*trailing_dim=*/1); + + ASSERT_EQ(count_ops_of_type(model), 0); + + pass::Manager m; + m.register_pass(); + OV_ASSERT_NO_THROW(m.run_passes(model)); + + ASSERT_EQ(count_ops_of_type(model), 1); +} + +// 4D pattern with wrong MVN axes: {1, 2} instead of {2, 3} +TEST(GroupNormalizationFusion4DNegativeEdgeCases, WrongMVNAxes) { + auto model = build_4d_pattern_model(PartialShape{1, 320, 64, 64}, + /*num_groups=*/32, + /*mvn_axes_vals=*/{1, 2}, + /*trailing_dim=*/1); + pass::Manager m; + m.register_pass(); + m.run_passes(model); + ASSERT_EQ(count_ops_of_type(model), 0); +} + +// 4D pattern with single MVN axis {2} — requires {2, 3} for 4D pattern +TEST(GroupNormalizationFusion4DNegativeEdgeCases, SingleMVNAxisWith4DReshape) { + auto model = build_4d_pattern_model(PartialShape{1, 320, 64, 64}, + /*num_groups=*/32, + /*mvn_axes_vals=*/{2}, + /*trailing_dim=*/1); + pass::Manager m; + m.register_pass(); + m.run_passes(model); + ASSERT_EQ(count_ops_of_type(model), 0); +} + +// 4D pattern with trailing dimension != 1 (e.g., 2) +TEST(GroupNormalizationFusion4DNegativeEdgeCases, TrailingDimNotOne) { + auto model = build_4d_pattern_model(PartialShape{1, 320, 64, 64}, + /*num_groups=*/32, + /*mvn_axes_vals=*/{2, 3}, + /*trailing_dim=*/2); + pass::Manager m; + m.register_pass(); + m.run_passes(model); + ASSERT_EQ(count_ops_of_type(model), 0); +} + +// 4D pattern with MVN axes {0, 1} — normalizing over wrong dimensions +TEST(GroupNormalizationFusion4DNegativeEdgeCases, MVNAxesOverBatchAndGroups) { + auto model = build_4d_pattern_model(PartialShape{1, 320, 64, 64}, + /*num_groups=*/32, + /*mvn_axes_vals=*/{0, 1}, + /*trailing_dim=*/1); + pass::Manager m; + m.register_pass(); + m.run_passes(model); + ASSERT_EQ(count_ops_of_type(model), 0); +} + +// 4D pattern with three MVN axes {1, 2, 3} — too many axes for 4D pattern +TEST(GroupNormalizationFusion4DNegativeEdgeCases, ThreeMVNAxes) { + auto model = build_4d_pattern_model(PartialShape{1, 320, 64, 64}, + /*num_groups=*/32, + /*mvn_axes_vals=*/{1, 2, 3}, + /*trailing_dim=*/1); + pass::Manager m; + m.register_pass(); + m.run_passes(model); + ASSERT_EQ(count_ops_of_type(model), 0); +} + +// 3D pattern with concrete (non -1) third dimension. +// Input {1, 320, 2, 4} with 32 groups → merged spatial = (320/32)*2*4 = 80 +// Reshape uses {1, 32, 80} instead of {0, 32, -1}. +TEST(GroupNormalizationFusion3DAdditionalTests, ConcreteSpatialDim) { + const PartialShape data_shape{1, 320, 2, 4}; + const int64_t num_groups = 32; + const int64_t num_channels = 320; + // (320 / 32) * 2 * 4 = 80 + const int64_t merged_spatial = (num_channels / num_groups) * 2 * 4; + + auto input = std::make_shared(element::f32, data_shape); + auto pre_mvn_shape_const = + op::v0::Constant::create(element::i64, Shape{3}, {1, num_groups, merged_spatial}); + auto pre_mvn_reshape = std::make_shared(input, pre_mvn_shape_const, false); + + auto mvn_axes_const = op::v0::Constant::create(element::i64, Shape{1}, {2}); + auto mvn = std::make_shared(pre_mvn_reshape, mvn_axes_const, true, 1e-5f, op::MVNEpsMode::INSIDE_SQRT); + + auto post_shape = std::make_shared(input); + auto post_reshape = std::make_shared(mvn, post_shape, true); + + auto gamma_const = op::v0::Constant::create(element::f32, + Shape{static_cast(num_channels), 1, 1}, + std::vector(num_channels, 1.0f)); + auto gamma_mul = std::make_shared(post_reshape, gamma_const); + auto beta_const = op::v0::Constant::create(element::f32, + Shape{1, static_cast(num_channels), 1, 1}, + std::vector(num_channels, 0.0f)); + auto beta_add = std::make_shared(gamma_mul, beta_const); + + auto model = std::make_shared(OutputVector{beta_add}, ParameterVector{input}); + + ASSERT_EQ(count_ops_of_type(model), 0); + + pass::Manager m; + m.register_pass(); + OV_ASSERT_NO_THROW(m.run_passes(model)); + + ASSERT_EQ(count_ops_of_type(model), 1); +} + } // namespace test } // namespace ov From d6617d5923f65e8785ff64294145725a57f099da Mon Sep 17 00:00:00 2001 From: Szymon Gutaj Date: Tue, 21 Apr 2026 16:16:41 +0200 Subject: [PATCH 011/545] Integer Underflow in ONNX Scan Converter num_scan_inputs leads to Out-of-bounds Read/Crash (#34761) - Fix and tests ### Details: - Fixed by adding assertions ### Tickets: - CVS-182187 - [PTK0006430](https://intelait.intel.com/x_volt2_csi_product_vulnerability.do?sysparm_tiny=2XQRMPMypIHMISVXAZBirXEgVUr17ZtI&sys_id=defbe40fdf577a105348b094c2388982&sysparm_record_row=31) --- src/frontends/onnx/frontend/src/op/scan.cpp | 94 +++++++++++--- src/frontends/onnx/tests/convert_tests.cpp | 19 +++ ...fewer_outputs_than_initial_values.prototxt | 116 ++++++++++++++++++ .../scan15_negative_num_scan_inputs.prototxt | 109 ++++++++++++++++ ...m_scan_inputs_exceeds_body_inputs.prototxt | 109 ++++++++++++++++ 5 files changed, 432 insertions(+), 15 deletions(-) create mode 100644 src/frontends/onnx/tests/models/scan15_body_fewer_outputs_than_initial_values.prototxt create mode 100644 src/frontends/onnx/tests/models/scan15_negative_num_scan_inputs.prototxt create mode 100644 src/frontends/onnx/tests/models/scan15_num_scan_inputs_exceeds_body_inputs.prototxt diff --git a/src/frontends/onnx/frontend/src/op/scan.cpp b/src/frontends/onnx/frontend/src/op/scan.cpp index 552a0374f7d5a2..d1acf633b3c2de 100644 --- a/src/frontends/onnx/frontend/src/op/scan.cpp +++ b/src/frontends/onnx/frontend/src/op/scan.cpp @@ -6,6 +6,7 @@ #include "core/null_node.hpp" #include "core/operator_set.hpp" #include "exceptions.hpp" +#include "openvino/frontend/exception.hpp" #include "openvino/op/constant.hpp" #include "openvino/op/squeeze.hpp" #include "openvino/op/tensor_iterator.hpp" @@ -24,13 +25,22 @@ namespace { ov::OutputVector scan_to_tensor_iterator(const ov::OutputVector& node_inputs, ov::ParameterVector& body_inputs, ov::OutputVector& body_outputs, - int64_t num_scan_inputs, + size_t num_scan_inputs, const std::vector& scan_input_axes, const std::vector& scan_input_directions, const std::vector& scan_output_axes, const std::vector& scan_output_directions, - int64_t in_offset = 0, + size_t in_offset = 0, const std::string& node_description = "") { + FRONT_END_OP_CONVERSION_CHECK( + node_inputs.size() >= in_offset && node_inputs.size() - in_offset >= body_inputs.size(), + node_description, + " Number of Scan node inputs (", + node_inputs.size(), + ") is less than required (", + in_offset + body_inputs.size(), + ") based on body graph parameters and in_offset"); + const size_t num_initial_values = body_inputs.size() - num_scan_inputs; const size_t num_scan_outputs = body_outputs.size() - num_initial_values; @@ -38,10 +48,10 @@ ov::OutputVector scan_to_tensor_iterator(const ov::OutputVector& node_inputs, if (r.is_static()) { axis = common::normalize_axis(node_description, axis, r); } else { - FRONT_END_GENERAL_CHECK(axis >= 0, - node_description, - " Rank must be static in order to normalize negative axis=", - axis); + FRONT_END_OP_CONVERSION_CHECK(axis >= 0, + node_description, + " Rank must be static in order to normalize negative axis=", + axis); } return axis; }; @@ -56,7 +66,7 @@ ov::OutputVector scan_to_tensor_iterator(const ov::OutputVector& node_inputs, // but in ONNX Scan the slice of input can has one dimension less, // so the parameter needs to have aligned rank with 1 at sliced axis, // and then squeezed to restore original shape. - for (int64_t i = 0; i < num_scan_inputs; ++i) { + for (size_t i = 0; i < num_scan_inputs; ++i) { const auto in_idx = num_initial_values + i; auto axis = scan_input_axes[i]; const auto axis_node = v0::Constant::create(ov::element::i64, ov::Shape{1}, {axis}); @@ -65,10 +75,10 @@ ov::OutputVector scan_to_tensor_iterator(const ov::OutputVector& node_inputs, axis = try_normalize_axis_for_static_rank(axis, shape.rank()); shape[axis] = 1; } else { - FRONT_END_GENERAL_CHECK(axis >= 0, - node_description, - " Rank must be static in order to normalize negative axis=", - axis); + FRONT_END_OP_CONVERSION_CHECK(axis >= 0, + node_description, + " Rank must be static in order to normalize negative axis=", + axis); } body_inputs[in_idx]->set_partial_shape(shape); body_inputs[in_idx]->validate_and_infer_types(); @@ -93,7 +103,7 @@ ov::OutputVector scan_to_tensor_iterator(const ov::OutputVector& node_inputs, tensor_iterator->set_function(ti_body); // Set slicing for Scan (TensorIterator) inputs - for (int64_t i = 0; i < num_scan_inputs; ++i) { + for (size_t i = 0; i < num_scan_inputs; ++i) { const auto in_idx = num_initial_values + i; const auto axis = try_normalize_axis_for_static_rank(scan_input_axes[i], @@ -127,8 +137,8 @@ ov::OutputVector scan_to_tensor_iterator(const ov::OutputVector& node_inputs, } ov::OutputVector import_onnx_scan(const ov::frontend::onnx::Node& node, - int64_t default_axis, - int64_t in_offset, + size_t default_axis, + size_t in_offset, std::string&& in_directions_attr_name) { const auto& node_inputs = node.get_ov_inputs(); @@ -147,22 +157,76 @@ ov::OutputVector import_onnx_scan(const ov::frontend::onnx::Node& node, } body_inputs = body_graph->get_parameters(); } - const int64_t num_scan_inputs = node.get_attribute_value("num_scan_inputs"); + + const int64_t num_scan_inputs_signed = node.get_attribute_value("num_scan_inputs"); + FRONT_END_OP_CONVERSION_CHECK(num_scan_inputs_signed >= 0, + node.get_description(), + " num_scan_inputs attribute is negative: ", + num_scan_inputs_signed); + const size_t num_scan_inputs = static_cast(num_scan_inputs_signed); + + FRONT_END_OP_CONVERSION_CHECK(body_inputs.size() >= num_scan_inputs, + node.get_description(), + " num_scan_inputs (", + num_scan_inputs, + ") negative or exceeds the number of body graph inputs (", + body_inputs.size(), + ")"); + const size_t num_initial_values = body_inputs.size() - num_scan_inputs; + FRONT_END_OP_CONVERSION_CHECK(body_outputs.size() >= num_initial_values, + node.get_description(), + " num_scan_outputs can't be negative: body outputs (", + body_outputs.size(), + ") is less than num_initial_values (", + num_initial_values, + ")"); + const size_t num_scan_outputs = body_outputs.size() - num_initial_values; std::vector scan_input_axes = node.get_attribute_value>("scan_input_axes", std::vector(num_scan_inputs, default_axis)); + FRONT_END_OP_CONVERSION_CHECK(scan_input_axes.size() >= num_scan_inputs, + node.get_description(), + " num_scan_inputs (", + num_scan_inputs, + ") exceeds the number of scan_input_axes (", + scan_input_axes.size(), + ")"); + std::vector scan_input_directions = node.get_attribute_value>(in_directions_attr_name, std::vector(num_scan_inputs, 0)); + FRONT_END_OP_CONVERSION_CHECK(scan_input_directions.size() >= num_scan_inputs, + node.get_description(), + " num_scan_inputs (", + num_scan_inputs, + ") exceeds the number of scan_input_directions (", + scan_input_directions.size(), + ")"); + std::vector scan_output_axes = node.get_attribute_value>("scan_output_axes", std::vector(num_scan_outputs, default_axis)); + FRONT_END_OP_CONVERSION_CHECK(scan_output_axes.size() >= num_scan_outputs, + node.get_description(), + " num_scan_outputs (", + num_scan_outputs, + ") exceeds the number of scan_output_axes (", + scan_output_axes.size(), + ")"); + std::vector scan_output_directions = node.get_attribute_value>("scan_output_directions", std::vector(num_scan_outputs, 0)); + FRONT_END_OP_CONVERSION_CHECK(scan_output_directions.size() >= num_scan_outputs, + node.get_description(), + " num_scan_outputs (", + num_scan_outputs, + ") exceeds the number of scan_output_directions (", + scan_output_directions.size(), + ")"); return scan_to_tensor_iterator(node_inputs, body_inputs, diff --git a/src/frontends/onnx/tests/convert_tests.cpp b/src/frontends/onnx/tests/convert_tests.cpp index b7af058a9341cf..e6762f6dca526a 100644 --- a/src/frontends/onnx/tests/convert_tests.cpp +++ b/src/frontends/onnx/tests/convert_tests.cpp @@ -9,6 +9,7 @@ #include "common_test_utils/file_utils.hpp" #include "common_test_utils/test_assertions.hpp" #include "onnx_utils.hpp" +#include "openvino/frontend/exception.hpp" #include "openvino/frontend/manager.hpp" using namespace ov::frontend::onnx::tests; @@ -76,3 +77,21 @@ TEST(ONNXFeConvertException, exception_if_qlinear_concat_invalid_x_input_triplet ov::AssertFailure, testing::AllOf(testing::HasSubstr("expected 2 + 3*N inputs"), testing::HasSubstr(" got: 6"))); } + +TEST(ONNXFeConvertException, exception_if_scan_num_scan_inputs_exceeds_body_inputs) { + OV_EXPECT_THROW(convert_model("scan15_num_scan_inputs_exceeds_body_inputs.onnx"), + ov::frontend::OpConversionFailure, + testing::HasSubstr("num_scan_inputs (10) negative or exceeds the number of body graph inputs (2)")); +} + +TEST(ONNXFeConvertException, exception_if_scan_negative_num_scan_inputs) { + OV_EXPECT_THROW(convert_model("scan15_negative_num_scan_inputs.onnx"), + ov::frontend::OpConversionFailure, + testing::HasSubstr("num_scan_inputs attribute is negative: -5")); +} + +TEST(ONNXFeConvertException, exception_if_scan_body_fewer_outputs_than_initial_values) { + OV_EXPECT_THROW(convert_model("scan15_body_fewer_outputs_than_initial_values.onnx"), + ov::frontend::OpConversionFailure, + testing::HasSubstr("num_scan_outputs can't be negative")); +} diff --git a/src/frontends/onnx/tests/models/scan15_body_fewer_outputs_than_initial_values.prototxt b/src/frontends/onnx/tests/models/scan15_body_fewer_outputs_than_initial_values.prototxt new file mode 100644 index 00000000000000..fd2f6803ed736b --- /dev/null +++ b/src/frontends/onnx/tests/models/scan15_body_fewer_outputs_than_initial_values.prototxt @@ -0,0 +1,116 @@ +ir_version: 8 +producer_name: "OpenVINO ONNX Frontend" +graph { + node { + input: "initial1" + input: "initial2" + input: "sequence" + output: "scan_end" + op_type: "Scan" + attribute { + name: "body" + g { + node { + input: "state1" + input: "seq_elem" + output: "sum" + op_type: "Add" + } + name: "body" + input { + name: "state1" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "state2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "seq_elem" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "sum" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + } + type: GRAPH + } + attribute { + name: "num_scan_inputs" + i: 1 + type: INT + } + } + name: "test-model-scan-fewer-body-outputs" + input { + name: "initial1" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "initial2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "sequence" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 5 + } + } + } + } + } + output { + name: "scan_end" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } +} +opset_import { + version: 15 +} diff --git a/src/frontends/onnx/tests/models/scan15_negative_num_scan_inputs.prototxt b/src/frontends/onnx/tests/models/scan15_negative_num_scan_inputs.prototxt new file mode 100644 index 00000000000000..6d940e8856c631 --- /dev/null +++ b/src/frontends/onnx/tests/models/scan15_negative_num_scan_inputs.prototxt @@ -0,0 +1,109 @@ +ir_version: 8 +producer_name: "OpenVINO ONNX Frontend" +graph { + node { + input: "initial" + input: "sequence" + output: "scan_end" + output: "scan_seq" + op_type: "Scan" + attribute { + name: "body" + g { + node { + input: "state" + input: "seq_elem" + output: "sum" + op_type: "Add" + } + name: "body" + input { + name: "state" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "seq_elem" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "sum" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + } + type: GRAPH + } + attribute { + name: "num_scan_inputs" + i: -5 + type: INT + } + } + name: "test-model-scan-neg" + input { + name: "initial" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "sequence" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 5 + } + } + } + } + } + output { + name: "scan_end" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "scan_seq" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 5 + } + } + } + } + } +} +opset_import { + version: 15 +} diff --git a/src/frontends/onnx/tests/models/scan15_num_scan_inputs_exceeds_body_inputs.prototxt b/src/frontends/onnx/tests/models/scan15_num_scan_inputs_exceeds_body_inputs.prototxt new file mode 100644 index 00000000000000..5557f012dd967f --- /dev/null +++ b/src/frontends/onnx/tests/models/scan15_num_scan_inputs_exceeds_body_inputs.prototxt @@ -0,0 +1,109 @@ +ir_version: 8 +producer_name: "OpenVINO ONNX Frontend" +graph { + node { + input: "initial" + input: "sequence" + output: "scan_end" + output: "scan_seq" + op_type: "Scan" + attribute { + name: "body" + g { + node { + input: "state" + input: "seq_elem" + output: "sum" + op_type: "Add" + } + name: "body" + input { + name: "state" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "seq_elem" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "sum" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + } + type: GRAPH + } + attribute { + name: "num_scan_inputs" + i: 10 + type: INT + } + } + name: "test-model-scan-invalid" + input { + name: "initial" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "sequence" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 5 + } + } + } + } + } + output { + name: "scan_end" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "scan_seq" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 5 + } + } + } + } + } +} +opset_import { + version: 15 +} From 1815ca416ec104e278951d8c2dfbef32c10e80c9 Mon Sep 17 00:00:00 2001 From: hyunback kim Date: Wed, 22 Apr 2026 10:16:50 +0900 Subject: [PATCH 012/545] [GPU] Fix Correct return type in DECLARE_LOWER/UPPER_BOUND macros (#35406) Fix GPU kernel compilation: correct return type in binary search macros. DECLARE_LOWER/UPPER_BOUND macros declared `Type*` return type but returned `uint` indices. This caused kernel compilation failure on certain GPU targets when bucketize uses float input + float16 buckets. Changed: `inline Type*`-> `inline uint` Compiler behavior may vary across hardware architectures, which is why this fix ensures consistent compilation across all targets. ### Tickets: - *185056* ### AI Assistance: - *AI assistance used: yes - *If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).* Signed-off-by: hyunback --- .../src/kernel_selector/cl_kernels/include/algorithm.cl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/include/algorithm.cl b/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/include/algorithm.cl index 909ccbcc05b6b2..e7e101a372be79 100644 --- a/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/include/algorithm.cl +++ b/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/include/algorithm.cl @@ -3,7 +3,7 @@ // #define DECLARE_LOWER_BOUND(Name, Type, ValType, GetIndex) \ - inline Type* FUNC(Name)(const Type* data, uint first_index, uint last_index, ValType value) { \ + inline uint FUNC(Name)(const Type* data, uint first_index, uint last_index, ValType value) { \ uint count = last_index - first_index; \ while (count > 0) { \ const uint step = count / 2; \ @@ -19,7 +19,7 @@ } #define DECLARE_UPPER_BOUND(Name, Type, ValType, GetIndex) \ - inline Type* FUNC(Name)(const Type* data, uint first_index, uint last_index, ValType value) { \ + inline uint FUNC(Name)(const Type* data, uint first_index, uint last_index, ValType value) { \ uint count = last_index - first_index; \ while (count > 0) { \ const uint step = count / 2; \ From 812a00ba47232745f19511da52847b89f3ad9e26 Mon Sep 17 00:00:00 2001 From: Mang Guo Date: Wed, 22 Apr 2026 09:21:41 +0800 Subject: [PATCH 013/545] Add shape size check to avoid overflow (#35277) ### Details: - *This PR adds overflow-safe validation for large shape/memory calculations in Tile and CPU blocked memory descriptor paths, to fail early with clear errors instead of reaching undefined arithmetic behavior or late allocation failures.* ### Tickets: - *CVS-184032* ### AI Assistance: - *AI assistance used: no / yes* - *If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).* --- .../include/openvino/util/common_util.hpp | 34 +++++++++++++++++ .../memory_desc/cpu_blocked_memory_desc.cpp | 37 ++++++++++--------- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/common/util/include/openvino/util/common_util.hpp b/src/common/util/include/openvino/util/common_util.hpp index 38c002ed43e83b..ac5baaca71de05 100644 --- a/src/common/util/include/openvino/util/common_util.hpp +++ b/src/common/util/include/openvino/util/common_util.hpp @@ -273,6 +273,40 @@ class StringViewStreamBuf : public std::streambuf { } }; +/** + * @brief Adds two integral values + * + * The result value is not valid if overflow detected. + * + * @param T Type of values to add. Must be an integral type. + * @param x First value to add. + * @param y Second value to add. + * @param result Reference to store result value. + * @return True if overflow occurs, false otherwise + */ +template +constexpr bool add_overflow(T x, T y, T& result) { + static_assert(std::is_integral_v, "T must be an integral type"); +#if defined(__GNUC__) || defined(__clang__) + return __builtin_add_overflow(x, y, &result); +#else + constexpr auto max = std::numeric_limits::max(); + + if constexpr (std::is_unsigned_v) { + if (x > max - y) { + return true; + } + } else { + constexpr auto min = std::numeric_limits::lowest(); + if ((y > 0 && x > max - y) || (y < 0 && x < min - y)) { + return true; + } + } + result = x + y; + return false; +#endif +} + /** * @brief Multiplies two integral values * diff --git a/src/plugins/intel_cpu/src/memory_desc/cpu_blocked_memory_desc.cpp b/src/plugins/intel_cpu/src/memory_desc/cpu_blocked_memory_desc.cpp index 94885ceac91565..3c2927ecb5bcf1 100644 --- a/src/plugins/intel_cpu/src/memory_desc/cpu_blocked_memory_desc.cpp +++ b/src/plugins/intel_cpu/src/memory_desc/cpu_blocked_memory_desc.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -16,7 +17,10 @@ #include "memory_desc/blocked_memory_desc.h" #include "memory_desc/cpu_memory_desc.h" #include "openvino/core/except.hpp" +#include "openvino/core/memory_util.hpp" +#include "openvino/core/shape_util.hpp" #include "openvino/core/type/element_type.hpp" +#include "openvino/util/common_util.hpp" #include "utils/general_utils.h" namespace ov::intel_cpu { @@ -151,11 +155,16 @@ bool CpuBlockedMemoryDesc::canComputeMemSizeZeroDims() const { } size_t CpuBlockedMemoryDesc::getCurrentMemSizeImp() const { - auto e_size = getOffsetPadding(); // size in bytes (from begin of data to last element) + size_t e_size = getOffsetPadding(); // elements from begin of data to the last addressed element if (!getShape().hasZeroDims()) { - e_size += 1; + OPENVINO_ASSERT(!ov::util::add_overflow(e_size, static_cast(1), e_size), + "CpuBlockedMemoryDesc::getCurrentMemSizeImp overflow while adding tail element"); for (size_t j = 0; j < getBlockDims().size(); j++) { - e_size += (getBlockDims()[j] - 1) * getStrides()[j]; + const auto dim_stride = ov::util::shape_size_safe(ov::Shape{getBlockDims()[j] - 1, getStrides()[j]}); + OPENVINO_ASSERT(dim_stride.has_value(), + "CpuBlockedMemoryDesc::getCurrentMemSizeImp overflow while multiplying dim and stride"); + OPENVINO_ASSERT(!ov::util::add_overflow(e_size, *dim_stride, e_size), + "CpuBlockedMemoryDesc::getCurrentMemSizeImp overflow while accumulating element count"); } } @@ -165,20 +174,14 @@ size_t CpuBlockedMemoryDesc::getCurrentMemSizeImp() const { return e_size; } - auto byte_size = e_size * prc.bitwidth(); - - if (any_of(prc, ov::element::u3, ov::element::u6)) { - constexpr size_t storage_unit_size = 24; - byte_size += storage_unit_size - 1; - byte_size /= storage_unit_size; - byte_size *= 3; - } else { - constexpr size_t storage_unit_size = 8; - byte_size += storage_unit_size - 1; - byte_size /= storage_unit_size; - } - - return byte_size; + const auto byte_size = ov::util::get_memory_size_safe(prc, e_size); + OPENVINO_ASSERT(byte_size.has_value(), + "CpuBlockedMemoryDesc::getCurrentMemSizeImp overflow while converting elements to byte size"); + OPENVINO_ASSERT(*byte_size <= static_cast(std::numeric_limits::max()), + "CpuBlockedMemoryDesc::getCurrentMemSizeImp requested allocation size { ", + *byte_size, + " } exceeds PTRDIFF_MAX"); + return *byte_size; } size_t CpuBlockedMemoryDesc::getMaxMemSize() const { From fb47e34425f241e8a8de2863df74595744f6a6f3 Mon Sep 17 00:00:00 2001 From: River Li Date: Wed, 22 Apr 2026 10:55:41 +0800 Subject: [PATCH 014/545] [GPU] Integrate grouped gemm to support moe_3gemm (#34974) ### Details: - Integrate grouped gemm to support moe_3gemm - Grouped gemm will be enabled by default, and can be disabled by MOE_USE_GROUPED_GEMM_PREFILL=0 - Test result if applied DNNL_ARG_HINT_MAX_GROUP_SIZE image ### Tickets: - *CVS-182465* - *CVS-179099* ### AI Assistance: - *AI assistance used: no / yes* - *If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).* --------- Co-authored-by: Chen Peter --- .../graph/impls/ocl_v2/moe/moe_3gemm_base.hpp | 1 + .../impls/ocl_v2/moe/moe_3gemm_swiglu_opt.cpp | 483 +++++++++++++++++- 2 files changed, 469 insertions(+), 15 deletions(-) diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_3gemm_base.hpp b/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_3gemm_base.hpp index a3b749021c1074..3cb9ab694f8dcf 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_3gemm_base.hpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_3gemm_base.hpp @@ -25,6 +25,7 @@ namespace ov::intel_gpu::ocl { #define MOE_INTERNAL_BUFFER_TOKEN_LEN_PER_ACTIVATED_EXPERT 11 // token len (input gather tokens) for each activated expert #define MOE_INTERNAL_BUFFER_TOKEN_IDX_PER_EXPERT 12 // token idx per expert #define MOE_INTERNAL_BUFFER_ACTUAL_USED_EXPERT_NUM 13 // num_actual_used_experts +#define MOE_INTERNAL_BUFFER_GROUPED_OFFSETS 14 // int32_t cumulative end-offsets per expert for OneDNN grouped GEMM #define ENABLE_MOE_MICRO_GEMM_POST_PROC_SILU_MUL 1 diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_3gemm_swiglu_opt.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_3gemm_swiglu_opt.cpp index 62933678c5e08d..f0978a26f0d2a7 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_3gemm_swiglu_opt.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_3gemm_swiglu_opt.cpp @@ -10,8 +10,10 @@ #define DEBUG_MOE_LOG 0 #ifdef ENABLE_ONEDNN_FOR_GPU +# include # include # include +# include # include # include # include @@ -534,9 +536,13 @@ static auto calc_thread_count(RuntimeParams& params, const size_t vector_size, c } class MoE3GemmSwigluPrefillGather : public KernelGenerator { public: - MoE3GemmSwigluPrefillGather() : KernelGenerator("moe_gather_ref", "prefill_gather") {} + explicit MoE3GemmSwigluPrefillGather(bool use_grouped_gemm = false) + : KernelGenerator("moe_gather_ref", "prefill_gather"), + m_use_grouped_gemm(use_grouped_gemm) {} protected: + bool m_use_grouped_gemm; + [[nodiscard]] JitConstants get_jit_constants(const RuntimeParams& params) const override { auto jit = KernelGenerator::get_jit_constants(params); auto desc = params.typed_desc(); @@ -556,6 +562,8 @@ class MoE3GemmSwigluPrefillGather : public KernelGenerator { jit.make("INPUT1_TYPE", "int"); jit.make("OUTPUT_TYPE", "half"); jit.make("OPTIONAL_SHAPE_INFO_ARG", ""); + if (m_use_grouped_gemm) + jit.make("ONEDNN_GROUPED_GEMM_USED", 1); GPU_DEBUG_TRACE_DETAIL << "MoE3GemmSwigluPrefillGather::get_jit_constants(): hidden_size: " << hidden_size << ", block_size: " << block_size << ", local_threads_count: " << local_threads_count << ", batches_per_thread: " << batches_per_thread @@ -577,9 +585,13 @@ class MoE3GemmSwigluPrefillGather : public KernelGenerator { class MoE3GemmSwigluPrefillSwiglu : public KernelGenerator { public: - MoE3GemmSwigluPrefillSwiglu() : KernelGenerator("moe_3gemm_swiglu_fuse", "prefill_swiglu") {} + explicit MoE3GemmSwigluPrefillSwiglu(bool use_grouped_gemm = false) + : KernelGenerator("moe_3gemm_swiglu_fuse", "prefill_swiglu"), + m_use_grouped_gemm(use_grouped_gemm) {} protected: + bool m_use_grouped_gemm; + [[nodiscard]] JitConstants get_jit_constants(const RuntimeParams& params) const override { auto jit = KernelGenerator::get_jit_constants(params); auto desc = params.typed_desc(); @@ -590,6 +602,8 @@ class MoE3GemmSwigluPrefillSwiglu : public KernelGenerator { jit.make("SUBGROUP_SIZE", info.arch >= gpu_arch::xe2 ? 32 : 16); jit.make("INTERMEDIA_SIZE", desc->_config.inter_size); jit.make("MOE_DTYPE", "half"); + if (m_use_grouped_gemm) + jit.make("ONEDNN_GROUPED_GEMM_USED", 1); return jit; } @@ -606,9 +620,13 @@ class MoE3GemmSwigluPrefillSwiglu : public KernelGenerator { class MoE3GemmSwigluPrefillScatterReduce : public KernelGenerator { public: - MoE3GemmSwigluPrefillScatterReduce() : KernelGenerator("moe_scatter_reduction_opt", "moe_scatter_reduction_ref") {} + explicit MoE3GemmSwigluPrefillScatterReduce(bool use_grouped_gemm = false) + : KernelGenerator("moe_scatter_reduction_opt", "moe_scatter_reduction_ref"), + m_use_grouped_gemm(use_grouped_gemm) {} protected: + bool m_use_grouped_gemm; + [[nodiscard]] JitConstants get_jit_constants(const RuntimeParams& params) const override { auto jit = KernelGenerator::get_jit_constants(params); auto desc = params.typed_desc(); @@ -634,6 +652,8 @@ class MoE3GemmSwigluPrefillScatterReduce : public KernelGenerator { jit.make("INPUT5_TYPE", "int"); // tokens len for experts jit.make("INPUT6_TYPE", "int"); // expert id jit.make("OUTPUT_TYPE", "half"); // output + if (m_use_grouped_gemm) + jit.make("ONEDNN_GROUPED_GEMM_USED", 1); return jit; } @@ -836,6 +856,7 @@ dnnl::memory convert2dnnl(const memory::ptr& ptr, const std::vector& di static bool use_micro_gemm_prefill; static bool use_gpu_mask_gen_prefill; +static bool use_grouped_gemm_prefill; class moe_3gemm_swiglu_opt_impl : public PrimitiveImplOCL { public: DECLARE_OBJECT_TYPE_SERIALIZATION(ov::intel_gpu::ocl::MoE3GemmSwigluImpl) @@ -855,6 +876,11 @@ class moe_3gemm_swiglu_opt_impl : public PrimitiveImplOCL { Stage::Ptr prefill_scatter_reduce = make_stage(); Stage::Ptr prefill_mask_gen = make_stage(); + // Grouped GEMM path: same OCL kernels but compiled with ONEDNN_GROUPED_GEMM_USED=1 + Stage::Ptr grouped_gemm_prefill_gather = make_stage(/*use_grouped_gemm=*/true); + Stage::Ptr grouped_gemm_prefill_swiglu = make_stage(/*use_grouped_gemm=*/true); + Stage::Ptr grouped_gemm_prefill_scatter_reduce = make_stage(/*use_grouped_gemm=*/true); + struct dnnl_weights { dnnl::memory weight; dnnl::memory scale; @@ -969,6 +995,22 @@ class moe_3gemm_swiglu_opt_impl : public PrimitiveImplOCL { use_micro_gemm_prefill = false; } + // grouped_gemm path: single OneDNN grouped matmul per GEMM layer (all experts at once). + // micro_gemm takes priority; grouped_gemm falls back to onednn loop by default. + auto use_grouped_gemm_prefill_str = std::getenv("MOE_USE_GROUPED_GEMM_PREFILL"); + if (use_grouped_gemm_prefill_str) { + GPU_DEBUG_TRACE_DETAIL << "MOE_USE_GROUPED_GEMM_PREFILL = " << use_grouped_gemm_prefill_str << std::endl; + use_grouped_gemm_prefill = std::stoi(use_grouped_gemm_prefill_str) != 0; + } else { + use_grouped_gemm_prefill = true; + } + // grouped_gemm supersedes micro_gemm + if (use_grouped_gemm_prefill) { + use_micro_gemm_prefill = false; + } + + GPU_DEBUG_TRACE_DETAIL << "[DEBUG] moe_3gemm_swiglu_opt_impl(): use_grouped_gemm_prefill=" << use_grouped_gemm_prefill << std::endl; + // Don't change the order of stages auto routing_type = node.as().get_primitive()->_config.routing_type; if (routing_type == ov::intel_gpu::op::MOECompressed::RoutingType::SOFTMAX) { @@ -992,6 +1034,11 @@ class moe_3gemm_swiglu_opt_impl : public PrimitiveImplOCL { add_stage(micro_gemm_down, params); add_stage(prefill_scatter_reduce, params); } + if (use_grouped_gemm_prefill) { + add_stage(grouped_gemm_prefill_gather, params); + add_stage(grouped_gemm_prefill_swiglu, params); + add_stage(grouped_gemm_prefill_scatter_reduce, params); + } } void init(const std::shared_ptr& cur_moe) { @@ -1245,7 +1292,8 @@ class moe_3gemm_swiglu_opt_impl : public PrimitiveImplOCL { internal_buffers.emplace_back(index_layout, true); // 7: expert_mask_batch internal_buffers.emplace_back(index_layout, true); // 8: expert_mask_topk - GPU_DEBUG_TRACE_DETAIL << "[DEBUG] get_internal_buffer_descs(): use_micro_gemm_prefill=" << use_micro_gemm_prefill << std::endl; + GPU_DEBUG_TRACE_DETAIL << "[DEBUG] get_internal_buffer_descs(): use_micro_gemm_prefill=" << use_micro_gemm_prefill + << ", use_grouped_gemm_prefill=" << use_grouped_gemm_prefill << std::endl; // for micro_gemm if (use_micro_gemm_prefill && token_num > 1) { layout layout_micro_gemm(ov::Shape{expert_num, token_num}, ov::element::i32, cldnn::format::bfyx); @@ -1257,6 +1305,21 @@ class moe_3gemm_swiglu_opt_impl : public PrimitiveImplOCL { layout layout_actual_used_expert_num(ov::Shape{1}, ov::element::i32, cldnn::format::bfyx); internal_buffers.emplace_back(layout_actual_used_expert_num, false); // 13: actual_used_expert_num } + // for grouped_gemm: shared metadata buffers (9-13) + int64_t expert-row-offsets (14) + if (use_grouped_gemm_prefill && token_num > 1) { + layout layout_meta(ov::Shape{expert_num, token_num}, ov::element::i32, cldnn::format::bfyx); + internal_buffers.emplace_back(layout_meta, true); // 9: activated expert ids + internal_buffers.emplace_back(layout_meta, true); // 10: token start offset per activated expert + internal_buffers.emplace_back(layout_meta, true); // 11: token len per activated expert + layout layout_token_idx(ov::Shape{token_num * max_topk}, ov::element::i32, cldnn::format::bfyx); + internal_buffers.emplace_back(layout_token_idx, true); // 12: flat token idx per expert (for gather) + layout layout_actual_used_expert_num(ov::Shape{1}, ov::element::i32, cldnn::format::bfyx); + internal_buffers.emplace_back(layout_actual_used_expert_num, false); // 13: actual_used_expert_num + // int32_t end-offsets per expert for OneDNN grouped memory descriptor + // offsets[e] = sum(n_0..n_e), the exclusive end index of expert e in the flat buffer + layout layout_grouped_offsets(ov::Shape{expert_num}, ov::element::i32, cldnn::format::bfyx); + internal_buffers.emplace_back(layout_grouped_offsets, true); // 14: grouped end-offsets + } return internal_buffers; } @@ -1842,6 +1905,25 @@ class moe_3gemm_swiglu_opt_impl : public PrimitiveImplOCL { using lru_cache_hash = LruCache, std::shared_ptr, PairHash>; lru_cache_hash _kernels = lru_cache_hash(1024); + + // --- grouped GEMM kernel cache (one primitive set per total-token count) --- + struct grouped_onednn_kernel { + dnnl::matmul gate_prim; + dnnl::matmul up_prim; + dnnl::matmul down_prim; + dnnl::matmul::primitive_desc gate_pd; + dnnl::matmul::primitive_desc up_pd; + dnnl::matmul::primitive_desc down_pd; + dnnl::memory::desc gate_scale_md; + dnnl::memory::desc up_scale_md; + dnnl::memory::desc down_scale_md; + dnnl::memory::desc gate_zp_md; + dnnl::memory::desc up_zp_md; + dnnl::memory::desc down_zp_md; + bool has_zp = false; + }; + using grouped_kernel_lru = LruCache>; + grouped_kernel_lru _grouped_kernels{128}; onednn_kernel& get_kernel(int n_token, int expert_no, typed_primitive_inst& instance) { auto key = std::make_pair(n_token, expert_no); if (_kernels.has(key)) { @@ -1902,6 +1984,100 @@ class moe_3gemm_swiglu_opt_impl : public PrimitiveImplOCL { return *_kernels.get(key); } + // Build (and cache) three grouped dnnl::matmul primitives for gate/up/down. + // Cache key is total_tokens only — the per-request max_tokens_per_expert + // dispatch hint is passed as a runtime argument (DNNL_ARG_HINT_MAX_GROUP_SIZE) + // at execute() time, so no recompilation is needed when it changes. + grouped_onednn_kernel& get_grouped_kernel(int total_tokens, typed_primitive_inst& instance) { + auto key = total_tokens; + if (_grouped_kernels.has(key)) { + return *_grouped_kernels.get(key); + } + + auto cur_moe = instance.get_typed_desc(); + const auto& config = cur_moe->_config; + auto& engine = instance.get_network().get_engine(); + auto& onednn_engine = engine.get_onednn_engine(); + + int num_experts = static_cast(config.num_expert); + auto a_dt = convert_data_type(instance.input_memory_ptr(static_cast(MOE3GemmInputIndex::HIDDEN_STATES))->get_layout().data_type); + auto gw_dt = convert_data_type(instance.input_memory_ptr(static_cast(MOE3GemmInputIndex::WEIGHT_0))->get_layout().data_type); + auto uw_dt = convert_data_type(instance.input_memory_ptr(static_cast(MOE3GemmInputIndex::WEIGHT_1))->get_layout().data_type); + auto dw_dt = convert_data_type(instance.input_memory_ptr(static_cast(MOE3GemmInputIndex::WEIGHT_2))->get_layout().data_type); + + // Use the model config to determine ZP presence (symmetric vs asymmetric quantization) + bool has_zp = config.has_zp; + + int K_gu = _hidden_size; // K for gate / up + int N_gu = _intermediate_size; // N for gate / up + int K_d = _intermediate_size; // K for down + int N_d = _hidden_size; // N for down + + // Helper: create one grouped matmul prim-desc [total_tokens, K]*W[E,K,N]->[total_tokens,N] + // Weights layout in memory is [E, N, K] (stored transposed), expressed as acb over dims {E,K,N}. + auto make_pd = [&](int K, int N, int group_size, dnnl::memory::data_type w_dt) { + dnnl::primitive_attr attr; + attr.set_fpmath_mode(dnnl::fpmath_mode::f16, true); + + bool has_k_groups = (group_size < K); + if (has_k_groups) { + // per-expert(0) x per-K-group(1) x per-N-channel(2) + attr.set_scales(DNNL_ARG_WEIGHTS, (1 << 0) | (1 << 1) | (1 << 2), {group_size, 1}, dnnl::memory::data_type::f16); + if (has_zp) { + attr.set_zero_points(DNNL_ARG_WEIGHTS, (1 << 0) | (1 << 1) | (1 << 2), {group_size, 1}, w_dt); + } + } else { + // per-expert(0) x per-N-channel(2), no K-grouping + attr.set_scales(DNNL_ARG_WEIGHTS, (1 << 0) | (1 << 2), {}, dnnl::memory::data_type::f16); + if (has_zp) { + attr.set_zero_points(DNNL_ARG_WEIGHTS, (1 << 0) | (1 << 2), {}, w_dt); + } + } + + // Grouped src/dst: tokens are grouped by expert along axis-0 + auto src_md = dnnl::memory::desc::grouped(dnnl::memory::dims{total_tokens, K}, a_dt, 0, num_experts, dnnl::memory::data_type::s32); + auto dst_md = dnnl::memory::desc::grouped(dnnl::memory::dims{total_tokens, N}, a_dt, 0, num_experts, dnnl::memory::data_type::s32); + // Weight: logical [E, K, N], physical layout acb -> stored as [E, N, K] + auto w_md = dnnl::memory::desc(dnnl::memory::dims{num_experts, K, N}, w_dt, dnnl::memory::format_tag::acb); + + return dnnl::matmul::primitive_desc(onednn_engine, src_md, w_md, dst_md, attr); + }; + + // Helper: create scale/ZP memory descriptor for grouped weights + auto make_quant_md = [&](int E, int K, int group_size, int N, dnnl::memory::data_type dt) { + int num_k_groups = K / group_size; + if (num_k_groups > 1) { + return dnnl::memory::desc({E, num_k_groups, N}, dt, dnnl::memory::format_tag::abc); + } else { + return dnnl::memory::desc({E, N}, dt, dnnl::memory::format_tag::ab); + } + }; + + auto gk = std::make_shared(); + gk->has_zp = has_zp; + + gk->gate_pd = make_pd(K_gu, N_gu, _gate_up_group_size, gw_dt); + gk->gate_prim = dnnl::matmul(gk->gate_pd); + gk->gate_scale_md = make_quant_md(num_experts, K_gu, _gate_up_group_size, N_gu, dnnl::memory::data_type::f16); + if (has_zp) + gk->gate_zp_md = make_quant_md(num_experts, K_gu, _gate_up_group_size, N_gu, gw_dt); + + gk->up_pd = make_pd(K_gu, N_gu, _gate_up_group_size, uw_dt); + gk->up_prim = dnnl::matmul(gk->up_pd); + gk->up_scale_md = gk->gate_scale_md; + if (has_zp) + gk->up_zp_md = gk->gate_zp_md; + + gk->down_pd = make_pd(K_d, N_d, _down_group_size, dw_dt); + gk->down_prim = dnnl::matmul(gk->down_pd); + gk->down_scale_md = make_quant_md(num_experts, K_d, _down_group_size, N_d, dnnl::memory::data_type::f16); + if (has_zp) + gk->down_zp_md = make_quant_md(num_experts, K_d, _down_group_size, N_d, dw_dt); + + _grouped_kernels.add(key, gk); + return *_grouped_kernels.get(key); + } + // inputs 0 is hidden_states, inputs 1 is router_logits[num_tokens, NUM_EXPERTS=128] // extra step Softmax_TopK is fused to give topk-id & router_weights // @@ -2016,6 +2192,278 @@ class moe_3gemm_swiglu_opt_impl : public PrimitiveImplOCL { return result_event; } + // Third prefill path: OneDNN grouped GEMM (one matmul call per GEMM layer, all experts together). + // This avoids the per-expert loop of exec_prefill_onednn while keeping full weight-format + // compatibility (quantized or fp16 weights). + // + // gather_by_expert(hidden_states, topk_id) -> scratch.x [total, hidden] + // grouped_matmul(scratch.x, W_gate) -> scratch.gate [total, inter] + // grouped_matmul(scratch.x, W_up) -> scratch.up [total, inter] + // silu(scratch.gate) * scratch.up -> scratch.gate [total, inter] + // grouped_matmul(scratch.gate, W_down) -> scratch.y [total, hidden] + // scatter_reduce(scratch.y, topk_id, topk_weights) -> output [token_num, hidden] + // + // Note: "total" = token_num * max_topk, sorted by expert assignment. + // + cldnn::event::ptr exec_prefill_grouped_gemm(const std::vector& events, + cldnn::stream& stream, + typed_primitive_inst& instance, + scratch_buffers& scratch) { + OV_ITT_SCOPED_TASK(ov::intel_gpu::itt::domains::intel_gpu_plugin, openvino::itt::handle("moe_3gemm_swiglu_opt_impl::exec_prefill_grouped_gemm")); + + auto cur_moe = instance.get_typed_desc(); + const auto& config = cur_moe->_config; + auto& dnn_stream = stream.get_onednn_stream(); + auto& engine = instance.get_network().get_engine(); + auto& onednn_engine = engine.get_onednn_engine(); + + auto [hidden_states_mem_ptr, hidden_states_layout] = get_input_info(instance, static_cast(MOE3GemmInputIndex::HIDDEN_STATES)); + auto token_num = get_seq_len(hidden_states_layout); + auto final_hidden_states_mem_ptr = instance.output_memory_ptr(0); + auto batch_mem_ptr = scratch.topk_id; + auto routing_mem_ptr = scratch.topk_weights; + const auto& intermediates_memories = instance.get_intermediates_memories(); + + int num_total_experts = static_cast(config.num_expert); + int max_topk = static_cast(config.top_k); + int num_actually_used_experts = 0; + + // ---------------------------------------------------------------- + // Step 1: CPU mask generation (topk_id already flushed by caller) + // ---------------------------------------------------------------- + cldnn::event::ptr ret_event = events.empty() ? nullptr : events[0]; + expert_mask_cpu expert_mask; + get_expert_mask_from_gpu(config, batch_mem_ptr, stream, expert_mask); + + // Flat list of source token indices per expert – input for prefill_gather + std::vector tokens_per_expert_cpu(static_cast(token_num) * max_topk, -1); + // Compact per-activated-expert metadata reused by scatter_reduce + std::vector tokens_lens_per_expert_cpu(num_total_experts, 0); + std::vector experts_id_cpu(num_total_experts, -1); + // int32_t cumulative end-offsets per expert for OneDNN grouped GEMM + // offsets[e] = sum(n_0..n_e) = exclusive end of expert e in the flat buffer. + // This is the s32 format expected by dnnl::memory::desc::grouped(). + std::vector grouped_offsets_cpu(num_total_experts, 0); + + { + int tokens_iter = 0; + int experts_iter = 0; + int32_t running_offset = 0; + for (int e = 0; e < num_total_experts; e++) { + auto n = static_cast(expert_mask.batch[e].size()); + running_offset += n; + grouped_offsets_cpu[e] = running_offset; // exclusive end of expert e + if (n > 0) { + experts_id_cpu[experts_iter] = e; + tokens_lens_per_expert_cpu[experts_iter] = n; + ++experts_iter; + ++num_actually_used_experts; + for (auto t : expert_mask.batch[e]) + tokens_per_expert_cpu[tokens_iter++] = t; + } + } + } + int total_gathered_tokens = static_cast(token_num) * max_topk; + + // Compute actual max tokens assigned to any single expert. + int max_tokens_per_expert = 0; + if (num_actually_used_experts > 0) { + max_tokens_per_expert = *std::max_element(tokens_lens_per_expert_cpu.begin(), tokens_lens_per_expert_cpu.begin() + num_actually_used_experts); + } + + GPU_DEBUG_TRACE_DETAIL << "\nexec_prefill_grouped_gemm: token_num=" << token_num << ", total_gathered_tokens=" << total_gathered_tokens + << ", max_tokens_per_expert=" << max_tokens_per_expert << ", num_actually_used_experts=" << num_actually_used_experts + << std::endl; + + // Upload scratch metadata for the scatter_reduce and gather kernels + intermediates_memories[MOE_INTERNAL_BUFFER_TOKEN_IDX_PER_EXPERT] + ->copy_from(stream, tokens_per_expert_cpu.data(), 0, 0, tokens_per_expert_cpu.size() * sizeof(int32_t), true); + // When ONEDNN_GROUPED_GEMM_USED, the scatter_reduce kernel reads: + // exp_offset_start = expert_id == 0 ? 0 : experts_start_offset[expert_id - 1] + // So experts_start_offset[k] must equal the start index of expert k+1 in the flat buffer + // = exclusive end of expert k = grouped_offsets_cpu[k]. + { + std::vector expert_start_offsets_per_id(static_cast(num_total_experts - 1)); + for (int e = 0; e < num_total_experts - 1; ++e) + expert_start_offsets_per_id[e] = grouped_offsets_cpu[e]; // end[e] == start[e+1] + intermediates_memories[MOE_INTERNAL_BUFFER_TOKEN_START_OFFSET_PER_EXPERT] + ->copy_from(stream, expert_start_offsets_per_id.data(), 0, 0, expert_start_offsets_per_id.size() * sizeof(int32_t), true); + } + intermediates_memories[MOE_INTERNAL_BUFFER_ACTIVATED_EXPERT_IDS] + ->copy_from(stream, experts_id_cpu.data(), 0, 0, static_cast(num_actually_used_experts) * sizeof(int32_t), true); + intermediates_memories[MOE_INTERNAL_BUFFER_TOKEN_LEN_PER_ACTIVATED_EXPERT] + ->copy_from(stream, tokens_lens_per_expert_cpu.data(), 0, 0, static_cast(num_actually_used_experts) * sizeof(int32_t), true); + intermediates_memories[MOE_INTERNAL_BUFFER_ACTUAL_USED_EXPERT_NUM]->copy_from(stream, &num_actually_used_experts, 0, 0, sizeof(int32_t), true); + // int32_t end-offsets for the OneDNN grouped descriptor + intermediates_memories[MOE_INTERNAL_BUFFER_GROUPED_OFFSETS] + ->copy_from(stream, grouped_offsets_cpu.data(), 0, 0, grouped_offsets_cpu.size() * sizeof(int32_t), true); + + // ---------------------------------------------------------------- + // Step 2: GPU gather – reorder input tokens sorted by expert + // ---------------------------------------------------------------- + { + auto hidden_size = _hidden_size; + auto block_size = get_vec_size(*instance.get_impl_params()); + auto [local_threads_count, batches_per_thread, unaligned_elements] = + calc_thread_count(const_cast(*instance.get_impl_params()), block_size, hidden_size); + + ret_event = execute_stage({ret_event}, + instance, + *grouped_gemm_prefill_gather, + {instance.input_memory_ptr(static_cast(MOE3GemmInputIndex::HIDDEN_STATES)), + intermediates_memories[MOE_INTERNAL_BUFFER_TOKEN_IDX_PER_EXPERT]}, + {scratch.x}, + {static_cast(total_gathered_tokens) * local_threads_count, 1, 1}, + {local_threads_count, 1, 1}); + } + + // ---------------------------------------------------------------- + // Steps 3-5: OneDNN grouped GEMM – gate, up, SiLU, down + // ---------------------------------------------------------------- + auto& gk = get_grouped_kernel(total_gathered_tokens, instance); + auto* offsets_ptr = intermediates_memories[MOE_INTERNAL_BUFFER_GROUPED_OFFSETS]->buffer_ptr(); + + // Runtime dispatch hint: actual max tokens assigned to any single expert. + // Passed as DNNL_ARG_HINT_MAX_GROUP_SIZE to each grouped matmul execute(), + // allowing the kernel to reduce per-expert workgroup dispatch without + // recompiling the primitive. + auto hint_md = dnnl::memory::desc::host_scalar(dnnl::memory::data_type::s32); + dnnl::memory hint_mem(hint_md, static_cast(max_tokens_per_expert)); + + // Helper: wrap a flat USM buffer as an OneDNN grouped memory (data + expert row-offsets) + auto make_grouped_mem = [&](const dnnl::memory::desc& md, void* buf_ptr) { + return dnnl::ocl_interop::make_memory(md, + onednn_engine, + dnnl::ocl_interop::memory_kind::usm, + {reinterpret_cast(buf_ptr), reinterpret_cast(offsets_ptr)}); + }; + + // gate GEMM: [total, hidden] * W_gate[E,hidden,inter] -> [total, inter] + { + auto src_mem = make_grouped_mem(gk.gate_pd.src_desc(), scratch.x->buffer_ptr()); + auto dst_mem = make_grouped_mem(gk.gate_pd.dst_desc(), scratch.gate->buffer_ptr()); + auto w_mem = dnnl::ocl_interop::make_memory(gk.gate_pd.weights_desc(), + onednn_engine, + dnnl::ocl_interop::memory_kind::usm, + scratch.moe_fusion_wei_addr.weight[0]->buffer_ptr()); + auto scale_mem = dnnl::ocl_interop::make_memory(gk.gate_scale_md, + onednn_engine, + dnnl::ocl_interop::memory_kind::usm, + scratch.moe_fusion_wei_addr.scale[0]->buffer_ptr()); + + std::unordered_map args{{DNNL_ARG_SRC, src_mem}, + {DNNL_ARG_WEIGHTS, w_mem}, + {DNNL_ARG_DST, dst_mem}, + {DNNL_ARG_ATTR_SCALES | DNNL_ARG_WEIGHTS, scale_mem}, + {DNNL_ARG_HINT_MAX_GROUP_SIZE, hint_mem}}; + if (gk.has_zp) { + args.insert({DNNL_ARG_ATTR_ZERO_POINTS | DNNL_ARG_WEIGHTS, + dnnl::ocl_interop::make_memory(gk.gate_zp_md, + onednn_engine, + dnnl::ocl_interop::memory_kind::usm, + scratch.moe_fusion_wei_addr.zp[0]->buffer_ptr())}); + } + gk.gate_prim.execute(dnn_stream, args); + } + + // up GEMM: [total, hidden] * W_up[E,hidden,inter] -> [total, inter] + { + auto src_mem = make_grouped_mem(gk.up_pd.src_desc(), scratch.x->buffer_ptr()); + auto dst_mem = make_grouped_mem(gk.up_pd.dst_desc(), scratch.up->buffer_ptr()); + auto w_mem = dnnl::ocl_interop::make_memory(gk.up_pd.weights_desc(), + onednn_engine, + dnnl::ocl_interop::memory_kind::usm, + scratch.moe_fusion_wei_addr.weight[1]->buffer_ptr()); + auto scale_mem = dnnl::ocl_interop::make_memory(gk.up_scale_md, + onednn_engine, + dnnl::ocl_interop::memory_kind::usm, + scratch.moe_fusion_wei_addr.scale[1]->buffer_ptr()); + + std::unordered_map args{{DNNL_ARG_SRC, src_mem}, + {DNNL_ARG_WEIGHTS, w_mem}, + {DNNL_ARG_DST, dst_mem}, + {DNNL_ARG_ATTR_SCALES | DNNL_ARG_WEIGHTS, scale_mem}, + {DNNL_ARG_HINT_MAX_GROUP_SIZE, hint_mem}}; + if (gk.has_zp) { + args.insert({DNNL_ARG_ATTR_ZERO_POINTS | DNNL_ARG_WEIGHTS, + dnnl::ocl_interop::make_memory(gk.up_zp_md, + onednn_engine, + dnnl::ocl_interop::memory_kind::usm, + scratch.moe_fusion_wei_addr.zp[1]->buffer_ptr())}); + } + gk.up_prim.execute(dnn_stream, args); + } + + // Step 4: SiLU(gate) * up -> scratch.gate (OCL prefill_swiglu kernel, compiled with ONEDNN_GROUPED_GEMM_USED) + // gate and up GEMMs are submitted to the same OCL queue as the OCL kernels; + // passing ret_event (from gather) as dependency ensures ordering within the queue. + { + const size_t subgroup_size = instance.get_impl_params()->get_device_info().arch >= gpu_arch::xe2 ? 32 : 16; + + ret_event = execute_stage({ret_event}, + instance, + *grouped_gemm_prefill_swiglu, + {intermediates_memories[MOE_INTERNAL_BUFFER_UP_OUTPUT], intermediates_memories[MOE_INTERNAL_BUFFER_GATE_OUTPUT]}, + {intermediates_memories[MOE_INTERNAL_BUFFER_GATE_OUTPUT]}, + {static_cast(_intermediate_size), static_cast(total_gathered_tokens), 1}, + {subgroup_size, 1, 1}); + } + + // down GEMM: [total, inter] * W_down[E,inter,hidden] -> [total, hidden] + { + auto src_mem = make_grouped_mem(gk.down_pd.src_desc(), scratch.gate->buffer_ptr()); + auto dst_mem = make_grouped_mem(gk.down_pd.dst_desc(), scratch.y->buffer_ptr()); + auto w_mem = dnnl::ocl_interop::make_memory(gk.down_pd.weights_desc(), + onednn_engine, + dnnl::ocl_interop::memory_kind::usm, + scratch.moe_fusion_wei_addr.weight[2]->buffer_ptr()); + auto scale_mem = dnnl::ocl_interop::make_memory(gk.down_scale_md, + onednn_engine, + dnnl::ocl_interop::memory_kind::usm, + scratch.moe_fusion_wei_addr.scale[2]->buffer_ptr()); + + std::unordered_map args{{DNNL_ARG_SRC, src_mem}, + {DNNL_ARG_WEIGHTS, w_mem}, + {DNNL_ARG_DST, dst_mem}, + {DNNL_ARG_ATTR_SCALES | DNNL_ARG_WEIGHTS, scale_mem}, + {DNNL_ARG_HINT_MAX_GROUP_SIZE, hint_mem}}; + if (gk.has_zp) { + args.insert({DNNL_ARG_ATTR_ZERO_POINTS | DNNL_ARG_WEIGHTS, + dnnl::ocl_interop::make_memory(gk.down_zp_md, + onednn_engine, + dnnl::ocl_interop::memory_kind::usm, + scratch.moe_fusion_wei_addr.zp[2]->buffer_ptr())}); + } + gk.down_prim.execute(dnn_stream, args); + } + + // ---------------------------------------------------------------- + // Step 6: scatter_reduce – weighted accumulate into output + // ---------------------------------------------------------------- + { + auto [local_threads_count, batches_per_thread, _unused] = + calc_thread_count(const_cast(*instance.get_impl_params()), 4, _hidden_size); + + ret_event = execute_stage({ret_event}, + instance, + *grouped_gemm_prefill_scatter_reduce, + {intermediates_memories[MOE_INTERNAL_BUFFER_DOWN_OUTPUT], + batch_mem_ptr, + routing_mem_ptr, + intermediates_memories[MOE_INTERNAL_BUFFER_TOKEN_IDX_PER_EXPERT], + intermediates_memories[MOE_INTERNAL_BUFFER_TOKEN_START_OFFSET_PER_EXPERT], + intermediates_memories[MOE_INTERNAL_BUFFER_TOKEN_LEN_PER_ACTIVATED_EXPERT], + intermediates_memories[MOE_INTERNAL_BUFFER_ACTIVATED_EXPERT_IDS], + intermediates_memories[MOE_INTERNAL_BUFFER_ACTUAL_USED_EXPERT_NUM]}, + {final_hidden_states_mem_ptr}, + {static_cast(token_num) * local_threads_count, 1, 1}, + {local_threads_count, 1, 1}, + true /*needs_completion_event*/); + } + + return ret_event; + } + cldnn::event::ptr execute(const std::vector& events, cldnn::primitive_inst& ins) override { OV_ITT_SCOPED_TASK(ov::intel_gpu::itt::domains::intel_gpu_plugin, openvino::itt::handle("moe_3gemm_swiglu_opt_impl::execute")); auto& instance = reinterpret_cast&>(ins); @@ -2076,21 +2524,28 @@ class moe_3gemm_swiglu_opt_impl : public PrimitiveImplOCL { } auto final_hidden_states_mem_ptr = instance.output_memory_ptr(0); - // onednn path will accumulate to the output - if (!use_micro_gemm_prefill) { + // only the per-expert onednn loop path accumulates into the output via index_add, + // so it needs the buffer pre-zeroed; micro_gemm and grouped_gemm use scatter_reduce + // which writes atomically and does not require pre-zeroing. + if (!use_micro_gemm_prefill && !use_grouped_gemm_prefill) { final_hidden_states_mem_ptr->fill(stream, false); } - const bool use_gpu_mask_gen = use_gpu_mask_gen_prefill; + // GPU mask gen is only supported for micro_gemm; both grouped_gemm and onednn loop + // always use CPU mask gen and therefore always need topk to be ready first. + const bool use_gpu_mask_gen = use_micro_gemm_prefill && use_gpu_mask_gen_prefill; if (!use_gpu_mask_gen) { // Wait for topk is ready topk_event->wait(); } GPU_DEBUG_TRACE_DETAIL << "\nMoE3GemmFusedCompressed exec(): token_num=" << token_num << ", max_topk=" << static_cast(config.top_k) - << ", use_micro_gemm_prefill=" << use_micro_gemm_prefill << std::endl; + << ", use_micro_gemm_prefill=" << use_micro_gemm_prefill << ", use_grouped_gemm_prefill=" << use_grouped_gemm_prefill + << std::endl; update_rt_params(instance); if (use_micro_gemm_prefill) { ret_env = exec_prefill_micro_gemm({topk_event}, instance, scratch, use_gpu_mask_gen); + } else if (use_grouped_gemm_prefill) { + ret_env = exec_prefill_grouped_gemm({topk_event}, stream, instance, scratch); } else { ret_env = exec_prefill_onednn({topk_event}, stream, instance, scratch); } @@ -2098,15 +2553,13 @@ class moe_3gemm_swiglu_opt_impl : public PrimitiveImplOCL { if (_has_shared_expert) { auto& engine = instance.get_network().get_engine(); init_shared_primitives(engine, scratch.moe_fusion_wei_addr, static_cast(token_num)); - // execute_shared_expert will read the output of moe_gemm_down, so it should be after ret_env is ready, but it doesn't need to wait for ret_env to - // be ready, since execute_shared_expert will be serialized with the following kernels by onednn stream, just make sure ret_env is submitted before - // execute_shared_expert. - // if (ret_env) - // ret_env->wait(); + // Shared expert's down_proj uses sum post-op (output += result), so the + // scatter_reduce must have written the MoE output first. Both are on the + // same in-order OCL queue, so submission order guarantees execution order. + // No explicit wait() is needed — the in-order queue serializes all GPU work, + // and any subsequent primitive on the same queue will see the completed output. execute_shared_expert(stream.get_onednn_stream(), static_cast(token_num), hidden_states_mem_ptr, final_hidden_states_mem_ptr, scratch); } - // Wait for the final event to be ready - // ret_env->wait(); return ret_env; } }; From 124ef35fb5eb907b358fdd4f7bafe411fc9e8567 Mon Sep 17 00:00:00 2001 From: Alicja Miloszewska Date: Wed, 22 Apr 2026 06:34:12 +0200 Subject: [PATCH 015/545] [Paddle FE] Migrate the InputModel class to use std::filesystem::path (#35423) ### Details: - _paddle/src/frontend.cpp_ passes std::filesystem::path directly instead of extracting .native() - Replaces` std::string/std::wstring` path parameters with `std::filesystem::path` in `InputModel` constructor, `load_consts()`, functions (`get_const_path`, `is_pdmodel`, `get_model_path`) - Removes no longer needed `get_path_sep()` template functions from paddle_utils.hpp ### Tickets: - part of [CVS-146661](https://jira.devtools.intel.com/browse/CVS-146661) ### AI Assistance: - yes, codebase analysis --- src/frontends/paddle/src/frontend.cpp | 8 +- src/frontends/paddle/src/input_model.cpp | 94 ++++------------------- src/frontends/paddle/src/input_model.hpp | 7 +- src/frontends/paddle/src/paddle_utils.hpp | 20 ----- 4 files changed, 24 insertions(+), 105 deletions(-) diff --git a/src/frontends/paddle/src/frontend.cpp b/src/frontends/paddle/src/frontend.cpp index 47bca7c006784a..b9299f20e8a4bb 100644 --- a/src/frontends/paddle/src/frontend.cpp +++ b/src/frontends/paddle/src/frontend.cpp @@ -151,7 +151,7 @@ std::istream* variant_to_stream_ptr(const ov::Any& variant, std::fstream& fs, st FRONT_END_INITIALIZATION_CHECK(ss && ss.good(), "Cannot open ov::tensor."); return &ss; } else if (const auto path = ov::frontend::get_path_from_any(variant)) { - fs.open(path.value(), std::ios::in | std::ifstream::binary); + fs.open(*path, std::ios::in | std::ifstream::binary); FRONT_END_INITIALIZATION_CHECK(fs && fs.is_open(), "Cannot open model file."); return &fs; } @@ -372,8 +372,8 @@ bool FrontEnd::supported_impl(const std::vector& variants) const { return false; // Validating first path, it must contain a model - if (const auto path = ov::frontend::get_path_from_any(variants[0])) { - std::filesystem::path model_path = path.value(); + if (auto path = ov::frontend::get_path_from_any(variants[0])) { + std::filesystem::path model_path = std::move(*path); if (ov::util::directory_exists(model_path)) { model_path /= "__model__"; FRONT_END_GENERAL_CHECK(util::file_exists(model_path), "Could not open the file: ", model_path); @@ -411,7 +411,7 @@ InputModel::Ptr FrontEnd::load_impl(const std::vector& variants) const if (variants.size() == 1 + extra_variants_num) { // The case when folder with __model__ and weight files is provided or .pdmodel file if (const auto path = ov::frontend::get_path_from_any(variants[0])) { - return std::make_shared(path.value().native(), m_telemetry); + return std::make_shared(*path, m_telemetry); } // The case with only model stream provided and no weights. This means model has // no learnable weights diff --git a/src/frontends/paddle/src/input_model.cpp b/src/frontends/paddle/src/input_model.cpp index dddbbacc6907e0..a0c33ecdd19687 100644 --- a/src/frontends/paddle/src/input_model.cpp +++ b/src/frontends/paddle/src/input_model.cpp @@ -4,10 +4,8 @@ #include "input_model.hpp" +#include #include -#if defined(__MINGW32__) || defined(__MINGW64__) -# include -#endif #include #include @@ -31,8 +29,7 @@ using namespace ::paddle::framework::proto; class InputModel::InputModelImpl { public: - template - InputModelImpl(const std::basic_string& path, + InputModelImpl(const std::filesystem::path& path, const InputModel& input_model, const std::shared_ptr& telemetry); InputModelImpl(const std::vector& streams, @@ -63,8 +60,7 @@ class InputModel::InputModelImpl { private: void load_places(); - template - void load_consts(const std::basic_string& folder_with_weights); + void load_consts(const std::filesystem::path& folder_with_weights); void load_consts(std::istream* weight_stream); void create_temp_consts(); std::vector> determine_cut_nodes() const; @@ -187,67 +183,26 @@ ov::Shape make_shape_checked(const DimsT& dims) { return shape; } -template -std::basic_string get_const_path(const std::basic_string& folder_with_weights, const std::string& name) { - return folder_with_weights + paddle::get_path_sep() + name; +std::filesystem::path get_const_path(const std::filesystem::path& folder_with_weights, const std::string& name) { + return folder_with_weights / ov::util::make_path(name); } -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -template <> -std::basic_string get_const_path(const std::basic_string& folder, const std::string& name) { - return folder + paddle::get_path_sep() + ov::util::string_to_wstring(name); +bool is_pdmodel(const std::filesystem::path& path) { + return path.extension() == ".pdmodel"; } -#endif -template -bool is_pdmodel(const std::basic_string& path) { - std::string ext = ".pdmodel"; - return ov::util::ends_with(path, ext); -} - -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -template <> -bool is_pdmodel(const std::basic_string& path) { - std::wstring ext = L".pdmodel"; - return ov::util::ends_with(path, ext); -} -#endif - -template -std::basic_string get_model_path(const std::basic_string& path, std::ifstream* weights_stream) { - std::string model_file{path}; - std::string ext = ".pdmodel"; - if (ov::util::ends_with(model_file, ext)) { - std::string params_ext = ".pdiparams"; - std::string weights_file{path}; - weights_file.replace(weights_file.size() - ext.size(), ext.size(), params_ext); +std::filesystem::path get_model_path(std::filesystem::path model_file, std::ifstream* weights_stream) { + if (is_pdmodel(model_file)) { + auto weights_file = model_file; + weights_file.replace_extension(".pdiparams"); weights_stream->open(weights_file, std::ios::binary); // Don't throw error if file isn't opened // It may mean that model don't have constants } else { - model_file += paddle::get_path_sep() + "__model__"; + model_file = model_file / "__model__"; } return model_file; } - -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -template <> -std::basic_string get_model_path(const std::basic_string& path, std::ifstream* weights_stream) { - std::wstring model_file{path}; - std::wstring ext = L".pdmodel"; - if (ov::util::ends_with(model_file, ext)) { - std::wstring params_ext = L".pdiparams"; - std::wstring weights_file{path}; - weights_file.replace(weights_file.size() - ext.size(), ext.size(), params_ext); - weights_stream->open(weights_file.c_str(), std::ios::binary); - // Don't throw error if file isn't opened - // It may mean that model don't have constants - } else { - model_file += paddle::get_path_sep() + L"__model__"; - } - return model_file; -} -#endif } // namespace std::vector> InputModel::InputModelImpl::get_op_places(const int32_t blck_idx) const { @@ -299,8 +254,7 @@ std::vector> InputModel::InputModelImpl::determine_cut_ } // load_consts with folder is compatible with old PaddlePaddle API. -template -void InputModel::InputModelImpl::load_consts(const std::basic_string& folder_with_weights) { +void InputModel::InputModelImpl::load_consts(const std::filesystem::path& folder_with_weights) { for (const auto& item : m_var_places) { const auto& var_desc = item.second->get_desc(); const auto& name = item.first; @@ -319,12 +273,7 @@ void InputModel::InputModelImpl::load_consts(const std::basic_string& folder_ bool read_succeed = false; if (!folder_with_weights.empty()) { -#if defined(__MINGW32__) || defined(__MINGW64__) - std::ifstream is(std::filesystem::path(get_const_path(folder_with_weights, name)), - std::ios::in | std::ifstream::binary); -#else std::ifstream is(get_const_path(folder_with_weights, name), std::ios::in | std::ifstream::binary); -#endif FRONT_END_GENERAL_CHECK(is && is.is_open(), "Cannot open file for constant value."); const size_t header_size = 16; std::vector header(header_size); @@ -438,20 +387,16 @@ void InputModel::InputModelImpl::load_consts(std::istream* weight_stream) { read *.pdmodel as model stream. read *.pdiparam as weight stream. */ -template -InputModel::InputModelImpl::InputModelImpl(const std::basic_string& path, +InputModel::InputModelImpl::InputModelImpl(const std::filesystem::path& path, const InputModel& input_model, const std::shared_ptr& telemetry) : m_fw_ptr{std::make_shared()}, m_input_model(input_model), m_telemetry(telemetry) { std::ifstream weights_stream; - std::ifstream pb_stream(get_model_path(path, &weights_stream).c_str(), std::ios::in | std::ifstream::binary); + std::ifstream pb_stream(get_model_path(path, &weights_stream), std::ios::in | std::ifstream::binary); - FRONT_END_GENERAL_CHECK(pb_stream && pb_stream.is_open(), - "Could not open the file: \"", - util::path_to_string(path), - '"'); + FRONT_END_GENERAL_CHECK(pb_stream && pb_stream.is_open(), "Could not open the file: ", path); FRONT_END_GENERAL_CHECK(m_fw_ptr->ParseFromIstream(&pb_stream), "Model can't be parsed"); // According to Paddle, the saved model has the framework version // For example Paddle 2.1.0 is encoded as 2001000. 0 means the latest framework. @@ -625,13 +570,8 @@ void InputModel::InputModelImpl::set_tensor_value(Place::Ptr place, const void* m_tensor_values[name] = constant; } -InputModel::InputModel(const std::string& path, const std::shared_ptr& telemetry) - : _impl{std::make_shared(path, *this, telemetry)} {} - -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -InputModel::InputModel(const std::wstring& path, const std::shared_ptr& telemetry) +InputModel::InputModel(const std::filesystem::path& path, const std::shared_ptr& telemetry) : _impl{std::make_shared(path, *this, telemetry)} {} -#endif InputModel::InputModel(const std::vector& streams, const std::shared_ptr& telemetry) : _impl{std::make_shared(streams, *this, telemetry)} {} diff --git a/src/frontends/paddle/src/input_model.hpp b/src/frontends/paddle/src/input_model.hpp index 4bdd6c58c68edb..b1fdacf36c3023 100644 --- a/src/frontends/paddle/src/input_model.hpp +++ b/src/frontends/paddle/src/input_model.hpp @@ -4,6 +4,8 @@ #pragma once +#include + #include "openvino/frontend/extension/telemetry.hpp" #include "openvino/frontend/paddle/frontend.hpp" @@ -16,10 +18,7 @@ class TensorPlace; class InputModel : public ov::frontend::InputModel { public: - explicit InputModel(const std::string& path, const std::shared_ptr& telemetry = {}); -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) - explicit InputModel(const std::wstring& path, const std::shared_ptr& telemetry = {}); -#endif + explicit InputModel(const std::filesystem::path& path, const std::shared_ptr& telemetry = {}); explicit InputModel(const std::vector& streams, const std::shared_ptr& telemetry = {}); std::vector get_inputs() const override; diff --git a/src/frontends/paddle/src/paddle_utils.hpp b/src/frontends/paddle/src/paddle_utils.hpp index 6ef7966668aaf8..5c8c8120b7da6e 100644 --- a/src/frontends/paddle/src/paddle_utils.hpp +++ b/src/frontends/paddle/src/paddle_utils.hpp @@ -10,26 +10,6 @@ namespace ov { namespace frontend { namespace paddle { -#ifdef _WIN32 -const char PATH_SEPARATOR = '\\'; -# if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) -const wchar_t WPATH_SEPARATOR = L'\\'; -# endif -#else -const char PATH_SEPARATOR = '/'; -#endif - -template -inline std::basic_string get_path_sep() { - return std::basic_string{PATH_SEPARATOR}; -} - -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -template <> -inline std::basic_string get_path_sep() { - return std::basic_string{WPATH_SEPARATOR}; -} -#endif std::shared_ptr reorder_axes(const Output& value, std::vector axes_order); } // namespace paddle From 4d59cfb0f17e2dcb70b845533dcfdf6f17053489 Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Wed, 22 Apr 2026 15:35:26 +0800 Subject: [PATCH 016/545] Allow the RotaryEmbedding decomposed by ONNX frontend to be fused by RoPEFusion (#34527) ### Details: RoteryEmbedding is decomposed in OpenVINO at [1] or [2] using gpt-oss style, but can't be fused by RoPEFusionGPTOSS[3]. The two main points are: 1. Split is used in decompose, but VariadicSplit is required in RoPEFusionGPTOSS. 2. Sub is used in decompose, but add with negative value is required in RoPEFusionGPTOSS. Using decomposed graph to excute will impact performance. So we should make the decomposed graph be fused again. This PR also extend RoPEFusionGPTOSS to support the last dimension represented by positive numbers for Concat. [1] [https://github.com/openvinotoolkit/openvino/blob/master/src/frontends/onnx/frontend/src/op/com.microsoft/rotary_embedding.cpp#L58](https://github.com/openvinotoolkit/openvino/blob/master/src/frontends/onnx/frontend/src/op/com.microsoft/rotary_embedding.cpp) [2][https://github.com/openvinotoolkit/openvino/blob/master/src/common/transformations/src/transformations/op_conversions/group_query_attention_decomposition.cpp#L256](https://github.com/openvinotoolkit/openvino/blob/master/src/common/transformations/src/transformations/op_conversions/group_query_attention_decomposition.cpp) [3][https://github.com/openvinotoolkit/openvino/blob/master/src/common/transformations/src/transformations/common_optimizations/fuse_rotary_positional_embeddings.cpp#L1138](https://github.com/openvinotoolkit/openvino/blob/master/src/common/transformations/src/transformations/common_optimizations/fuse_rotary_positional_embeddings.cpp) ### Jira Ticket: - https://jira.devtools.intel.com/browse/CVS-182397 --- .../group_query_attention_decomposition.hpp | 1 - .../fuse_rotary_positional_embeddings.cpp | 13 +- .../group_query_attention_decomposition.cpp | 111 ++++++++------- .../fuse_rotary_positional_embeddings.cpp | 122 +++++++++++++++++ .../src/op/com.microsoft/rotary_embedding.cpp | 129 ++++++++++-------- 5 files changed, 272 insertions(+), 104 deletions(-) diff --git a/src/common/transformations/include/transformations/op_conversions/group_query_attention_decomposition.hpp b/src/common/transformations/include/transformations/op_conversions/group_query_attention_decomposition.hpp index 3e61e50cbf4da9..5fcf8c848ee742 100644 --- a/src/common/transformations/include/transformations/op_conversions/group_query_attention_decomposition.hpp +++ b/src/common/transformations/include/transformations/op_conversions/group_query_attention_decomposition.hpp @@ -27,7 +27,6 @@ class ov::pass::GroupQueryAttentionDecomposition : public ov::pass::MatcherPass std::shared_ptr get_dimensions(const std::shared_ptr& shape, const std::vector& dims); std::shared_ptr get_dimensions(const std::shared_ptr& node, const std::vector& dims); - ov::OutputVector make_split(const ov::Output& value, int64_t num_splits, int64_t axis); std::shared_ptr rotaryEmbedding(ov::Output input, ov::Output cos, ov::Output sin, diff --git a/src/common/transformations/src/transformations/common_optimizations/fuse_rotary_positional_embeddings.cpp b/src/common/transformations/src/transformations/common_optimizations/fuse_rotary_positional_embeddings.cpp index 8cc867939584f8..da356a41efc3a8 100644 --- a/src/common/transformations/src/transformations/common_optimizations/fuse_rotary_positional_embeddings.cpp +++ b/src/common/transformations/src/transformations/common_optimizations/fuse_rotary_positional_embeddings.cpp @@ -1162,7 +1162,7 @@ RoPEFusionGPTOSS::RoPEFusionGPTOSS() { auto first_half_mul_sin = pattern::wrap_type({vsplit_out0, t_sin}, {{"auto_broadcast", "numpy"}}); auto add_Add = pattern::wrap_type({second_half_mul_cos, first_half_mul_sin}, {{"auto_broadcast", "numpy"}}); - auto concat_result = pattern::wrap_type({sub_Subtract, add_Add}, {{"axis", -1}}); + auto concat_result = pattern::wrap_type({sub_Subtract, add_Add}); auto result = concat_result; @@ -1172,6 +1172,17 @@ RoPEFusionGPTOSS::RoPEFusionGPTOSS() { const auto& x_val = pattern_map.at(x); const auto& v_cos = pattern_map.at(t_cos); + // Verify concat axis is the last dimension (accepts both -1 and positive equivalent) + auto concat_node = ov::as_type_ptr(root); + if (!concat_node) + return false; + auto concat_rank = concat_node->get_output_partial_shape(0).rank(); + if (concat_rank.is_dynamic()) + return false; + int64_t concat_axis = concat_node->get_axis(); + if (concat_axis != -1 && concat_axis != concat_rank.get_length() - 1) + return false; + auto symbols = m.get_symbols(); const auto& half_ndims = symbols["half_ndims"]; if (!half_ndims.is_integer()) { diff --git a/src/common/transformations/src/transformations/op_conversions/group_query_attention_decomposition.cpp b/src/common/transformations/src/transformations/op_conversions/group_query_attention_decomposition.cpp index ce1d86b258d438..728596da41dcec 100644 --- a/src/common/transformations/src/transformations/op_conversions/group_query_attention_decomposition.cpp +++ b/src/common/transformations/src/transformations/op_conversions/group_query_attention_decomposition.cpp @@ -24,11 +24,11 @@ #include "openvino/op/select.hpp" #include "openvino/op/shape_of.hpp" #include "openvino/op/slice.hpp" -#include "openvino/op/split.hpp" #include "openvino/op/squeeze.hpp" #include "openvino/op/subtract.hpp" #include "openvino/op/transpose.hpp" #include "openvino/op/unsqueeze.hpp" +#include "openvino/op/variadic_split.hpp" #include "openvino/pass/pattern/op/wrap_type.hpp" using ov::pass::pattern::Matcher; @@ -110,7 +110,10 @@ ov::OutputVector ov::pass::GroupQueryAttentionDecomposition::decompose( ov::Output position_ids = register_new_node(zero_without_shape, curr_seqlen_scalar, one_without_shape, ov::element::i64); if (node->get_input_size() > 9 && !is_null(node->input_value(9))) { - position_ids = node->input_value(9).get_node_shared_ptr(); + // Flatten position_ids to 1D so that Gather produces 2D [seqlen, head_size/2] output, + // ensuring correct 4D shapes after Unsqueeze in rotaryEmbedding. + const auto neg_one = register_new_node(v0::Constant::create(ov::element::i64, ov::Shape{1}, {-1})); + position_ids = register_new_node(node->input_value(9), neg_one, false); } else { position_ids = register_new_node(position_ids, past_seqlen); } @@ -230,16 +233,6 @@ ov::OutputVector ov::pass::GroupQueryAttentionDecomposition::decompose( return {output, present_k, present_v}; } -// make split functions is a copy-past from ONNX FE. TODO: move it to one place -ov::OutputVector ov::pass::GroupQueryAttentionDecomposition::make_split(const ov::Output& value, - int64_t num_splits, - int64_t axis) { - const auto axis_node = register_new_node(v0::Constant::create(ov::element::i64, ov::Shape{}, {axis})); - const auto split = register_new_node(value, axis_node, num_splits); - - return split->outputs(); -} - std::shared_ptr ov::pass::GroupQueryAttentionDecomposition::get_dimensions( const std::shared_ptr& shape, const std::vector& dims) { @@ -258,42 +251,66 @@ std::shared_ptr ov::pass::GroupQueryAttentionDecomposition::rotaryEmbe ov::Output cos, ov::Output sin, bool interleaved) { - auto zero = v0::Constant::create(ov::element::i64, ov::Shape{1}, {0}); - auto one = v0::Constant::create(ov::element::i64, ov::Shape{1}, {1}); - + auto two = v0::Constant::create(ov::element::i64, ov::Shape{1}, {2}); + + // Unsqueeze cos/sin to 4D [1, 1, seqlen, head_size/2] to match RoPE fusion pattern + auto unsqueeze_axes = v0::Constant::create(ov::element::i64, ov::Shape{2}, {0, 1}); + auto cos_4d = register_new_node(cos, unsqueeze_axes); + auto sin_4d = register_new_node(sin, unsqueeze_axes); + + // For interleaved mode, deinterleave first so the core RoPE formula is identical + ov::Output rope_input = input; + std::shared_ptr input_shape; + std::shared_ptr dim_bns, half_head_size; + std::shared_ptr perm_5d; if (interleaved) { - auto two = v0::Constant::create(ov::element::i64, ov::Shape{1}, {2}); - auto cos_last_dim = get_dimensions(cos.get_node_shared_ptr(), {-1}); - auto input_shape = register_new_node(input); - auto dim_bns = get_dimensions(input_shape, {0, 1, 2}); - - auto negtive_one = v0::Constant::create(ov::element::i64, ov::Shape{1}, {-1}); - auto split_input_shape = register_new_node(ov::NodeVector{dim_bns, cos_last_dim, two}, 0); - auto reshaped_input = register_new_node(input, split_input_shape, false); - - auto in_split = make_split(reshaped_input, 2, -1); - split_input_shape = register_new_node(ov::NodeVector{dim_bns, cos_last_dim}, 0); - auto in_split_0 = register_new_node(in_split[0], split_input_shape, false); - auto in_split_1 = register_new_node(in_split[1], split_input_shape, false); - - auto res_0 = register_new_node(register_new_node(in_split_0, cos), - register_new_node(in_split_1, sin)); - auto res_1 = register_new_node(register_new_node(in_split_0, sin), - register_new_node(in_split_1, cos)); - - split_input_shape = register_new_node(ov::NodeVector{dim_bns, cos_last_dim, one}, 0); - auto res_0_5d = register_new_node(res_0, split_input_shape, false); - auto res_1_5d = register_new_node(res_1, split_input_shape, false); - - auto concat_ret = register_new_node(ov::NodeVector{res_0_5d, res_1_5d}, -1); - return register_new_node(concat_ret, input_shape, false); - } else { - auto in_split = make_split(input, 2, -1); - auto res_0 = register_new_node(register_new_node(in_split[0], cos), - register_new_node(in_split[1], sin)); - auto res_1 = register_new_node(register_new_node(in_split[0], sin), - register_new_node(in_split[1], cos)); + input_shape = register_new_node(input); + dim_bns = get_dimensions(input_shape, {0, 1, 2}); + half_head_size = get_dimensions(cos.get_node_shared_ptr(), {-1}); + perm_5d = v0::Constant::create(ov::element::i64, ov::Shape{5}, {0, 1, 2, 4, 3}); + + // Deinterleave: [bs,nh,seq,head_size] + // -> reshape [bs,nh,seq,head_size/2,2] + // -> transpose [bs,nh,seq,2,head_size/2] + // -> reshape [bs,nh,seq,head_size] (now [first_half, second_half]) + auto deinterleave_5d = register_new_node(ov::NodeVector{dim_bns, half_head_size, two}, 0); + auto reshaped_5d = register_new_node(input, deinterleave_5d, false); + auto transposed_5d = register_new_node(reshaped_5d, perm_5d); + rope_input = register_new_node(transposed_5d, input_shape, false); + } - return register_new_node(ov::NodeVector{res_0, res_1}, -1); + // Core RoPE formula (matches RoPEFusionGPTOSS pattern for both modes) + // first_ = first_half * cos - second_half * sin + // second_ = second_half * cos + first_half * sin + const auto& cos_partial_shape = cos.get_partial_shape(); + const auto half_head_size_val = + static_cast(cos_partial_shape[cos_partial_shape.rank().get_length() - 1].get_length()); + const auto split_axis = v0::Constant::create(ov::element::i64, ov::Shape{}, {-1}); + const auto split_lengths = + v0::Constant::create(ov::element::i64, ov::Shape{2}, {half_head_size_val, half_head_size_val}); + // Split along last axis using constant split_lengths to enable RoPE fusion pattern matching + auto in_split = register_new_node(rope_input, split_axis, split_lengths)->outputs(); + auto first_half_mul_cos = register_new_node(in_split[0], cos_4d); + auto second_half_mul_sin = register_new_node(in_split[1], sin_4d); + auto neg_one = v0::Constant::create(ov::element::f32, ov::Shape{}, {-1.0f}); + auto neg_second_sin = register_new_node(second_half_mul_sin, neg_one); + auto res_0 = register_new_node(first_half_mul_cos, neg_second_sin); + auto second_half_mul_cos = register_new_node(in_split[1], cos_4d); + auto first_half_mul_sin = register_new_node(in_split[0], sin_4d); + auto res_1 = register_new_node(second_half_mul_cos, first_half_mul_sin); + ov::Output output = register_new_node(ov::NodeVector{res_0, res_1}, -1); + + // For interleaved mode, re-interleave the result + if (interleaved) { + // Re-interleave: [bs,nh,seq,head_size] + // -> reshape [bs,nh,seq,2,head_size/2] + // -> transpose [bs,nh,seq,head_size/2,2] + // -> reshape [bs,nh,seq,head_size] + auto reinterleave_5d = register_new_node(ov::NodeVector{dim_bns, two, half_head_size}, 0); + auto result_5d = register_new_node(output, reinterleave_5d, false); + auto result_transposed = register_new_node(result_5d, perm_5d); + output = register_new_node(result_transposed, input_shape, false); } + + return output.get_node_shared_ptr(); } diff --git a/src/common/transformations/tests/common_optimizations/fuse_rotary_positional_embeddings.cpp b/src/common/transformations/tests/common_optimizations/fuse_rotary_positional_embeddings.cpp index 781110418ed08a..8de0a7dfb619d9 100644 --- a/src/common/transformations/tests/common_optimizations/fuse_rotary_positional_embeddings.cpp +++ b/src/common/transformations/tests/common_optimizations/fuse_rotary_positional_embeddings.cpp @@ -2123,3 +2123,125 @@ TEST_F(TransformationTestsF, ConvertToROPE_LtxVideo) { model_ref = std::make_shared(OutputVector{rope}, ParameterVector{input, cos_freqs, sin_freqs}); } } + +TEST_F(TransformationTestsF, ConvertToROPE_GPTOSS_concat_axis_negative) { + disable_rt_info_check(); + const int batch = 2; + const int num_head = 32; + const int seq_len = 16; + const int ndims = 128; + const int half_ndims = ndims / 2; + using namespace ov; + { + // gpt-oss style RoPE pattern with concat axis = -1 + auto input = std::make_shared(element::f32, PartialShape{batch, num_head, seq_len, ndims}); + auto t_cos = std::make_shared(element::f32, PartialShape{batch, 1, seq_len, half_ndims}); + auto t_sin = std::make_shared(element::f32, PartialShape{batch, 1, seq_len, half_ndims}); + + auto split_lengths = makeConst(element::i64, {2}, std::vector{half_ndims, half_ndims}); + auto axis_const = makeConst(element::i64, {}, std::vector{-1}); + auto vsplit = makeOP({input, axis_const, split_lengths}); + + // first_ = first_half * cos - second_half * sin + auto first_half_mul_cos = makeOP({vsplit->output(0), t_cos}, {{"auto_broadcast", "numpy"}}); + auto second_half_mul_sin = makeOP({vsplit->output(1), t_sin}, {{"auto_broadcast", "numpy"}}); + auto neg = makeOP({second_half_mul_sin, -1.0f}, {{"auto_broadcast", "numpy"}}); + auto first_ = makeOP({first_half_mul_cos, neg}, {{"auto_broadcast", "numpy"}}); + + // second_ = second_half * cos + first_half * sin + auto second_half_mul_cos = makeOP({vsplit->output(1), t_cos}, {{"auto_broadcast", "numpy"}}); + auto first_half_mul_sin = makeOP({vsplit->output(0), t_sin}, {{"auto_broadcast", "numpy"}}); + auto second_ = makeOP({second_half_mul_cos, first_half_mul_sin}, {{"auto_broadcast", "numpy"}}); + + auto result = makeOP({first_, second_}, {{"axis", -1}}); + + model = std::make_shared(OutputVector{result}, ParameterVector{input, t_cos, t_sin}); + } + manager.register_pass(); + { + auto input = std::make_shared(element::f32, PartialShape{batch, num_head, seq_len, ndims}); + auto t_cos = std::make_shared(element::f32, PartialShape{batch, 1, seq_len, half_ndims}); + auto t_sin = std::make_shared(element::f32, PartialShape{batch, 1, seq_len, half_ndims}); + + auto rope = makeOP({input, t_cos, t_sin}, + {{"config.slice_start", 0}, + {"config.slice_stop", 0}, + {"config.input_trans0213", false}, + {"config.output_trans0213", false}, + {"config.is_interleaved", false}, + {"config.rotary_ndims", ndims}, + {"config.cos_sin_ndims", half_ndims}, + {"config.is_chatglm", false}, + {"config.support_2d_rope", false}, + {"config.support_3d_rope", false}, + {"config.is_qwen", false}, + {"config.use_rope_cache", false}, + {"config.is_ltx_video", false}, + {"config.head_cnt", 0}, + {"config.head_size", 0}, + {"config.gather_position_arg_id", 0}}); + + model_ref = std::make_shared(OutputVector{rope}, ParameterVector{input, t_cos, t_sin}); + } +} + +TEST_F(TransformationTestsF, ConvertToROPE_GPTOSS_concat_axis_positive) { + disable_rt_info_check(); + const int batch = 2; + const int num_head = 32; + const int seq_len = 16; + const int ndims = 128; + const int half_ndims = ndims / 2; + using namespace ov; + { + // gpt-oss style RoPE pattern with concat axis = 3 (positive last axis for rank-4 tensor) + auto input = std::make_shared(element::f32, PartialShape{batch, num_head, seq_len, ndims}); + auto t_cos = std::make_shared(element::f32, PartialShape{batch, 1, seq_len, half_ndims}); + auto t_sin = std::make_shared(element::f32, PartialShape{batch, 1, seq_len, half_ndims}); + + auto split_lengths = makeConst(element::i64, {2}, std::vector{half_ndims, half_ndims}); + auto axis_const = makeConst(element::i64, {}, std::vector{-1}); + auto vsplit = makeOP({input, axis_const, split_lengths}); + + // first_ = first_half * cos - second_half * sin + auto first_half_mul_cos = makeOP({vsplit->output(0), t_cos}, {{"auto_broadcast", "numpy"}}); + auto second_half_mul_sin = makeOP({vsplit->output(1), t_sin}, {{"auto_broadcast", "numpy"}}); + auto neg = makeOP({second_half_mul_sin, -1.0f}, {{"auto_broadcast", "numpy"}}); + auto first_ = makeOP({first_half_mul_cos, neg}, {{"auto_broadcast", "numpy"}}); + + // second_ = second_half * cos + first_half * sin + auto second_half_mul_cos = makeOP({vsplit->output(1), t_cos}, {{"auto_broadcast", "numpy"}}); + auto first_half_mul_sin = makeOP({vsplit->output(0), t_sin}, {{"auto_broadcast", "numpy"}}); + auto second_ = makeOP({second_half_mul_cos, first_half_mul_sin}, {{"auto_broadcast", "numpy"}}); + + auto result = makeOP({first_, second_}, {{"axis", 3}}); + + model = std::make_shared(OutputVector{result}, ParameterVector{input, t_cos, t_sin}); + } + manager.register_pass(); + { + auto input = std::make_shared(element::f32, PartialShape{batch, num_head, seq_len, ndims}); + auto t_cos = std::make_shared(element::f32, PartialShape{batch, 1, seq_len, half_ndims}); + auto t_sin = std::make_shared(element::f32, PartialShape{batch, 1, seq_len, half_ndims}); + + auto rope = makeOP({input, t_cos, t_sin}, + {{"config.slice_start", 0}, + {"config.slice_stop", 0}, + {"config.input_trans0213", false}, + {"config.output_trans0213", false}, + {"config.is_interleaved", false}, + {"config.rotary_ndims", ndims}, + {"config.cos_sin_ndims", half_ndims}, + {"config.is_chatglm", false}, + {"config.support_2d_rope", false}, + {"config.support_3d_rope", false}, + {"config.is_qwen", false}, + {"config.use_rope_cache", false}, + {"config.is_ltx_video", false}, + {"config.head_cnt", 0}, + {"config.head_size", 0}, + {"config.gather_position_arg_id", 0}}); + + model_ref = std::make_shared(OutputVector{rope}, ParameterVector{input, t_cos, t_sin}); + } +} diff --git a/src/frontends/onnx/frontend/src/op/com.microsoft/rotary_embedding.cpp b/src/frontends/onnx/frontend/src/op/com.microsoft/rotary_embedding.cpp index 6c8a4aa743424f..647f0a5e377970 100644 --- a/src/frontends/onnx/frontend/src/op/com.microsoft/rotary_embedding.cpp +++ b/src/frontends/onnx/frontend/src/op/com.microsoft/rotary_embedding.cpp @@ -10,25 +10,16 @@ #include "openvino/core/rt_info.hpp" #include "openvino/frontend/exception.hpp" #include "openvino/op/add.hpp" -#include "openvino/op/broadcast.hpp" #include "openvino/op/concat.hpp" #include "openvino/op/constant.hpp" -#include "openvino/op/convert.hpp" #include "openvino/op/gather.hpp" -#include "openvino/op/greater.hpp" -#include "openvino/op/greater_eq.hpp" #include "openvino/op/multiply.hpp" -#include "openvino/op/range.hpp" #include "openvino/op/reshape.hpp" -#include "openvino/op/scaled_dot_product_attention.hpp" -#include "openvino/op/select.hpp" #include "openvino/op/shape_of.hpp" -#include "openvino/op/slice.hpp" #include "openvino/op/split.hpp" -#include "openvino/op/squeeze.hpp" -#include "openvino/op/subtract.hpp" #include "openvino/op/transpose.hpp" #include "openvino/op/unsqueeze.hpp" +#include "openvino/op/variadic_split.hpp" #include "openvino/pass/pattern/op/wrap_type.hpp" #include "utils/common.hpp" #include "utils/reshape.hpp" @@ -41,14 +32,6 @@ namespace onnx { namespace com_microsoft { namespace opset_1 { -ov::OutputVector make_split(const ov::Output& value, int64_t num_splits, int64_t axis) { - using namespace ov::op; - const auto axis_node = v0::Constant::create(ov::element::i64, ov::Shape{}, {axis}); - const auto split = std::make_shared(value, axis_node, num_splits); - - return split->outputs(); -} - std::shared_ptr get_dimensions(const std::shared_ptr& shape, const std::vector& dims) { static const auto zero = v0::Constant::create(ov::element::i32, ov::Shape{}, {0}); const auto dims_const = v0::Constant::create(ov::element::i32, ov::Shape{dims.size()}, dims); @@ -60,7 +43,7 @@ ov::OutputVector rotary_embedding(const ov::frontend::onnx::Node& node) { // Original documentation: // https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.RotaryEmbedding const auto inputs = node.get_ov_inputs(); - const auto& input = inputs[0]; // [bs,seqlen,hidden] or [bs,num_heads,seqlen,headsize] + const auto& input = inputs[0]; // [bs,seqlen,hidden] or [bs,num_heads,seqlen,head_size] const auto& position_ids = inputs[1]; // [seqlen] or [bs, seqlen] const auto& cos_cache = inputs[2]; // [max_seqlen, head_size/2] const auto& sin_cache = inputs[3]; // [max_seqlen, head_size/2] @@ -68,7 +51,6 @@ ov::OutputVector rotary_embedding(const ov::frontend::onnx::Node& node) { const auto interleaved = node.get_attribute_value("interleaved"); // required const auto minus_one = v0::Constant::create(ov::element::i64, ov::Shape{1}, {-1}); const auto zero = v0::Constant::create(ov::element::i64, ov::Shape{1}, {0}); - const auto one = v0::Constant::create(ov::element::i64, ov::Shape{1}, {1}); const auto two = v0::Constant::create(ov::element::i64, ov::Shape{1}, {2}); const auto cos = std::make_shared(cos_cache, @@ -89,7 +71,8 @@ ov::OutputVector rotary_embedding(const ov::frontend::onnx::Node& node) { last_dim.is_static(), "cos_cache last dimension must be static to derive head size, got: ", cos_cache_shape); - const auto headsize_val = static_cast(last_dim.get_length() * 2); + const auto half_head_size_val = static_cast(last_dim.get_length()); + const auto head_size_val = half_head_size_val * 2; const auto input_shape = std::make_shared(input); const auto input_rank = input.get_partial_shape().rank(); @@ -98,46 +81,82 @@ ov::OutputVector rotary_embedding(const ov::frontend::onnx::Node& node) { ov::Output input_4d = input; if (input_is_3d) { - const auto headsize = v0::Constant::create(ov::element::i64, ov::Shape{1}, {headsize_val}); + const auto headsize = v0::Constant::create(ov::element::i64, ov::Shape{1}, {head_size_val}); const auto input_shape_prev_2 = get_dimensions(input_shape, {0, 1}); auto new_input_shape = std::make_shared(ov::NodeVector{input_shape_prev_2, minus_one, headsize}, 0); auto input_reshaped = - std::make_shared(input, new_input_shape, false); // [bs,seqlen,num_heads,headsize] - input_4d = std::make_shared(input_reshaped, perm); // [bs,num_heads,seqlen,headsize] + std::make_shared(input, new_input_shape, false); // [bs,seqlen,num_heads,head_size] + input_4d = std::make_shared(input_reshaped, perm); // [bs,num_heads,seqlen,head_size] } - ov::Output output; - if (interleaved) { - auto input_4d_shape = std::make_shared(input_4d); - auto dim_bns = get_dimensions(input_4d_shape, {0, 1, 2}); - auto half_head_size = v0::Constant::create(ov::element::i64, ov::Shape{1}, {last_dim.get_length()}); - auto split_input_shape = std::make_shared(ov::NodeVector{dim_bns, half_head_size, two}, 0); - auto reshaped_input = std::make_shared(input_4d, split_input_shape, false); - - auto in_split = make_split(reshaped_input, 2, -1); - split_input_shape = std::make_shared(ov::NodeVector{dim_bns, half_head_size}, 0); - auto in_split_0 = std::make_shared(in_split[0], split_input_shape, false); - auto in_split_1 = std::make_shared(in_split[1], split_input_shape, false); - - auto res_0 = std::make_shared(std::make_shared(in_split_0, cos), - std::make_shared(in_split_1, sin)); - auto res_1 = std::make_shared(std::make_shared(in_split_0, sin), - std::make_shared(in_split_1, cos)); - - split_input_shape = std::make_shared(ov::NodeVector{dim_bns, half_head_size, one}, 0); - auto res_0_5d = std::make_shared(res_0, split_input_shape, false); - auto res_1_5d = std::make_shared(res_1, split_input_shape, false); - - auto concat_ret = std::make_shared(ov::NodeVector{res_0_5d, res_1_5d}, -1); - output = std::make_shared(concat_ret, input_4d_shape, - false); // [bs,num_heads,seqlen,headsize] + // Unsqueeze cos/sin to 4D [?, 1, ?, head_size/2] to match RoPE fusion pattern + ov::Output cos_4d, sin_4d; + const auto cos_out_rank = cos->get_output_partial_shape(0).rank(); + if (cos_out_rank.is_static() && cos_out_rank.get_length() == 2) { + // cos is [seqlen, head_size/2] → [1, 1, seqlen, head_size/2] + auto axes = v0::Constant::create(ov::element::i64, ov::Shape{2}, {0, 1}); + cos_4d = std::make_shared(cos, axes); + sin_4d = std::make_shared(sin, axes); } else { - auto in_split = make_split(input_4d, 2, -1); // [bs,num_heads,seqlen,headsize/2] - auto res_0 = std::make_shared(std::make_shared(in_split[0], cos), - std::make_shared(in_split[1], sin)); - auto res_1 = std::make_shared(std::make_shared(in_split[0], sin), - std::make_shared(in_split[1], cos)); - output = std::make_shared(ov::NodeVector{res_0, res_1}, -1); // [bs,num_heads,seqlen,headsize] + // cos is [bs, seqlen, head_size/2] → [bs, 1, seqlen, head_size/2] + auto axes = v0::Constant::create(ov::element::i64, ov::Shape{1}, {1}); + cos_4d = std::make_shared(cos, axes); + sin_4d = std::make_shared(sin, axes); + } + + // For interleaved mode, deinterleave first so the core RoPE formula is identical + ov::Output rope_input = input_4d; + std::shared_ptr input_4d_shape; + std::shared_ptr dim_bns; + std::shared_ptr half_head_size; + std::shared_ptr perm_5d; + if (interleaved) { + input_4d_shape = std::make_shared(input_4d); + dim_bns = get_dimensions(input_4d_shape, {0, 1, 2}); + half_head_size = v0::Constant::create(ov::element::i64, ov::Shape{1}, {half_head_size_val}); + perm_5d = v0::Constant::create(ov::element::i64, ov::Shape{5}, {0, 1, 2, 4, 3}); + + // Deinterleave: [bs,num_heads,seqlen,head_size] + // → reshape [bs,num_heads,seqlen,head_size/2,2] + // → transpose [bs,num_heads,seqlen,2,head_size/2] + // → reshape [bs,num_heads,seqlen,head_size] (now [first_half, second_half]) + auto deinterleave_5d = std::make_shared(ov::NodeVector{dim_bns, half_head_size, two}, 0); + auto reshaped_5d = std::make_shared(input_4d, deinterleave_5d, false); + auto transposed_5d = std::make_shared(reshaped_5d, perm_5d); + rope_input = std::make_shared(transposed_5d, input_4d_shape, false); + } + + // Core RoPE formula (matches RoPEFusionGPTOSS pattern for both modes) + // first_ = first_half * cos - second_half * sin + // second_ = second_half * cos + first_half * sin + const auto split_axis = v0::Constant::create(ov::element::i64, ov::Shape{}, {-1}); + const auto split_lengths = + v0::Constant::create(ov::element::i64, ov::Shape{2}, {half_head_size_val, half_head_size_val}); + // Split along last axis using constant split_lengths to enable RoPE fusion pattern matching + auto in_split = std::make_shared(rope_input, split_axis, split_lengths)->outputs(); + auto first_half_mul_cos = std::make_shared(in_split[0], cos_4d); + auto second_half_mul_sin = std::make_shared(in_split[1], sin_4d); + const auto neg_one = v0::Constant::create(ov::element::f32, ov::Shape{}, {-1.0f}); + auto neg_second_sin = std::make_shared(second_half_mul_sin, neg_one); + auto res_0 = std::make_shared(first_half_mul_cos, neg_second_sin); + auto second_half_mul_cos = std::make_shared(in_split[1], cos_4d); + auto first_half_mul_sin = std::make_shared(in_split[0], sin_4d); + auto res_1 = std::make_shared(second_half_mul_cos, first_half_mul_sin); + ov::Output output = + std::make_shared(ov::NodeVector{res_0, res_1}, -1); // [bs,num_heads,seqlen,head_size] + + // For interleaved mode, re-interleave the result + if (interleaved) { + // Re-interleave: [bs,num_heads,seqlen,head_size] + // → reshape [bs,num_heads,seqlen,2,head_size/2] + // → transpose [bs,num_heads,seqlen,head_size/2,2] + // → reshape [bs,num_heads,seqlen,head_size] + auto reinterleave_5d = std::make_shared(ov::NodeVector{dim_bns, two, half_head_size}, 0); + auto result_5d = std::make_shared(output, reinterleave_5d, false); + auto result_transposed = std::make_shared(result_5d, perm_5d); + output = std::make_shared(result_transposed, + input_4d_shape, + false); // [bs,num_heads,seqlen,head_size] } if (input_is_3d) { From 69df8b5323df31c903ea865f526240703b9cbc3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:51:08 +0200 Subject: [PATCH 017/545] Bump the npm-packages group in /samples/js/node with 2 updates (#35421) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the npm-packages group in /samples/js/node with 2 updates: [@napi-rs/canvas](https://github.com/Brooooooklyn/canvas) and [eslint](https://github.com/eslint/eslint). Updates `@napi-rs/canvas` from 0.1.98 to 0.1.99
Release notes

Sourced from @​napi-rs/canvas's releases.

v0.1.99

What's Changed

New Contributors

Full Changelog: https://github.com/Brooooooklyn/canvas/compare/v0.1.98...v0.1.99

Changelog

Sourced from @​napi-rs/canvas's changelog.

0.1.99 (2026-04-18)

Bug Fixes

  • drawImage gray halo on transparent PNG edges with imageSmoothingEnabled (#1252) (a748f3f)
Commits
  • 0372a4a 0.1.99
  • adc6e17 ci: allow canvas package postinstall script for benchmark CI (#1253)
  • a748f3f fix: drawImage gray halo on transparent PNG edges with imageSmoothingEnabled ...
  • d2d01c3 chore(deps): update yarn to v4.14.1 (#1251)
  • 1f595c6 chore(deps): update yarn to v4.14.0 (#1249)
  • See full diff in compare view

Updates `eslint` from 10.2.0 to 10.2.1
Release notes

Sourced from eslint's releases.

v10.2.1

Bug Fixes

  • 14be92b fix: model generator yield resumption paths in code path analysis (#20665) (sethamus)
  • 84a19d2 fix: no-async-promise-executor false positives for shadowed Promise (#20740) (xbinaryx)
  • af764af fix: clarify language and processor validation errors (#20729) (Pixel998)
  • e251b89 fix: update eslint (#20715) (renovate[bot])

Documentation

  • ca92ca0 docs: reuse markdown-it instance for markdown filter (#20768) (Amaresh S M)
  • 57d2ee2 docs: Enable Eleventy incremental mode for watch (#20767) (Amaresh S M)
  • c1621b9 docs: fix typos in code-path-analyzer.js (#20700) (Ayush Shukla)
  • 1418d52 docs: Update README (GitHub Actions Bot)
  • 39771e6 docs: Update README (GitHub Actions Bot)
  • 71e0469 docs: fix incomplete JSDoc param description in no-shadow rule (#20728) (kuldeep kumar)
  • 22119ce docs: clarify scope of for-direction rule with dead code examples (#20723) (Amaresh S M)
  • 8f3fb77 docs: document meta.docs.dialects (#20718) (Pixel998)

Chores

  • 7ddfea9 chore: update dependency prettier to v3.8.2 (#20770) (renovate[bot])
  • fac40e1 ci: bump pnpm/action-setup from 5.0.0 to 6.0.0 (#20763) (dependabot[bot])
  • 7246f92 test: add tests for SuppressionsService.load() error handling (#20734) (kuldeep kumar)
  • 4f34b1e chore: update pnpm/action-setup action to v5 (#20762) (renovate[bot])
  • 51080eb test: processor service (#20731) (kuldeep kumar)
  • e7e1889 chore: remove stale babel-eslint10 fixture and test (#20727) (kuldeep kumar)
  • 4e1a87c test: remove redundant async/await in flat config array tests (#20722) (Pixel998)
  • 066eabb test: add rule metadata coverage for languages and docs.dialects (#20717) (Pixel998)
Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/js/node/package-lock.json | 108 +++++++++++++++--------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/samples/js/node/package-lock.json b/samples/js/node/package-lock.json index 20fc9a1d68994f..540fd68c614935 100644 --- a/samples/js/node/package-lock.json +++ b/samples/js/node/package-lock.json @@ -205,9 +205,9 @@ } }, "node_modules/@napi-rs/canvas": { - "version": "0.1.98", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.98.tgz", - "integrity": "sha512-WDg3lxYMqlrg49sDVUlrHVfIEPsd5AjYDRuGD6Fu82K5agJx0UnWA+l5qd53GNLRiMN2WhOw7FLR+Er5QB/0SA==", + "version": "0.1.99", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.99.tgz", + "integrity": "sha512-zN4eQlK3eBf7aJBcTHZilpBH3tDekBzPMIWC8r0s94Ecl73XfOyFi4w7yKFMRVUT0lvNQjtOL8YSrwqQj6mZFg==", "dev": true, "license": "MIT", "workspaces": [ @@ -221,23 +221,23 @@ "url": "https://github.com/sponsors/Brooooooklyn" }, "optionalDependencies": { - "@napi-rs/canvas-android-arm64": "0.1.98", - "@napi-rs/canvas-darwin-arm64": "0.1.98", - "@napi-rs/canvas-darwin-x64": "0.1.98", - "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.98", - "@napi-rs/canvas-linux-arm64-gnu": "0.1.98", - "@napi-rs/canvas-linux-arm64-musl": "0.1.98", - "@napi-rs/canvas-linux-riscv64-gnu": "0.1.98", - "@napi-rs/canvas-linux-x64-gnu": "0.1.98", - "@napi-rs/canvas-linux-x64-musl": "0.1.98", - "@napi-rs/canvas-win32-arm64-msvc": "0.1.98", - "@napi-rs/canvas-win32-x64-msvc": "0.1.98" + "@napi-rs/canvas-android-arm64": "0.1.99", + "@napi-rs/canvas-darwin-arm64": "0.1.99", + "@napi-rs/canvas-darwin-x64": "0.1.99", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.99", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.99", + "@napi-rs/canvas-linux-arm64-musl": "0.1.99", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.99", + "@napi-rs/canvas-linux-x64-gnu": "0.1.99", + "@napi-rs/canvas-linux-x64-musl": "0.1.99", + "@napi-rs/canvas-win32-arm64-msvc": "0.1.99", + "@napi-rs/canvas-win32-x64-msvc": "0.1.99" } }, "node_modules/@napi-rs/canvas-android-arm64": { - "version": "0.1.98", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.98.tgz", - "integrity": "sha512-O45Ifr0WZJUrSyg0QgB+67TiC0zYBRkBK+d43ZV4JtlwH3XttiVxLvlxEeULiH5y1MSELruspF0bjF6xXwJNPQ==", + "version": "0.1.99", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.99.tgz", + "integrity": "sha512-9OCRt8VVxA17m32NWZKyNC2qamdaS/SC5CEOIQwFngRq0DIeVm4PDal+6Ljnhqm2whZiC63DNuKZ4xSp2nbj9w==", "cpu": [ "arm64" ], @@ -256,9 +256,9 @@ } }, "node_modules/@napi-rs/canvas-darwin-arm64": { - "version": "0.1.98", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.98.tgz", - "integrity": "sha512-1b/nQhw6Isdv14JokUqat+i5wrAYD+ce3egiotedBGRUjVxYSj4s2uQCh2bFsyX5/9A5iTKVGsWoQhFft+j7Lg==", + "version": "0.1.99", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.99.tgz", + "integrity": "sha512-lupMDMy1+H38dhyCcLirOKKVUyzzlxi7j7rGPLI3vViMHOoPjcXO1b10ivy+ad+q6MiwHfoLjKTCoLke5ySOBg==", "cpu": [ "arm64" ], @@ -277,9 +277,9 @@ } }, "node_modules/@napi-rs/canvas-darwin-x64": { - "version": "0.1.98", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.98.tgz", - "integrity": "sha512-oefzfBM8mwnyYp6S+yNXwjCoLdkOalFG24mssHgvrJDS0FulOryyI35Q7GdJGmrzuL4oo1XW3ZTOcTBLdJ8Zkg==", + "version": "0.1.99", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.99.tgz", + "integrity": "sha512-fdz02t4w8n6Ii/rYhWig6STb/zcTmCC/6YZTGmjoDeidDwn9Wf0ukQVynhCPEs29vqUc66wHZKsuIgMs9tycCg==", "cpu": [ "x64" ], @@ -298,9 +298,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { - "version": "0.1.98", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.98.tgz", - "integrity": "sha512-NDH5QXGmf8wlo5yhijCNGVFiJk7an5GvHwb2LHyfLQWY/6/S48i5+YtY6FPqPVVCUckNGudYOfXEJnb3/FiJGQ==", + "version": "0.1.99", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.99.tgz", + "integrity": "sha512-w4FwVwlNo00ezeRhfY62IVIyt6G3u8wodkPtiqWc52BUHx+VDBUM2vkS3ogfANaLI7hnf3s6WK4LyZVUjBg1lA==", "cpu": [ "arm" ], @@ -319,9 +319,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-gnu": { - "version": "0.1.98", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.98.tgz", - "integrity": "sha512-KBLLM6tu1xs80LSAqdSLBKkgct0S23MCEf/aq8yxzg5imAceqp1ulKeELgWaYm27MgpUhm3Q7jmegX12FfphwA==", + "version": "0.1.99", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.99.tgz", + "integrity": "sha512-8JvHeexKQ8c7g0q7YJ29NVQwnf1ePghP9ys9ZN0R0qzyqJQ9Uw6N9qnDINArlm3IYHexB7LjzArIfhQiqSDGvQ==", "cpu": [ "arm64" ], @@ -340,9 +340,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-musl": { - "version": "0.1.98", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.98.tgz", - "integrity": "sha512-mfMNhjN5zDcJafqQ6sHj4Tc3YMTRxP5UA3MHtp/ssytBR/k6XO0x+1IIPtscnUKwha+ql1++WjDCGEgqu8OfWQ==", + "version": "0.1.99", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.99.tgz", + "integrity": "sha512-Z+6nyLdJXWzLPVxi4H6g9TJop4DwN3KSgHWto5JCbZV5/uKoVqcSynPs0tGlUHOoWI8S8tEvJspz51GQkvr07w==", "cpu": [ "arm64" ], @@ -361,9 +361,9 @@ } }, "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { - "version": "0.1.98", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.98.tgz", - "integrity": "sha512-nfW8esrcaeuhrO3qGA5cwuyk4Ak6cn2eB0LtEYtqROIl+fz06CNGNCU0M95+Tspw5ZgfSbc98SaigT5r5B3LVQ==", + "version": "0.1.99", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.99.tgz", + "integrity": "sha512-jAnfOUv4IO1l8Levk5t85oVtEBOXLa07KnIUgWo1CDlPxiqpxS3uBfiE38Lvj/CQgHaNF6Nxk/SaemwLgsVJgw==", "cpu": [ "riscv64" ], @@ -382,9 +382,9 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-gnu": { - "version": "0.1.98", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.98.tgz", - "integrity": "sha512-318UT8j6Gro2bTjtutjQXHWp9SLTNw+WRS4wQ6XIRPAyzBGnGHg7x2ndD+oqkPrrSRIbYLA5WoBcCasaF7lSTQ==", + "version": "0.1.99", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.99.tgz", + "integrity": "sha512-mIkXw3fGmbYyFjSmfWEvty4jN+rwEOmv0+Dy9bRvvTzLYWCgm3RMgUEQVfAKFw96nIRFnyNZiK83KNQaVVFjng==", "cpu": [ "x64" ], @@ -403,9 +403,9 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-musl": { - "version": "0.1.98", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.98.tgz", - "integrity": "sha512-0vZhI74UxnA4VqlW4UvM0dFRrjE1RLEe/OXSBjzytGIxV+yOG4exlrhGoIpAQaIpQQQXMCdb1EmbvPC1k9vEqQ==", + "version": "0.1.99", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.99.tgz", + "integrity": "sha512-f3Uz2P0RgrtBHISxZqr6yiYXJlTDyCVBumDacxo+4AmSg7z0HiqYZKGWC/gszq3fbPhyQUya1W2AEteKxT9Y6A==", "cpu": [ "x64" ], @@ -424,9 +424,9 @@ } }, "node_modules/@napi-rs/canvas-win32-arm64-msvc": { - "version": "0.1.98", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.98.tgz", - "integrity": "sha512-oiC/IxgFEEVcZ7VH7JXXlmgsqRvmFb57PIQ4gQck35IKFZCNUvdNCcN3OeoLP7Hpf5160MWJf9jj/+E5V0bSvw==", + "version": "0.1.99", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.99.tgz", + "integrity": "sha512-XE6KUkfqRsCNejcoRMiMr3RaUeObxNf6y7dut3hrq2rn7PzfRTZgrjF1F/B2C7FcdgqY/vSHWpQeMuNz1vTNHg==", "cpu": [ "arm64" ], @@ -445,9 +445,9 @@ } }, "node_modules/@napi-rs/canvas-win32-x64-msvc": { - "version": "0.1.98", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.98.tgz", - "integrity": "sha512-ZqstKAJBSyZetU8udUvBQWPlGN9buawFvjuo9mgCAxzbOoJAgXX39ihec/nn42T5Vb6/qyn45eTimx5ND9kMEw==", + "version": "0.1.99", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.99.tgz", + "integrity": "sha512-plMYGVbc/vmmPF9MtmHbwNk1rL1Aj53vQZt+Gnv1oZn6gmd9jEHHJ0n9Nd2nxa5sKH7TS5IjkCDM6289O0d6PQ==", "cpu": [ "x64" ], @@ -892,18 +892,18 @@ } }, "node_modules/eslint": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz", - "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.1.tgz", + "integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.4", - "@eslint/config-helpers": "^0.5.4", - "@eslint/core": "^1.2.0", - "@eslint/plugin-kit": "^0.7.0", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.5.5", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", From e7edd09c3b390082725bc905baa4e8a06be8f447 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 07:51:47 +0000 Subject: [PATCH 018/545] Bump packaging from 26.0 to 26.1 (#35351) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [packaging](https://github.com/pypa/packaging) from 26.0 to 26.1.
Release notes

Sourced from packaging's releases.

26.1

Features:

Behavior adaptations:

Pylock (PEP 751) updates:

Fixes:

Performance:

... (truncated)

Changelog

Sourced from packaging's changelog.

26.1 - 2026-04-14


Features:
  • PEP 783: add handling for Emscripten wheel tags in (:pull:804)
  • PEP 803: add handling for the abi3.abi3t free-threading tag in (:pull:1099)
  • PEP 723: add packaging.dependency_groups module, based on the dependency-groups package in (:pull:1065)
  • Add the packaging.direct_url module in (:pull:944)
  • Add the packaging.errors module in (:pull:1071)
  • Add SpecifierSet.is_unsatisfiable using ranges (new internals that will be expanded in future versions) in (:pull:1119)
  • Add create_compatible_tags_selector to select compatible tags in (:pull:1110)
  • Add a key argument to SpecifierSet.filter() in (:pull:1068)
  • Support &amp; and | for Marker's in (:pull:1146)
  • Normalize Version.__replace__ and add Version.from_parts in (:pull:1078)
  • Add an option to validate compressed tag set sort order in parse_wheel_filename in (:pull:1150)

Behavior adaptations:

  • Narrow exclusion of pre-releases for &lt;V.postN to match spec in (:pull:1140)
  • Narrow exclusion of post-releases for &gt;V to match spec in (:pull:1141)
  • Rename format_full_version to _format_full_version to make it visibly private in (:pull:1125)
  • Restrict local version to ASCII in (:pull:1102)

Pylock (PEP 751) updates:

  • Add pylock select function in (:pull:1092)
  • Document pylock select() method and PylockSelectError in (:pull:1153)
  • Add filename property to PackageSdist and PackageWheel, more validation in (:pull:1095)
  • Give preference to path over url in (:pull:1128)
  • Validate name/version consistency in file names in (:pull:1114)

Fixes:

  • Fix &gt; comparison for versions with dev+local segments in (:pull:1097)
  • Fix incorrect self-comparison for InfinityType and NegativeInfinityType in (:pull:1093)
  • Canonicalize when deduplicating specifiers in SpecifierSet in (:pull:1109)
  • Fix charset error message formatting in (:pull:1121)
  • Handle the key parameter in SpecifierSet.filter when specifiers are empty and prerelease is False in (:pull:1096)
  • Standardize inner components of repr output in (:pull:1090)
  • Specifier's === uses original string, not normalized, when available in (:pull:1124)
  • Propagate int-max-str-digits ValueError in (:pull:1155)

Performance:

  • Add fast path for parsing simple versions (digits and dots only) in (:pull:1082)
  • Add fast path for Version to Version comparison by skipping _key property in (:pull:1083)
  • Cache Version hash value in dedicated slot in (:pull:1118)
  • Overhaul _cmpkey to remove use of custom objects in (:pull:1116)
  • Skip __replace__ in Specifier comparison if not needed in (:pull:1081)
    </tr></table>

... (truncated)

Commits
  • c1a88a3 Bump for release
  • 702c25e docs: update changelog for 26.1 (#1156)
  • 3f4f5d4 Implement is_unsatisfiable on SpecifierSet using ranges (#1119)
  • 06c6555 Propagate int-max-str-digits ValueError (#1155)
  • 905c90c feat: option to validate compressed tag set sort order in `parse_wheel_filena...
  • af0026c docs(pylock): document select() method and PylockSelectError (#1153)
  • 668da86 Rename format_full_version to _format_full_version to make it visibly private...
  • f294d52 tests: do not reload the tags module (#1152)
  • 2c6c7df feat: add handling for Emscripten wheels tags per PEP 783 (#804)
  • 6762eea docs(markers): document & and | operators for combining Marker objects (#1151)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=packaging&package-manager=pip&previous-version=26.0&new-version=26.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 147f7148b9f0a5..7fd2f054326a57 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -19,7 +19,7 @@ lxml>=4.9.2 MarkupSafe==3.0.3 mistune==3.2.0 myst-parser==4.0.1 -packaging==26.0 +packaging==26.1 pluggy==1.6.0 pydata-sphinx-theme==0.14.4 Pygments==2.20.0 From 3db307976120557e410b8d643ce614098d1c84f2 Mon Sep 17 00:00:00 2001 From: Kartikeya Srivastava <87693802+KarSri7694@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:59:06 +0530 Subject: [PATCH 019/545] Fix incorrect data type size check in rearrange_cache function (#34669) This fixes issue #34668 Co-authored-by: Pavel Durandin --- .../intel_gpu/src/plugin/multi_tensor_variable_state.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/intel_gpu/src/plugin/multi_tensor_variable_state.cpp b/src/plugins/intel_gpu/src/plugin/multi_tensor_variable_state.cpp index 5c04dfd9fa03c5..971c025235ad75 100644 --- a/src/plugins/intel_gpu/src/plugin/multi_tensor_variable_state.cpp +++ b/src/plugins/intel_gpu/src/plugin/multi_tensor_variable_state.cpp @@ -106,7 +106,7 @@ static void rearrange_cache(cldnn::memory::ptr kv_in_mem, cldnn::memory::ptr bt_ if (ov::element::Type(kv_layout.data_type).size() == 2) copy_element(kv_in_ptr.data(), kv_out_ptr.data(), in_offset, out_offset); - else if (ov::element::Type(kv_layout.data_type).size() == 2) + else if (ov::element::Type(kv_layout.data_type).size() == 4) copy_element(kv_in_ptr.data(), kv_out_ptr.data(), in_offset, out_offset); } } From c6169ed200d3e7d5d6c1280a1856897ee2bacf98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:32:56 +0000 Subject: [PATCH 020/545] Bump typescript-eslint from 8.58.2 to 8.59.0 in /src/bindings/js/node in the npm-packages group (#35439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the npm-packages group in /src/bindings/js/node with 1 update: [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint). Updates `typescript-eslint` from 8.58.2 to 8.59.0
Release notes

Sourced from typescript-eslint's releases.

v8.59.0

8.59.0 (2026-04-20)

🚀 Features

  • eslint-plugin: [no-unnecessary-type-assertion] report more cases based on assignability (#11789)

❤️ Thank You

  • Ulrich Stark

See GitHub Releases for more information.

You can read about our versioning strategy and releases on our website.

Changelog

Sourced from typescript-eslint's changelog.

8.59.0 (2026-04-20)

This was a version bump only for typescript-eslint to align it with other projects, there were no code changes.

See GitHub Releases for more information.

You can read about our versioning strategy and releases on our website.

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=typescript-eslint&package-manager=npm_and_yarn&previous-version=8.58.2&new-version=8.59.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/bindings/js/node/package-lock.json | 122 ++++++++++++------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/src/bindings/js/node/package-lock.json b/src/bindings/js/node/package-lock.json index 21bfb8f476d31b..fb289a0e794ad4 100644 --- a/src/bindings/js/node/package-lock.json +++ b/src/bindings/js/node/package-lock.json @@ -283,17 +283,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", - "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.0.tgz", + "integrity": "sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/type-utils": "8.58.2", - "@typescript-eslint/utils": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/type-utils": "8.59.0", + "@typescript-eslint/utils": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -306,7 +306,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.58.2", + "@typescript-eslint/parser": "^8.59.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -322,16 +322,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", - "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.0.tgz", + "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3" }, "engines": { @@ -347,14 +347,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", - "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.0.tgz", + "integrity": "sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.2", - "@typescript-eslint/types": "^8.58.2", + "@typescript-eslint/tsconfig-utils": "^8.59.0", + "@typescript-eslint/types": "^8.59.0", "debug": "^4.4.3" }, "engines": { @@ -369,14 +369,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", - "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.0.tgz", + "integrity": "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2" + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -387,9 +387,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", - "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.0.tgz", + "integrity": "sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==", "dev": true, "license": "MIT", "engines": { @@ -404,15 +404,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", - "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.0.tgz", + "integrity": "sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2", - "@typescript-eslint/utils": "8.58.2", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/utils": "8.59.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -429,9 +429,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", - "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.0.tgz", + "integrity": "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==", "dev": true, "license": "MIT", "engines": { @@ -443,16 +443,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", - "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.0.tgz", + "integrity": "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.58.2", - "@typescript-eslint/tsconfig-utils": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", + "@typescript-eslint/project-service": "8.59.0", + "@typescript-eslint/tsconfig-utils": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -510,16 +510,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", - "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.0.tgz", + "integrity": "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2" + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -534,13 +534,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", - "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.0.tgz", + "integrity": "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/types": "8.59.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2400,16 +2400,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.2.tgz", - "integrity": "sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.0.tgz", + "integrity": "sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.58.2", - "@typescript-eslint/parser": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2", - "@typescript-eslint/utils": "8.58.2" + "@typescript-eslint/eslint-plugin": "8.59.0", + "@typescript-eslint/parser": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/utils": "8.59.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" From 091d0b366993939d25d704f55c30fb5a2471bede Mon Sep 17 00:00:00 2001 From: Alicja Miloszewska Date: Wed, 22 Apr 2026 10:52:55 +0200 Subject: [PATCH 021/545] Migrate ONNX frontend InputModel and other to use std::fs::path (#35091) ### Details: This pull request refactors the ONNX frontend codebase to consistently use `std::filesystem::path` instead of `std::string` or `std::wstring` for file and directory paths. - uses `std::filesystem::path` in ` InputModel` , `ONNXModelEditor` and `onnx::Graph`. `onnx::Tensor` used in `onnx::Graph` already uses `std::filesystem::path`. No change needed here: https://github.com/openvinotoolkit/openvino/blob/eee1d087bbf32e1e59e44b87812722e8167be9a3/src/frontends/onnx/frontend/src/core/graph.cpp#L118 - removes `.native()` from _src/frontends/onnx/frontend/src/frontend.cpp_ - removes `.string()` from _src/frontends/onnx/frontend/src/utils/onnx_internal.cpp_ which can fail on Windows. - migrates ` parse_from_file(const std::filesystem::path& file_path)` - migrates `void serialize(const std::filesystem::path& out_file_path) const;` even though it looks unused: https://github.com/openvinotoolkit/openvino/blob/7df21f758f62c3c8ec918423e6ce09adb6840d3a/src/frontends/onnx/frontend/src/editor.hpp#L200 - Migrate cache map: https://github.com/openvinotoolkit/openvino/blob/e91a5131b0002a68071a71f6b4d00a6f03db93d3/src/frontends/onnx/frontend/src/core/graph_iterator_proto.cpp#L282 Not migrated: - [TensorMetaInfo::m_external_location](https://github.com/openvinotoolkit/openvino/blob/4b0dd4066890768e7aae7337e783da9db104eec9/src/frontends/onnx/frontend/include/openvino/frontend/onnx/decoder.hpp#L22) - part of public api - TensorExternalData.m_data_location - it can be ORT_MEM_ADDR and should be considered separately TensorONNXPlace.data_location https://github.com/openvinotoolkit/openvino/blob/ff36e229c3ce3565d7fe9e2f08ceba3801009156/src/frontends/onnx/frontend/src/core/tensor.hpp#L153 ### Tickets: - [CVS-183681](https://jira.devtools.intel.com/browse/CVS-183681) ### AI Assistance: - *AI assistance used: yes* - codebase analysis --- .../onnx/frontend/src/core/attribute.hpp | 6 ++-- .../onnx/frontend/src/core/graph.cpp | 4 +-- .../onnx/frontend/src/core/graph.hpp | 9 +++--- .../src/core/graph_iterator_proto.cpp | 30 +++++++++--------- .../src/core/graph_iterator_proto.hpp | 10 ++---- .../onnx/frontend/src/core/sparse_tensor.hpp | 2 +- .../onnx/frontend/src/core/tensor.cpp | 4 +-- .../onnx/frontend/src/core/tensor.hpp | 8 ++--- src/frontends/onnx/frontend/src/editor.cpp | 31 +++++-------------- src/frontends/onnx/frontend/src/editor.hpp | 16 ++++------ src/frontends/onnx/frontend/src/frontend.cpp | 4 +-- .../onnx/frontend/src/input_model.cpp | 21 +++---------- .../onnx/frontend/src/input_model.hpp | 13 ++------ .../onnx/frontend/src/utils/onnx_internal.cpp | 8 ++--- .../onnx/frontend/src/utils/onnx_internal.hpp | 5 +-- .../src/utils/tensor_external_data.cpp | 21 +++++++------ .../src/utils/tensor_external_data.hpp | 11 ++++--- .../include/onnx_common/parser.hpp | 6 ++-- src/frontends/onnx/onnx_common/src/parser.cpp | 22 ++----------- 19 files changed, 87 insertions(+), 144 deletions(-) diff --git a/src/frontends/onnx/frontend/src/core/attribute.hpp b/src/frontends/onnx/frontend/src/core/attribute.hpp index fe69b53b7d15cb..155c8a601a2dc3 100644 --- a/src/frontends/onnx/frontend/src/core/attribute.hpp +++ b/src/frontends/onnx/frontend/src/core/attribute.hpp @@ -6,6 +6,8 @@ #include +#include + #include "core/sparse_tensor.hpp" #include "core/tensor.hpp" #include "openvino/core/except.hpp" @@ -185,7 +187,7 @@ class Attribute { Attribute() = delete; Attribute(const AttributeProto& attribute_proto, - const std::string& model_dir, + const std::filesystem::path& model_dir, detail::MappedMemoryHandles mmap_cache) : m_attribute_proto{&attribute_proto}, m_model_dir{model_dir}, @@ -339,7 +341,7 @@ class Attribute { private: const AttributeProto* m_attribute_proto; - std::string m_model_dir; + std::filesystem::path m_model_dir; detail::MappedMemoryHandles m_mmap_cache; }; diff --git a/src/frontends/onnx/frontend/src/core/graph.cpp b/src/frontends/onnx/frontend/src/core/graph.cpp index 6e19ff9f4315ee..2007ebf59fca95 100644 --- a/src/frontends/onnx/frontend/src/core/graph.cpp +++ b/src/frontends/onnx/frontend/src/core/graph.cpp @@ -90,13 +90,13 @@ ov::frontend::ExtensionHolder subgraph_required_extensions( } } // namespace detail -Graph::Graph(const std::string& model_dir, +Graph::Graph(const std::filesystem::path& model_dir, const std::shared_ptr& model_proto, detail::MappedMemoryHandles mmap_cache, ov::frontend::ExtensionHolder extensions) : Graph(model_dir, model_proto, common::make_unique(), mmap_cache, std::move(extensions)) {} -Graph::Graph(const std::string& model_dir, +Graph::Graph(const std::filesystem::path& model_dir, const std::shared_ptr& model_proto, std::unique_ptr&& cache, detail::MappedMemoryHandles mmap_cache, diff --git a/src/frontends/onnx/frontend/src/core/graph.hpp b/src/frontends/onnx/frontend/src/core/graph.hpp index a564d439f7198f..69b7873d3ecb6c 100644 --- a/src/frontends/onnx/frontend/src/core/graph.hpp +++ b/src/frontends/onnx/frontend/src/core/graph.hpp @@ -6,6 +6,7 @@ #include +#include #include #include #include @@ -23,7 +24,7 @@ namespace frontend { namespace onnx { class Graph : public std::enable_shared_from_this { public: - Graph(const std::string& model_dir, + Graph(const std::filesystem::path& model_dir, const std::shared_ptr& model_proto, detail::MappedMemoryHandles mmap_cache, ov::frontend::ExtensionHolder extensions = {}); @@ -40,7 +41,7 @@ class Graph : public std::enable_shared_from_this { const std::string& get_name() const { return m_model->get_graph().name(); } - const std::string& model_dir() const { + const std::filesystem::path& model_dir() const { return m_model_dir; } detail::MappedMemoryHandles get_mmap_cache() const { @@ -67,7 +68,7 @@ class Graph : public std::enable_shared_from_this { } protected: - Graph(const std::string& model_dir, + Graph(const std::filesystem::path& model_dir, const std::shared_ptr& model, std::unique_ptr&& cache, detail::MappedMemoryHandles mmap_cache, @@ -92,7 +93,7 @@ class Graph : public std::enable_shared_from_this { private: std::vector m_nodes; - std::string m_model_dir; + std::filesystem::path m_model_dir; detail::MappedMemoryHandles m_mmap_cache; OperatorsBridge m_ops_bridge; }; diff --git a/src/frontends/onnx/frontend/src/core/graph_iterator_proto.cpp b/src/frontends/onnx/frontend/src/core/graph_iterator_proto.cpp index 62ab25b48700c0..e70d02a1387151 100644 --- a/src/frontends/onnx/frontend/src/core/graph_iterator_proto.cpp +++ b/src/frontends/onnx/frontend/src/core/graph_iterator_proto.cpp @@ -21,7 +21,6 @@ #include "openvino/frontend/graph_iterator.hpp" #include "openvino/frontend/onnx/graph_iterator.hpp" #include "openvino/util/file_util.hpp" -#include "openvino/util/wstring_convert_util.hpp" #include "transform.hpp" namespace { @@ -264,8 +263,8 @@ bool extract_tensor_external_data(ov::frontend::onnx::TensorMetaInfo& tensor_met m_sha1_digest = entry.value(); } } - const auto full_path = - ov::util::get_absolute_file_path(ov::util::path_join({graph_iterator->get_model_dir(), ext_location})); + const auto full_path = ov::util::get_absolute_file_path( + ov::util::path_join({graph_iterator->get_model_dir(), ov::util::make_path(ext_location)})); const int64_t file_size = ov::util::file_size(full_path); if ((file_size <= 0 && ext_data_length > 0) || ext_data_length > static_cast(file_size) || ext_data_offset > static_cast(file_size) - ext_data_length) { @@ -279,8 +278,6 @@ bool extract_tensor_external_data(ov::frontend::onnx::TensorMetaInfo& tensor_met ? static_cast(ext_data_length) : static_cast(file_size) - static_cast(ext_data_offset); auto memory_mode = graph_iterator->get_memory_management_mode(); - // Remove when cache map will use path instead string. - const auto full_path_str = ov::util::path_to_string(full_path); if (ext_location == "*/_ORT_MEM_ADDR_/*") { // Specific ONNX Runtime Case when it passes a model with self-managed data tensor_meta_info.m_is_raw = true; @@ -290,13 +287,13 @@ bool extract_tensor_external_data(ov::frontend::onnx::TensorMetaInfo& tensor_met return true; } else if (memory_mode == External_MMAP) { auto cache = graph_iterator->get_mmap_cache(); - auto cached_mapped_memory = cache->find(full_path_str); + auto cached_mapped_memory = cache->find(full_path); std::shared_ptr mapped_memory; if (cached_mapped_memory != cache->end()) { mapped_memory = cached_mapped_memory->second; } else { mapped_memory = ov::load_mmap_object(full_path); - (*cache)[full_path_str] = mapped_memory; + (*cache)[full_path] = mapped_memory; } tensor_meta_info.m_is_raw = true; tensor_meta_info.m_tensor_data = @@ -306,7 +303,7 @@ bool extract_tensor_external_data(ov::frontend::onnx::TensorMetaInfo& tensor_met } else if (memory_mode == External_Stream) { auto cache = graph_iterator->get_stream_cache(); FRONT_END_GENERAL_CHECK(cache, "Stream cache is not initialized for external stream mode"); - auto cached_stream = cache->find(full_path_str); + auto cached_stream = cache->find(full_path); std::shared_ptr external_data_stream; if (cached_stream != cache->end()) { external_data_stream = cached_stream->second; @@ -316,7 +313,7 @@ bool extract_tensor_external_data(ov::frontend::onnx::TensorMetaInfo& tensor_met p->close(); delete p; }}; - (*cache)[full_path_str] = external_data_stream; + (*cache)[full_path] = external_data_stream; } if (external_data_stream->fail() || !external_data_stream->good()) { @@ -335,7 +332,7 @@ bool extract_tensor_external_data(ov::frontend::onnx::TensorMetaInfo& tensor_met tensor_meta_info.m_tensor_data_size); return true; } else if (memory_mode == Internal_MMAP || memory_mode == Internal_Stream) { - tensor_meta_info.m_external_location = std::make_shared(full_path_str); + tensor_meta_info.m_external_location = std::make_shared(ov::util::path_to_string(full_path)); tensor_meta_info.m_tensor_data = reinterpret_cast(ext_data_offset); tensor_meta_info.m_tensor_data_size = ext_data_length; return true; @@ -457,10 +454,12 @@ GraphIteratorProto::GraphIteratorProto(const GraphIteratorProtoMemoryManagementM : m_graph(nullptr), m_parent(nullptr), m_mode(mode), - m_mmap_cache{mode == External_MMAP ? std::make_shared>>() - : nullptr}, - m_stream_cache{mode == External_Stream ? std::make_shared>>() - : nullptr}, + m_mmap_cache{mode == External_MMAP + ? std::make_shared>>() + : nullptr}, + m_stream_cache{mode == External_Stream + ? std::make_shared>>() + : nullptr}, m_data_holder{mode == External_Stream ? std::make_shared>>() : nullptr} {} GraphIteratorProto::GraphIteratorProto(GraphIteratorProto* parent, const GraphProto* graph_def) { @@ -476,10 +475,9 @@ GraphIteratorProto::GraphIteratorProto(GraphIteratorProto* parent, const GraphPr void GraphIteratorProto::initialize(const std::filesystem::path& path) { m_model_dir = ov::util::get_directory(path); - const auto path_string = ov::util::path_to_string(path); try { std::ifstream model_file(path, std::ios::binary | std::ios::in); - FRONT_END_GENERAL_CHECK(model_file && model_file.is_open(), "Could not open the file: \"", path_string, "\""); + FRONT_END_GENERAL_CHECK(model_file && model_file.is_open(), "Could not open the file: ", path); m_model = std::make_shared(); FRONT_END_GENERAL_CHECK(m_model->ParseFromIstream(&model_file), "Model can't be parsed"); diff --git a/src/frontends/onnx/frontend/src/core/graph_iterator_proto.hpp b/src/frontends/onnx/frontend/src/core/graph_iterator_proto.hpp index 3cea90ad6300b5..5015a114e29a65 100644 --- a/src/frontends/onnx/frontend/src/core/graph_iterator_proto.hpp +++ b/src/frontends/onnx/frontend/src/core/graph_iterator_proto.hpp @@ -11,7 +11,6 @@ #include "openvino/frontend/onnx/graph_iterator.hpp" #include "openvino/util/file_util.hpp" #include "openvino/util/mmap_object.hpp" -#include "openvino/util/wstring_convert_util.hpp" using ::ONNX_NAMESPACE::AttributeProto_AttributeType; using ::ONNX_NAMESPACE::GraphProto; @@ -29,9 +28,9 @@ namespace frontend { namespace onnx { class DecoderProtoTensor; -using MappedMemoryHandles = std::shared_ptr>>; +using MappedMemoryHandles = std::shared_ptr>>; using LocalMemoryHandles = std::shared_ptr>>; -using LocalStreamHandles = std::shared_ptr>>; +using LocalStreamHandles = std::shared_ptr>>; enum GraphIteratorProtoMemoryManagementMode : int { Undefined = 0, @@ -71,10 +70,7 @@ class GraphIteratorProto : public ov::frontend::onnx::GraphIterator { /// Verifies file is supported template static bool is_supported(const std::basic_string& path) { - FRONT_END_GENERAL_CHECK(ov::util::file_exists(path), - "Could not open the file: \"", - ov::util::path_to_string(path), - '"'); + FRONT_END_GENERAL_CHECK(ov::util::file_exists(path), "Could not open the file: ", path); try { std::streamsize file_size = ov::util::file_size(path); // Skip files which less than size of file identifier diff --git a/src/frontends/onnx/frontend/src/core/sparse_tensor.hpp b/src/frontends/onnx/frontend/src/core/sparse_tensor.hpp index a50d4e672395be..b24b556144b4b5 100644 --- a/src/frontends/onnx/frontend/src/core/sparse_tensor.hpp +++ b/src/frontends/onnx/frontend/src/core/sparse_tensor.hpp @@ -21,7 +21,7 @@ class SparseTensor { public: SparseTensor() = delete; SparseTensor(const SparseTensorProto& sparse_tensor, - const std::string& model_dir, + const std::filesystem::path& model_dir, detail::MappedMemoryHandles mmap_cache) : m_values{sparse_tensor.values(), model_dir, mmap_cache}, m_indices{sparse_tensor.indices(), model_dir, mmap_cache}, diff --git a/src/frontends/onnx/frontend/src/core/tensor.cpp b/src/frontends/onnx/frontend/src/core/tensor.cpp index 2d0b36ce755cf1..9e98277a5a2206 100644 --- a/src/frontends/onnx/frontend/src/core/tensor.cpp +++ b/src/frontends/onnx/frontend/src/core/tensor.cpp @@ -455,9 +455,9 @@ std::shared_ptr Tensor::get_ov_constant() const { if (ext_data.data_location() == detail::ORT_MEM_ADDR) { constant_buffer = ext_data.load_external_mem_data(); } else if (m_mmap_cache) { - constant_buffer = ext_data.load_external_mmap_data(m_model_dir.string(), m_mmap_cache); + constant_buffer = ext_data.load_external_mmap_data(m_model_dir, m_mmap_cache); } else { - constant_buffer = ext_data.load_external_data(m_model_dir.string()); + constant_buffer = ext_data.load_external_data(m_model_dir); } if (element_count == 0 && constant_buffer) { element_count = constant_buffer->size() * 8 / ov_type.bitwidth(); diff --git a/src/frontends/onnx/frontend/src/core/tensor.hpp b/src/frontends/onnx/frontend/src/core/tensor.hpp index 1a13c18462b65d..6e811373d29f52 100644 --- a/src/frontends/onnx/frontend/src/core/tensor.hpp +++ b/src/frontends/onnx/frontend/src/core/tensor.hpp @@ -75,8 +75,8 @@ inline std::vector __get_raw_data(const std::string& raw_data, int onnx_data_ } // namespace } // namespace detail -using MappedMemoryHandles = std::shared_ptr>>; -using LocalStreamHandles = std::shared_ptr>>; +using MappedMemoryHandles = std::shared_ptr>>; +using LocalStreamHandles = std::shared_ptr>>; class TensorONNXPlace : public ov::frontend::onnx::TensorPlace { public: @@ -317,9 +317,9 @@ class Tensor { if (ext_data.data_location() == detail::ORT_MEM_ADDR) { buffer = ext_data.load_external_mem_data(); } else if (m_mmap_cache) { - buffer = ext_data.load_external_mmap_data(m_model_dir.string(), m_mmap_cache); + buffer = ext_data.load_external_mmap_data(m_model_dir, m_mmap_cache); } else { - buffer = ext_data.load_external_data(m_model_dir.string()); + buffer = ext_data.load_external_data(m_model_dir); } return std::vector(buffer->get_ptr(), buffer->get_ptr() + (buffer->size() / sizeof(T))); } diff --git a/src/frontends/onnx/frontend/src/editor.cpp b/src/frontends/onnx/frontend/src/editor.cpp index 627b4c64575bf9..ef53ed9f9333e1 100644 --- a/src/frontends/onnx/frontend/src/editor.cpp +++ b/src/frontends/onnx/frontend/src/editor.cpp @@ -313,45 +313,28 @@ struct ONNXModelEditor::Impl { graph_topological_sort(m_model_proto->mutable_graph()); } - Impl(const std::string& model_path) : Impl(std::make_shared(parse_from_file(model_path))) {} + Impl(const std::filesystem::path& model_path) : Impl(std::make_shared(parse_from_file(model_path))) {} Impl(std::istream& model_stream) : Impl(std::make_shared(parse_from_istream(model_stream))) {} - -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) - Impl(const std::wstring& model_path) : Impl(std::make_shared(parse_from_file(model_path))) {} -#endif }; -ONNXModelEditor::ONNXModelEditor(const std::string& model_path, +ONNXModelEditor::ONNXModelEditor(const std::filesystem::path& model_path, const bool enable_mmap, frontend::ExtensionHolder extensions) : m_model_path{model_path}, - m_mmap_cache{enable_mmap ? std::make_shared>>() + m_mmap_cache{enable_mmap ? std::make_shared>>() : nullptr}, m_extensions{std::move(extensions)}, m_pimpl{new ONNXModelEditor::Impl{model_path}, [](Impl* impl) { delete impl; }} {} -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -ONNXModelEditor::ONNXModelEditor(const std::wstring& model_path, - const bool enable_mmap, - frontend::ExtensionHolder extensions) - : m_extensions{std::move(extensions)}, - m_model_path{ov::util::wstring_to_string(model_path)}, - m_mmap_cache{enable_mmap ? std::make_shared>>() - : nullptr}, - m_pimpl{new ONNXModelEditor::Impl{model_path}, [](Impl* impl) { - delete impl; - }} {} -#endif - ONNXModelEditor::ONNXModelEditor(std::istream& model_stream, - const std::string& model_path, + const std::filesystem::path& model_path, const bool enable_mmap, frontend::ExtensionHolder extensions) : m_model_path{model_path}, - m_mmap_cache{enable_mmap ? std::make_shared>>() + m_mmap_cache{enable_mmap ? std::make_shared>>() : nullptr}, m_extensions{std::move(extensions)}, m_pimpl{new ONNXModelEditor::Impl{model_stream}, [](Impl* impl) { @@ -366,11 +349,11 @@ ONNXModelEditor::ONNXModelEditor(std::shared_ptr model_proto, fronte delete impl; }} {} -const std::string& ONNXModelEditor::model_path() const { +const std::filesystem::path& ONNXModelEditor::model_path() const { return m_model_path; } -void ONNXModelEditor::serialize(const std::string& out_file_path) const { +void ONNXModelEditor::serialize(const std::filesystem::path& out_file_path) const { std::ofstream out_file{out_file_path, std::ios::out | std::ios::binary}; OPENVINO_ASSERT(out_file.is_open(), "Could not open the file: ", out_file_path); diff --git a/src/frontends/onnx/frontend/src/editor.hpp b/src/frontends/onnx/frontend/src/editor.hpp index eccc2cb6cb4dc8..d17dc0bb8d7eca 100644 --- a/src/frontends/onnx/frontend/src/editor.hpp +++ b/src/frontends/onnx/frontend/src/editor.hpp @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -34,14 +35,9 @@ class ONNXModelEditor final { /// \param model_path Path to the file containing the model. /// \param enable_mmap Enable mapping files with external weights instead of reading. /// \param extensions Holder for custom extensions (like custom ops). - explicit ONNXModelEditor(const std::string& model_path, + explicit ONNXModelEditor(const std::filesystem::path& model_path, const bool enable_mmap = false, frontend::ExtensionHolder extensions = {}); -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) - ONNXModelEditor(const std::wstring& model_path, - const bool enable_mmap = false, - frontend::ExtensionHolder extensions = {}); -#endif /// \brief Creates an editor from a model stream. The stream is parsed and loaded /// into the m_model_proto member variable. @@ -52,7 +48,7 @@ class ONNXModelEditor final { /// \param enable_mmap Enable mapping files with external weights instead of reading. /// \param extensions Holder for custom extensions (like custom ops). explicit ONNXModelEditor(std::istream& model_stream, - const std::string& path = {}, + const std::filesystem::path& path = {}, const bool enable_mmap = false, frontend::ExtensionHolder extensions = {}); @@ -195,13 +191,13 @@ class ONNXModelEditor final { bool is_output(const OutputEdge& edge) const; /// \brief Returns the path to the original model file - const std::string& model_path() const; + const std::filesystem::path& model_path() const; /// \brief Saves the possibly modified model held by this class to a file. /// Serializes in binary mode. /// /// \param out_file_path A path to the file where the modified model should be dumped. - void serialize(const std::string& out_file_path) const; + void serialize(const std::filesystem::path& out_file_path) const; /// \brief Returns the InputEdge based on a node (node name or output name) /// and an input (input name or input index). @@ -313,7 +309,7 @@ class ONNXModelEditor final { private: void update_mapper_if_needed() const; - const std::string m_model_path; + const std::filesystem::path m_model_path; ov::frontend::onnx::detail::MappedMemoryHandles m_mmap_cache; frontend::ExtensionHolder m_extensions; diff --git a/src/frontends/onnx/frontend/src/frontend.cpp b/src/frontends/onnx/frontend/src/frontend.cpp index afec5715d0937c..0b3602661cd080 100644 --- a/src/frontends/onnx/frontend/src/frontend.cpp +++ b/src/frontends/onnx/frontend/src/frontend.cpp @@ -186,7 +186,7 @@ ov::frontend::InputModel::Ptr FrontEnd::load_impl(const std::vector& va if (const auto path = get_path_from_any(variants[0])) { if (!gi_enabled) { - return std::make_shared(path.value().native(), enable_mmap, m_extensions); + return std::make_shared(path.value(), enable_mmap, m_extensions); } return create_iterator_model(path.value()); } @@ -194,7 +194,7 @@ ov::frontend::InputModel::Ptr FrontEnd::load_impl(const std::vector& va const auto stream = variants[0].as(); if (variants.size() > 1) if (const auto path = get_path_from_any(variants[1])) { - return std::make_shared(*stream, path.value().native(), enable_mmap, m_extensions); + return std::make_shared(*stream, path.value(), enable_mmap, m_extensions); } return std::make_shared(*stream, enable_mmap, m_extensions); } diff --git a/src/frontends/onnx/frontend/src/input_model.cpp b/src/frontends/onnx/frontend/src/input_model.cpp index 21f9a4c6059143..1e29ab34dc7800 100644 --- a/src/frontends/onnx/frontend/src/input_model.cpp +++ b/src/frontends/onnx/frontend/src/input_model.cpp @@ -16,31 +16,18 @@ using namespace ov; using namespace ov::frontend::onnx; -InputModel::InputModel(const std::string& path, const bool enable_mmap, frontend::ExtensionHolder extensions) +InputModel::InputModel(const std::filesystem::path& path, const bool enable_mmap, frontend::ExtensionHolder extensions) : m_editor{std::make_shared(path, enable_mmap, std::move(extensions))} {} -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -InputModel::InputModel(const std::wstring& path, const bool enable_mmap, frontend::ExtensionHolder extensions) - : m_editor{std::make_shared(path, enable_mmap, std::move(extensions))} {} -#endif - InputModel::InputModel(std::istream& model_stream, const bool enable_mmap, frontend::ExtensionHolder extensions) : m_editor{std::make_shared(model_stream, "", enable_mmap, std::move(extensions))} {} InputModel::InputModel(std::istream& model_stream, - const std::string& path, + const std::filesystem::path& path, const bool enable_mmap, frontend::ExtensionHolder extensions) : m_editor{std::make_shared(model_stream, path, enable_mmap, std::move(extensions))} {} -#ifdef OPENVINO_ENABLE_UNICODE_PATH_SUPPORT -InputModel::InputModel(std::istream& model_stream, - const std::wstring& path, - const bool enable_mmap, - frontend::ExtensionHolder extensions) - : InputModel(model_stream, ov::util::wstring_to_string(path), enable_mmap, std::move(extensions)) {} -#endif - InputModel::InputModel(std::shared_ptr model_proto, frontend::ExtensionHolder extensions) : m_editor{std::make_shared(model_proto, std::move(extensions))} {} @@ -865,11 +852,11 @@ InputModel::InputModelONNXImpl::InputModelONNXImpl(const GraphIterator::Ptr& gra m_model_dir = graph_iterator->get_model_dir(); } if (m_enable_mmap) { - m_mmap_cache = std::make_shared>>(); + m_mmap_cache = std::make_shared>>(); m_stream_cache = nullptr; } else { m_mmap_cache = nullptr; - m_stream_cache = std::make_shared>>(); + m_stream_cache = std::make_shared>>(); } load_model(); } diff --git a/src/frontends/onnx/frontend/src/input_model.hpp b/src/frontends/onnx/frontend/src/input_model.hpp index f62f726794751d..6d40aabb304c0c 100644 --- a/src/frontends/onnx/frontend/src/input_model.hpp +++ b/src/frontends/onnx/frontend/src/input_model.hpp @@ -23,23 +23,14 @@ namespace onnx { class InputModel : public ov::frontend::InputModel { public: - InputModel(const std::string& path, const bool enable_mmap = false, ExtensionHolder extensions = {}); -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) - InputModel(const std::wstring& path, const bool enable_mmap = false, ExtensionHolder extensions = {}); -#endif + InputModel(const std::filesystem::path& path, const bool enable_mmap = false, ExtensionHolder extensions = {}); InputModel(std::istream& model_stream, const bool enable_mmap = false, ExtensionHolder extensions = {}); // The path can be required even if the model is passed as a stream because it is necessary // for ONNX external data feature InputModel(std::istream& model_stream, - const std::string& path, + const std::filesystem::path& path, const bool enable_mmap = false, ExtensionHolder extensions = {}); -#ifdef OPENVINO_ENABLE_UNICODE_PATH_SUPPORT - InputModel(std::istream& model_stream, - const std::wstring& path, - const bool enable_mmap = false, - ExtensionHolder extensions = {}); -#endif InputModel(std::shared_ptr model_proto, ExtensionHolder extensions = {}); std::vector get_inputs() const override; diff --git a/src/frontends/onnx/frontend/src/utils/onnx_internal.cpp b/src/frontends/onnx/frontend/src/utils/onnx_internal.cpp index c2f9f975f6b2fe..8de419110d28c7 100644 --- a/src/frontends/onnx/frontend/src/utils/onnx_internal.cpp +++ b/src/frontends/onnx/frontend/src/utils/onnx_internal.cpp @@ -88,21 +88,21 @@ void convert_decoded_model(std::shared_ptr model) { } std::shared_ptr import_onnx_model(std::shared_ptr model_proto, - const std::string& model_path, + const std::filesystem::path& model_path, detail::MappedMemoryHandles mmap_cache, ov::frontend::ExtensionHolder extensions) { apply_transformations(*model_proto); - Graph graph{ov::util::get_directory(model_path).string(), model_proto, mmap_cache, std::move(extensions)}; + Graph graph{ov::util::get_directory(model_path), model_proto, mmap_cache, std::move(extensions)}; return graph.convert(); } std::shared_ptr decode_to_framework_nodes(std::shared_ptr model_proto, - const std::string& model_path, + const std::filesystem::path& model_path, detail::MappedMemoryHandles mmap_cache, ov::frontend::ExtensionHolder extensions) { apply_transformations(*model_proto); auto graph = - std::make_shared(ov::util::get_directory(model_path).string(), model_proto, mmap_cache, extensions); + std::make_shared(ov::util::get_directory(model_path), model_proto, mmap_cache, std::move(extensions)); return graph->decode(); } } // namespace ov::frontend::onnx::detail diff --git a/src/frontends/onnx/frontend/src/utils/onnx_internal.hpp b/src/frontends/onnx/frontend/src/utils/onnx_internal.hpp index 88add1d3422ac9..b9eddbe7b0ab6a 100644 --- a/src/frontends/onnx/frontend/src/utils/onnx_internal.hpp +++ b/src/frontends/onnx/frontend/src/utils/onnx_internal.hpp @@ -4,6 +4,7 @@ #pragma once +#include #include #include @@ -38,7 +39,7 @@ using ::ONNX_NAMESPACE::ModelProto; /// \return An ov::Model that represents a single output from the created /// graph. std::shared_ptr import_onnx_model(std::shared_ptr model_proto, - const std::string& model_path, + const std::filesystem::path& model_path, detail::MappedMemoryHandles mmap_cache, ov::frontend::ExtensionHolder extensions = {}); @@ -51,7 +52,7 @@ std::shared_ptr import_onnx_model(std::shared_ptr model_p /// \param extensions An object containing a collection of frontend extensions to use during the import process /// \return A ov::Model with ONNXFrameworkNodes std::shared_ptr decode_to_framework_nodes(std::shared_ptr model_proto, - const std::string& model_path, + const std::filesystem::path& model_path, detail::MappedMemoryHandles mmap_cache, ov::frontend::ExtensionHolder extensions = {}); diff --git a/src/frontends/onnx/frontend/src/utils/tensor_external_data.cpp b/src/frontends/onnx/frontend/src/utils/tensor_external_data.cpp index f715d9f4558aae..78bb94d97a4a70 100644 --- a/src/frontends/onnx/frontend/src/utils/tensor_external_data.cpp +++ b/src/frontends/onnx/frontend/src/utils/tensor_external_data.cpp @@ -39,22 +39,24 @@ TensorExternalData::TensorExternalData(const std::string& location, size_t offse m_data_length = size; } -Buffer TensorExternalData::load_external_mmap_data(const std::string& model_dir, +Buffer TensorExternalData::load_external_mmap_data(const std::filesystem::path& model_dir, MappedMemoryHandles cache) const { - const auto full_path = model_dir.empty() ? ov::util::make_path(m_data_location) - : ov::util::make_path(ov::util::path_join({model_dir, m_data_location})); + const auto full_path = + model_dir.empty() + ? ov::util::make_path(m_data_location) + : ov::util::get_absolute_file_path(ov::util::path_join({model_dir, ov::util::make_path(m_data_location)})); const int64_t file_size = ov::util::file_size(full_path); if (file_size <= 0 || m_data_length > static_cast(file_size) || m_offset > static_cast(file_size) - m_data_length) { throw error::invalid_external_data{*this}; } - auto cached_mapped_memory = cache->find(ov::util::path_to_string(full_path)); + auto cached_mapped_memory = cache->find(full_path); std::shared_ptr mapped_memory; if (cached_mapped_memory != cache->end()) { mapped_memory = cached_mapped_memory->second; } else { mapped_memory = ov::load_mmap_object(full_path); - (*cache)[ov::util::path_to_string(full_path)] = mapped_memory; + (*cache)[full_path] = mapped_memory; } if (m_data_length > mapped_memory->size() || mapped_memory->size() == 0) { throw error::invalid_external_data{*this}; @@ -65,10 +67,11 @@ Buffer TensorExternalData::load_external_mmap_data(const std:: mapped_memory); } -Buffer TensorExternalData::load_external_data(const std::string& model_dir) const { - const auto full_path = model_dir.empty() ? ov::util::make_path(m_data_location) - : std::filesystem::absolute(std::filesystem::weakly_canonical( - ov::util::path_join({model_dir, m_data_location}))); +Buffer TensorExternalData::load_external_data(const std::filesystem::path& model_dir) const { + const auto full_path = + model_dir.empty() + ? ov::util::make_path(m_data_location) + : ov::util::get_absolute_file_path(ov::util::path_join({model_dir, ov::util::make_path(m_data_location)})); std::ifstream external_data_stream(full_path, std::ios::binary | std::ios::in | std::ios::ate); if (external_data_stream.fail()) { diff --git a/src/frontends/onnx/frontend/src/utils/tensor_external_data.hpp b/src/frontends/onnx/frontend/src/utils/tensor_external_data.hpp index b1682f6fd42653..f02395037137ba 100644 --- a/src/frontends/onnx/frontend/src/utils/tensor_external_data.hpp +++ b/src/frontends/onnx/frontend/src/utils/tensor_external_data.hpp @@ -6,6 +6,8 @@ #include +#include + #include "openvino/runtime/aligned_buffer.hpp" #include "openvino/runtime/shared_buffer.hpp" #include "openvino/util/mmap_object.hpp" @@ -17,8 +19,8 @@ namespace detail { using ::ONNX_NAMESPACE::TensorProto; template using Buffer = std::shared_ptr>>; -using MappedMemoryHandles = std::shared_ptr>>; -using LocalStreamHandles = std::shared_ptr>>; +using MappedMemoryHandles = std::shared_ptr>>; +using LocalStreamHandles = std::shared_ptr>>; /// \brief Helper class used to load tensor data from external files class TensorExternalData { public: @@ -32,7 +34,7 @@ class TensorExternalData { /// the invalid_external_data exception is thrown. /// /// \return External binary data loaded into the SharedBuffer - Buffer load_external_data(const std::string& model_dir) const; + Buffer load_external_data(const std::filesystem::path& model_dir) const; /// \brief Map (mmap for lin, MapViewOfFile for win) external data from tensor passed to constructor /// @@ -41,7 +43,8 @@ class TensorExternalData { /// the invalid_external_data exception is thrown. /// /// \return External binary data loaded into the SharedBuffer - Buffer load_external_mmap_data(const std::string& model_dir, MappedMemoryHandles cache) const; + Buffer load_external_mmap_data(const std::filesystem::path& model_dir, + MappedMemoryHandles cache) const; /// \brief Load external data from existing shared memory when m_data_location is ORT_MEM_ADDR /// diff --git a/src/frontends/onnx/onnx_common/include/onnx_common/parser.hpp b/src/frontends/onnx/onnx_common/include/onnx_common/parser.hpp index ac142208621530..25e1c6fce7e9b0 100644 --- a/src/frontends/onnx/onnx_common/include/onnx_common/parser.hpp +++ b/src/frontends/onnx/onnx_common/include/onnx_common/parser.hpp @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include @@ -18,10 +19,7 @@ using namespace ::ONNX_NAMESPACE; /// \param file_path Path to the file containing an ONNX model. /// /// \return The parsed in-memory representation of the ONNX model -ModelProto parse_from_file(const std::string& file_path); -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -ModelProto parse_from_file(const std::wstring& file_path); -#endif +ModelProto parse_from_file(const std::filesystem::path& file_path); /// \brief Parses an ONNX model from a stream (representing for example a file) /// diff --git a/src/frontends/onnx/onnx_common/src/parser.cpp b/src/frontends/onnx/onnx_common/src/parser.cpp index b7a83a0b99ffbf..0d665b1670c8c5 100644 --- a/src/frontends/onnx/onnx_common/src/parser.cpp +++ b/src/frontends/onnx/onnx_common/src/parser.cpp @@ -17,32 +17,16 @@ namespace ov { namespace frontend { namespace onnx { namespace common { -ModelProto parse_from_file(const std::string& file_path) { - std::ifstream file_stream{file_path.c_str(), std::ios::in | std::ios::binary}; +ModelProto parse_from_file(const std::filesystem::path& file_path) { + std::ifstream file_stream{file_path, std::ios::binary}; - if (!file_stream.is_open()) { - OPENVINO_THROW("Could not open the file: \"" + file_path, '"'); - }; + OPENVINO_ASSERT(file_stream.is_open(), "Could not open the file: ", file_path); auto model_proto = parse_from_istream(file_stream); file_stream.close(); return model_proto; } -#if defined(OPENVINO_ENABLE_UNICODE_PATH_SUPPORT) && defined(_WIN32) -ModelProto parse_from_file(const std::wstring& file_path) { - std::ifstream file_stream{file_path.c_str(), std::ios::in | std::ios::binary}; - - if (!file_stream.is_open()) { - OPENVINO_THROW("Could not open the file: \"", ov::util::wstring_to_string(file_path), '"'); - }; - - auto model_proto = parse_from_istream(file_stream); - file_stream.close(); - return model_proto; -} -#endif - ModelProto parse_from_istream(std::istream& model_stream) { if (!model_stream.good()) { model_stream.clear(); From b344668c5e7a5d36c31df3c40d4b18698fca0dca Mon Sep 17 00:00:00 2001 From: Evgeny Kotov Date: Wed, 22 Apr 2026 11:00:47 +0200 Subject: [PATCH 022/545] Introduce new check for CompressFloatConstantsImpl (#35415) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Details: This PR builds on top of #35106 and addresses the open review comments from @nshchego that were left unanswered there. ### Carried from #35106 Previously, `CompressFloatConstantsImpl` relied on checking whether f64/f32 Constant values fit into the finite f16 range. If 75% of the values did not fit, the Constant's conversion was skipped. However, even when f64/f32 values are inside the f16 range, they might not always be converted with high precision, as f16 has a narrower mantissa. The larger the values grow, the bigger the round-trip error becomes. For values > 1024 the absolute round-trip error can reach 1.0 or more and may cause significant accuracy degradation — this was observed for the LTX-Video model where the RoPE frequency values were compressed to f16 with substantial degradation. To prevent this, both absolute and relative error are now accounted for: 1. If a large absolute error (`> f16_compression_max_abs_error`, currently `1.0`) is observed for any in-range value of the Constant, the Constant is kept in its original precision (abs-error veto, `has_lossy=true`). 2. Values with a large relative round-trip error (`> f16_compression_max_rel_error`, currently `1e-4`) are accumulated into the same rejection count as true out-of-range values, and fed into the existing `f16_compression_keep_threshold` (75%) rule. The logic works uniformly for scalar and non-scalar Constants. This preserves precision-sensitive Constants (RoPE tables, attention scale factors like `log(16)`, …) while keeping typical dense weights compressed, and does not visibly affect compilation time or IR `.bin` size. On wwb the similarity on the generated IR reaches 0.98. ### Additional changes on top of #35106 1. In the slow (non-JIT) `change_constant_precision_to_fp16`, the `src_data[i]` value is cast to `double` before subtracting the f16 round-trip, so the absolute and relative error are measured in `double` precision (the original code lost f32 precision on the f32 source path). The subnormal branch now also writes `static_cast(src_data[i])` into `dst_data[i]` instead of relying on the Constant's zero-initialisation — this matches the x86 fast path, which produces an FP16 subnormal there. 2. A new `jit_check_f16_compression_avx512` JIT kernel is added. It processes 16 floats per iteration using `zmm` registers and opmasks, with an F16C `vcvtps2ph`/`vcvtph2ps` round-trip and a masked load for the tail. The selection cascade in `check_f16_compression()` becomes `avx512_core + fp16` → `avx2 + fp16` → C++ fallback. 3. The `vcvtps2ph` immediate is changed from `0` (use MXCSR) to `0x08` (force RNE and suppress all FP exceptions — equivalent to `_MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC`) in both the AVX2 and AVX512 paths, and applied consistently across every `vcvtps2ph` call site in this file. This makes the JIT result independent of the caller's MXCSR state and bit-identical to `static_cast` used in the C++ fallback. Note: `0x04` is `_MM_FROUND_CUR_DIRECTION` (use MXCSR), not RNE — a common point of confusion flagged during review. 4. The AVX-512 kernel is written without `POPCNT` or `BMI2` (`bzhi`) instructions — neither is guaranteed by `mayiuse(avx512_core)` / `mayiuse(fp16)`. Lane counts use a branchless 16-bit SWAR popcount; the tail mask uses a baseline `SHL cl` / `dec`. 5. The back-compat symbol `count_out_of_f16_range()` is kept in `openvino/reference/convert.hpp` as a thin C++ wrapper — external developer-package consumers that linked against the pre-PR symbol stay source- and link-compatible. The in-tree compression path uses `check_f16_compression()`. 6. JIT register and vector aliases are declared as `const auto&` instead of `auto`, so the global Xbyak `Reg64`/`Ymm`/`Zmm` instances are taken by reference instead of copied. 7. Shared FP16 range / threshold constants (`kF16MaxPos`, `kF16MaxNeg`, `kF16MinPos`, `kF16MinNeg`, `kF16CompressionAbsErrVal`, `kF16CompressionRelErrVal`, `kAbsMaskVal`, `kVcvtps2phRneNoExc`) are lifted to the translation-unit anonymous namespace as `inline const` / `inline constexpr`, deduplicating the per-kernel copies. The shared `is_out_of_f16_range` predicate is used from both the `check_f16_compression` slow path and the `count_out_of_f16_range` back-compat shim. 8. `layer_tests/ovc_python_api_tests/test_pytorch.py::create_pytorch_module_convert_pytorch_frontend_oob` no longer uses unseeded `torch.rand` for the weight tensor. Random draws ~half the time produce uniformly-distributed values whose relative round-trip error exceeds the threshold for ≥75% of elements, which — correctly, under the new combined check — rejects compression and diverges from the reference model. Weights are now `torch.full([1, 3, 3, 3], 0.5)` (0.5 is exactly representable in FP16), so the outcome is deterministic regardless of RNG state. ### Tickets: - 180611 --- .../compress_float_constants.cpp | 104 +-- .../compress_float_constants_test.cpp | 79 +- .../include/openvino/reference/convert.hpp | 46 +- src/core/reference/src/op/convert.cpp | 701 ++++++++++++++---- .../ovc_python_api_tests/test_pytorch.py | 6 +- 5 files changed, 744 insertions(+), 192 deletions(-) diff --git a/src/common/transformations/src/transformations/common_optimizations/compress_float_constants.cpp b/src/common/transformations/src/transformations/common_optimizations/compress_float_constants.cpp index b27f20a9105bf5..716d9e3b855a6f 100644 --- a/src/common/transformations/src/transformations/common_optimizations/compress_float_constants.cpp +++ b/src/common/transformations/src/transformations/common_optimizations/compress_float_constants.cpp @@ -32,6 +32,25 @@ namespace ov::pass { namespace { +/// Slow (non-JIT) FP32/FP64 -> FP16 compression for a single Constant node. +/// +/// Returns either: +/// * the new `v0::Constant` with FP16 data (fully converted and approved), or +/// * `constant` unchanged when `postponed == true` (the Constant is kept in the +/// graph and compressed later during serialization), or +/// * `nullptr` if the tensor must stay in its original precision. +/// +/// The tensor is rejected (returns `nullptr`) when: +/// * any in-range element has absolute round-trip error above +/// `f16_compression_max_abs_error` (RoPE-like high-magnitude values), or +/// * the combined count of elements outside the finite FP16 range *plus* +/// elements whose relative round-trip error exceeds +/// `f16_compression_max_rel_error` reaches +/// `f16_compression_keep_threshold` (75% by default). +/// +/// Used by `CompressFloatConstantsImpl` for f64 constants on all platforms and +/// for both f32/f64 on non-x86 architectures where the JIT fast-path +/// `ov::reference::check_f16_compression` is unavailable. template std::shared_ptr change_constant_precision_to_fp16(std::shared_ptr& constant, bool postponed = false) { @@ -45,28 +64,44 @@ std::shared_ptr change_constant_precision_to_fp16(std::shared_ptr(src_data[i]); + num_rejected++; } else if (src_data[i] > std::numeric_limits::max()) { dst_data[i] = std::numeric_limits::max(); - num_out_of_range++; + num_rejected++; } else if (src_data[i] < std::numeric_limits::lowest()) { dst_data[i] = std::numeric_limits::lowest(); - num_out_of_range++; + num_rejected++; } else { - dst_data[i] = static_cast(src_data[i]); + constexpr double max_relative_error = ov::reference::f16_compression_max_rel_error; + constexpr double max_abs_error = ov::reference::f16_compression_max_abs_error; + const auto f16_val = static_cast(src_data[i]); + const double src_val = static_cast(src_data[i]); + const double roundtripped = static_cast(static_cast(f16_val)); + const double abs_diff = std::abs(src_val - roundtripped); + + if (abs_diff > max_abs_error) { + return nullptr; + } + + if (src_val != 0.0 && abs_diff / std::abs(src_val) > max_relative_error) { + num_rejected++; + } + dst_data[i] = f16_val; } } - // if more than 75% of a FP32 constant do not fit into FP16 keep in FP32 - const float keep_threshold = 0.75f; - const float out_of_range_proportion = static_cast(num_out_of_range) / static_cast(size); + // If the combined count of rejected elements (values outside the finite FP16 + // range PLUS in-range values whose FP16 round-trip relative error exceeds + // f16_compression_max_rel_error) reaches f16_compression_keep_threshold + // (inclusive, 75% by default), keep the Constant in its original precision. + const double rejected_proportion = static_cast(num_rejected) / static_cast(size); - if (out_of_range_proportion >= keep_threshold) { + if (rejected_proportion >= ov::reference::f16_compression_keep_threshold) { return nullptr; } @@ -138,27 +173,6 @@ void compress_model_to_f16(const std::shared_ptr& model, bool postponed) } } -namespace { - -// Returns true if a scalar constant of type T loses significant precision when rounded to FP16. -// Used to protect mathematical scale factors (e.g., log(16) in attention bucketing) from FP16 -// rounding errors that cascade through every computation referencing them. -template -bool scalar_has_high_f16_error(const ov::op::v0::Constant& const_node) { - constexpr double max_relative_error = 1e-4; - static_assert(sizeof(T) >= 4); - const T src = *const_node.get_data_ptr(); - if (std::isfinite(src) && src != T{0}) { - const double src_val = static_cast(src); - const ov::float16 f16_val = static_cast(src); - const double roundtripped = static_cast(static_cast(f16_val)); - return std::abs(src_val - roundtripped) / std::abs(src_val) > max_relative_error; - } - return false; -} - -} // namespace - CompressFloatConstantsImpl::CompressFloatConstantsImpl(bool postponed) { MATCHER_SCOPE(CompressFloatConstantsImpl); auto const_pattern = pattern::wrap_type(); @@ -176,16 +190,6 @@ CompressFloatConstantsImpl::CompressFloatConstantsImpl(bool postponed) { auto c_type = const_node->get_element_type(); - // Skip FP16 compression for scalar constants with significant rounding error. - // Scalar constants often serve as mathematical scale factors (e.g., log(16) in attention - // bucketing) where FP16 rounding error cascades through every computation that uses them. - if (ov::shape_size(const_node->get_shape()) == 1) { - if (c_type == ov::element::f32 && scalar_has_high_f16_error(*const_node)) - return false; - if (c_type == ov::element::f64 && scalar_has_high_f16_error(*const_node)) - return false; - } - std::shared_ptr new_const; #if !defined(OPENVINO_ARCH_X86) && !defined(OPENVINO_ARCH_X86_64) @@ -201,13 +205,19 @@ CompressFloatConstantsImpl::CompressFloatConstantsImpl(bool postponed) { auto size = shape_size(const_node->get_output_shape(0)); if (size == 0) return false; - auto num_out_of_range = - ov::reference::count_out_of_f16_range(const_node->get_data_ptr(), size); + const auto* src_data = const_node->get_data_ptr(); + + // Single-pass check: counts out-of-range and bails on any lossy element (JIT accelerated) + auto check = ov::reference::check_f16_compression(src_data, size); + if (check.has_lossy) + return false; - // if more than 75% of a FP32 constant do not fit into FP16 keep in FP32 - const float keep_threshold = 0.75f; - const float out_of_range_proportion = static_cast(num_out_of_range) / static_cast(size); - if (out_of_range_proportion >= keep_threshold) + // If the combined count of rejected elements (values outside the finite FP16 + // range PLUS in-range values whose FP16 round-trip relative error exceeds + // f16_compression_max_rel_error) reaches f16_compression_keep_threshold + // (inclusive, 75% by default), keep the Constant in FP32. + const float rejected_proportion = static_cast(check.rejected_count) / static_cast(size); + if (rejected_proportion >= ov::reference::f16_compression_keep_threshold) return false; if (postponed) { diff --git a/src/common/transformations/tests/common_optimizations/compress_float_constants_test.cpp b/src/common/transformations/tests/common_optimizations/compress_float_constants_test.cpp index addcf8241ec9c6..a7d7050b436975 100644 --- a/src/common/transformations/tests/common_optimizations/compress_float_constants_test.cpp +++ b/src/common/transformations/tests/common_optimizations/compress_float_constants_test.cpp @@ -656,11 +656,10 @@ TEST_F(TransformationTestsF, CompressConstants_compress_scalar_exact_f16) { comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES); } -// Non-scalar constants should be compressed even if individual elements have high FP16 -// rounding error. The scalar error check only applies to numel == 1 constants. -TEST_F(TransformationTestsF, CompressConstants_compress_non_scalar_with_high_error) { +TEST_F(TransformationTestsF, CompressConstants_skip_non_scalar_with_high_error_at_least_75) { // Model: Parameter -> Multiply(input, Const({2.7725887, 2.7725887, 2.7725887, 2.7725887})) -> Result - // Non-scalar (numel=4) with high per-element error -> still compressed. + // Non-scalar (numel=4) with high per-element error in at least 75% of elements -> skipped (not compressed). + // The keep-threshold comparison is inclusive (>= f16_compression_keep_threshold). { auto input = std::make_shared(ov::element::f32, ov::Shape{1, 4}); auto scale = @@ -674,10 +673,76 @@ TEST_F(TransformationTestsF, CompressConstants_compress_non_scalar_with_high_err { auto input = std::make_shared(ov::element::f32, ov::Shape{1, 4}); - // Non-scalar constant compressed to FP16 + Convert (error check is scalar-only) + // Stays FP32: relative round-trip error exceeds threshold for all 4 elements (>75%). auto scale = - v0::Constant::create(ov::element::f16, ov::Shape{4}, {2.7725887f, 2.7725887f, 2.7725887f, 2.7725887f}); - auto convert = std::make_shared(scale, ov::element::f32); + v0::Constant::create(ov::element::f32, ov::Shape{4}, {2.7725887f, 2.7725887f, 2.7725887f, 2.7725887f}); + auto mul = std::make_shared(input, scale); + model_ref = std::make_shared(ov::OutputVector{mul}, ov::ParameterVector{input}); + } + comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES); +} + +// Non-scalar constant with high absolute FP16 error (e.g. RoPE frequency table) +// should NOT be compressed — value 5002.0 has FP16 abs error = 2.0 (ULP=4 in [4096,8192) range). +TEST_F(TransformationTestsF, CompressConstants_skip_non_scalar_with_high_abs_error) { + { + auto input = std::make_shared(ov::element::f32, ov::Shape{1, 4}); + auto freqs = v0::Constant::create(ov::element::f32, ov::Shape{4}, {1.0f, 100.0f, 500.0f, 5002.0f}); + auto mul = std::make_shared(input, freqs); + model = std::make_shared(ov::OutputVector{mul}, ov::ParameterVector{input}); + + manager.register_pass(); + manager.register_pass(); + } + + { + auto input = std::make_shared(ov::element::f32, ov::Shape{1, 4}); + // Stays FP32: 5002.0 rounds to 5000.0 in FP16, abs error = 2.0 > threshold 1.0 + auto freqs = v0::Constant::create(ov::element::f32, ov::Shape{4}, {1.0f, 100.0f, 500.0f, 5002.0f}); + auto mul = std::make_shared(input, freqs); + model_ref = std::make_shared(ov::OutputVector{mul}, ov::ParameterVector{input}); + } + comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES); +} + +// f64 constant with high absolute FP16 error should NOT be compressed (same logic as f32 path). +TEST_F(TransformationTestsF, CompressConstants_skip_f64_non_scalar_with_high_abs_error) { + { + auto input = std::make_shared(ov::element::f64, ov::Shape{1, 4}); + auto freqs = v0::Constant::create(ov::element::f64, ov::Shape{4}, {1.0, 100.0, 500.0, 5002.0}); + auto mul = std::make_shared(input, freqs); + model = std::make_shared(ov::OutputVector{mul}, ov::ParameterVector{input}); + + manager.register_pass(); + manager.register_pass(); + } + + { + auto input = std::make_shared(ov::element::f64, ov::Shape{1, 4}); + // Stays FP64: 5002.0 rounds to 5000.0 in FP16, abs error = 2.0 > threshold 1.0 + auto freqs = v0::Constant::create(ov::element::f64, ov::Shape{4}, {1.0, 100.0, 500.0, 5002.0}); + auto mul = std::make_shared(input, freqs); + model_ref = std::make_shared(ov::OutputVector{mul}, ov::ParameterVector{input}); + } + comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES); +} + +// Non-scalar constant with low absolute FP16 error (normal weights) should be compressed. +TEST_F(TransformationTestsF, CompressConstants_compress_non_scalar_with_low_abs_error) { + { + auto input = std::make_shared(ov::element::f32, ov::Shape{1, 4}); + auto weights = v0::Constant::create(ov::element::f32, ov::Shape{4}, {0.5f, 1.23f, -3.14f, 7.77f}); + auto mul = std::make_shared(input, weights); + model = std::make_shared(ov::OutputVector{mul}, ov::ParameterVector{input}); + + manager.register_pass(); + manager.register_pass(); + } + + { + auto input = std::make_shared(ov::element::f32, ov::Shape{1, 4}); + auto weights = v0::Constant::create(ov::element::f16, ov::Shape{4}, {0.5f, 1.23f, -3.14f, 7.77f}); + auto convert = std::make_shared(weights, ov::element::f32); auto mul = std::make_shared(input, convert); model_ref = std::make_shared(ov::OutputVector{mul}, ov::ParameterVector{input}); } diff --git a/src/core/reference/include/openvino/reference/convert.hpp b/src/core/reference/include/openvino/reference/convert.hpp index 17f00f54012889..dc823b54628321 100644 --- a/src/core/reference/include/openvino/reference/convert.hpp +++ b/src/core/reference/include/openvino/reference/convert.hpp @@ -82,7 +82,51 @@ void convert(const bfloat16* arg, float* out, size_t count); template <> void convert(const int32_t* arg, float16* out, size_t count); -// Count how many f32 values is out of normal finite numbers range when converted to f16 +/// Maximum tolerated |abs(src - round_trip_f16(src))| for an in-range element. +/// If any element exceeds this threshold, the whole tensor is kept in its +/// original precision (FP32 / FP64 — acts as a tensor-wide veto in +/// check_f16_compression()). +inline constexpr double f16_compression_max_abs_error = 1.0; +/// Maximum tolerated relative round-trip error abs_diff / |src| for an +/// in-range element. Elements above this threshold are accumulated into the +/// combined rejection count used together with f16_compression_keep_threshold. +inline constexpr double f16_compression_max_rel_error = 1e-4; +/// Proportion of combined rejections (out-of-FP16-range + high relative-error) +/// at which the Constant is kept in its original precision (FP32 / FP64), i.e. +/// no FP16 compression is applied. +inline constexpr float f16_compression_keep_threshold = 0.75f; + +/// Result of a single-pass FP16-compression feasibility check produced by +/// check_f16_compression(). Used by CompressFloatConstantsImpl to decide +/// whether a floating-point Constant may be safely compressed to FP16. +struct CompressionCheckResult { + /// Combined count of rejected elements: values outside the finite FP16 + /// range PLUS in-range values whose FP16 conversion exceeds + /// f16_compression_max_rel_error. Compared against + /// f16_compression_keep_threshold to keep the Constant in its original + /// precision. For a pure out-of-range count, use count_out_of_f16_range(). + size_t rejected_count; + /// Early-bail flag: true iff any in-range element has |abs error| greater + /// than f16_compression_max_abs_error after the FP16 round-trip. When set, + /// rejected_count is not guaranteed to be complete. + bool has_lossy; +}; + +/// Single-pass combined FP16-compression feasibility check. +/// +/// Bails immediately if any in-range value has significant precision loss +/// (|round-trip error| > f16_compression_max_abs_error), setting +/// CompressionCheckResult::has_lossy. Otherwise accumulates a combined +/// rejection count (out-of-FP16-range + high relative-error) in +/// CompressionCheckResult::rejected_count, to be compared against +/// f16_compression_keep_threshold. JIT/AVX-512 (or AVX2+F16C) accelerated on +/// x86; falls back to a scalar loop elsewhere. +CompressionCheckResult check_f16_compression(const float* arg, size_t count); + +/// Counts elements in `arg` that fall outside the finite FP16 range (subnormal +/// when rounded, or larger in magnitude than float16::max()). Distinct from +/// check_f16_compression(), which also accounts for relative-error rejections. +/// Kept as a backward-compatible helper for external consumers of this header. size_t count_out_of_f16_range(const float* arg, size_t count); // Convert values from f32 to f16 with clamping to f16 min/max when value is out of normal finite numbers range diff --git a/src/core/reference/src/op/convert.cpp b/src/core/reference/src/op/convert.cpp index 27f56a4b71bb4b..7a87da29a6901e 100644 --- a/src/core/reference/src/op/convert.cpp +++ b/src/core/reference/src/op/convert.cpp @@ -20,6 +20,27 @@ namespace reference { namespace { #ifdef OV_CORE_USE_XBYAK_JIT +// vcvtps2ph immediate: force round-to-nearest-even and suppress all FP +// exceptions, so the rounding is independent of caller MXCSR state and +// bit-identical to static_cast(float). Equivalent to +// _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC from . +// (Not 0x04 — that is _MM_FROUND_CUR_DIRECTION, i.e. "use MXCSR".) +inline constexpr uint8_t kVcvtps2phRneNoExc = 0x08; + +// Shared FP16 range constants used by multiple JIT kernels (fp32<->fp16 conversion, +// fp16 compression check). ov::float16::operator float() is not constexpr so the +// ov::float16-derived values must be initialised at runtime; the scalar-error / +// mask thresholds are plain literals and stay constexpr. We keep them as named +// file-scope constants to avoid duplicating std::numeric_limits<> lookups and +// literal values across every JIT body. +inline const float kF16MaxPos = std::numeric_limits::max(); +inline const float kF16MaxNeg = std::numeric_limits::lowest(); +inline const float kF16MinPos = ov::float16::from_bits(0x0001); +inline const float kF16MinNeg = -ov::float16::from_bits(0x0001); +inline constexpr float kF16CompressionAbsErrVal = static_cast(ov::reference::f16_compression_max_abs_error); +inline constexpr float kF16CompressionRelErrVal = static_cast(ov::reference::f16_compression_max_rel_error); +inline constexpr uint32_t kAbsMaskVal = 0x7FFFFFFFu; + template void jit_convert_vec(jit::Generator&, const Xbyak::RegExp&, const Xbyak::RegExp&) {} @@ -36,7 +57,7 @@ void jit_convert_vec(jit::Generator& gen, const Xbyak::RegExp& gen.movq(u8vec, gen.qword[src]); gen.vpmovzxbd(i32vec, u8vec); gen.vcvtdq2ps(fvec, i32vec); - gen.vcvtps2ph(f16vec, fvec, 0); + gen.vcvtps2ph(f16vec, fvec, kVcvtps2phRneNoExc); gen.vzeroupper(); gen.vmovdqu(gen.xword[dst], f16vec); } @@ -57,7 +78,7 @@ void jit_convert_vec(jit::Generator& gen, const Xbyak::RegExp& s auto f32vec = gen.ymm4; gen.vmovups(f32vec, gen.yword[src]); - gen.vcvtps2ph(f16vec, f32vec, 0); + gen.vcvtps2ph(f16vec, f32vec, kVcvtps2phRneNoExc); gen.vmovdqu(gen.xword[dst], f16vec); } @@ -66,10 +87,10 @@ void jit_convert_vec(jit::Generator& gen, const Xbyak::RegExp const auto f32vec = gen.ymm4; const auto f16vec = gen.xmm3; - gen.vpmovzxwd(f32vec, gen.yword[src]); // load bf16 into tmp - gen.vpslld(f32vec, f32vec, 16); // convert bf16->f32 by bit shift - gen.vcvtps2ph(f16vec, f32vec, 0); // convert f32 -> f16 - gen.vmovdqu(gen.xword[dst], f16vec); // move result to destination + gen.vpmovzxwd(f32vec, gen.yword[src]); // load bf16 into tmp + gen.vpslld(f32vec, f32vec, 16); // convert bf16->f32 by bit shift + gen.vcvtps2ph(f16vec, f32vec, kVcvtps2phRneNoExc); // convert f32 -> f16 + gen.vmovdqu(gen.xword[dst], f16vec); // move result to destination } template <> @@ -80,12 +101,12 @@ void jit_convert_vec(jit::Generator& gen, const Xbyak:: auto upper_bound = gen.ymm5; auto lower_bound = gen.ymm6; - gen.vpmovzxwd(f32vec, gen.yword[src]); // load bf16 into tmp - gen.vpslld(f32vec, f32vec, 16); // convert bf16->f32 by bit shift - gen.vminps(f32vec, f32vec, upper_bound); // clamp f16 max - gen.vmaxps(f32vec, f32vec, lower_bound); // clamp f16 lowest - gen.vcvtps2ph(f16vec, f32vec, 0); // convert f32 -> f16 - gen.vmovdqu(gen.xword[dst], f16vec); // move result to destination + gen.vpmovzxwd(f32vec, gen.yword[src]); // load bf16 into tmp + gen.vpslld(f32vec, f32vec, 16); // convert bf16->f32 by bit shift + gen.vminps(f32vec, f32vec, upper_bound); // clamp f16 max + gen.vmaxps(f32vec, f32vec, lower_bound); // clamp f16 lowest + gen.vcvtps2ph(f16vec, f32vec, kVcvtps2phRneNoExc); // convert f32 -> f16 + gen.vmovdqu(gen.xword[dst], f16vec); // move result to destination } template <> @@ -103,14 +124,14 @@ void jit_convert_vec_prepare(jit::Generator& gen) { auto lower_bound = gen.ymm6; auto addr = gen.r15; - static const float f16_max = std::numeric_limits::max(); - static const float f16_min = std::numeric_limits::lowest(); - static const float upper_bounds[8] = {f16_max, f16_max, f16_max, f16_max, f16_max, f16_max, f16_max, f16_max}; - static const float lower_bounds[8] = {f16_min, f16_min, f16_min, f16_min, f16_min, f16_min, f16_min, f16_min}; + static const float upper_bounds[8] = + {kF16MaxPos, kF16MaxPos, kF16MaxPos, kF16MaxPos, kF16MaxPos, kF16MaxPos, kF16MaxPos, kF16MaxPos}; + static const float lower_bounds[8] = + {kF16MaxNeg, kF16MaxNeg, kF16MaxNeg, kF16MaxNeg, kF16MaxNeg, kF16MaxNeg, kF16MaxNeg, kF16MaxNeg}; - gen.mov(addr, (size_t)upper_bounds); + gen.mov(addr, reinterpret_cast(upper_bounds)); gen.vmovdqu(upper_bound, gen.yword[addr]); - gen.mov(addr, (size_t)lower_bounds); + gen.mov(addr, reinterpret_cast(lower_bounds)); gen.vmovdqu(lower_bound, gen.yword[addr]); } @@ -129,7 +150,7 @@ void jit_convert_vec(jit::Generator& gen, const Xbyak::Reg gen.vmovups(f32vec, gen.yword[src]); gen.vminps(f32vec, f32vec, upper_bound); gen.vmaxps(f32vec, f32vec, lower_bound); - gen.vcvtps2ph(f16vec, f32vec, 0); + gen.vcvtps2ph(f16vec, f32vec, kVcvtps2phRneNoExc); gen.vmovdqu(gen.xword[dst], f16vec); } @@ -138,11 +159,11 @@ void jit_convert_vec_prepare(jit::Generator& gen) { auto order = gen.ymm1; auto addr = gen.r15; - static const int8_t offsets[32] = {0, 4, 8, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, 0, 4, 8, 12, -1, -1, -1, -1, -1, -1, -1, -1}; + static constexpr int8_t offsets[32] = {0, 4, 8, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 0, 4, 8, 12, -1, -1, -1, -1, -1, -1, -1, -1}; - gen.mov(addr, (size_t)offsets); // get offsets[] address - gen.vmovdqu(order, gen.yword[addr]); // save offsets[] to ymm register + gen.mov(addr, reinterpret_cast(offsets)); // get offsets[] address + gen.vmovdqu(order, gen.yword[addr]); // save offsets[] to ymm register } template <> @@ -268,7 +289,7 @@ class jit_convert_array : public jit::Generator { // Since the JIT code always resides in memory, and ASAN's memory management may remove executable // permissions, we need to restore executable permissions for the generated code. generator.setProtectModeRE(false); - return (fn_t)generator.getCode(); + return generator.getCode(); } return nullptr; } @@ -283,107 +304,6 @@ void jit_count_out_of_range_vec(jit::Generator&, const Xbyak::RegExp&); template void jit_count_out_of_range_vec_finalize(jit::Generator&, const Xbyak::RegExp&) {} -template <> -void jit_count_out_of_range_vec_prepare(jit::Generator& gen) { - auto accum_vec = gen.ymm4; - auto f16_max_pos_vec = gen.ymm5; - auto f16_max_neg_vec = gen.ymm6; - auto f16_min_pos_vec = gen.ymm7; - auto f16_min_neg_vec = gen.ymm8; - auto f16_zero_vec = gen.ymm9; - auto i32_ones_vec = gen.ymm10; - auto addr = gen.r15; - - static const float f16_max_pos = std::numeric_limits::max(); - static const float f16_max_neg = std::numeric_limits::lowest(); - static const float f16_min_pos = ov::float16::from_bits(0x0001); - static const float f16_min_neg = -ov::float16::from_bits(0x0001); - static const int32_t i32_one = 1; - - static const float max_pos_bounds[8] = - {f16_max_pos, f16_max_pos, f16_max_pos, f16_max_pos, f16_max_pos, f16_max_pos, f16_max_pos, f16_max_pos}; - static const float max_neg_bounds[8] = - {f16_max_neg, f16_max_neg, f16_max_neg, f16_max_neg, f16_max_neg, f16_max_neg, f16_max_neg, f16_max_neg}; - static const float min_pos_bounds[8] = - {f16_min_pos, f16_min_pos, f16_min_pos, f16_min_pos, f16_min_pos, f16_min_pos, f16_min_pos, f16_min_pos}; - static const float min_neg_bounds[8] = - {f16_min_neg, f16_min_neg, f16_min_neg, f16_min_neg, f16_min_neg, f16_min_neg, f16_min_neg, f16_min_neg}; - static const int32_t i32_ones[8] = {i32_one, i32_one, i32_one, i32_one, i32_one, i32_one, i32_one, i32_one}; - - auto load_vec = [&gen, &addr](Xbyak::Ymm vec, size_t ptr) { - gen.mov(addr, ptr); - gen.vmovdqu(vec, gen.yword[addr]); - }; - - load_vec(f16_max_pos_vec, (size_t)max_pos_bounds); - load_vec(f16_max_neg_vec, (size_t)max_neg_bounds); - load_vec(f16_min_pos_vec, (size_t)min_pos_bounds); - load_vec(f16_min_neg_vec, (size_t)min_neg_bounds); - load_vec(i32_ones_vec, (size_t)i32_ones); - gen.vxorps(f16_zero_vec, f16_zero_vec, f16_zero_vec); - gen.vxorps(accum_vec, accum_vec, accum_vec); -} - -template <> -void jit_count_out_of_range_vec(jit::Generator& gen, const Xbyak::RegExp& data) { - auto data_vec = gen.ymm1; - auto mask_vec = gen.ymm2; - auto mask_vec_xmm = gen.xmm2; - auto tmp_vec = gen.ymm3; - auto accum_vec = gen.ymm4; - auto f16_max_pos_vec = gen.ymm5; - auto f16_max_neg_vec = gen.ymm6; - auto f16_min_pos_vec = gen.ymm7; - auto f16_min_neg_vec = gen.ymm8; - auto f16_zero_vec = gen.ymm9; - auto i32_ones_vec = gen.ymm10; - - const unsigned char _cmp_lt_os = 1; - const unsigned char _cmp_neq_uq = 4; - const unsigned char _cmp_gt_os = 6; - - // std::abs(data) < ov::float16::from_bits(0x0001) - gen.vmovups(data_vec, gen.yword[data]); - gen.vcmpps(tmp_vec, data_vec, f16_min_pos_vec, _cmp_lt_os); - gen.vcmpps(mask_vec, data_vec, f16_min_neg_vec, _cmp_gt_os); - gen.vandps(mask_vec, mask_vec, tmp_vec); - - // data != 0.0f - gen.vcmpps(tmp_vec, data_vec, f16_zero_vec, _cmp_neq_uq); - gen.vandps(mask_vec, mask_vec, tmp_vec); - - // data > std::numeric_limits::max() - gen.vcmpps(tmp_vec, data_vec, f16_max_pos_vec, _cmp_gt_os); - gen.vorps(mask_vec, mask_vec, tmp_vec); - - // data < std::numeric_limits::lowest() - gen.vcmpps(tmp_vec, data_vec, f16_max_neg_vec, _cmp_lt_os); - gen.vorps(mask_vec, mask_vec, tmp_vec); - - // addition to i64 accumulator - gen.vandps(mask_vec, mask_vec, i32_ones_vec); - gen.vphaddd(mask_vec, mask_vec, mask_vec); - gen.vpermq(mask_vec, mask_vec, 0x08); - gen.vpmovsxdq(mask_vec, mask_vec_xmm); - gen.vpaddq(accum_vec, accum_vec, mask_vec); -} - -template <> -void jit_count_out_of_range_vec_finalize(jit::Generator& gen, const Xbyak::RegExp& dst) { - auto tmp_vec_xmm0 = gen.xmm2; // reuse mask_vec - auto tmp_vec_xmm1 = gen.xmm3; // reuse tmp_vec - auto accum_vec_ymm = gen.ymm4; - auto accum_vec_xmm = gen.xmm4; - - // horizontal sum of four i64 values - gen.vextractf128(tmp_vec_xmm0, accum_vec_ymm, 0); - gen.vextractf128(tmp_vec_xmm1, accum_vec_ymm, 1); - gen.vpaddq(accum_vec_xmm, tmp_vec_xmm0, tmp_vec_xmm1); - gen.vpermilpd(tmp_vec_xmm0, accum_vec_xmm, 0x01); - gen.vpaddq(accum_vec_xmm, accum_vec_xmm, tmp_vec_xmm0); - gen.vmovq(gen.qword[dst], accum_vec_xmm); -} - class jit_count_out_of_range : public jit::Generator { typedef struct context { struct { @@ -476,12 +396,476 @@ class jit_count_out_of_range : public jit::Generator { // Since the JIT code always resides in memory, and ASAN's memory management may remove executable // permissions, we need to restore executable permissions for the generated code. generator.setProtectModeRE(false); - return (fn_t)generator.getCode(); + return generator.getCode(); } return nullptr; } }; +// Combined single-pass JIT kernel for AVX-512: counts out-of-range AND detects lossy f16 compression. +// Processes 16 floats per iteration using zmm + opmask + F16C (vcvtps2ph/vcvtph2ps). Bails immediately +// if any in-range element has significant precision loss (abs error > 1.0). Tail handled via masked load. +class jit_check_f16_compression_avx512 : public jit::Generator { +public: + typedef struct { + const void* src; + void* rejected_dst; // size_t* — output: combined out-of-range + high-relative-error count + void* lossy_dst; // size_t* — output: lossy count (0 or non-zero) + const size_t count; + } args_t; + + typedef void (*fn_t)(const args_t*); + + static fn_t get() { + if (is_x64() && mayiuse(jit::avx512_core) && mayiuse(jit::fp16)) { + static jit_check_f16_compression_avx512 generator; + + // Since the JIT code always resides in memory, and ASAN's memory management may remove executable + // permissions, we need to restore executable permissions for the generated code. + generator.setProtectModeRE(false); + return generator.getCode(); + } + return nullptr; + } + +private: + jit_check_f16_compression_avx512() : jit::Generator(jit::avx512_core) { + using namespace Xbyak; + + const uint32_t vlen = 16u; // 16 floats per zmm + + // GP register allocation + const auto& reg_src = rax; + const auto& reg_rejected_dst = rbx; // callee-saved + const auto& reg_lossy_dst = r12; // callee-saved + const auto& reg_count = rdx; + const auto& reg_rejected_accum = + r14; // callee-saved — 64-bit combined rejection accumulator (OOR + high-rel-err) + const auto& reg_main_iters = r8; + const auto& reg_idx = rsi; + const auto& reg_tmp = r9; + const auto& reg_addr = r15; // callee-saved — used to load constants + + // ZMM register allocation + const auto& data_vec = zmm0; // chunk of 16 floats + const auto& rt_vec = zmm1; // f32->f16->f32 roundtripped + const auto& rt_ymm = ymm1; // low 256-bit alias for vcvtps2ph dest + const auto& diff_vec = zmm2; // |data - roundtripped| + const auto& abs_data_vec = zmm3; // |data| + const auto& rel_thresh_vec = zmm4; // |data| * 1e-4 + const auto& abs_mask_vec = zmm5; // 0x7FFFFFFF broadcast + const auto& abs_err_vec = zmm6; // 1.0f broadcast + const auto& rel_err_vec = zmm7; // 1e-4f broadcast + const auto& f16_max_pos_vec = zmm8; + const auto& f16_max_neg_vec = zmm9; + const auto& f16_min_pos_vec = zmm10; + const auto& f16_min_neg_vec = zmm11; + const auto& zero_vec = zmm12; + + // Opmasks (k0 reserved by ISA — represents "no mask") + const auto& k_oor = k1; // OOR per chunk (subnormal | overflow), then |= relative-error + const auto& k_lossy = k2; // (abs_diff > 1.0) AND NOT k_oor — early exit + const auto& k_rel = k3; // (abs_diff > |data|*1e-4) AND NOT k_oor + const auto& k_tail = k4; // tail mask (built once before tail iteration) + const auto& k_tmp1 = k5; + + Label main_loop, tail_block, normal_exit, lossy_exit, done; + + preamble(); + xor_(reg_rejected_accum, reg_rejected_accum); + + auto bcast = [&, this](const Xbyak::Zmm& vec, const void* p) { + mov(reg_addr, reinterpret_cast(p)); + vbroadcastss(vec, dword[reg_addr]); + }; + + bcast(f16_max_pos_vec, &kF16MaxPos); + bcast(f16_max_neg_vec, &kF16MaxNeg); + bcast(f16_min_pos_vec, &kF16MinPos); + bcast(f16_min_neg_vec, &kF16MinNeg); + bcast(abs_err_vec, &kF16CompressionAbsErrVal); + bcast(rel_err_vec, &kF16CompressionRelErrVal); + bcast(abs_mask_vec, &kAbsMaskVal); + vpxorq(zero_vec, zero_vec, zero_vec); + + // --- Load args --- + mov(reg_src, ptr[param + offsetof(args_t, src)]); + mov(reg_rejected_dst, ptr[param + offsetof(args_t, rejected_dst)]); + mov(reg_lossy_dst, ptr[param + offsetof(args_t, lossy_dst)]); + mov(reg_count, ptr[param + offsetof(args_t, count)]); + + constexpr uint8_t cmp_lt_os = 1; + constexpr uint8_t cmp_neq_uq = 4; + constexpr uint8_t cmp_gt_os = 6; + + // Per-chunk emission. If `masked` is true, all opmasks are AND-ed with k_tail + // to defensively clear out-of-tail lanes (masked load already zeroed them, but + // this guards against any non-zero residual from sign-bit operations on -0.0). + auto emit_chunk = [&, this](bool masked) { + // === Build OOR mask in k_oor === + // subnormal-when-rounded: |data| < min_pos AND data != 0 + vcmpps(k_tmp1, data_vec, f16_min_pos_vec, cmp_lt_os); // data < min_pos + vcmpps(k_oor | k_tmp1, + data_vec, + f16_min_neg_vec, + cmp_gt_os); // merge-masked: k_oor = k_tmp1 & (data > min_neg) + vcmpps(k_tmp1, data_vec, zero_vec, cmp_neq_uq); // data != 0 + kandw(k_oor, k_oor, k_tmp1); + // overflow: data > max_pos OR data < max_neg + vcmpps(k_tmp1, data_vec, f16_max_pos_vec, cmp_gt_os); + korw(k_oor, k_oor, k_tmp1); + vcmpps(k_tmp1, data_vec, f16_max_neg_vec, cmp_lt_os); + korw(k_oor, k_oor, k_tmp1); + if (masked) { + kandw(k_oor, k_oor, k_tail); + } + + // === Roundtrip f32 -> f16 -> f32 === + vcvtps2ph(rt_ymm, data_vec, kVcvtps2phRneNoExc); + vcvtph2ps(rt_vec, rt_ymm); + + // === abs_diff = |data - roundtripped| === + vsubps(diff_vec, data_vec, rt_vec); + vpandd(diff_vec, diff_vec, abs_mask_vec); // mask off sign bit + + // === Lossy check: (abs_diff > 1.0) AND NOT k_oor -> early exit === + vcmpps(k_lossy, diff_vec, abs_err_vec, cmp_gt_os); + kandnw(k_lossy, k_oor, k_lossy); // k_lossy = ~k_oor & k_lossy + if (masked) { + kandw(k_lossy, k_lossy, k_tail); + } + kortestw(k_lossy, k_lossy); + jnz(lossy_exit, T_NEAR); + + // === Relative-error check: (abs_diff > |data| * 1e-4) AND NOT k_oor === + vpandd(abs_data_vec, data_vec, abs_mask_vec); + vmulps(rel_thresh_vec, abs_data_vec, rel_err_vec); + vcmpps(k_rel, diff_vec, rel_thresh_vec, cmp_gt_os); + kandnw(k_rel, k_oor, k_rel); + korw(k_oor, k_oor, k_rel); + if (masked) { + kandw(k_oor, k_oor, k_tail); + } + + // === Accumulate popcount(k_oor) into reg_rejected_accum === + // Branchless 16-bit SWAR popcount — avoids POPCNT (not gated by mayiuse(avx512_core)). + kmovw(reg_tmp.cvt32(), k_oor); // x = k_oor (16 bits in low word) + mov(reg_addr.cvt32(), reg_tmp.cvt32()); // tmp2 = x + shr(reg_addr.cvt32(), 1); + and_(reg_addr.cvt32(), 0x5555); + sub(reg_tmp.cvt32(), reg_addr.cvt32()); // x -= (x >> 1) & 0x5555 + mov(reg_addr.cvt32(), reg_tmp.cvt32()); + and_(reg_addr.cvt32(), 0x3333); + shr(reg_tmp.cvt32(), 2); + and_(reg_tmp.cvt32(), 0x3333); + add(reg_tmp.cvt32(), reg_addr.cvt32()); // x = (x & 0x3333) + ((x >> 2) & 0x3333) + mov(reg_addr.cvt32(), reg_tmp.cvt32()); + shr(reg_addr.cvt32(), 4); + add(reg_tmp.cvt32(), reg_addr.cvt32()); + and_(reg_tmp.cvt32(), 0x0F0F); // x = (x + (x >> 4)) & 0x0F0F + mov(reg_addr.cvt32(), reg_tmp.cvt32()); + shr(reg_addr.cvt32(), 8); + add(reg_tmp.cvt32(), reg_addr.cvt32()); // low byte = popcount (max 16) + and_(reg_tmp, 0xFF); // zero upper bits before 64-bit add + add(reg_rejected_accum, reg_tmp); + }; + + // --- Main loop: 16 elements per iteration --- + mov(reg_main_iters, reg_count); + shr(reg_main_iters, 4); + xor_(reg_idx, reg_idx); + + L(main_loop); + cmp(reg_idx, reg_main_iters); + jge(tail_block, T_NEAR); + + vmovups(data_vec, zword[reg_src]); + emit_chunk(/*masked=*/false); + add(reg_src, vlen * sizeof(float)); + inc(reg_idx); + jmp(main_loop, T_NEAR); + + // --- Tail: count & 15 elements via opmask --- + L(tail_block); + and_(reg_count, 15); + jz(normal_exit, T_NEAR); + + // k_tail = (1 << reg_count) - 1, using baseline SHL (BMI2 bzhi not gated by mayiuse(avx512_core)). + // rcx is free at this point: args_t pointer was consumed in the preamble. + mov(rcx, reg_count); + mov(reg_tmp, 1); + shl(reg_tmp, cl); + dec(reg_tmp); + kmovw(k_tail, reg_tmp.cvt32()); + + // Masked zeroing-load: out-of-mask lanes read as +0.0 (benign — pass none of the predicates) + vmovups(data_vec | k_tail | T_z, zword[reg_src]); + emit_chunk(/*masked=*/true); + + // --- Normal exit: write OOR accumulator and lossy=0 --- + L(normal_exit); + mov(qword[reg_rejected_dst], reg_rejected_accum); + xor_(reg_tmp, reg_tmp); + mov(qword[reg_lossy_dst], reg_tmp); + jmp(done, T_NEAR); + + // --- Lossy exit: partial OOR count is meaningless, write 0; lossy=1 --- + L(lossy_exit); + xor_(reg_tmp, reg_tmp); + mov(qword[reg_rejected_dst], reg_tmp); + mov(reg_tmp, 1); + mov(qword[reg_lossy_dst], reg_tmp); + + L(done); + postamble(); + } +}; + +// Combined single-pass JIT kernel: counts out-of-range AND detects lossy f16 compression. +// Processes 8 floats per iteration using AVX2+F16C. Bails immediately if any in-range +// element has significant precision loss (abs error > 1.0). +class jit_check_f16_compression : public jit::Generator { +public: + typedef struct { + const void* src; + void* rejected_dst; // size_t* — output: combined out-of-range + high-relative-error count + void* lossy_dst; // size_t* — output: lossy count (0 or non-zero) + const size_t count; + } args_t; + + typedef void (*fn_t)(const args_t*); + + static fn_t get() { + if (is_x64() && mayiuse(jit::avx2) && mayiuse(jit::fp16)) { + static jit_check_f16_compression generator; + + // Since the JIT code always resides in memory, and ASAN's memory management may remove executable + // permissions, we need to restore executable permissions for the generated code. + generator.setProtectModeRE(false); + return generator.getCode(); + } + return nullptr; + } + +private: + jit_check_f16_compression() { + using namespace Xbyak; + + const uint32_t vlen = 8u; + + // GP register allocation + const auto& reg_src = rax; + const auto& reg_rejected_dst = rbx; + const auto& reg_lossy_dst = r12; // callee-saved, preserved by preamble + const auto& reg_sz = rdx; + const auto& reg_saved_rsp = r13; // callee-saved, for stack cleanup on lossy exit + + // YMM register allocation + // Range check constants (same as jit_count_out_of_range) + const auto& data_vec = ymm1; + const auto& mask_vec = ymm2; // OOR mask per chunk + const auto& mask_vec_xmm = xmm2; + const auto& tmp_vec = ymm3; + const auto& rejected_accum = ymm4; + const auto& rejected_accum_xmm = xmm4; + const auto& f16_max_pos_vec = ymm5; + const auto& f16_max_neg_vec = ymm6; + const auto& f16_min_pos_vec = ymm7; + const auto& f16_min_neg_vec = ymm8; + const auto& f16_zero_vec = ymm9; + const auto& i32_ones_vec = ymm10; + // Lossy check constants + const auto& abs_err_vec = ymm11; // 1.0f broadcast + const auto& abs_mask_vec = ymm0; // 0x7FFFFFFF broadcast (for fabs) + const auto& rel_err_vec = ymm12; // 1e-4f broadcast (for relative error threshold) + // Temps for lossy computation (reused per chunk) + const auto& diff_vec = ymm14; + const auto& rt_vec_xmm = xmm15; // for f32->f16->f32 roundtrip + + Label exit_lossy, exit_normal, done; + + preamble(); + mov(reg_saved_rsp, rsp); // save rsp for lossy exit stack cleanup + + // --- Load constants --- + // 256-bit (8-lane) broadcast arrays for AVX2 (no vbroadcastss on ymm here; we use vmovdqu). + // Arrays filled from FP16-derived constants (non-constexpr — see kF16* above) are static const; + // arrays filled from literal/constexpr thresholds are static constexpr. + static const float max_pos_bounds[8] = + {kF16MaxPos, kF16MaxPos, kF16MaxPos, kF16MaxPos, kF16MaxPos, kF16MaxPos, kF16MaxPos, kF16MaxPos}; + static const float max_neg_bounds[8] = + {kF16MaxNeg, kF16MaxNeg, kF16MaxNeg, kF16MaxNeg, kF16MaxNeg, kF16MaxNeg, kF16MaxNeg, kF16MaxNeg}; + static const float min_pos_bounds[8] = + {kF16MinPos, kF16MinPos, kF16MinPos, kF16MinPos, kF16MinPos, kF16MinPos, kF16MinPos, kF16MinPos}; + static const float min_neg_bounds[8] = + {kF16MinNeg, kF16MinNeg, kF16MinNeg, kF16MinNeg, kF16MinNeg, kF16MinNeg, kF16MinNeg, kF16MinNeg}; + static constexpr int32_t i32_ones[8] = {1, 1, 1, 1, 1, 1, 1, 1}; + static constexpr float abs_errs[8] = {kF16CompressionAbsErrVal, + kF16CompressionAbsErrVal, + kF16CompressionAbsErrVal, + kF16CompressionAbsErrVal, + kF16CompressionAbsErrVal, + kF16CompressionAbsErrVal, + kF16CompressionAbsErrVal, + kF16CompressionAbsErrVal}; + static constexpr float rel_errs[8] = {kF16CompressionRelErrVal, + kF16CompressionRelErrVal, + kF16CompressionRelErrVal, + kF16CompressionRelErrVal, + kF16CompressionRelErrVal, + kF16CompressionRelErrVal, + kF16CompressionRelErrVal, + kF16CompressionRelErrVal}; + static constexpr uint32_t abs_masks[8] = + {kAbsMaskVal, kAbsMaskVal, kAbsMaskVal, kAbsMaskVal, kAbsMaskVal, kAbsMaskVal, kAbsMaskVal, kAbsMaskVal}; + + auto load_vec = [this](Ymm vec, size_t ptr) { + mov(r15, ptr); + vmovdqu(vec, yword[r15]); + }; + + load_vec(f16_max_pos_vec, reinterpret_cast(max_pos_bounds)); + load_vec(f16_max_neg_vec, reinterpret_cast(max_neg_bounds)); + load_vec(f16_min_pos_vec, reinterpret_cast(min_pos_bounds)); + load_vec(f16_min_neg_vec, reinterpret_cast(min_neg_bounds)); + load_vec(i32_ones_vec, reinterpret_cast(i32_ones)); + load_vec(abs_err_vec, reinterpret_cast(abs_errs)); + load_vec(rel_err_vec, reinterpret_cast(rel_errs)); + load_vec(abs_mask_vec, reinterpret_cast(abs_masks)); + vxorps(f16_zero_vec, f16_zero_vec, f16_zero_vec); + vxorps(rejected_accum, rejected_accum, rejected_accum); + + // Load args + mov(reg_src, ptr[param + offsetof(args_t, src)]); + mov(reg_rejected_dst, ptr[param + offsetof(args_t, rejected_dst)]); + mov(reg_lossy_dst, ptr[param + offsetof(args_t, lossy_dst)]); + mov(reg_sz, ptr[param + offsetof(args_t, count)]); + + constexpr uint8_t cmp_lt_os = 1; + constexpr uint8_t cmp_neq_uq = 4; + constexpr uint8_t cmp_gt_os = 6; + + // Kernel lambda: emits the combined check code for 8 elements at src_ptr. + // Called twice (main loop + tail) — each call emits a copy of the instructions. + auto emit_kernel = [&](const RegExp& src_ptr) { + // Load 8 floats + vmovups(data_vec, yword[src_ptr]); + + // === Out-of-range check (same algorithm as jit_count_out_of_range_vec) === + // subnormal: data in (-f16_min_pos, f16_min_pos) and != 0 + vcmpps(tmp_vec, data_vec, f16_min_pos_vec, cmp_lt_os); + vcmpps(mask_vec, data_vec, f16_min_neg_vec, cmp_gt_os); + vandps(mask_vec, mask_vec, tmp_vec); + vcmpps(tmp_vec, data_vec, f16_zero_vec, cmp_neq_uq); + vandps(mask_vec, mask_vec, tmp_vec); + // overflow: data > f16_max or data < f16_lowest + vcmpps(tmp_vec, data_vec, f16_max_pos_vec, cmp_gt_os); + vorps(mask_vec, mask_vec, tmp_vec); + vcmpps(tmp_vec, data_vec, f16_max_neg_vec, cmp_lt_os); + vorps(mask_vec, mask_vec, tmp_vec); + // mask_vec now has per-element OOR mask (0xFFFFFFFF for OOR, 0 for in-range) + + // === Lossy check for in-range elements === + // Roundtrip: f32 -> f16 -> f32 (f16 part written to low xmm of rt_vec_xmm) + vcvtps2ph(rt_vec_xmm, data_vec, kVcvtps2phRneNoExc); // f32 -> f16 (8 values) + vcvtph2ps(diff_vec, rt_vec_xmm); // f16 -> f32 (roundtripped) + + // abs_diff = |data - roundtripped| + vsubps(diff_vec, data_vec, diff_vec); + vandps(diff_vec, diff_vec, abs_mask_vec); // diff_vec = |diff| + + // lossy = |diff| > abs_error (1.0) + vcmpps(tmp_vec, diff_vec, abs_err_vec, cmp_gt_os); // |diff| > 1.0 + + // Exclude out-of-range elements: keep only in-range lossy + vandnps(tmp_vec, mask_vec, tmp_vec); // tmp_vec = NOT(oor) AND lossy + + // Early exit if ANY element is lossy + vtestps(tmp_vec, tmp_vec); // ZF=1 if all zeros + jnz(exit_lossy, T_NEAR); // bail if any lossy + + // === Relative error check: count elements where |diff| > |value| * 1e-4 === + // (equivalent to abs_diff / |value| > 1e-4, but avoids division) + vandps(tmp_vec, data_vec, abs_mask_vec); // tmp_vec = |data| + vmulps(tmp_vec, tmp_vec, rel_err_vec); // tmp_vec = |data| * 1e-4 + vcmpps(tmp_vec, diff_vec, tmp_vec, cmp_gt_os); // 1 where |diff| > |data|*1e-4 + vandnps(tmp_vec, mask_vec, tmp_vec); // exclude OOR (avoid double-count) + vorps(mask_vec, mask_vec, tmp_vec); // combine OOR + relative-error + + // === Accumulate combined OOR + relative-error count === + vandps(mask_vec, mask_vec, i32_ones_vec); + vphaddd(mask_vec, mask_vec, mask_vec); + vpermq(mask_vec, mask_vec, 0x08); + vpmovsxdq(mask_vec, mask_vec_xmm); + vpaddq(rejected_accum, rejected_accum, mask_vec); + }; + + // --- Main loop: 8 elements per iteration --- + Label main_loop, main_loop_exit; + xor_(rsi, rsi); + mov(r8, reg_sz); + shr(r8, 3); + + L(main_loop); + cmp(rsi, r8); + jge(main_loop_exit, T_NEAR); + + emit_kernel(reg_src); + add(reg_src, static_cast(sizeof(float) * vlen)); + + add(rsi, 1); + jmp(main_loop, T_NEAR); + L(main_loop_exit); + + // --- Tail: remaining elements (< 8), zero-padded --- + shl(rsi, 3); + sub(reg_sz, rsi); + test(reg_sz, reg_sz); + jz(exit_normal, T_NEAR); + + sub(rsp, vlen * sizeof(float)); + mov(r8, rsp); + + vpxor(mask_vec, mask_vec, mask_vec); // zero + vmovups(yword[r8], mask_vec); // zero-pad buffer + + copy(r8, reg_src, reg_sz); // copy remaining elements + emit_kernel(r8); // process (zeros won't trigger OOR or lossy) + + add(rsp, vlen * sizeof(float)); // free stack (only reached if no lossy) + + // --- Normal exit: no lossy elements found --- + L(exit_normal); + { + // Horizontal sum of rejected_accum (4 x i64) -> single i64 + const auto& tmp0 = xmm2; // reuse mask_vec + const auto& tmp1 = xmm3; // reuse tmp_vec + vextractf128(tmp0, rejected_accum, 0); + vextractf128(tmp1, rejected_accum, 1); + vpaddq(rejected_accum_xmm, tmp0, tmp1); + vpermilpd(tmp0, rejected_accum_xmm, 0x01); + vpaddq(rejected_accum_xmm, rejected_accum_xmm, tmp0); + vmovq(qword[reg_rejected_dst], rejected_accum_xmm); + + // lossy = 0 + xor_(rsi, rsi); + mov(qword[reg_lossy_dst], rsi); + } + jmp(done, T_NEAR); + + // --- Lossy exit: bail immediately (jumped from kernel via jnz) --- + L(exit_lossy); + mov(rsp, reg_saved_rsp); // restore rsp (handles both main loop and tail cases) + xor_(rsi, rsi); + mov(qword[reg_rejected_dst], rsi); // rejected_count = 0 (meaningless, caller checks lossy first) + mov(rsi, 1); + mov(qword[reg_lossy_dst], rsi); // lossy = 1 + + L(done); + postamble(); + } +}; + #endif // OV_CORE_USE_XBYAK_JIT template @@ -550,22 +934,67 @@ void convert_from_bf16_to_f16_with_clamp(const bfloat16* arg, float16* out, size // CVS-125496: duplicate and stub for ARM, provide optimized solution } -size_t count_out_of_f16_range(const float* arg, size_t count) { +namespace { +// Predicate: true iff the FP16 representation of `v` falls outside the finite FP16 +// normal range (subnormal when rounded, or magnitude larger than float16::max()). +// Shared between check_f16_compression() slow-path and count_out_of_f16_range(). +inline bool is_out_of_f16_range(float v) { + return (std::abs(v) < float16::from_bits(0x0001) && v != 0.0f) || v > std::numeric_limits::max() || + v < std::numeric_limits::lowest(); +} +} // namespace + +#ifdef OV_CORE_USE_XBYAK_JIT +namespace { +// Invoke a compiled FP16-compression JIT kernel (AVX-512 or AVX2+F16C) and +// marshal its two size_t outputs into a CompressionCheckResult. Shared by +// both JIT dispatches in check_f16_compression(). +template +CompressionCheckResult run_check_f16_compression_jit(typename Jit::fn_t fn, const float* arg, size_t count) { + CompressionCheckResult result{0, false}; + size_t lossy_count = 0; + typename Jit::args_t args = {arg, &result.rejected_count, &lossy_count, count}; + fn(&args); + result.has_lossy = lossy_count > 0; + return result; +} +} // namespace +#endif // OV_CORE_USE_XBYAK_JIT + +CompressionCheckResult check_f16_compression(const float* arg, size_t count) { #ifdef OV_CORE_USE_XBYAK_JIT if (util::may_i_use_dynamic_code()) { - if (auto converter = jit_count_out_of_range::get()) { - size_t num_out_of_range = 0; - jit_count_out_of_range::args_t args = {arg, &num_out_of_range, count}; - converter(&args); - return num_out_of_range; + if (auto fn = jit_check_f16_compression_avx512::get()) { + return run_check_f16_compression_jit(fn, arg, count); + } + if (auto fn = jit_check_f16_compression::get()) { + return run_check_f16_compression_jit(fn, arg, count); } } #endif // OV_CORE_USE_XBYAK_JIT - const auto is_out_of_f16_range = [](const float v) { - return (std::abs(v) < float16::from_bits(0x0001) && v != 0.0f) || (v > std::numeric_limits::max()) || - (v < std::numeric_limits::lowest()); - }; + CompressionCheckResult result{0, false}; + for (size_t i = 0; i < count; ++i) { + const float v = arg[i]; + if (is_out_of_f16_range(v)) { + ++result.rejected_count; + } else { + const double roundtripped = static_cast(static_cast(static_cast(v))); + const double abs_diff = std::abs(v - roundtripped); + if (abs_diff > f16_compression_max_abs_error) { + return {0, true}; + } + if (v != 0.0f && abs_diff / std::abs(v) > f16_compression_max_rel_error) { + ++result.rejected_count; + } + } + } + return result; +} +size_t count_out_of_f16_range(const float* arg, size_t count) { + // Backward-compatible helper: strict FP16-range count only (no relative-error accounting). + // The in-tree FP16 compression path uses check_f16_compression(); this wrapper exists for + // external developer-package consumers that linked against the pre-PR symbol. return std::count_if(arg, arg + count, is_out_of_f16_range); } diff --git a/tests/layer_tests/ovc_python_api_tests/test_pytorch.py b/tests/layer_tests/ovc_python_api_tests/test_pytorch.py index 5046d3ba29ad9a..1805c22f13166e 100644 --- a/tests/layer_tests/ovc_python_api_tests/test_pytorch.py +++ b/tests/layer_tests/ovc_python_api_tests/test_pytorch.py @@ -585,7 +585,11 @@ def create_pytorch_module_convert_pytorch_frontend_oob(tmp_dir): class ConvModel(torch.nn.Module): def __init__(self): super(ConvModel, self).__init__() - self.weights = torch.rand([1, 3, 3, 3]) + # Deterministic weights with zero FP16 round-trip error so the + # CompressFloatConstants decision doesn't depend on a random state. + # 0.5 is exactly representable in FP16 (abs/rel round-trip error = 0), + # so compression always happens and the reference model below matches. + self.weights = torch.full([1, 3, 3, 3], 0.5) def forward(self, x): return F.conv2d(x, self.weights) From 53d3b91efc83d822d753d0c97f2c013aac11d27d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:03:43 +0000 Subject: [PATCH 023/545] Update setuptools requirement from <=82.0.0,>=77 to >=77,<=82.0.1 (#35078) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the requirements on [setuptools](https://github.com/pypa/setuptools) to permit the latest version.
Changelog

Sourced from setuptools's changelog.

v82.0.1

Bugfixes

  • Fix the loading of launcher manifest.xml file. (#5047)
  • Replaced deprecated json.__version__ with fixture in tests. (#5186)

Improved Documentation

  • Add advice about how to improve predictability when installing sdists. (#5168)

Misc

v82.0.0

Deprecations and Removals

  • pkg_resources has been removed from Setuptools. Most common uses of pkg_resources have been superseded by the importlib.resources <https://docs.python.org/3/library/importlib.resources.html>_ and importlib.metadata <https://docs.python.org/3/library/importlib.metadata.html>_ projects. Projects and environments relying on pkg_resources for namespace packages or other behavior should depend on older versions of setuptools. (#3085)

v81.0.0

Deprecations and Removals

  • Removed support for the --dry-run parameter to setup.py. This one feature by its nature threads through lots of core and ancillary functionality, adding complexity and friction. Removal of this parameter will help decouple the compiler functionality from distutils and thus the eventual full integration of distutils. These changes do affect some class and function signatures, so any derivative functionality may require some compatibility shims to support their expected interface. Please report any issues to the Setuptools project for investigation. (#4872)

v80.10.2

Bugfixes

  • Update vendored dependencies. (#5159)

Misc

... (truncated)

Commits
  • 5a13876 Bump version: 82.0.0 → 82.0.1
  • 51ab8f1 Avoid using (deprecated) 'json.version' in tests (#5194)
  • f9c37b2 Docs/CI: Fix intersphinx references (#5195)
  • 8173db2 Docs: Fix intersphinx references
  • 09bafbc Fix past tense on newsfragment
  • 461ea56 Add news fragment
  • c4ffe53 Avoid using (deprecated) 'json.version' in tests
  • 749258b Cleanup pkg_resources dependencies and configuration (#5175)
  • 2019c16 Parse ext-module.define-macros from pyproject.toml as list of tuples (#5169)
  • b809c86 Sync setuptools schema with validate-pyproject (#5157)
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Przemyslaw Wysocki Co-authored-by: Mikhail Ryzhov --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a9b2c89d61093c..406030e1a43e68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ [build-system] requires = [ - "setuptools>=77,<=82.0.0", + "setuptools>=77,<=82.0.1", "wheel<=0.45.1", "cmake<=4.3.0", "patchelf<=0.17.2.4; sys_platform == 'linux' and platform_machine == 'x86_64'" From 0c81a247d3a189f828a6ab7b4a7418b706f4242e Mon Sep 17 00:00:00 2001 From: Alexandru Enache Date: Wed, 22 Apr 2026 13:06:05 +0300 Subject: [PATCH 024/545] [NPU] Add NPU_COMPILER_VERSION to CompiledModel property list (#35360) ### Details: - added `ov::intel_npu::compiler_version` as a CompiledModel property - added the compiler version in the Plugin metadata payload since it might be needed during import ### Tickets: - *C-180802* ### AI Assistance: - *AI assistance used: no / yes* - *If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).* --------- Signed-off-by: alexandruenache1111 --- .../al/include/intel_npu/config/options.hpp | 22 ++++++++++ .../intel_npu/src/plugin/include/metadata.hpp | 40 +++++++++++++++--- .../src/plugin/src/compiled_model.cpp | 8 +++- .../intel_npu/src/plugin/src/metadata.cpp | 42 ++++++++++++++++++- .../intel_npu/src/plugin/src/plugin.cpp | 13 ++++-- .../intel_npu/src/plugin/src/properties.cpp | 1 + .../behavior/compiled_model/property.cpp | 12 ++++-- .../behavior/compiled_model/property.hpp | 36 +++++++++++++++- 8 files changed, 158 insertions(+), 16 deletions(-) diff --git a/src/plugins/intel_npu/src/al/include/intel_npu/config/options.hpp b/src/plugins/intel_npu/src/al/include/intel_npu/config/options.hpp index 7dc2aaf72ba4e6..80a4d481ff6604 100644 --- a/src/plugins/intel_npu/src/al/include/intel_npu/config/options.hpp +++ b/src/plugins/intel_npu/src/al/include/intel_npu/config/options.hpp @@ -863,6 +863,28 @@ struct COMPILER_TYPE final : OptionBase { + static std::string_view key() { + return ov::intel_npu::compiler_version.name(); + } + + static uint32_t defaultValue() { + return 0; + } + + static OptionMode mode() { + return OptionMode::RunTime; + } + + static bool isPublic() { + return true; + } + + static ov::PropertyMutability mutability() { + return ov::PropertyMutability::RO; + } +}; + struct COMPILATION_MODE final : OptionBase { static std::string_view key() { return ov::intel_npu::compilation_mode.name(); diff --git a/src/plugins/intel_npu/src/plugin/include/metadata.hpp b/src/plugins/intel_npu/src/plugin/include/metadata.hpp index cd125566ee3ad5..5f0b15ef997823 100644 --- a/src/plugins/intel_npu/src/plugin/include/metadata.hpp +++ b/src/plugins/intel_npu/src/plugin/include/metadata.hpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -50,15 +49,17 @@ class MetadataBase { */ virtual std::optional> get_init_sizes() const; - virtual std::optional> get_input_layouts() const; - - virtual std::optional> get_output_layouts() const; - /** * @returns Batch size. Populated in case of plugin batching. */ virtual std::optional get_batch_size() const; + virtual std::optional> get_input_layouts() const; + + virtual std::optional> get_output_layouts() const; + + virtual std::optional get_compiler_version() const; + virtual ~MetadataBase() = default; static std::streampos getFileSize(std::istream& stream); @@ -137,11 +138,12 @@ constexpr uint32_t METADATA_VERSION_2_0{MetadataBase::make_version(2, 0)}; constexpr uint32_t METADATA_VERSION_2_1{MetadataBase::make_version(2, 1)}; constexpr uint32_t METADATA_VERSION_2_2{MetadataBase::make_version(2, 2)}; constexpr uint32_t METADATA_VERSION_2_3{MetadataBase::make_version(2, 3)}; +constexpr uint32_t METADATA_VERSION_2_4{MetadataBase::make_version(2, 4)}; /** * @brief Current metadata version. */ -constexpr uint32_t CURRENT_METADATA_VERSION{METADATA_VERSION_2_3}; +constexpr uint32_t CURRENT_METADATA_VERSION{METADATA_VERSION_2_4}; constexpr uint16_t CURRENT_METADATA_MAJOR_VERSION{MetadataBase::get_major(CURRENT_METADATA_VERSION)}; constexpr uint16_t CURRENT_METADATA_MINOR_VERSION{MetadataBase::get_minor(CURRENT_METADATA_VERSION)}; @@ -316,6 +318,32 @@ class Metadata : public Metadata { std::optional> _outputLayouts; }; +/** + * @brief Stores the compiler version. + */ +template <> +class Metadata : public Metadata { +public: + Metadata(uint64_t blobSize, + const std::optional& ovVersion = std::nullopt, + const std::optional>& initSizes = std::nullopt, + const std::optional batchSize = std::nullopt, + const std::optional>& inputLayouts = std::nullopt, + const std::optional>& outputLayouts = std::nullopt, + const std::optional compilerVersion = std::nullopt); + + void read() override; + + void write(std::ostream& stream) override; + + size_t get_metadata_size() const override; + + std::optional get_compiler_version() const override; + +private: + std::optional _compilerVersion; +}; + /** * @brief Creates a Metadata object. * diff --git a/src/plugins/intel_npu/src/plugin/src/compiled_model.cpp b/src/plugins/intel_npu/src/plugin/src/compiled_model.cpp index 3eb3074c52a869..e813a41d71c6c1 100644 --- a/src/plugins/intel_npu/src/plugin/src/compiled_model.cpp +++ b/src/plugins/intel_npu/src/plugin/src/compiled_model.cpp @@ -97,12 +97,18 @@ void CompiledModel::export_model(std::ostream& stream) const { std::dynamic_pointer_cast(nodeOutput.get_node_shared_ptr())->get_layout()); } + std::optional compilerVersion = std::nullopt; + if (_propertiesManager->getConfig().has(ov::intel_npu::compiler_version.name())) { + compilerVersion = _propertiesManager->getConfig().get(); + } + Metadata(blobSizesBeforeVersioning, CURRENT_OPENVINO_VERSION, std::move(initBlobSizes), _batchSize, std::move(inputLayouts), - std::move(outputLayouts)) + std::move(outputLayouts), + compilerVersion) .write(stream); } } diff --git a/src/plugins/intel_npu/src/plugin/src/metadata.cpp b/src/plugins/intel_npu/src/plugin/src/metadata.cpp index 70a446817b7039..1fc0b2c321c88e 100644 --- a/src/plugins/intel_npu/src/plugin/src/metadata.cpp +++ b/src/plugins/intel_npu/src/plugin/src/metadata.cpp @@ -10,7 +10,6 @@ #include "intel_npu/utils/utils.hpp" #include "openvino/runtime/shared_buffer.hpp" -#include "openvino/util/variant_visitor.hpp" namespace intel_npu { @@ -96,6 +95,18 @@ Metadata::Metadata(uint64_t blobSize, _version = METADATA_VERSION_2_3; } +Metadata::Metadata(uint64_t blobSize, + const std::optional& ovVersion, + const std::optional>& initSizes, + const std::optional batchSize, + const std::optional>& inputLayouts, + const std::optional>& outputLayouts, + const std::optional compilerVersion) + : Metadata{blobSize, ovVersion, initSizes, batchSize, inputLayouts, outputLayouts}, + _compilerVersion{compilerVersion} { + _version = METADATA_VERSION_2_4; +} + void MetadataBase::read(std::istream& tensor) { _source = Source(tensor); read(); @@ -208,6 +219,14 @@ void Metadata::read() { _outputLayouts = readNLayouts(numberOfOutputLayouts, "Output"); } +void Metadata::read() { + Metadata::read(); + + uint32_t compilerVersion; + read_data_from_source(reinterpret_cast(&compilerVersion), sizeof(compilerVersion)); + _compilerVersion = compilerVersion != 0 ? std::optional(compilerVersion) : std::nullopt; +} + void Metadata::write(std::ostream& stream) { stream.write(reinterpret_cast(&_version), sizeof(_version)); _ovVersion.write(stream); @@ -254,6 +273,13 @@ void Metadata::write(std::ostream& stream) { writeLayouts(_inputLayouts); writeLayouts(_outputLayouts); +} + +void Metadata::write(std::ostream& stream) { + Metadata::write(stream); + + uint32_t compilerVersion = _compilerVersion.value_or(0); + stream.write(reinterpret_cast(&compilerVersion), sizeof(compilerVersion)); append_padding_blob_size_and_magic(stream); } @@ -280,6 +306,8 @@ std::unique_ptr create_metadata(uint32_t version, uint64_t blobSiz return std::make_unique>(blobSize); case METADATA_VERSION_2_3: return std::make_unique>(blobSize); + case METADATA_VERSION_2_4: + return std::make_unique>(blobSize); default: return nullptr; } @@ -399,6 +427,10 @@ std::optional> MetadataBase::get_output_layouts() const return std::nullopt; } +std::optional MetadataBase::get_compiler_version() const { + return std::nullopt; +} + std::optional> Metadata::get_init_sizes() const { return _initSizes; } @@ -415,6 +447,10 @@ std::optional> Metadata::get_outpu return _outputLayouts; } +std::optional Metadata::get_compiler_version() const { + return _compilerVersion; +} + size_t Metadata::get_metadata_size() const { return sizeof(_version) + _ovVersion.get_openvino_version_size(); } @@ -455,4 +491,8 @@ size_t Metadata::get_metadata_size() const { return metadataSize; } +size_t Metadata::get_metadata_size() const { + return Metadata::get_metadata_size() + sizeof(_compilerVersion.value()); +} + } // namespace intel_npu diff --git a/src/plugins/intel_npu/src/plugin/src/plugin.cpp b/src/plugins/intel_npu/src/plugin/src/plugin.cpp index 43e9c084385343..0fa70372e0438f 100644 --- a/src/plugins/intel_npu/src/plugin/src/plugin.cpp +++ b/src/plugins/intel_npu/src/plugin/src/plugin.cpp @@ -4,8 +4,6 @@ #include "plugin.hpp" -#include - #include "compiled_model.hpp" #include "intel_npu/common/compiler_adapter_factory.hpp" #include "intel_npu/common/device_helpers.hpp" @@ -17,7 +15,6 @@ #include "intel_npu/config/npuw.hpp" #include "intel_npu/config/options.hpp" #include "intel_npu/utils/utils.hpp" -#include "intel_npu/utils/zero/zero_init.hpp" #include "metrics.hpp" #include "npuw/compiled_model.hpp" #include "npuw/llm_compiled_model.hpp" @@ -230,6 +227,7 @@ void init_config(const IEngineBackend* backend, OptionsDesc& options, FilteredCo REGISTER_OPTION(TILES); REGISTER_OPTION(COMPILATION_MODE); REGISTER_OPTION(COMPILER_TYPE); + REGISTER_OPTION(COMPILER_VERSION); REGISTER_OPTION(PLATFORM); REGISTER_OPTION(CREATE_EXECUTOR); REGISTER_OPTION(DYNAMIC_SHAPE_TO_STATIC); @@ -453,6 +451,7 @@ std::shared_ptr Plugin::compile_model(const std::shared_ptr< OV_ITT_TASK_CHAIN(PLUGIN_COMPILE_MODEL, itt::domains::NPUPlugin, "Plugin::compile_model", "fork_local_config"); FilteredConfig localConfig = _propertiesManager->getConfigForSpecificCompiler(localProperties, compiler.get()); + localConfig.update({{ov::intel_npu::compiler_version.name(), std::to_string(compiler->get_version())}}); auto updateBatchMode = [&](ov::intel_npu::BatchMode mode) { std::stringstream strStream; @@ -848,6 +847,14 @@ std::shared_ptr Plugin::parse(const ov::Tensor& tensorBig, ? metadata->get_blob_size() - std::accumulate(initSizes->begin(), initSizes->end(), accumulator) : metadata->get_blob_size(); batchSize = metadata->get_batch_size(); + + std::optional compilerVersion = metadata->get_compiler_version(); + if (compilerVersion.has_value()) { + localConfig.update({{ov::intel_npu::compiler_version.name(), std::to_string(compilerVersion.value())}}); + _logger.debug("Imported model was compiled with compiler version: %u.%u", + ONEAPI_VERSION_MAJOR(compilerVersion.value()), + ONEAPI_VERSION_MINOR(compilerVersion.value())); + } } else { _logger.info("Blob compatibility check skipped."); } diff --git a/src/plugins/intel_npu/src/plugin/src/properties.cpp b/src/plugins/intel_npu/src/plugin/src/properties.cpp index 5087ad818f52b0..38871a550c1381 100644 --- a/src/plugins/intel_npu/src/plugin/src/properties.cpp +++ b/src/plugins/intel_npu/src/plugin/src/properties.cpp @@ -730,6 +730,7 @@ void Properties::registerCompiledModelProperties() { // Properties we shall only enable if they were set prior-to-compilation TRY_REGISTER_COMPILEDMODEL_PROPERTY_IFSET(ov::intel_npu::compiler_type, COMPILER_TYPE); + TRY_REGISTER_COMPILEDMODEL_PROPERTY_IFSET(ov::intel_npu::compiler_version, COMPILER_VERSION); TRY_REGISTER_COMPILEDMODEL_PROPERTY_IFSET(ov::weights_path, WEIGHTS_PATH); TRY_REGISTER_COMPILEDMODEL_PROPERTY_IFSET(ov::cache_dir, CACHE_DIR); TRY_REGISTER_COMPILEDMODEL_PROPERTY_IFSET(ov::enable_profiling, PERF_COUNT); diff --git a/src/plugins/intel_npu/tests/functional/behavior/compiled_model/property.cpp b/src/plugins/intel_npu/tests/functional/behavior/compiled_model/property.cpp index 2de8235f006763..783cded12853d2 100644 --- a/src/plugins/intel_npu/tests/functional/behavior/compiled_model/property.cpp +++ b/src/plugins/intel_npu/tests/functional/behavior/compiled_model/property.cpp @@ -49,8 +49,7 @@ std::vector> compat_plugin_internal_mutable_prop }; std::vector> plugin_internal_mutable_properties = { - {ov::intel_npu::stepping.name(), ov::Any(4)} -}; + {ov::intel_npu::stepping.name(), ov::Any(4)}}; std::vector> plugin_public_immutable_properties = { {ov::device::uuid.name(), ov::Any("deadbeef")}, @@ -64,8 +63,7 @@ std::vector> plugin_public_immutable_properties {ov::optimal_number_of_infer_requests.name(), ov::Any(4)}, {ov::intel_npu::device_alloc_mem_size.name(), ov::Any(2)}, {ov::intel_npu::device_total_mem_size.name(), ov::Any(2)}, - {ov::intel_npu::max_tiles.name(), ov::Any(9999)} -}; + {ov::intel_npu::max_tiles.name(), ov::Any(9999)}}; std::vector> invalid_device_ids = { {ov::device::id.name(), "NPU.1"}, @@ -152,4 +150,10 @@ INSTANTIATE_TEST_SUITE_P(smoke_BehaviorTests_CheckCompilerType, ::testing::ValuesIn(valid_device_ids)), CheckCompilerTypeProperty::getTestCaseName); +INSTANTIATE_TEST_SUITE_P(smoke_BehaviorTests_CheckCompilerVersion, + CheckCompilerVersionProperty, + ::testing::Combine(::testing::Values(ov::test::utils::DEVICE_NPU), + ::testing::ValuesIn(valid_device_ids)), + CheckCompilerVersionProperty::getTestCaseName); + } // namespace diff --git a/src/plugins/intel_npu/tests/functional/behavior/compiled_model/property.hpp b/src/plugins/intel_npu/tests/functional/behavior/compiled_model/property.hpp index 62a43b9ffc9d4a..322e347aa0004e 100644 --- a/src/plugins/intel_npu/tests/functional/behavior/compiled_model/property.hpp +++ b/src/plugins/intel_npu/tests/functional/behavior/compiled_model/property.hpp @@ -14,7 +14,6 @@ #include "intel_npu/npu_private_properties.hpp" #include "openvino/core/log.hpp" #include "zero_backend.hpp" -#include "zero_device.hpp" using namespace ov::test::behavior; @@ -549,6 +548,41 @@ TEST_P(CheckCompilerTypeProperty, GetCompilerVersion) { logs.clear(); } +using CheckCompilerVersionProperty = ClassExecutableNetworkGetPropertiesTestNPU; + +TEST_P(CheckCompilerVersionProperty, GetCompilerVersionFromCompiledModel) { + ov::Core core; + ov::CompiledModel compiled_model; + OV_ASSERT_NO_THROW(compiled_model = core.compile_model(model, deviceName)); + + uint32_t compiled_model_version = 0; + OV_ASSERT_NO_THROW(compiled_model_version = compiled_model.get_property(ov::intel_npu::compiler_version)); + + uint32_t plugin_version = 0; + OV_ASSERT_NO_THROW(plugin_version = core.get_property(deviceName, ov::intel_npu::compiler_version)); + ASSERT_EQ(compiled_model_version, plugin_version); +} + +TEST_P(CheckCompilerVersionProperty, CompilerVersionAvailableAfterImport) { + ov::Core core_compile, core_import; + ov::CompiledModel compiled_model, imported_model; + std::stringstream export_stream; + + OV_ASSERT_NO_THROW(compiled_model = core_compile.compile_model(model, deviceName)); + + uint32_t compiled_version = 0; + OV_ASSERT_NO_THROW(compiled_version = compiled_model.get_property(ov::intel_npu::compiler_version)); + + OV_ASSERT_NO_THROW(compiled_model.export_model(export_stream)); + compiled_model = {}; + + OV_ASSERT_NO_THROW(imported_model = core_import.import_model(export_stream, deviceName)); + + uint32_t imported_version = 0; + OV_ASSERT_NO_THROW(imported_version = imported_model.get_property(ov::intel_npu::compiler_version)); + ASSERT_EQ(imported_version, compiled_version); +} + using CheckCompilerPropertyWhenImporting = ClassExecutableNetworkGetPropertiesTestNPU; TEST_P(CheckCompilerPropertyWhenImporting, ExpectedThrowFromImportWithUnsupportedProperty) { From 27e83577936c37567bde004676e859ff991ecabe Mon Sep 17 00:00:00 2001 From: River Li Date: Wed, 22 Apr 2026 18:25:06 +0800 Subject: [PATCH 025/545] [GPU] use parallel ifstream to boost file reading latency (#34679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Details: - Goal: Maximize IO throughput when loading large OpenVINO GPU model caches (Blobs) on NVMe SSDs. - Bottleneck: The original implementation used single-threaded std::istream (read/sgetn). Due to standard library double-buffering and CPU memory copy overhead, the throughput was capped at 1GB/s, failing to saturate modern NVMe hardware (3.5GB/s+). image - Soultion - Create custom std::streambuf subclass that internally uses parallel I/O for large reads, exposing a standard std::istream-compatible interface. - Any code that currently accepts a std::istream gets the speedup transparently — no per-plugin changes required. image - iGPU test result: image image - dGPU test result: commit id - bd3a56440846aa4af883753a022ef6bc32522d08 image - TODO list - [x] To verify on Windows + iGPU - [x] To verify on Linux + iGPU - [x] To verify on Linux + dGPU ### Tickets: - CVS-179677 ### AI Assistance: - *AI assistance used: yes* - *If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).* --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../include/openvino/util/parallel_io.hpp | 85 +++ .../openvino/util/parallel_read_streambuf.hpp | 76 +++ src/common/util/src/os/lin/parallel_io.cpp | 69 +++ src/common/util/src/os/win/parallel_io.cpp | 92 +++ .../util/src/parallel_read_streambuf.cpp | 241 ++++++++ src/inference/src/cache_manager.hpp | 4 +- src/inference/src/single_file_storage.cpp | 6 +- .../unit/parallel_read_streambuf_test.cpp | 525 ++++++++++++++++++ .../tests/unit/single_file_storage.cpp | 43 ++ .../include/intel_gpu/primitives/data.hpp | 56 +- .../common_utils/parallel_mem_streambuf.cpp | 284 ++++++++++ .../common_utils/parallel_mem_streambuf.hpp | 49 ++ src/plugins/intel_gpu/src/graph/data.cpp | 27 +- src/plugins/intel_gpu/src/plugin/plugin.cpp | 5 +- .../parallel_mem_streambuf_test.cpp | 497 +++++++++++++++++ 15 files changed, 2048 insertions(+), 11 deletions(-) create mode 100644 src/common/util/include/openvino/util/parallel_io.hpp create mode 100644 src/common/util/include/openvino/util/parallel_read_streambuf.hpp create mode 100644 src/common/util/src/os/lin/parallel_io.cpp create mode 100644 src/common/util/src/os/win/parallel_io.cpp create mode 100644 src/common/util/src/parallel_read_streambuf.cpp create mode 100644 src/inference/tests/unit/parallel_read_streambuf_test.cpp create mode 100644 src/plugins/intel_gpu/src/graph/common_utils/parallel_mem_streambuf.cpp create mode 100644 src/plugins/intel_gpu/src/graph/common_utils/parallel_mem_streambuf.hpp create mode 100644 src/plugins/intel_gpu/tests/unit/test_utils/parallel_mem_streambuf_test.cpp diff --git a/src/common/util/include/openvino/util/parallel_io.hpp b/src/common/util/include/openvino/util/parallel_io.hpp new file mode 100644 index 00000000000000..234a23af23c759 --- /dev/null +++ b/src/common/util/include/openvino/util/parallel_io.hpp @@ -0,0 +1,85 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +/** + * @brief A header file for definition of platform-specific parallel I/O primitives. + * @file parallel_io.hpp + */ + +#pragma once + +#include +#include + +#include "openvino/util/mmap_object.hpp" + +namespace ov::util { + +#ifndef _WIN32 +inline constexpr FileHandle INVALID_HANDLE_VALUE = -1; +#endif + +inline constexpr size_t default_parallel_io_threshold = 4UL * 1024 * 1024; ///< 4 MB default threshold for parallel I/O +inline constexpr size_t default_parallel_io_min_chunk = 2UL * 1024 * 1024; ///< 2 MB minimum chunk size per thread + +/** + * @brief Open a file for reading and retrieve its size. + * + * On Linux, uses open(O_RDONLY | O_CLOEXEC) + fstat. + * On Windows, uses CreateFileW(GENERIC_READ) + GetFileSizeEx. + * + * @param path Path to the file to open. + * @param file_offset Header offset to validate (must be within [0, file_size]). + * @param out_handle [out] The opened file handle / descriptor. + * @param out_size [out] Total size of the file in bytes. + * @throws std::runtime_error If the file cannot be opened or its size cannot be queried. + * @throws std::out_of_range If file_offset is outside [0, file_size]. + */ +void get_file_handle_and_size(const std::filesystem::path& path, + std::streamoff file_offset, + FileHandle& out_handle, + std::streamoff& out_size); + +/** + * @brief Close a platform file handle. + * + * On Linux, calls close(fd). On Windows, calls CloseHandle(handle). + * Safe to call with an invalid handle (no-op). + * + * @param handle The file handle to close. + */ +void close_file_handle(FileHandle handle); + +/** + * @brief Open a file for reading (lightweight, for per-thread file handles). + * + * Each worker thread in a parallel read should open its own file handle so that + * the OS readahead state is independent per thread. + * + * On Linux, uses open(O_RDONLY | O_CLOEXEC). + * On Windows, uses CreateFileW(GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE). + * + * @param path Path to the file. + * @return A valid file handle, or the platform-specific invalid value on failure. + */ +FileHandle open_file_for_read(const std::filesystem::path& path); + +/** + * @brief Read bytes from a file at a given absolute offset (single-threaded). + * + * On Linux, uses pread() in a loop. + * On Windows, uses SetFilePointerEx + ReadFile in a loop. + * + * @note This function is @b not thread-safe for a given handle. Callers that + * perform parallel reads must open a separate handle per thread (see + * open_file_for_read). + * + * @param handle File handle / descriptor. + * @param dst Destination buffer. + * @param size Number of bytes to read. + * @param file_offset Absolute byte offset in the file. + * @return true if all bytes were read successfully, false on I/O error. + */ +bool positional_read(FileHandle handle, char* dst, size_t size, size_t file_offset); +} // namespace ov::util \ No newline at end of file diff --git a/src/common/util/include/openvino/util/parallel_read_streambuf.hpp b/src/common/util/include/openvino/util/parallel_read_streambuf.hpp new file mode 100644 index 00000000000000..2d3c94e821895c --- /dev/null +++ b/src/common/util/include/openvino/util/parallel_read_streambuf.hpp @@ -0,0 +1,76 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +/** + * @brief A header file for definition of a parallel I/O streambuf for file-based reads. + * @file parallel_read_streambuf.hpp + */ + +#pragma once + +#include +#include +#include + +#include "openvino/util/parallel_io.hpp" + +namespace ov::util { + +/** + * @brief A std::streambuf that reads from a file using parallel I/O for large + * reads, bypassing the OS page cache pressure that mmap+memcpy incurs. + * + * For reads >= threshold bytes, the read is split across N threads where each + * thread issues its own independent positional read operation using + * platform-specific APIs. Smaller reads fall through to a single positional call. + * + * Usage: + * @code + * ParallelReadStreamBuf buf(cache_path, blob_offset_in_file); + * std::istream stream(&buf); + * cldnn::BinaryInputBuffer ib(stream, engine); + * ib >> ...; + * @endcode + */ +class ParallelReadStreamBuf : public std::streambuf { +public: + /** + * @param path Path to the file to read. + * @param header_offset Initial file position (absolute offset from the start + * of the file; the stream starts reading from here). + * @param threshold Minimum read size to trigger parallel I/O. + */ + explicit ParallelReadStreamBuf(const std::filesystem::path& path, + std::streamoff header_offset = 0, + size_t threshold = default_parallel_io_threshold); + + ~ParallelReadStreamBuf() override; + + ParallelReadStreamBuf(const ParallelReadStreamBuf&) = delete; + ParallelReadStreamBuf& operator=(const ParallelReadStreamBuf&) = delete; + +protected: + std::streamsize xsgetn(char_type* dst, std::streamsize n) override; + int_type underflow() override; + pos_type seekoff(off_type off, std::ios_base::seekdir way, std::ios_base::openmode which) override; + pos_type seekpos(pos_type pos, std::ios_base::openmode which) override; + std::streamsize showmanyc() override; + +private: + bool single_read(char* dst, size_t size, size_t file_offset); + bool parallel_read(char* dst, size_t size, size_t file_offset); + + static constexpr size_t UNDERFLOW_BUF = 8192; ///< batch size for char-by-char reads + + std::filesystem::path m_path; + FileHandle m_handle; ///< platform file handle + + std::streamoff m_file_offset = 0; ///< absolute file offset of next byte to read + std::streamoff m_header_offset = 0; ///< absolute file offset of logical stream start + std::streamoff m_file_size = 0; + size_t m_threshold = default_parallel_io_threshold; + std::unique_ptr m_underflow_buf; ///< lazily allocated buffer for underflow() +}; + +} // namespace ov::util \ No newline at end of file diff --git a/src/common/util/src/os/lin/parallel_io.cpp b/src/common/util/src/os/lin/parallel_io.cpp new file mode 100644 index 00000000000000..0c42d1597d9711 --- /dev/null +++ b/src/common/util/src/os/lin/parallel_io.cpp @@ -0,0 +1,69 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "openvino/util/parallel_io.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "openvino/util/file_util.hpp" + +namespace ov::util { + +void get_file_handle_and_size(const std::filesystem::path& path, + std::streamoff file_offset, + FileHandle& out_handle, + std::streamoff& out_size) { + out_handle = ::open(path.c_str(), O_RDONLY | O_CLOEXEC); + if (out_handle == -1) { + throw std::runtime_error("Cannot open file: " + ov::util::path_to_string(path)); + } + struct stat st = {}; + if (::fstat(out_handle, &st) != 0) { + ::close(out_handle); + out_handle = -1; + throw std::runtime_error("Cannot stat file: " + ov::util::path_to_string(path)); + } + out_size = static_cast(st.st_size); + if (file_offset < 0 || file_offset > out_size) { + ::close(out_handle); + out_handle = -1; + throw std::out_of_range("header_offset is out of range for file: " + ov::util::path_to_string(path)); + } +} + +void close_file_handle(FileHandle handle) { + if (handle != -1) { + ::close(handle); + } +} + +FileHandle open_file_for_read(const std::filesystem::path& path) { + return ::open(path.c_str(), O_RDONLY | O_CLOEXEC); +} + +bool positional_read(FileHandle handle, char* dst, size_t size, size_t file_offset) { + char* cur = dst; + size_t remaining = size; + off_t cur_offset = static_cast(file_offset); + while (remaining > 0) { + const ssize_t n = ::pread(handle, cur, remaining, cur_offset); + if (n <= 0) { + return false; + } + cur += n; + cur_offset += n; + remaining -= static_cast(n); + } + return true; +} + +} // namespace ov::util diff --git a/src/common/util/src/os/win/parallel_io.cpp b/src/common/util/src/os/win/parallel_io.cpp new file mode 100644 index 00000000000000..fd5bf2f6b20146 --- /dev/null +++ b/src/common/util/src/os/win/parallel_io.cpp @@ -0,0 +1,92 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "openvino/util/parallel_io.hpp" + +// clang-format off +#ifndef NOMINMAX +# define NOMINMAX +#endif +#include +// clang-format on + +#include +#include + +#include "openvino/util/file_util.hpp" + +namespace ov::util { + +void get_file_handle_and_size(const std::filesystem::path& path, + std::streamoff file_offset, + FileHandle& out_handle, + std::streamoff& out_size) { + out_handle = CreateFileW(path.c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr); + if (out_handle == INVALID_HANDLE_VALUE) { + throw std::runtime_error("Cannot open file: " + ov::util::path_to_string(path)); + } + LARGE_INTEGER file_size = {}; + if (!GetFileSizeEx(out_handle, &file_size)) { + CloseHandle(out_handle); + out_handle = INVALID_HANDLE_VALUE; + throw std::runtime_error("Cannot get file size: " + ov::util::path_to_string(path)); + } + out_size = static_cast(file_size.QuadPart); + if (file_offset < 0 || file_offset > out_size) { + CloseHandle(out_handle); + out_handle = INVALID_HANDLE_VALUE; + throw std::out_of_range("header_offset is out of range for file: " + ov::util::path_to_string(path)); + } +} + +void close_file_handle(FileHandle handle) { + if (handle != INVALID_HANDLE_VALUE) { + CloseHandle(handle); + } +} + +FileHandle open_file_for_read(const std::filesystem::path& path) { + return CreateFileW(path.native().c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr); +} + +bool positional_read(FileHandle handle, char* dst, size_t size, size_t file_offset) { + char* cur = dst; + size_t remaining = size; + size_t cur_offset = file_offset; + while (remaining > 0) { + const DWORD to_read = static_cast((std::min)(remaining, static_cast(UINT_MAX - 1024u))); + // Use OVERLAPPED to specify the file offset directly, making the read + // positional and independent of the file pointer. This is the Windows + // equivalent of Linux pread() and is safe for concurrent use on + // separate handles (no TOCTOU between SetFilePointerEx + ReadFile). + OVERLAPPED ov = {}; + ov.Offset = static_cast(cur_offset & 0xFFFFFFFFULL); + ov.OffsetHigh = static_cast((cur_offset >> 32) & 0xFFFFFFFFULL); + DWORD bytes_read = 0; + if (!ReadFile(handle, cur, to_read, &bytes_read, &ov)) { + return false; + } + if (bytes_read == 0) { + return false; + } + cur += bytes_read; + cur_offset += bytes_read; + remaining -= bytes_read; + } + return true; +} + +} // namespace ov::util \ No newline at end of file diff --git a/src/common/util/src/parallel_read_streambuf.cpp b/src/common/util/src/parallel_read_streambuf.cpp new file mode 100644 index 00000000000000..0ab43588763854 --- /dev/null +++ b/src/common/util/src/parallel_read_streambuf.cpp @@ -0,0 +1,241 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "openvino/util/parallel_read_streambuf.hpp" + +#ifdef _WIN32 +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "openvino/util/file_util.hpp" +#include "openvino/util/parallel_io.hpp" + +namespace ov::util { + +ParallelReadStreamBuf::ParallelReadStreamBuf(const std::filesystem::path& path, + std::streamoff header_offset, + size_t threshold) + : m_path(path), + m_file_offset(header_offset), + m_header_offset(header_offset), + m_threshold(threshold) { + get_file_handle_and_size(path, m_file_offset, m_handle, m_file_size); +} + +ParallelReadStreamBuf::~ParallelReadStreamBuf() { + close_file_handle(m_handle); +} + +// xsgetn: main hot path - called by sgetn() for all bulk reads +std::streamsize ParallelReadStreamBuf::xsgetn(char_type* dst, std::streamsize n) { + if (n <= 0) + return 0; + + std::streamsize total = 0; + + // Drain any chars previously buffered by underflow() + if (gptr() != nullptr && gptr() < egptr()) { + const std::streamsize avail = static_cast(egptr() - gptr()); + const std::streamsize from_buf = (std::min)(n, avail); + std::memcpy(dst, gptr(), static_cast(from_buf)); + // Safe: from_buf <= UNDERFLOW_BUF (8192), always fits in int. + static_assert(UNDERFLOW_BUF <= static_cast((std::numeric_limits::max)()), + "UNDERFLOW_BUF must fit in int for gbump()"); + gbump(static_cast(from_buf)); + total += from_buf; + dst += from_buf; + n -= from_buf; + } + + if (n <= 0 || m_file_offset >= m_file_size) { + return total; + } + + const std::streamoff remaining = m_file_size - m_file_offset; + const std::streamsize to_read = static_cast((std::min)(static_cast(n), remaining)); + + const size_t bytes = static_cast(to_read); + const size_t offset = static_cast(m_file_offset); + + bool ok = (bytes >= m_threshold) ? parallel_read(dst, bytes, offset) : single_read(dst, bytes, offset); + + if (ok) { + m_file_offset += to_read; + total += to_read; + } + + return total; +} + +// underflow: called for single-char peek / non-bulk reads (e.g. std::getline) +ParallelReadStreamBuf::int_type ParallelReadStreamBuf::underflow() { + if (m_file_offset >= m_file_size) { + return traits_type::eof(); + } + if (!m_underflow_buf) { + m_underflow_buf = std::make_unique(UNDERFLOW_BUF); + } + // Read a batch of up to UNDERFLOW_BUF bytes so that character-by-character + // consumers (std::getline, operator>>) don't issue one pread per char. + const size_t to_read = + static_cast((std::min)(static_cast(UNDERFLOW_BUF), m_file_size - m_file_offset)); + if (!single_read(m_underflow_buf.get(), to_read, static_cast(m_file_offset))) { + return traits_type::eof(); + } + // Advance m_file_offset past the bytes we just read into the get area. + // m_file_offset now points to the byte after egptr(), consistent with + // the seekoff(0, cur) formula: logical_pos = m_file_offset - (egptr - gptr). + m_file_offset += static_cast(to_read); + setg(m_underflow_buf.get(), m_underflow_buf.get(), m_underflow_buf.get() + to_read); + return traits_type::to_int_type(m_underflow_buf[0]); +} + +ParallelReadStreamBuf::pos_type ParallelReadStreamBuf::seekoff(off_type off, + std::ios_base::seekdir way, + std::ios_base::openmode /* which */) { + // All internal positions (m_file_offset, m_file_size, m_header_offset) are + // absolute byte offsets from the start of the file. The public-facing + // stream positions are *logical* offsets: 0 == header_offset in the file. + std::streamoff new_pos = 0; + if (way == std::ios_base::beg) { + // off is a logical offset; translate to absolute file offset. + new_pos = m_header_offset + off; + } else if (way == std::ios_base::cur) { + // Account for the buffered chars from underflow() not yet consumed. + const std::streamsize ahead = (gptr() != nullptr) ? static_cast(egptr() - gptr()) : 0; + new_pos = m_file_offset - ahead + off; // stays absolute + // Pure tell (off == 0): return current position without any side effects + // on the get area or m_file_offset. Discarding the underflow buffer on a + // tell would force the next read to re-issue a pread for data that is + // already buffered, breaking interleaved getline()+tellg() patterns. + if (off == 0) { + if (new_pos < m_header_offset || new_pos > m_file_size) + return pos_type(off_type(-1)); + return pos_type(new_pos - m_header_offset); + } + } else { + new_pos = m_file_size + off; // stays absolute + } + + // Reject seeks before the logical stream start or past the file end. + if (new_pos < m_header_offset || new_pos > m_file_size) { + return pos_type(off_type(-1)); + } + + setg(nullptr, nullptr, nullptr); // invalidate get-area + m_file_offset = new_pos; + // Return the logical position (0 == start of exposed stream). + return pos_type(m_file_offset - m_header_offset); +} + +ParallelReadStreamBuf::pos_type ParallelReadStreamBuf::seekpos(pos_type pos, std::ios_base::openmode /* which */) { + return seekoff(off_type(pos), std::ios_base::beg, std::ios_base::in); +} + +std::streamsize ParallelReadStreamBuf::showmanyc() { + // Report both buffered characters (in the get area) and remaining + // bytes in the underlying file. + // Per [streambuf.virt.get]/6: return -1 when the next call to + // underflow() would return traits_type::eof() (i.e. stream truly + // exhausted). Return 0 when availability is unknown. Here both + // the file and the get-area are fully accounted for, so -1 at + // total==0 is correct — underflow() would indeed return EOF. + std::streamsize buffered = 0; + if (gptr() != nullptr && egptr() != nullptr && egptr() > gptr()) { + buffered = static_cast(egptr() - gptr()); + } + std::streamoff remaining_off = m_file_size - m_file_offset; + if (remaining_off < 0) { + remaining_off = 0; + } + const std::streamsize remaining = remaining_off > 0 ? static_cast(remaining_off) : 0; + const std::streamsize total = buffered + remaining; + return total > 0 ? total : static_cast(-1); +} + +// Single-threaded positional read +bool ParallelReadStreamBuf::single_read(char* dst, size_t size, size_t file_offset) { + return positional_read(m_handle, dst, size, file_offset); +} + +// Parallel positional read +bool ParallelReadStreamBuf::parallel_read(char* dst, size_t size, size_t file_offset) { + const size_t hw_threads = (std::max)(size_t{1}, static_cast(std::thread::hardware_concurrency())); + const size_t max_by_size = size / (1024 * 1024); // 1 thread per MB + const size_t num_threads = (std::max)(size_t{1}, (std::min)(hw_threads, max_by_size)); + + if (num_threads == 1) { + return single_read(dst, size, file_offset); + } + + // Round chunk_size UP to the next 4 KiB boundary so that every thread's + // start offset is page-aligned (better I/O coalescing on NVMe/direct I/O). + // Because rounding up means num_threads * chunk_size >= size, two extra + // guards are required: + // 1. Non-last threads: cap read to min(chunk_size, size - cur_offset) so + // they never stride past EOF when the aligned chunk extends beyond it. + // 2. Last thread: use (size - cur_offset) to capture every remaining byte + // including the fragment that lies beyond (num_threads-1) * chunk_size + // but before size. Using chunk_size here would silently drop those bytes. + size_t chunk_size = size / num_threads; + chunk_size = (chunk_size + 4095u) & ~size_t{4095u}; + + std::atomic success{true}; + // Each worker opens its own file descriptor so that Linux's per-file- + // description readahead state (file_ra_state / f_ra) is independent per + // thread. Sharing a single fd causes concurrent pread() calls to corrupt + // each other's sequential readahead prediction, collapsing throughput from + // ~3.5 GB/s sequential to ~0.5 GB/s. + std::vector workers; + workers.reserve(num_threads); + for (size_t ithr = 0; ithr < num_threads; ++ithr) { + try { + workers.emplace_back([&, ithr]() { + const size_t cur_offset = ithr * chunk_size; + if (cur_offset >= size) { + return; // page-alignment rounding created a surplus worker slot + } + // Last thread: read everything remaining (includes the fragment that + // falls between (num_threads-1)*chunk_size and size). + // Non-last threads: cap to min(chunk_size, size - cur_offset) so we + // never read past eof when alignment pushed the chunk boundary beyond it. + const size_t read_size = + (ithr == num_threads - 1) ? (size - cur_offset) : (std::min)(chunk_size, size - cur_offset); + char* const ptr = dst + cur_offset; + const size_t thread_file_offset = file_offset + cur_offset; + + FileHandle t_handle = open_file_for_read(m_path); + if (t_handle == INVALID_HANDLE_VALUE) { + success = false; + return; + } + + if (!positional_read(t_handle, ptr, read_size, thread_file_offset)) { + success = false; + } + close_file_handle(t_handle); + }); // workers.emplace_back + } catch (...) { + success = false; + break; + } + } + for (auto& t : workers) { + t.join(); + } + return success.load(); +} + +} // namespace ov::util diff --git a/src/inference/src/cache_manager.hpp b/src/inference/src/cache_manager.hpp index 54f8d0e8d4bcae..ff4cdc5d9ae5d2 100644 --- a/src/inference/src/cache_manager.hpp +++ b/src/inference/src/cache_manager.hpp @@ -20,6 +20,7 @@ #include "openvino/runtime/tensor.hpp" #include "openvino/util/file_util.hpp" #include "openvino/util/mmap_object.hpp" +#include "openvino/util/parallel_read_streambuf.hpp" namespace ov { @@ -75,7 +76,8 @@ class FileStorageCacheManager final : public ICacheManager { CompiledBlobVariant compiled_blob{std::in_place_index<0>, ov::read_tensor_data(blob_path)}; reader(compiled_blob); } else { - std::ifstream stream(blob_path, std::ios_base::binary); + ov::util::ParallelReadStreamBuf par_buf(blob_path); + std::istream stream(&par_buf); CompiledBlobVariant compiled_blob{std::in_place_index<1>, std::ref(stream)}; reader(compiled_blob); } diff --git a/src/inference/src/single_file_storage.cpp b/src/inference/src/single_file_storage.cpp index 6c846a055d20cb..e9171ac7c550a4 100644 --- a/src/inference/src/single_file_storage.cpp +++ b/src/inference/src/single_file_storage.cpp @@ -7,6 +7,7 @@ #include "openvino/runtime/aligned_buffer.hpp" #include "openvino/util/file_util.hpp" #include "openvino/util/mmap_object.hpp" +#include "openvino/util/parallel_read_streambuf.hpp" #include "openvino/util/variant_visitor.hpp" namespace ov::runtime { @@ -259,8 +260,9 @@ void SingleFileStorage::read_cache_entry(const std::string& blob_id, bool enable blob_pos)}; reader(compiled_blob); } else { - std::ifstream stream(m_file_path, std::ios::binary); - stream.seekg(blob_pos); + // Use parallel file I/O to saturate NVMe bandwidth instead of single-threaded ifstream. + ov::util::ParallelReadStreamBuf par_buf(m_file_path, static_cast(blob_pos)); + std::istream stream(&par_buf); CompiledBlobVariant compiled_blob{std::in_place_index<1>, std::ref(stream)}; reader(compiled_blob); } diff --git a/src/inference/tests/unit/parallel_read_streambuf_test.cpp b/src/inference/tests/unit/parallel_read_streambuf_test.cpp new file mode 100644 index 00000000000000..40c21e801aee2f --- /dev/null +++ b/src/inference/tests/unit/parallel_read_streambuf_test.cpp @@ -0,0 +1,525 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "openvino/util/parallel_read_streambuf.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "common_test_utils/common_utils.hpp" + +namespace ov::test { + +namespace { + +/** + * @brief Fill a vector with a deterministic pattern unique per byte position. + * + * byte[i] = (i % 251) -- 251 is prime so the period never aligns with any + * power-of-two chunk/page size. + */ +void fill_pattern(std::vector& buf, size_t start_index = 0) { + for (size_t i = 0; i < buf.size(); ++i) { + buf[i] = static_cast((start_index + i) % 251u); + } +} + +/** + * @brief Write data to a file, preceded by prefix_size bytes of 0xFF garbage + * so that non-zero-offset tests can verify the header bytes are never + * surfaced through the streambuf. + */ +// ASSERT_* macros expand to `return` (void), so they cannot be used directly +// in a non-void function. The canonical GTest pattern is to delegate to a +// void helper, then check HasFatalFailure() before continuing. +void write_temp_file_impl(const std::filesystem::path& path, const std::vector& data, size_t prefix_size) { + std::ofstream ofs(path, std::ios::binary | std::ios::trunc); + ASSERT_TRUE(ofs.is_open()) << "Cannot create temp file: " << path; + if (prefix_size > 0) { + std::vector prefix(prefix_size, static_cast(0xFFu)); + ofs.write(prefix.data(), static_cast(prefix_size)); + } + ofs.write(data.data(), static_cast(data.size())); +} + +} // namespace + +// Test fixture – creates a temporary file and removes it in TearDown +class ParallelReadStreamBufTest : public ::testing::Test { +protected: + std::filesystem::path m_tmp_path; + + void SetUp() override { + m_tmp_path = ov::test::utils::generateTestFilePrefix() + "_par_read.bin"; + } + + void setup_temp_file(const std::vector& data, size_t prefix_size = 0) { + ASSERT_FALSE(m_tmp_path.empty()); + write_temp_file_impl(m_tmp_path, data, prefix_size); + } + + void TearDown() override { + if (!m_tmp_path.empty() && std::filesystem::exists(m_tmp_path)) { + std::filesystem::remove(m_tmp_path); + } + } +}; + +// 1. Full sequential read – threshold=1 forces parallel_read() to be called; +// num_threads collapses to 1 for < 1 MB so single_read() is used, but the +// dispatch code path (chunk math, atomic success flag) is exercised. +TEST_F(ParallelReadStreamBufTest, FullReadSmallThreshold) { + constexpr size_t k_size = 16 * 1024; // 16 KB + std::vector expected(k_size); + fill_pattern(expected); + setup_temp_file(expected); + + util::ParallelReadStreamBuf buf(m_tmp_path, /*header_offset=*/0, /*threshold=*/1); + std::istream stream(&buf); + + std::vector got(k_size); + ASSERT_TRUE(stream.read(got.data(), static_cast(k_size))); + EXPECT_EQ(got, expected); +} + +// 2. Non-zero header_offset: the file starts with a "garbage" prefix that +// must never appear in reads made through the streambuf. +TEST_F(ParallelReadStreamBufTest, NonZeroHeaderOffsetSmallData) { + constexpr size_t k_prefix_size = 512; + constexpr size_t k_payload_size = 4 * 1024; + + std::vector payload(k_payload_size); + fill_pattern(payload); + setup_temp_file(payload, k_prefix_size); + + util::ParallelReadStreamBuf buf(m_tmp_path, + static_cast(k_prefix_size), + /*threshold=*/1); + std::istream stream(&buf); + + std::vector got(k_payload_size); + ASSERT_TRUE(stream.read(got.data(), static_cast(k_payload_size))); + EXPECT_EQ(got, payload); +} + +// 3. Multiple consecutive reads – each partial read must pick up exactly +// where the previous one left off. +TEST_F(ParallelReadStreamBufTest, ChunkedReads) { + constexpr size_t k_size = 8 * 1024; + std::vector expected(k_size); + fill_pattern(expected); + setup_temp_file(expected); + + util::ParallelReadStreamBuf buf(m_tmp_path, 0, /*threshold=*/1); + std::istream stream(&buf); + + std::vector got(k_size); + constexpr size_t k_chunk = 1000; // intentionally not a power-of-2 + size_t offset = 0; + while (offset < k_size) { + const size_t n = std::min(k_chunk, k_size - offset); + ASSERT_TRUE(stream.read(got.data() + offset, static_cast(n))); + offset += n; + } + EXPECT_EQ(got, expected); +} + +// 4. underflow() path: reading character-by-character exercises the internal +// 8 KB underflow buffer and the get-area bookkeeping. +TEST_F(ParallelReadStreamBufTest, CharByCharUnderflow) { + constexpr size_t k_size = 300; // small enough to fit in a single underflow fill + std::vector expected(k_size); + fill_pattern(expected); + setup_temp_file(expected); + + util::ParallelReadStreamBuf buf(m_tmp_path, 0, /*threshold=*/SIZE_MAX); // force all reads via underflow + std::istream stream(&buf); + + std::vector got; + got.reserve(k_size); + int ch; + while ((ch = stream.get()) != std::char_traits::eof()) { + got.push_back(static_cast(ch)); + } + ASSERT_EQ(got.size(), k_size); + EXPECT_EQ(got, expected); +} + +// 5. seekg(pos, beg): absolute seek then read must return bytes at that +// logical position (relative to the start exposed by the streambuf, i.e. +// after the header_offset). +TEST_F(ParallelReadStreamBufTest, SeekFromBeginning) { + constexpr size_t k_size = 2 * 1024; + std::vector expected(k_size); + fill_pattern(expected); + setup_temp_file(expected); + + util::ParallelReadStreamBuf buf(m_tmp_path, 0, /*threshold=*/1); + std::istream stream(&buf); + + // Seek to byte 500 and read 16 bytes + constexpr std::streamoff k_seek_pos = 500; + constexpr size_t k_read_len = 16; + stream.seekg(k_seek_pos, std::ios::beg); + ASSERT_TRUE(stream.good()); + + std::vector got(k_read_len); + ASSERT_TRUE(stream.read(got.data(), static_cast(k_read_len))); + + std::vector slice(expected.begin() + k_seek_pos, expected.begin() + k_seek_pos + k_read_len); + EXPECT_EQ(got, slice); +} + +// 6. seekg(off, cur): seek relative to current position. +TEST_F(ParallelReadStreamBufTest, SeekFromCurrent) { + constexpr size_t k_size = 2 * 1024; + std::vector expected(k_size); + fill_pattern(expected); + setup_temp_file(expected); + + util::ParallelReadStreamBuf buf(m_tmp_path, 0, /*threshold=*/1); + std::istream stream(&buf); + + // Read 100 bytes, skip 200 forward, read another 50 + constexpr size_t k_first_read = 100; + constexpr std::streamoff k_skip = 200; + constexpr size_t k_second_read = 50; + + std::vector first(k_first_read); + ASSERT_TRUE(stream.read(first.data(), static_cast(k_first_read))); + EXPECT_EQ(first, std::vector(expected.begin(), expected.begin() + k_first_read)); + + stream.seekg(k_skip, std::ios::cur); + ASSERT_TRUE(stream.good()); + + std::vector second(k_second_read); + ASSERT_TRUE(stream.read(second.data(), static_cast(k_second_read))); + + const size_t expected_start = k_first_read + static_cast(k_skip); + std::vector expected_slice(expected.begin() + expected_start, + expected.begin() + expected_start + k_second_read); + EXPECT_EQ(second, expected_slice); +} + +// 7. seekg(off, end): seek backward from end-of-file. +TEST_F(ParallelReadStreamBufTest, SeekFromEnd) { + constexpr size_t k_size = 2 * 1024; + std::vector expected(k_size); + fill_pattern(expected); + setup_temp_file(expected); + + util::ParallelReadStreamBuf buf(m_tmp_path, 0, /*threshold=*/1); + std::istream stream(&buf); + + constexpr std::streamoff k_from_end = 64; + stream.seekg(-k_from_end, std::ios::end); + ASSERT_TRUE(stream.good()); + + std::vector got(static_cast(k_from_end)); + ASSERT_TRUE(stream.read(got.data(), k_from_end)); + + std::vector tail(expected.end() - k_from_end, expected.end()); + EXPECT_EQ(got, tail); +} + +// 8. seekg(0, end) then tellg() should equal the file (payload) size. +TEST_F(ParallelReadStreamBufTest, TellgAtEnd) { + constexpr size_t k_size = 1024; + std::vector data(k_size, static_cast(0xAA)); + setup_temp_file(data); + + util::ParallelReadStreamBuf buf(m_tmp_path, 0, /*threshold=*/1); + std::istream stream(&buf); + + stream.seekg(0, std::ios::end); + ASSERT_TRUE(stream.good()); + EXPECT_EQ(static_cast(stream.tellg()), k_size); +} + +// 9. Seek with non-zero header_offset: logical pos 0 == file offset (prefix). +// seeking to the end should give the payload size, not the whole file size. +TEST_F(ParallelReadStreamBufTest, SeekRespectsHeaderOffset) { + constexpr size_t k_prefix_size = 256; + constexpr size_t k_payload_size = 1024; + + std::vector payload(k_payload_size); + fill_pattern(payload); + setup_temp_file(payload, k_prefix_size); + + util::ParallelReadStreamBuf buf(m_tmp_path, + static_cast(k_prefix_size), + /*threshold=*/1); + std::istream stream(&buf); + + // tellg at start should be 0 (relative to payload start) + EXPECT_EQ(static_cast(stream.tellg()), 0u); + + // seekg to end, tellg should equal payload size + stream.seekg(0, std::ios::end); + ASSERT_TRUE(stream.good()); + EXPECT_EQ(static_cast(stream.tellg()), k_payload_size); + + // Seek to byte 100 and read 8 bytes + constexpr std::streamoff k_pos = 100; + stream.seekg(k_pos, std::ios::beg); + std::vector got(8); + ASSERT_TRUE(stream.read(got.data(), 8)); + std::vector expected(payload.begin() + k_pos, payload.begin() + k_pos + 8); + EXPECT_EQ(got, expected); +} + +// 10. Out-of-range seek returns pos_type(-1) and leaves stream in a fail state. +TEST_F(ParallelReadStreamBufTest, OutOfRangeSeekFails) { + constexpr size_t k_size = 64; + std::vector data(k_size, static_cast(0x55)); + setup_temp_file(data); + + util::ParallelReadStreamBuf buf(m_tmp_path, 0, /*threshold=*/1); + std::istream stream(&buf); + + // Seek before start + const auto pos = stream.seekg(-1, std::ios::beg).tellg(); + EXPECT_EQ(pos, std::streampos(-1)); +} + +// 11. Reading exactly at EOF: request more bytes than remain – stream.read() +// must return false and gcount() must equal the bytes that were available. +TEST_F(ParallelReadStreamBufTest, ReadAtEof) { + constexpr size_t k_size = 100; + std::vector expected(k_size); + fill_pattern(expected); + setup_temp_file(expected); + + util::ParallelReadStreamBuf buf(m_tmp_path, 0, /*threshold=*/SIZE_MAX); // use underflow path + std::istream stream(&buf); + + // Read all but last 10 bytes + std::vector buf1(k_size - 10); + ASSERT_TRUE(stream.read(buf1.data(), static_cast(k_size - 10))); + + // Now try to read 20 bytes when only 10 remain + std::vector buf2(20, 0); + const bool ok = static_cast(stream.read(buf2.data(), 20)); + EXPECT_FALSE(ok); + EXPECT_TRUE(stream.eof()); + ASSERT_EQ(stream.gcount(), 10); + EXPECT_TRUE(std::equal(buf2.begin(), buf2.begin() + 10, expected.end() - 10)); +} + +// 12. PARALLEL PATH CORRECTNESS – large read (>= 2 MB) with threshold=1 so +// parallel_read() is always invoked. On ≥ 2-core machines the actual +// parallel dispatch fires; on single-core machines num_threads==1 still +// exercises the chunk-boundary math via single_read(). +// +// The test verifies: +// a) The full buffer is byte-exact after a parallel read. +// b) A second consecutive parallel read immediately following also +// produces the correct data (no state corruption between calls). +TEST_F(ParallelReadStreamBufTest, ParallelDispatchFullReadCorrectness) { + // Use hw_threads * 1 MB + 1 byte so that on any N-core machine, + // min(hw, size/1MB) > 1 whenever hw >= 2. To avoid excessive memory / I/O + // on very high-core CI runners, cap the effective hw used for sizing. + constexpr size_t k_max_hw_for_size = 16; + const size_t raw_hw = std::max(size_t{2}, static_cast(std::thread::hardware_concurrency())); + const size_t hw = std::min(k_max_hw_for_size, raw_hw); + const size_t k_size = hw * 1024 * 1024 + 1; + + std::vector expected(k_size); + fill_pattern(expected); + setup_temp_file(expected); + + util::ParallelReadStreamBuf buf(m_tmp_path, 0, /*threshold=*/1); + std::istream stream(&buf); + + // a) First parallel read: the full buffer must be byte-exact. + std::vector got(k_size); + ASSERT_TRUE(stream.read(got.data(), static_cast(k_size))); + EXPECT_EQ(got, expected) << "First parallel read produced incorrect data"; + + // b) Seek back to start and do a second full parallel read immediately. + // Verifies no state corruption (m_file_offset, fd, etc.) between calls. + stream.clear(); + stream.seekg(0, std::ios::beg); + ASSERT_TRUE(stream.good()); + + std::vector got2(k_size); + ASSERT_TRUE(stream.read(got2.data(), static_cast(k_size))); + EXPECT_EQ(got2, expected) << "Second consecutive parallel read produced incorrect data"; +} + +// 13. PARALLEL PATH with NON-ZERO header_offset and a seek in the middle: +// file = 4-KB header + (hw*1 MB) payload. After reading half the payload, +// seek back to position 0 (start of payload), read the whole payload again. +TEST_F(ParallelReadStreamBufTest, ParallelDispatchNonZeroOffset_AndSeek) { + constexpr size_t k_prefix_size = 4 * 1024; + constexpr size_t k_max_hw_for_size = 16; + const size_t hw = + std::min(k_max_hw_for_size, std::max(size_t{2}, static_cast(std::thread::hardware_concurrency()))); + const size_t k_payload_size = hw * 1024 * 1024; + + std::vector payload(k_payload_size); + fill_pattern(payload); + setup_temp_file(payload, k_prefix_size); + + util::ParallelReadStreamBuf buf(m_tmp_path, + static_cast(k_prefix_size), + /*threshold=*/1); + std::istream stream(&buf); + + // First pass: read the first half + const size_t k_half = k_payload_size / 2; + std::vector first_half(k_half); + ASSERT_TRUE(stream.read(first_half.data(), static_cast(k_half))); + EXPECT_TRUE(std::equal(first_half.begin(), first_half.end(), payload.begin())) + << "First-half read produced incorrect data"; + + // Seek back to the logical start of the payload + stream.seekg(0, std::ios::beg); + ASSERT_TRUE(stream.good()); + + // Second pass: read the whole payload + std::vector full_read(k_payload_size); + ASSERT_TRUE(stream.read(full_read.data(), static_cast(k_payload_size))); + EXPECT_EQ(full_read, payload) << "Full read after seek produced incorrect data"; +} + +// 14. Mixed underflow + xsgetn: read a few chars via get() (exercises the +// underflow buffer), then read a large block via read() which triggers +// xsgetn to flush the underflow remainder. +TEST_F(ParallelReadStreamBufTest, MixedUnderflowAndBulkRead) { + constexpr size_t k_size = 10 * 1024; + std::vector expected(k_size); + fill_pattern(expected); + setup_temp_file(expected); + + // threshold > k_size so all reads go through underflow() first, but we mix + // with a large stream.read() to exercise the drain-from-get-area code in xsgetn. + util::ParallelReadStreamBuf buf(m_tmp_path, 0, /*threshold=*/SIZE_MAX); + std::istream stream(&buf); + + // Read 5 chars individually – this fills the 8 KB underflow buffer + std::vector prefix; + for (int i = 0; i < 5; ++i) { + const int ch = stream.get(); + ASSERT_NE(ch, std::char_traits::eof()); + prefix.push_back(static_cast(ch)); + } + EXPECT_EQ(prefix, std::vector(expected.begin(), expected.begin() + 5)); + + // Now do a bulk read for the rest of the file. + std::vector rest(k_size - 5); + ASSERT_TRUE(stream.read(rest.data(), static_cast(k_size - 5))); + EXPECT_EQ(rest, std::vector(expected.begin() + 5, expected.end())) + << "Bulk read after char-by-char prefix produced incorrect data"; +} + +// 15. seekg(0, cur) used as tellg() must reflect the current logical position +// correctly after both underflow-buffered reads and bulk reads. +TEST_F(ParallelReadStreamBufTest, TellgIsConsistent) { + constexpr size_t k_size = 512; + std::vector data(k_size, static_cast(0xBB)); + setup_temp_file(data); + + util::ParallelReadStreamBuf buf(m_tmp_path, 0, /*threshold=*/SIZE_MAX); + std::istream stream(&buf); + + EXPECT_EQ(stream.tellg(), std::streampos(0)); + + // After reading 100 bytes via bulk read + std::vector tmp(100); + stream.read(tmp.data(), 100); + EXPECT_EQ(stream.tellg(), std::streampos(100)); + + // After reading 10 more chars individually + for (int i = 0; i < 10; ++i) { + stream.get(); + } + EXPECT_EQ(stream.tellg(), std::streampos(110)); + + // After seekg + stream.seekg(200, std::ios::beg); + EXPECT_EQ(stream.tellg(), std::streampos(200)); +} + +// 16. showmanyc() / in_avail() reports remaining bytes accurately, including +// both buffered characters (from the underflow buffer) and unbuffered +// bytes still in the underlying file. +TEST_F(ParallelReadStreamBufTest, ShowmanycReflectsRemainingBytes) { + constexpr size_t k_size = 256; + std::vector data(k_size, static_cast(0x77u)); + setup_temp_file(data); + + util::ParallelReadStreamBuf buf(m_tmp_path, 0, /*threshold=*/SIZE_MAX); + std::istream stream(&buf); + + // Before any read, in_avail() should report the full file size. + EXPECT_EQ(stream.rdbuf()->in_avail(), static_cast(k_size)); + + // After a bulk read of 100 bytes (via xsgetn, no underflow buffer involved) + std::vector tmp(100); + stream.read(tmp.data(), 100); + EXPECT_EQ(stream.rdbuf()->in_avail(), static_cast(k_size - 100)); + + // After a single get() which triggers underflow() and fills the 8 KB + // internal buffer with the remaining 156 bytes, in_avail() should still + // reflect the correct total: buffered chars minus the one consumed by get(). + stream.get(); + // The underflow buffer now holds min(UNDERFLOW_BUF, 156) = 156 bytes. + // get() consumed 1, so 155 remain in the buffer. m_file_offset + // advanced past the buffer, so file_remaining == 0. + // Total = 155 + 0 = 155. + EXPECT_EQ(stream.rdbuf()->in_avail(), static_cast(k_size - 100 - 1)); + + // Consume everything that remains + std::vector rest(k_size - 101); + stream.read(rest.data(), static_cast(k_size - 101)); + // Now exhausted + EXPECT_EQ(stream.rdbuf()->in_avail(), -1); +} + +// 17. Backward seek from current position: read some bytes, seek backward +// relative to current, verify the re-read returns the correct earlier +// bytes. Also verifies that the underflow buffer is properly invalidated. +TEST_F(ParallelReadStreamBufTest, BackwardSeekFromCurrent) { + constexpr size_t k_size = 2 * 1024; + std::vector expected(k_size); + fill_pattern(expected); + setup_temp_file(expected); + + // Use SIZE_MAX threshold so reads go through underflow + xsgetn drain, + // making the backward seek invalidate a non-empty underflow buffer. + util::ParallelReadStreamBuf buf(m_tmp_path, 0, /*threshold=*/SIZE_MAX); + std::istream stream(&buf); + + // Read 200 bytes + constexpr size_t k_first_read = 200; + std::vector first(k_first_read); + ASSERT_TRUE(stream.read(first.data(), static_cast(k_first_read))); + EXPECT_EQ(stream.tellg(), std::streampos(k_first_read)); + + // Read 5 chars individually to populate the underflow buffer + for (int i = 0; i < 5; ++i) { + ASSERT_NE(stream.get(), std::char_traits::eof()); + } + EXPECT_EQ(stream.tellg(), std::streampos(205)); + + // Seek backward 100 bytes from current position + stream.seekg(-100, std::ios::cur); + ASSERT_TRUE(stream.good()); + EXPECT_EQ(stream.tellg(), std::streampos(105)); + + // Read 50 bytes; they must match expected[105..154] + constexpr size_t k_reread = 50; + std::vector got(k_reread); + ASSERT_TRUE(stream.read(got.data(), static_cast(k_reread))); + std::vector slice(expected.begin() + 105, expected.begin() + 105 + k_reread); + EXPECT_EQ(got, slice); +} + +} // namespace ov::test diff --git a/src/inference/tests/unit/single_file_storage.cpp b/src/inference/tests/unit/single_file_storage.cpp index 955990cf49ccc5..919caabf817204 100644 --- a/src/inference/tests/unit/single_file_storage.cpp +++ b/src/inference/tests/unit/single_file_storage.cpp @@ -193,6 +193,49 @@ TEST_F(SingleFileStorageTest, AppendOnlyCacheEntry) { EXPECT_TRUE(read_called); } +// Large blob test: write >= 4 MB so that the real DEFAULT_THRESHOLD (4 MB) in +// ParallelReadStreamBuf is crossed on the non-mmap read path. This exercises +// the SingleFileStorage → ParallelReadStreamBuf integration end-to-end, +// including the blob alignment padding before the data and the non-zero +// header_offset passed to the streambuf constructor. +TEST_F(SingleFileStorageTest, WriteReadLargeBlob_ParallelPath) { + // 5 MB + 1 byte so that even after padding the blob data itself exceeds the + // 4 MB parallel threshold. + constexpr size_t k_blob_size = 5UL * 1024 * 1024 + 1; + const std::string blob_id = "999"; + + // Build a deterministic pattern so byte-exact comparison is possible. + std::vector expected(k_blob_size); + for (size_t i = 0; i < k_blob_size; ++i) { + expected[i] = static_cast(i % 251u); + } + + m_storage->write_cache_entry(blob_id, [&](std::ostream& s) { + s.write(reinterpret_cast(expected.data()), static_cast(k_blob_size)); + }); + + // --- non-mmap path (ParallelReadStreamBuf) --- + m_storage->read_cache_entry(blob_id, /*enable_mmap=*/false, [&](const ICacheManager::CompiledBlobVariant& cv) { + auto& stream = std::get>(cv).get(); + + std::vector got(k_blob_size); + ASSERT_TRUE(stream.read(reinterpret_cast(got.data()), static_cast(k_blob_size))); + EXPECT_EQ(got, expected) << "Parallel read returned incorrect data for large blob"; + }); + + // Reset and re-open to ensure the content survives a round-trip through + // build_content_index (re-scans the file and rebuilds m_blob_index). + m_storage.reset(); + SingleFileStorage reopened(m_file_path); + reopened.read_cache_entry(blob_id, /*enable_mmap=*/false, [&](const ICacheManager::CompiledBlobVariant& cv) { + auto& stream = std::get>(cv).get(); + + std::vector got(k_blob_size); + ASSERT_TRUE(stream.read(reinterpret_cast(got.data()), static_cast(k_blob_size))); + EXPECT_EQ(got, expected) << "Parallel read after re-open returned incorrect data for large blob"; + }); +} + TEST_F(SingleFileStorageTest, ContextMetaWriteRead) { weight_sharing::Context test_context; test_context.m_weight_registry[1][11] = {100, 200, element::Type_t::f32}; diff --git a/src/plugins/intel_gpu/include/intel_gpu/primitives/data.hpp b/src/plugins/intel_gpu/include/intel_gpu/primitives/data.hpp index 61d52dc2885954..178b3448b2b287 100644 --- a/src/plugins/intel_gpu/include/intel_gpu/primitives/data.hpp +++ b/src/plugins/intel_gpu/include/intel_gpu/primitives/data.hpp @@ -422,13 +422,65 @@ struct data : public primitive_base { if (is_alloc_host_accessible(_allocation_type)) { ib >> make_data(mem->buffer_ptr(), data_size); } else { - const size_t DATA_BLOCK_SIZE = 2 * 1024 * 1024; - auto& strm = ib.get_engine().get_service_stream(); + const size_t DATA_BLOCK_SIZE = 4 * 1024 * 1024; + auto& eng = ib.get_engine(); + auto& strm = eng.get_service_stream(); if (data_size < DATA_BLOCK_SIZE || output_layout.format.is_image_2d()) { std::vector _buf(data_size); ib >> make_data(_buf.data(), data_size); mem->copy_from(strm, _buf.data()); + } else if (eng.supports_allocation(allocation_type::usm_host)) { + // Use USM host staging buffers so the driver can DMA directly + // from pinned host memory to device, avoiding the hidden + // pageable-to-pinned bounce copy that std::vector staging incurs. + // The pipeline is the same ping-pong as the fallback below: + // CPU reads into host_bufA while GPU copies from host_bufB + // Safety: each staging buffer is reused every other iteration; + // the wait() before re-submitting a copy ensures the previous + // GPU read from that buffer has finished before the CPU writes + // new data into it (the OTHER buffer's event is waited, but that + // event was submitted after waiting for THIS buffer's previous + // event, so this buffer is always free by the time we reuse it). + const layout staging_layout{{static_cast(DATA_BLOCK_SIZE)}, + data_types::u8, format::bfyx}; + auto host_buf1 = eng.allocate_memory(staging_layout, allocation_type::usm_host, false); + auto host_buf2 = eng.allocate_memory(staging_layout, allocation_type::usm_host, false); + bool buf_flag = true; + event::ptr ev1, ev2; + ev1 = ev2 = nullptr; + size_t dst_offset = 0; + while (dst_offset < data_size) { + const bool is_blocking = false; + const size_t src_offset = 0; + size_t copy_size = + (data_size > (dst_offset + DATA_BLOCK_SIZE)) ? DATA_BLOCK_SIZE : (data_size - dst_offset); + if (buf_flag) { + ib >> make_data(static_cast(host_buf1->buffer_ptr()), copy_size); + if (ev2 != nullptr) { + ev2->wait(); + ev2 = nullptr; + } + ev1 = mem->copy_from(strm, *host_buf1, src_offset, dst_offset, copy_size, is_blocking); + } else { + ib >> make_data(static_cast(host_buf2->buffer_ptr()), copy_size); + if (ev1 != nullptr) { + ev1->wait(); + ev1 = nullptr; + } + ev2 = mem->copy_from(strm, *host_buf2, src_offset, dst_offset, copy_size, is_blocking); + } + dst_offset += DATA_BLOCK_SIZE; + buf_flag = !buf_flag; + } + if (ev2 != nullptr) { + ev2->wait(); + } + if (ev1 != nullptr) { + ev1->wait(); + } } else { + // Fallback for devices that do not support USM host allocation: + // use pageable std::vector staging buffers (original behaviour). std::vector _buf1(DATA_BLOCK_SIZE); std::vector _buf2(DATA_BLOCK_SIZE); bool buf_flag = true; diff --git a/src/plugins/intel_gpu/src/graph/common_utils/parallel_mem_streambuf.cpp b/src/plugins/intel_gpu/src/graph/common_utils/parallel_mem_streambuf.cpp new file mode 100644 index 00000000000000..246af70806f76e --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/common_utils/parallel_mem_streambuf.cpp @@ -0,0 +1,284 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "parallel_mem_streambuf.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +// clang-format off +#ifndef NOMINMAX +# define NOMINMAX +#endif +#include +#include +#include +// clang-format on +#else +# include +#endif + +#include "openvino/util/parallel_io.hpp" + +namespace ov::intel_gpu { + +#ifdef _WIN32 +// Convert a kernel device path (\Device\HarddiskVolume3\foo\bar) to a +// Win32 drive path (C:\foo\bar). +static bool resolve_device_path(const wchar_t* dev_path, wchar_t* out, DWORD out_len) { + const size_t MAX_DRIVES_LEN = 512; + wchar_t drives[MAX_DRIVES_LEN] = {}; + if (!GetLogicalDriveStringsW(MAX_DRIVES_LEN, drives)) + return false; + for (const wchar_t* d = drives; *d; d += wcslen(d) + 1) { + wchar_t drive[3] = {d[0], d[1], L'\0'}; + wchar_t dev_name[MAX_PATH] = {}; + if (!QueryDosDeviceW(drive, dev_name, MAX_PATH)) + continue; + const size_t dev_name_len = wcslen(dev_name); + if (wcsncmp(dev_path, dev_name, dev_name_len) == 0 && (dev_path[dev_name_len] == L'\\' || dev_path[dev_name_len] == L'\0')) { + swprintf_s(out, out_len, L"%s%s", drive, dev_path + dev_name_len); + return true; + } + } + return false; +} +#endif + +// Detect whether a memory address is backed by a file-based mmap. +// On Linux, parses /proc/self/maps. +// On Windows, uses VirtualQuery + GetMappedFileNameW + drive letter resolution. +// param addr The memory address to inspect. +// param out_path [out] Path to the backing file (only set on success). +// param out_offset [out] Absolute byte offset within the file corresponding to addr. +// return true if the address is file-backed, false otherwise. +static bool get_mmap_file_info(const void* addr, std::filesystem::path& out_path, std::streamoff& out_offset) { +#ifdef _WIN32 + MEMORY_BASIC_INFORMATION mbi{}; + if (!VirtualQuery(addr, &mbi, sizeof(mbi)) || mbi.Type != MEM_MAPPED) { + return false; + } + void* query_addr = reinterpret_cast(reinterpret_cast(addr)); + wchar_t dev_path[MAX_PATH] = {}; + if (GetMappedFileNameW(GetCurrentProcess(), query_addr, dev_path, MAX_PATH) == 0) { + return false; + } + wchar_t win32_path[MAX_PATH] = {}; + if (!resolve_device_path(dev_path, win32_path, MAX_PATH)) { + return false; + } + out_path = std::filesystem::path(win32_path); + out_offset = reinterpret_cast(addr) - reinterpret_cast(mbi.AllocationBase); + return true; +#else + std::ifstream maps_file("/proc/self/maps"); + if (!maps_file.is_open()) + return false; + const auto addr_val = reinterpret_cast(addr); + std::string line; + while (std::getline(maps_file, line)) { + // Format: start-end perms offset dev inode [pathname] + std::istringstream iss(line); + std::string addr_range, perms, offset_str, dev, inode_str; + if (!(iss >> addr_range >> perms >> offset_str >> dev >> inode_str)) + continue; + const auto dash = addr_range.find('-'); + if (dash == std::string::npos) + continue; + uintptr_t range_start = 0, range_end = 0; + try { + range_start = static_cast(std::stoull(addr_range.substr(0, dash), nullptr, 16)); + range_end = static_cast(std::stoull(addr_range.substr(dash + 1), nullptr, 16)); + } catch (...) { + continue; + } + if (addr_val < range_start || addr_val >= range_end) + continue; + // Skip whitespace after inode to reach the optional pathname field. + // Use getline instead of operator>> to handle paths that contain spaces. + std::string pathname; + std::getline(iss >> std::ws, pathname); + if (pathname.empty()) + return false; // anonymous mapping (no pathname) + std::filesystem::path path(pathname); + if (!path.is_absolute()) + return false; // special region like [heap], [stack], [vdso] + out_path = path; + std::streamoff map_offset = 0; + try { + map_offset = static_cast(std::stoull(offset_str, nullptr, 16)); + } catch (...) { + return false; + } + out_offset = map_offset + static_cast(addr_val - range_start); + return true; + } + return false; +#endif +} + +// Issue an asynchronous prefetch hint for a memory region. +// On Linux, calls madvise(MADV_WILLNEED) (with page-aligned address). +// On Windows, calls PrefetchVirtualMemory. +// addr Start of the memory region. +// size Size of the region in bytes. +static void prefetch_memory(const void* addr, size_t size) { +#ifdef _WIN32 + void* prefetch_addr = reinterpret_cast(reinterpret_cast(addr)); + WIN32_MEMORY_RANGE_ENTRY prefetch_range{prefetch_addr, size}; + PrefetchVirtualMemory(GetCurrentProcess(), 1, &prefetch_range, 0); +#else + // madvise(2) requires addr to be page-aligned; round down and extend size. + const uintptr_t base = reinterpret_cast(addr); + const uintptr_t aligned_base = base & ~uintptr_t{4095}; + // madvise is advisory — failure is non-fatal; the read path will still work. + (void)madvise(reinterpret_cast(aligned_base), size + (base - aligned_base), MADV_WILLNEED); +#endif +} + +ParallelMemStreamBuf::ParallelMemStreamBuf(const void* data, size_t size, size_t threshold) + : m_begin(static_cast(data)), + m_end(static_cast(data) + size), + m_current(static_cast(data)), + m_threshold(threshold) { + // Detect whether this memory is a file-backed mmap region. + // If so, build a ParallelReadStreamBuf over the same file+offset so + // direct reads are used instead of mmap+memcpy. This avoids 2x RAM + // pressure (mmap working-set + destination buffer) that causes + // working-set thrashing for multi-GB models. + if (size >= threshold) { + std::filesystem::path file_path; + std::streamoff file_off = 0; + if (get_mmap_file_info(data, file_path, file_off)) { + try { + m_file_buf = std::make_unique(file_path, file_off, threshold); + } catch (...) { + // File became inaccessible after mmap detection; fall through to memcpy path. + } + } + } + // For non-file-backed memory (anonymous mmap, USM host buffers, etc.) + // fall back to async prefetch + parallel memcpy. + if (!m_file_buf) { + prefetch_memory(data, size); + } +} + +std::streamsize ParallelMemStreamBuf::xsgetn(char_type* dst, std::streamsize n) { + if (m_file_buf) { + return m_file_buf->sgetn(dst, n); + } + if (n <= 0 || m_current >= m_end) { + return 0; + } + const std::streamsize avail = static_cast(m_end - m_current); + const std::streamsize to_copy = std::min(n, avail); + + if (static_cast(to_copy) >= m_threshold) { + parallel_copy(dst, m_current, static_cast(to_copy)); + } else { + std::memcpy(dst, m_current, static_cast(to_copy)); + } + + m_current += to_copy; + return to_copy; +} + +ParallelMemStreamBuf::int_type ParallelMemStreamBuf::underflow() { + if (m_file_buf) { + return m_file_buf->sgetc(); + } + if (m_current >= m_end) { + return traits_type::eof(); + } + return traits_type::to_int_type(*m_current); +} + +ParallelMemStreamBuf::int_type ParallelMemStreamBuf::uflow() { + if (m_file_buf) { + return m_file_buf->sbumpc(); + } + if (m_current >= m_end) { + return traits_type::eof(); + } + return traits_type::to_int_type(*m_current++); +} + +ParallelMemStreamBuf::pos_type ParallelMemStreamBuf::seekoff(off_type off, std::ios_base::seekdir way, std::ios_base::openmode which) { + if (m_file_buf) { + return m_file_buf->pubseekoff(off, way, which); + } + const char* new_pos = nullptr; + if (way == std::ios_base::beg) { + new_pos = m_begin + off; + } else if (way == std::ios_base::cur) { + new_pos = m_current + off; + } else { + new_pos = m_end + off; + } + + if (new_pos < m_begin || new_pos > m_end) { + return pos_type(off_type(-1)); + } + + m_current = new_pos; + return pos_type(static_cast(m_current - m_begin)); +} + +ParallelMemStreamBuf::pos_type ParallelMemStreamBuf::seekpos(pos_type pos, std::ios_base::openmode which) { + if (m_file_buf) { + return m_file_buf->pubseekpos(pos, which); + } + return seekoff(off_type(pos), std::ios_base::beg, std::ios_base::in); +} + +std::streamsize ParallelMemStreamBuf::showmanyc() { + if (m_file_buf) { + return m_file_buf->in_avail(); + } + const std::streamsize avail = static_cast(m_end - m_current); + return avail > 0 ? avail : -1; +} + +void ParallelMemStreamBuf::parallel_copy(char* dst, const char* src, size_t size) { + // Cap threads: too many concurrent threads cause OS scheduling overhead + // (Linux) or PFN-lock contention (Windows). Use hardware_concurrency as + // the upper bound, consistent with parallel_read. + const size_t hw_conc = std::max(size_t{1}, static_cast(std::thread::hardware_concurrency())); + const size_t num_chunks = std::max(size_t{1}, std::min(size / ov::util::default_parallel_io_min_chunk, hw_conc)); + const size_t chunk_size = (size + num_chunks - 1) / num_chunks; + prefetch_memory(src, size); + + std::vector workers; + workers.reserve(num_chunks); + for (size_t i = 0; i < num_chunks; ++i) { + try { + workers.emplace_back([&, i]() { + const size_t offset = i * chunk_size; + const size_t copy_size = (i + 1 == num_chunks) ? (size - offset) : std::min(chunk_size, size - offset); + std::memcpy(dst + offset, src + offset, copy_size); + }); + } catch (...) { + for (auto& t : workers) + t.join(); + const size_t done = i * chunk_size; + if (done < size) + std::memcpy(dst + done, src + done, size - done); + return; + } + } + for (auto& t : workers) { + t.join(); + } +} + +} // namespace ov::intel_gpu diff --git a/src/plugins/intel_gpu/src/graph/common_utils/parallel_mem_streambuf.hpp b/src/plugins/intel_gpu/src/graph/common_utils/parallel_mem_streambuf.hpp new file mode 100644 index 00000000000000..4fcec9d34ed265 --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/common_utils/parallel_mem_streambuf.hpp @@ -0,0 +1,49 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include +#include + +#include "openvino/util/parallel_read_streambuf.hpp" + +namespace ov::intel_gpu { + +// A temporary solution to improve SSD reading bandwidth and avoid the 2x-RAM cost of mmap+memcpy for mmap files. +// Once L0 handle SSD reading bandwidth issue, this can be removed and the original mmap+memcpy path can be restored. +// +// A std::streambuf that reads from an in-memory buffer using parallel +// memcpy for large reads. When the source is a file-backed mmap, the +// constructor detects this and delegates to ParallelReadStreamBuf so that +// direct pread/ReadFile calls replace the 2x-RAM mmap+memcpy path. +class ParallelMemStreamBuf : public std::streambuf { +public: + explicit ParallelMemStreamBuf(const void* data, size_t size, size_t threshold = ov::util::default_parallel_io_threshold); + + ~ParallelMemStreamBuf() override = default; + + ParallelMemStreamBuf(const ParallelMemStreamBuf&) = delete; + ParallelMemStreamBuf& operator=(const ParallelMemStreamBuf&) = delete; + +protected: + std::streamsize xsgetn(char_type* dst, std::streamsize n) override; + int_type underflow() override; + int_type uflow() override; + pos_type seekoff(off_type off, std::ios_base::seekdir way, std::ios_base::openmode which) override; + pos_type seekpos(pos_type pos, std::ios_base::openmode which) override; + std::streamsize showmanyc() override; + +private: + void parallel_copy(char* dst, const char* src, size_t size); + + const char* m_begin; + const char* m_end; + const char* m_current; + size_t m_threshold; + std::unique_ptr m_file_buf; +}; + +} // namespace ov::intel_gpu diff --git a/src/plugins/intel_gpu/src/graph/data.cpp b/src/plugins/intel_gpu/src/graph/data.cpp index 68a9b414267666..ce82565e6c5261 100644 --- a/src/plugins/intel_gpu/src/graph/data.cpp +++ b/src/plugins/intel_gpu/src/graph/data.cpp @@ -1,14 +1,19 @@ // Copyright (C) 2018-2026 Intel Corporation // SPDX-License-Identifier: Apache-2.0 // + #include "data_inst.h" +#include "json_object.h" #include "primitive_type_base.h" + #include "intel_gpu/runtime/memory.hpp" +#include "openvino/core/parallel.hpp" +#include "openvino/util/parallel_io.hpp" -#include "json_object.h" -#include -#include #include +#include +#include +#include namespace cldnn { GPU_DEFINE_PRIMITIVE_TYPE_ID(data) @@ -22,7 +27,21 @@ memory::ptr attach_or_copy_data(network& network, memory::ptr mem) { memory::ptr result = engine.allocate_memory(mem->get_layout(), false); mem_lock src(mem, network.get_stream()); mem_lock dst(result, network.get_stream()); - std::copy(src.begin(), src.end(), dst.begin()); + const size_t data_size = src.size(); + if (data_size >= ov::util::default_parallel_io_threshold) { + char* src_ptr = src.data(); + char* dst_ptr = dst.data(); + const size_t max_threads = static_cast(parallel_get_max_threads()); + const size_t num_chunks = std::max(size_t{1}, std::min(data_size / ov::util::default_parallel_io_min_chunk, max_threads)); + const size_t chunk_size = (data_size + num_chunks - 1) / num_chunks; + ov::parallel_for(num_chunks, [src_ptr, dst_ptr, chunk_size, data_size, num_chunks](size_t i) { + const size_t offset = i * chunk_size; + const size_t copy_size = (i + 1 == num_chunks) ? (data_size - offset) : std::min(chunk_size, data_size - offset); + std::memcpy(dst_ptr + offset, src_ptr + offset, copy_size); + }); + } else { + std::copy(src.begin(), src.end(), dst.begin()); + } return result; } } // namespace diff --git a/src/plugins/intel_gpu/src/plugin/plugin.cpp b/src/plugins/intel_gpu/src/plugin/plugin.cpp index b503b089de3c66..15d6d9202fc1b7 100644 --- a/src/plugins/intel_gpu/src/plugin/plugin.cpp +++ b/src/plugins/intel_gpu/src/plugin/plugin.cpp @@ -38,6 +38,7 @@ #include "openvino/runtime/plugin_config.hpp" #include "openvino/runtime/properties.hpp" #include "openvino/runtime/shared_buffer.hpp" +#include "common_utils/parallel_mem_streambuf.hpp" #include "openvino/runtime/weightless_properties_utils.hpp" #include "openvino/util/file_util.hpp" #include "openvino/util/weights_path.hpp" @@ -459,8 +460,8 @@ std::shared_ptr Plugin::import_model(const ov::Tensor& model std::shared_ptr Plugin::import_model(const ov::Tensor& model, const ov::SoPtr& context, const ov::AnyMap& config) const{ - SharedStreamBuffer buf{model.data(), model.get_byte_size()}; - std::istream stream(&buf); + ov::intel_gpu::ParallelMemStreamBuf par_buf(model.data(), model.get_byte_size()); + std::istream stream(&par_buf); return import_model(stream, context, config); } diff --git a/src/plugins/intel_gpu/tests/unit/test_utils/parallel_mem_streambuf_test.cpp b/src/plugins/intel_gpu/tests/unit/test_utils/parallel_mem_streambuf_test.cpp new file mode 100644 index 00000000000000..7c02b8a715293e --- /dev/null +++ b/src/plugins/intel_gpu/tests/unit/test_utils/parallel_mem_streambuf_test.cpp @@ -0,0 +1,497 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "common_utils/parallel_mem_streambuf.hpp" + +#include + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include +#else +# include +# include +# include +#endif + +namespace { + +void fill_pattern(std::vector& buf, size_t start_index = 0) { + for (size_t i = 0; i < buf.size(); ++i) { + buf[i] = static_cast((start_index + i) % 251u); + } +} + +} // namespace + +// 1. Small read – threshold=SIZE_MAX forces the single memcpy path. +TEST(ParallelMemStreamBufTest, FullReadSingleMemcpyPath) { + constexpr size_t k_size = 4 * 1024; + std::vector src(k_size); + fill_pattern(src); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/SIZE_MAX); + std::istream stream(&buf); + + std::vector got(k_size); + ASSERT_TRUE(stream.read((got.data()), static_cast(k_size))); + EXPECT_EQ(got, src); +} + +// 2. threshold=1 forces parallel_copy() to be called on every bulk read. +TEST(ParallelMemStreamBufTest, FullReadParallelMemcpyPath) { + constexpr size_t k_size = 8 * 1024; + std::vector src(k_size); + fill_pattern(src); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/1); + std::istream stream(&buf); + + std::vector got(k_size); + ASSERT_TRUE(stream.read((got.data()), static_cast(k_size))); + EXPECT_EQ(got, src); +} + +// 3. Non-zero logical start: construct on a sub-span of a larger allocation. +TEST(ParallelMemStreamBufTest, NonZeroPointerOffset) { + constexpr size_t k_prefix_size = 512; + constexpr size_t k_payload_size = 2 * 1024; + std::vector backing(k_prefix_size + k_payload_size); + std::fill(backing.begin(), backing.begin() + k_prefix_size, 0xFFu); + std::vector payload(k_payload_size); + fill_pattern(payload); + std::memcpy(backing.data() + k_prefix_size, payload.data(), k_payload_size); + + const char* payload_ptr = (backing.data() + k_prefix_size); + ov::intel_gpu::ParallelMemStreamBuf buf(payload_ptr, k_payload_size, /*threshold=*/1); + std::istream stream(&buf); + + std::vector got(k_payload_size); + ASSERT_TRUE(stream.read((got.data()), static_cast(k_payload_size))); + EXPECT_EQ(got, payload); +} + +// 4. Multiple consecutive partial reads consume bytes in order. +TEST(ParallelMemStreamBufTest, ChunkedReads) { + constexpr size_t k_size = 8 * 1024; + constexpr size_t k_chunk = 1000; + std::vector src(k_size); + fill_pattern(src); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/1); + std::istream stream(&buf); + + std::vector got(k_size); + size_t offset = 0; + while (offset < k_size) { + const size_t n = std::min(k_chunk, k_size - offset); + ASSERT_TRUE(stream.read((got.data() + offset), static_cast(n))); + offset += n; + } + EXPECT_EQ(got, src); +} + +// 5. underflow() + uflow() – char-by-char consumption via stream.get(). +TEST(ParallelMemStreamBufTest, CharByCharRead) { + constexpr size_t k_size = 200; + std::vector src(k_size); + fill_pattern(src); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/SIZE_MAX); + std::istream stream(&buf); + + std::vector got; + got.reserve(k_size); + int ch; + while ((ch = stream.get()) != std::char_traits::eof()) { + got.push_back(static_cast(ch)); + } + ASSERT_EQ(got.size(), k_size); + EXPECT_EQ(got, src); +} + +// 6. seekg(pos, beg) then read returns bytes at that logical position. +TEST(ParallelMemStreamBufTest, SeekFromBeginning) { + constexpr size_t k_size = 1024; + std::vector src(k_size); + fill_pattern(src); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/1); + std::istream stream(&buf); + + constexpr std::streamoff k_seek_pos = 300; + constexpr size_t k_read_len = 20; + stream.seekg(k_seek_pos, std::ios::beg); + ASSERT_TRUE(stream.good()); + + std::vector got(k_read_len); + ASSERT_TRUE(stream.read((got.data()), static_cast(k_read_len))); + + std::vector expected(src.begin() + k_seek_pos, src.begin() + k_seek_pos + k_read_len); + EXPECT_EQ(got, expected); +} + +// 7. seekg(off, cur) – relative forward seek after an initial read. +TEST(ParallelMemStreamBufTest, SeekFromCurrent) { + constexpr size_t k_size = 1024; + std::vector src(k_size); + fill_pattern(src); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/1); + std::istream stream(&buf); + + constexpr size_t k_first_read = 100; + constexpr std::streamoff k_skip = 150; + constexpr size_t k_second_read = 30; + + std::vector first(k_first_read); + ASSERT_TRUE(stream.read((first.data()), static_cast(k_first_read))); + EXPECT_EQ(first, std::vector(src.begin(), src.begin() + k_first_read)); + + stream.seekg(k_skip, std::ios::cur); + ASSERT_TRUE(stream.good()); + + std::vector second(k_second_read); + ASSERT_TRUE(stream.read((second.data()), static_cast(k_second_read))); + + const size_t expected_start = k_first_read + static_cast(k_skip); + std::vector expected_slice(src.begin() + expected_start, src.begin() + expected_start + k_second_read); + EXPECT_EQ(second, expected_slice); +} + +// 8. seekg(off, end) – backward seek from the end. +TEST(ParallelMemStreamBufTest, SeekFromEnd) { + constexpr size_t k_size = 1024; + std::vector src(k_size); + fill_pattern(src); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/1); + std::istream stream(&buf); + + constexpr std::streamoff k_from_end = 48; + stream.seekg(-k_from_end, std::ios::end); + ASSERT_TRUE(stream.good()); + + std::vector got(static_cast(k_from_end)); + ASSERT_TRUE(stream.read((got.data()), k_from_end)); + + std::vector expected(src.end() - k_from_end, src.end()); + EXPECT_EQ(got, expected); +} + +// 9. seekg(0, end) then tellg() must equal the buffer size. +TEST(ParallelMemStreamBufTest, TellgAtEnd) { + constexpr size_t k_size = 512; + std::vector src(k_size, 0xAAu); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/1); + std::istream stream(&buf); + + stream.seekg(0, std::ios::end); + ASSERT_TRUE(stream.good()); + EXPECT_EQ(static_cast(stream.tellg()), k_size); +} + +// 10. tellg() reflects the current position accurately after mixed reads/seeks. +TEST(ParallelMemStreamBufTest, TellgIsConsistent) { + constexpr size_t k_size = 512; + std::vector src(k_size, 0xBBu); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/SIZE_MAX); + std::istream stream(&buf); + + EXPECT_EQ(stream.tellg(), std::streampos(0)); + + std::vector tmp(100); + stream.read(tmp.data(), 100); + EXPECT_EQ(stream.tellg(), std::streampos(100)); + + for (int i = 0; i < 10; ++i) { + stream.get(); + } + EXPECT_EQ(stream.tellg(), std::streampos(110)); + + stream.seekg(200, std::ios::beg); + EXPECT_EQ(stream.tellg(), std::streampos(200)); +} + +// 11. Out-of-range seek returns pos_type(-1). +TEST(ParallelMemStreamBufTest, OutOfRangeSeekFails) { + constexpr size_t k_size = 64; + std::vector src(k_size, 0x55u); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/1); + std::istream stream(&buf); + + const auto pos = stream.seekg(-1, std::ios::beg).tellg(); + EXPECT_EQ(pos, std::streampos(-1)); +} + +// 12. Partial read at EOF. +TEST(ParallelMemStreamBufTest, ReadAtEof) { + constexpr size_t k_size = 80; + std::vector src(k_size); + fill_pattern(src); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/SIZE_MAX); + std::istream stream(&buf); + + std::vector discard(k_size - 10); + ASSERT_TRUE(stream.read((discard.data()), static_cast(k_size - 10))); + + std::vector tail(20, 0xFFu); + const bool ok = static_cast(stream.read((tail.data()), 20)); + EXPECT_FALSE(ok); + EXPECT_TRUE(stream.eof()); + ASSERT_EQ(stream.gcount(), 10); + EXPECT_TRUE(std::equal(tail.begin(), tail.begin() + 10, src.end() - 10)); +} + +// 13. showmanyc() / in_avail(). +TEST(ParallelMemStreamBufTest, ShowmanycReflectsRemainingBytes) { + constexpr size_t k_size = 256; + std::vector src(k_size, 0x77u); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/SIZE_MAX); + std::istream stream(&buf); + + EXPECT_EQ(stream.rdbuf()->in_avail(), static_cast(k_size)); + + std::vector tmp(100); + stream.read(tmp.data(), 100); + EXPECT_EQ(stream.rdbuf()->in_avail(), static_cast(k_size - 100)); + + std::vector rest(k_size - 100); + stream.read(rest.data(), static_cast(k_size - 100)); + EXPECT_EQ(stream.rdbuf()->in_avail(), -1); +} + +// 14. Mixed underflow + bulk read. +TEST(ParallelMemStreamBufTest, MixedCharAndBulkRead) { + constexpr size_t k_size = 1024; + std::vector src(k_size); + fill_pattern(src); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/SIZE_MAX); + std::istream stream(&buf); + + for (int i = 0; i < 8; ++i) { + const int ch = stream.get(); + ASSERT_NE(ch, std::char_traits::eof()); + EXPECT_EQ(static_cast(ch), src[static_cast(i)]); + } + + std::vector rest(k_size - 8); + ASSERT_TRUE(stream.read((rest.data()), static_cast(k_size - 8))); + EXPECT_EQ(rest, std::vector(src.begin() + 8, src.end())); +} + +// 15. Seek back to position 0 and re-read. +TEST(ParallelMemStreamBufTest, SeekToZeroAndReread) { + constexpr size_t k_size = 1024; + std::vector src(k_size); + fill_pattern(src); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/1); + std::istream stream(&buf); + + std::vector first(k_size); + ASSERT_TRUE(stream.read((first.data()), static_cast(k_size))); + EXPECT_EQ(first, src); + + stream.clear(); + stream.seekg(0, std::ios::beg); + ASSERT_TRUE(stream.good()); + + std::vector second(k_size); + ASSERT_TRUE(stream.read((second.data()), static_cast(k_size))); + EXPECT_EQ(second, src); +} + +// 16. Parallel path correctness with large buffer. +TEST(ParallelMemStreamBufTest, ParallelDispatchFullReadCorrectness) { + const size_t k_max_hw = 16; + const size_t hw_raw = std::max(size_t{2}, static_cast(std::thread::hardware_concurrency())); + const size_t hw = hw_raw > k_max_hw ? k_max_hw : hw_raw; + const size_t k_size = 2u * hw * 2u * 1024u * 1024u + 1u; + + std::vector src(k_size); + fill_pattern(src); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/1); + std::istream stream(&buf); + + std::vector got(k_size); + ASSERT_TRUE(stream.read((got.data()), static_cast(k_size))); + EXPECT_EQ(got, src) << "Parallel memcpy produced incorrect data"; +} + +// 17. Parallel path with mid-stream seek. +TEST(ParallelMemStreamBufTest, ParallelDispatchSeekAndReread) { + const size_t k_max_hw = 16; + const size_t hw_raw = std::max(size_t{2}, static_cast(std::thread::hardware_concurrency())); + const size_t hw = hw_raw > k_max_hw ? k_max_hw : hw_raw; + const size_t k_size = 2u * hw * 2u * 1024u * 1024u; + + std::vector src(k_size); + fill_pattern(src); + + ov::intel_gpu::ParallelMemStreamBuf buf(src.data(), k_size, /*threshold=*/1); + std::istream stream(&buf); + + const size_t k_half = k_size / 2; + std::vector first_half(k_half); + ASSERT_TRUE(stream.read((first_half.data()), static_cast(k_half))); + EXPECT_TRUE(std::equal(first_half.begin(), first_half.end(), src.begin())); + + stream.seekg(0, std::ios::beg); + ASSERT_TRUE(stream.good()); + + std::vector full(k_size); + ASSERT_TRUE(stream.read((full.data()), static_cast(k_size))); + EXPECT_EQ(full, src) << "Full read after seek produced incorrect data"; +} + +// 18. File-backed mmap detection: create a file, mmap it, pass the mmap +// pointer to ParallelMemStreamBuf, and verify correct data is returned. +// This exercises the get_mmap_file_info() detection + delegation to +// ParallelReadStreamBuf that is otherwise untested by in-memory tests. +#ifndef _WIN32 +TEST(ParallelMemStreamBufTest, FileBackedMmapDetection) { + constexpr size_t k_size = 16 * 1024; // 16 KB + std::vector expected(k_size); + fill_pattern(expected); + + auto tmp_path = std::filesystem::temp_directory_path() / "par_mem_mmap_test.bin"; + { + std::ofstream ofs(tmp_path, std::ios::binary | std::ios::trunc); + ASSERT_TRUE(ofs.is_open()) << "Cannot create temp file: " << tmp_path; + ofs.write(expected.data(), static_cast(k_size)); + } + + int fd = ::open(tmp_path.c_str(), O_RDONLY); + ASSERT_NE(fd, -1) << "Cannot open temp file for mmap"; + void* mapped = ::mmap(nullptr, k_size, PROT_READ, MAP_PRIVATE, fd, 0); + ::close(fd); + ASSERT_NE(mapped, MAP_FAILED) << "mmap failed"; + + { + // threshold=1 so the constructor attempts mmap file detection on any size + ov::intel_gpu::ParallelMemStreamBuf buf(mapped, k_size, /*threshold=*/1); + std::istream stream(&buf); + + std::vector got(k_size); + ASSERT_TRUE(stream.read(got.data(), static_cast(k_size))); + EXPECT_EQ(got, expected); + + // Verify seek + re-read works through the delegated ParallelReadStreamBuf + stream.clear(); + stream.seekg(0, std::ios::beg); + ASSERT_TRUE(stream.good()); + + std::vector got2(k_size); + ASSERT_TRUE(stream.read(got2.data(), static_cast(k_size))); + EXPECT_EQ(got2, expected); + } + + ::munmap(mapped, k_size); + std::filesystem::remove(tmp_path); +} + +// 19. File-backed mmap with non-zero offset: mmap a file at a page-aligned +// offset and verify the detection correctly computes the file offset. +TEST(ParallelMemStreamBufTest, FileBackedMmapNonZeroOffset) { + const size_t page_size = static_cast(sysconf(_SC_PAGESIZE)); + const size_t k_prefix = page_size; // one page of prefix + const size_t k_payload = 8 * 1024; + const size_t k_total = k_prefix + k_payload; + + std::vector file_content(k_total); + // Fill prefix with 0xFF, payload with deterministic pattern + std::memset(file_content.data(), 0xFF, k_prefix); + for (size_t i = 0; i < k_payload; ++i) { + file_content[k_prefix + i] = static_cast((i) % 251u); + } + + auto tmp_path = std::filesystem::temp_directory_path() / "par_mem_mmap_offset_test.bin"; + { + std::ofstream ofs(tmp_path, std::ios::binary | std::ios::trunc); + ASSERT_TRUE(ofs.is_open()); + ofs.write(file_content.data(), static_cast(k_total)); + } + + int fd = ::open(tmp_path.c_str(), O_RDONLY); + ASSERT_NE(fd, -1); + // mmap the entire file, then pass a pointer into the payload region + void* mapped = ::mmap(nullptr, k_total, PROT_READ, MAP_PRIVATE, fd, 0); + ::close(fd); + ASSERT_NE(mapped, MAP_FAILED); + + const void* payload_ptr = static_cast(mapped) + k_prefix; + + { + ov::intel_gpu::ParallelMemStreamBuf buf(payload_ptr, k_payload, /*threshold=*/1); + std::istream stream(&buf); + + std::vector got(k_payload); + ASSERT_TRUE(stream.read(got.data(), static_cast(k_payload))); + // Verify we got the payload, not the prefix + std::vector expected_payload(file_content.begin() + k_prefix, file_content.end()); + EXPECT_EQ(got, expected_payload); + } + + ::munmap(mapped, k_total); + std::filesystem::remove(tmp_path); +} +#else // _WIN32 +TEST(ParallelMemStreamBufTest, FileBackedMmapDetection) { + constexpr size_t k_size = 16 * 1024; + std::vector expected(k_size); + fill_pattern(expected); + + auto tmp_path = std::filesystem::temp_directory_path() / L"par_mem_mmap_test.bin"; + { + std::ofstream ofs(tmp_path, std::ios::binary | std::ios::trunc); + ASSERT_TRUE(ofs.is_open()) << "Cannot create temp file"; + ofs.write(expected.data(), static_cast(k_size)); + } + + HANDLE hFile = CreateFileW(tmp_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + ASSERT_NE(hFile, INVALID_HANDLE_VALUE) << "Cannot open temp file"; + HANDLE hMapping = CreateFileMappingW(hFile, nullptr, PAGE_READONLY, 0, 0, nullptr); + ASSERT_NE(hMapping, nullptr) << "CreateFileMapping failed"; + void* mapped = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); + ASSERT_NE(mapped, nullptr) << "MapViewOfFile failed"; + + { + ov::intel_gpu::ParallelMemStreamBuf buf(mapped, k_size, /*threshold=*/1); + std::istream stream(&buf); + + std::vector got(k_size); + ASSERT_TRUE(stream.read(got.data(), static_cast(k_size))); + EXPECT_EQ(got, expected); + + stream.clear(); + stream.seekg(0, std::ios::beg); + ASSERT_TRUE(stream.good()); + + std::vector got2(k_size); + ASSERT_TRUE(stream.read(got2.data(), static_cast(k_size))); + EXPECT_EQ(got2, expected); + } + + UnmapViewOfFile(mapped); + CloseHandle(hMapping); + CloseHandle(hFile); + std::filesystem::remove(tmp_path); +} +#endif From e9187467d18f6947d4d462e6a71de21ce4569e44 Mon Sep 17 00:00:00 2001 From: Marcin Kruszynski Date: Wed, 22 Apr 2026 14:05:15 +0200 Subject: [PATCH 026/545] [GHA][Coverage] Add GPU tests code coverage (#35236) ### Details: Enable GPU-only tests on iGPU and dGPU runners in code coverage workflow. --- .github/scripts/coverage/ci_reports.py | 55 ++ .../scripts/coverage/collect_cpp_coverage.py | 13 +- .github/scripts/coverage/config/tests_cpp.yml | 85 ++- .github/scripts/coverage/config/tests_js.yml | 19 +- .../scripts/coverage/config/tests_python.yml | 189 +++--- .github/scripts/coverage/coverage.py | 153 ++--- .github/scripts/coverage/run_tests.py | 52 +- .github/workflows/coverage.yml | 634 +++++++++++++++--- 8 files changed, 820 insertions(+), 380 deletions(-) diff --git a/.github/scripts/coverage/ci_reports.py b/.github/scripts/coverage/ci_reports.py index 06f3e6e789c749..83c39a705e9954 100644 --- a/.github/scripts/coverage/ci_reports.py +++ b/.github/scripts/coverage/ci_reports.py @@ -22,6 +22,23 @@ METADATA_FILE = "coverage-artifact-metadata.json" +UPLOAD_DEFS = { + "cpp_cpu": ("coverage-cpp-cpu", "coverage.info"), + "cpp_igpu_unit": ("coverage-cpp-igpu-unit", "coverage.info"), + "cpp_igpu_func": ("coverage-cpp-igpu-func", "coverage.info"), + "cpp_dgpu_unit": ("coverage-cpp-dgpu-unit", "coverage.info"), + "cpp_dgpu_func": ("coverage-cpp-dgpu-func", "coverage.info"), + "python_cpu_xml": ("coverage-python-cpu", "python-coverage.xml"), + "python_cpu_info": ("coverage-python-cpu", "coverage.info"), + "python_igpu_xml": ("coverage-python-igpu", "python-coverage.xml"), + "python_igpu_info": ("coverage-python-igpu", "coverage.info"), + "python_dgpu_xml": ("coverage-python-dgpu", "python-coverage.xml"), + "python_dgpu_info": ("coverage-python-dgpu", "coverage.info"), + "js_cpu_lcov": ("coverage-js-cpu", "js-lcov.info"), + "js_cpu_info": ("coverage-js-cpu", "coverage.info"), +} + + SUITE_DEFS = { "cpp": { "label": "C++", @@ -310,6 +327,37 @@ def merge_durations(*, workspace: Path, output: Path) -> None: print(f"Wrote {len(rows)} duration row(s) to {output}") +def _find_upload_file(*, workspace: Path, artifact_name: str, filename: str) -> Path | None: + root = workspace / "artifacts" + if not root.exists(): + return None + + for metadata_path in sorted(root.rglob(METADATA_FILE)): + metadata = _read_json_file(metadata_path) + if str(metadata.get("artifact_name", "")).strip() != artifact_name: + continue + candidate = metadata_path.parent / filename + if candidate.is_file(): + return candidate.resolve() + + for candidate in sorted(root.rglob(filename)): + if candidate.is_file() and artifact_name in candidate.parts: + return candidate.resolve() + + return None + + +def resolve_uploads(*, workspace: Path, output_file: Path) -> None: + output_lines = [] + for output_name, (artifact_name, filename) in UPLOAD_DEFS.items(): + path = _find_upload_file(workspace=workspace, artifact_name=artifact_name, filename=filename) + output_lines.append(f"{output_name}={path or ''}") + print(f"{output_name}: {path or ''}") + + with output_file.open("a", encoding="utf-8") as handle: + handle.write("\n".join(output_lines) + "\n") + + def _parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Coverage CI report helpers") subparsers = parser.add_subparsers(dest="command", required=True) @@ -332,6 +380,10 @@ def _parse_args() -> argparse.Namespace: merge.add_argument("--workspace", type=Path, required=True) merge.add_argument("--output", type=Path, required=True) + uploads = subparsers.add_parser("resolve-uploads", help="Resolve Codecov upload files from downloaded artifacts") + uploads.add_argument("--workspace", type=Path, required=True) + uploads.add_argument("--output-file", type=Path, required=True) + return parser.parse_args() @@ -358,6 +410,9 @@ def main() -> int: if args.command == "merge-durations": merge_durations(workspace=args.workspace.resolve(), output=args.output.resolve()) return 0 + if args.command == "resolve-uploads": + resolve_uploads(workspace=args.workspace.resolve(), output_file=args.output_file.resolve()) + return 0 raise ValueError(f"Unsupported command: {args.command}") diff --git a/.github/scripts/coverage/collect_cpp_coverage.py b/.github/scripts/coverage/collect_cpp_coverage.py index 678b39ffb12548..6aeeae6a9e65db 100644 --- a/.github/scripts/coverage/collect_cpp_coverage.py +++ b/.github/scripts/coverage/collect_cpp_coverage.py @@ -199,17 +199,17 @@ def _classify_unwanted_source(rel_path: Path) -> str | None: return None -def _classify_profile_unwanted_source(rel_path: Path, *, run_gpu_tests: bool, run_npu_tests: bool) -> str | None: +def _classify_profile_unwanted_source(rel_path: Path, *, run_gpu_tests: bool) -> str | None: """Return profile-specific exclusion reasons for source paths.""" rel = rel_path.as_posix() if not run_gpu_tests and rel.startswith("src/plugins/intel_gpu/"): return "gpu-plugin-disabled" - if not run_npu_tests and rel.startswith("src/plugins/intel_npu/"): + if rel.startswith("src/plugins/intel_npu/"): return "npu-plugin-disabled" return None -def _prune_unwanted_gcda(root: Path, *, label: str, run_gpu_tests: bool, run_npu_tests: bool) -> None: +def _prune_unwanted_gcda(root: Path, *, label: str, run_gpu_tests: bool) -> None: """Delete gcda files that are known to be excluded from the final report. This reduces lcov capture time by removing whole classes of files that we @@ -224,7 +224,7 @@ def _prune_unwanted_gcda(root: Path, *, label: str, run_gpu_tests: bool, run_npu rel = gcda.relative_to(root) reason = _classify_unwanted_gcda(rel) if reason is None: - reason = _classify_profile_unwanted_source(rel, run_gpu_tests=run_gpu_tests, run_npu_tests=run_npu_tests) + reason = _classify_profile_unwanted_source(rel, run_gpu_tests=run_gpu_tests) if reason is None: continue try: @@ -292,7 +292,6 @@ def _normalize_and_filter_tracefile( workspace: Path, debug_dir: Path, run_gpu_tests: bool, - run_npu_tests: bool, ) -> dict[str, int]: """Normalize ``SF:`` paths and drop records that should not be uploaded.""" stats: dict[str, int] = { @@ -342,7 +341,6 @@ def flush_record(lines: list[str]) -> None: exclude_reason = _classify_profile_unwanted_source( rel_source, run_gpu_tests=run_gpu_tests, - run_npu_tests=run_npu_tests, ) if exclude_reason is not None: stats["dropped_excluded"] += 1 @@ -669,14 +667,12 @@ def run(ctx: CoverageContext) -> None: ctx.paths.build_dir, label="main build", run_gpu_tests=ctx.run_gpu_tests, - run_npu_tests=ctx.run_npu_tests, ) if ctx.paths.build_js_dir.exists(): _prune_unwanted_gcda( ctx.paths.build_js_dir, label="js build", run_gpu_tests=ctx.run_gpu_tests, - run_npu_tests=ctx.run_npu_tests, ) _prefilter_incompatible_gcda(ctx.paths.build_dir, label="main build") @@ -760,7 +756,6 @@ def run(ctx: CoverageContext) -> None: workspace=src_dir, debug_dir=trace_dir, run_gpu_tests=ctx.run_gpu_tests, - run_npu_tests=ctx.run_npu_tests, ) if not merged_info.exists() or merged_info.stat().st_size == 0: diff --git a/.github/scripts/coverage/config/tests_cpp.yml b/.github/scripts/coverage/config/tests_cpp.yml index 0d5891d8b8ae80..a5a070673175a0 100644 --- a/.github/scripts/coverage/config/tests_cpp.yml +++ b/.github/scripts/coverage/config/tests_cpp.yml @@ -3,34 +3,48 @@ suite: cpp tests: - name: ov_api_conformance_tests binary: ov_api_conformance_tests - mode: gtest_single - args: "*mandatory*" + mode: raw + profiles: [cpu, gpu] + args: + cpu: "--device=CPU --gtest_filter=*mandatory*" + gpu: "--device=GPU --gtest_filter=*mandatory*" - name: ov_auto_batch_func_tests binary: ov_auto_batch_func_tests mode: gtest_single - args: "*smoke*" + profiles: [cpu, gpu] + args: + cpu: "*smoke*" + gpu: "*GPU*:*nightly_OVClassCompiledModelGetPropertyTest*:*nightly_OVClassCompiledModelGetIncorrectPropertyTest*:*nightly_Auto_Batch_OVClassCompileModelWithCorrectPropertiesAutoBatchingTest*" - name: ov_auto_batch_unit_tests binary: ov_auto_batch_unit_tests mode: gtest_single + profiles: [cpu] - name: ov_auto_func_tests binary: ov_auto_func_tests mode: gtest_single + profiles: [cpu] args: "*smoke*" - name: ov_auto_unit_tests binary: ov_auto_unit_tests mode: gtest_single + profiles: [cpu] - name: ov_capi_test binary: ov_capi_test mode: gtest_single + profiles: [cpu, gpu] + args: + cpu: "-*ov_core_gpu*:*intel_gpu*" + gpu: "*ov_core_gpu*:*intel_gpu*" - name: ov_conditional_compilation_tests binary: ov_conditional_compilation_tests mode: gtest_single + profiles: [cpu] - name: ov_core_unit_tests binary: ov_core_unit_tests @@ -39,45 +53,55 @@ tests: args: cpu: "-*IE_GPU*" gpu: "*IE_GPU*" - default: "" - name: ov_cpu_func_tests binary: ov_cpu_func_tests mode: gtest_single + profiles: [cpu] args: "*smoke*" - name: ov_cpu_unit_tests binary: ov_cpu_unit_tests mode: gtest_single + profiles: [cpu] - name: ov_cpu_unit_tests_vectorized binary: ov_cpu_unit_tests_vectorized mode: gtest_single + profiles: [cpu] - name: ov_hetero_func_tests binary: ov_hetero_func_tests mode: gtest_single - args: "*smoke*:-nightly*" + profiles: [cpu, gpu] + args: + cpu: "*smoke*:-nightly*" + gpu: "*GPU*:*nightly_HETERO_OVClassCompileModelWithCorrectPropertiesTest*/*/2:*nightly_HETERO_OVClassCompileModelWithCorrectPropertiesTest*/*/4:*nightly_HETERO_OVClassCompileModelWithCorrectSecondaryPropertiesTest*:*nightly_OVClassCompiledModelGetPropertyTest*:*nightly_OVClassCompiledModelGetIncorrectPropertyTest*" - name: ov_hetero_unit_tests binary: ov_hetero_unit_tests mode: gtest_single + profiles: [cpu] - name: ov_inference_functional_tests binary: ov_inference_functional_tests mode: gtest_single + profiles: [cpu] - name: ov_inference_unit_tests binary: ov_inference_unit_tests mode: gtest_single + profiles: [cpu] - name: ov_ir_frontend_tests binary: ov_ir_frontend_tests mode: gtest_single + profiles: [cpu] - name: ov_lp_transformations_tests binary: ov_lp_transformations_tests mode: gtest_single + profiles: [cpu] - name: ov_onnx_frontend_tests binary: ov_onnx_frontend_tests @@ -86,7 +110,6 @@ tests: args: cpu: "-*IE_GPU*" gpu: "*IE_GPU*" - default: "" - name: ov_onnx_frontend_tests (ONNX_ITERATOR=0) binary: ov_onnx_frontend_tests @@ -96,33 +119,40 @@ tests: args: cpu: "-*IE_GPU*" gpu: "*IE_GPU*" - default: "" - name: ov_op_conformance_tests binary: ov_op_conformance_tests mode: raw - args: "--device=TEMPLATE --gtest_filter=*OpImpl*" + profiles: [cpu, gpu] + args: + cpu: "--device=TEMPLATE --gtest_filter=*OpImpl*" + gpu: "--device=GPU --gtest_filter=*OpImpl*" - name: ov_proxy_plugin_tests binary: ov_proxy_plugin_tests mode: gtest_single + profiles: [cpu] - name: ov_snippets_func_tests binary: ov_snippets_func_tests mode: gtest_single + profiles: [cpu] - name: ov_subgraphs_dumper_tests binary: ov_subgraphs_dumper_tests mode: gtest_single + profiles: [cpu] - name: ov_template_func_tests binary: ov_template_func_tests mode: gtest_single + profiles: [cpu] args: "*smoke*" - name: ov_tensorflow_common_tests binary: ov_tensorflow_common_tests mode: gtest_single + profiles: [cpu] - name: ov_tensorflow_frontend_tests binary: ov_tensorflow_frontend_tests @@ -131,69 +161,50 @@ tests: args: cpu: "-*IE_GPU*" gpu: "*IE_GPU*" - default: "" - name: ov_tensorflow_lite_frontend_tests binary: ov_tensorflow_lite_frontend_tests mode: gtest_single + profiles: [cpu] - name: ov_transformations_tests binary: ov_transformations_tests mode: gtest_single + profiles: [cpu] - name: ov_util_tests binary: ov_util_tests mode: gtest_single + profiles: [cpu] - name: ov_paddle_tests binary: ov_paddle_tests mode: gtest_single - - - name: ov_npu_unit_tests - binary: ov_npu_unit_tests - mode: gtest_single - profiles: [npu] - profile_skip_reason: "NPU profile is OFF" - - - name: ov_npu_func_tests - binary: ov_npu_func_tests - mode: gtest_single - args: "*smoke*" - profiles: [npu] - profile_skip_reason: "NPU profile is OFF" - - - name: ov_nvidia_func_tests - binary: ov_nvidia_func_tests - mode: gtest_single - skip_reason: "unsupported in coverage workflow" + profiles: [cpu] - name: ov_gpu_unit_tests binary: ov_gpu_unit_tests mode: gtest_single profiles: [gpu] - profile_skip_reason: "GPU profile is OFF" - name: ov_gpu_func_tests binary: ov_gpu_func_tests mode: gtest_single args: "*smoke*" profiles: [gpu] - profile_skip_reason: "GPU profile is OFF" - name: test_inference_async - binary: test_inference_async + binary: memory_tests/test_inference_async mode: raw - profiles: [cpu, gpu, npu] + profiles: [cpu, gpu] args: + cpu: "__MODEL_PATH__ CPU" gpu: "__MODEL_PATH__ GPU" - npu: "__MODEL_PATH__ NPU" - default: "__MODEL_PATH__ CPU" - name: test_inference_sync - binary: test_inference_sync + binary: memory_tests/test_inference_sync mode: raw - profiles: [cpu, gpu, npu] + profiles: [cpu, gpu] args: + cpu: "__MODEL_PATH__ CPU" gpu: "__MODEL_PATH__ GPU" - npu: "__MODEL_PATH__ NPU" - default: "__MODEL_PATH__ CPU" diff --git a/.github/scripts/coverage/config/tests_js.yml b/.github/scripts/coverage/config/tests_js.yml index 2f0d6d08578d01..9f6c7fc9553084 100644 --- a/.github/scripts/coverage/config/tests_js.yml +++ b/.github/scripts/coverage/config/tests_js.yml @@ -3,58 +3,65 @@ suite: js tests: - name: node tests/setup.js (covered) kind: command + profiles: [cpu] command: "npx c8 --reporter=lcov --reporter=text --report-dir ${OV_WORKSPACE}/js-coverage --clean=true --exclude 'tests/**' --exclude 'thirdparty/**' node ./tests/setup.js" - name: node scripts/download-runtime.js --ignore-if-exists (covered) kind: command + profiles: [cpu] command: "npx c8 --reporter=lcov --reporter=text --report-dir ${OV_WORKSPACE}/js-coverage --clean=false --exclude 'tests/**' --exclude 'thirdparty/**' node ./scripts/download-runtime.js --ignore-if-exists" - name: node unit core.test.js kind: command + profiles: [cpu] command: "npx c8 --reporter=lcov --reporter=text --report-dir ${OV_WORKSPACE}/js-coverage --clean=false --exclude 'tests/**' --exclude 'thirdparty/**' node --test ./tests/unit/core.test.js" - name: node unit model.test.js kind: command + profiles: [cpu] command: "npx c8 --reporter=lcov --reporter=text --report-dir ${OV_WORKSPACE}/js-coverage --clean=false --exclude 'tests/**' --exclude 'thirdparty/**' node --test ./tests/unit/model.test.js" - name: node unit read_model.test.js kind: command + profiles: [cpu] command: "npx c8 --reporter=lcov --reporter=text --report-dir ${OV_WORKSPACE}/js-coverage --clean=false --exclude 'tests/**' --exclude 'thirdparty/**' node --test ./tests/unit/read_model.test.js" - name: node unit basic.test.js kind: command + profiles: [cpu] command: "npx c8 --reporter=lcov --reporter=text --report-dir ${OV_WORKSPACE}/js-coverage --clean=false --exclude 'tests/**' --exclude 'thirdparty/**' node --test ./tests/unit/basic.test.js" - name: node unit compiled_model.test.js kind: command + profiles: [cpu] command: "npx c8 --reporter=lcov --reporter=text --report-dir ${OV_WORKSPACE}/js-coverage --clean=false --exclude 'tests/**' --exclude 'thirdparty/**' node --test ./tests/unit/compiled_model.test.js" - name: node unit infer_request.test.js kind: command + profiles: [cpu] command: "npx c8 --reporter=lcov --reporter=text --report-dir ${OV_WORKSPACE}/js-coverage --clean=false --exclude 'tests/**' --exclude 'thirdparty/**' node --test ./tests/unit/infer_request.test.js" - name: node unit async_infer_queue.test.js kind: command + profiles: [cpu] command: "npx c8 --reporter=lcov --reporter=text --report-dir ${OV_WORKSPACE}/js-coverage --clean=false --exclude 'tests/**' --exclude 'thirdparty/**' node --test ./tests/unit/async_infer_queue.test.js" - name: node unit tensor.test.js kind: command + profiles: [cpu] command: "npx c8 --reporter=lcov --reporter=text --report-dir ${OV_WORKSPACE}/js-coverage --clean=false --exclude 'tests/**' --exclude 'thirdparty/**' node --test ./tests/unit/tensor.test.js" - name: node unit partial_shape.test.js kind: command + profiles: [cpu] command: "npx c8 --reporter=lcov --reporter=text --report-dir ${OV_WORKSPACE}/js-coverage --clean=false --exclude 'tests/**' --exclude 'thirdparty/**' node --test ./tests/unit/partial_shape.test.js" - name: node unit pre_post_processor.test.js kind: command + profiles: [cpu] command: "npx c8 --reporter=lcov --reporter=text --report-dir ${OV_WORKSPACE}/js-coverage --clean=false --exclude 'tests/**' --exclude 'thirdparty/**' node --test ./tests/unit/pre_post_processor.test.js" - name: npm run test:e2e kind: command + profiles: [cpu] command: "npx c8 --reporter=lcov --reporter=text --report-dir ${OV_WORKSPACE}/js-coverage --clean=false --exclude 'tests/**' --exclude 'thirdparty/**' npm run test:e2e --loglevel=silly" - - - name: js_gpu_compile_model_smoke - kind: command - profiles: [gpu] - profile_skip_reason: "GPU profile is OFF" - command: "npx c8 --reporter=lcov --reporter=text --report-dir ${OV_WORKSPACE}/js-coverage --clean=false --exclude 'tests/**' --exclude 'thirdparty/**' node -e \"const path=require('path'); const { addon: ov } = require('.'); const core = new ov.Core(); const devices = core.getAvailableDevices(); if (!devices.includes('GPU')) { throw new Error('GPU device is not available'); } const model = core.readModelSync(path.resolve('./tests/unit/test_models/add_model.xml')); const compiled = core.compileModelSync(model, 'GPU'); if (!compiled) { throw new Error('Failed to compile model on GPU'); } console.log('GPU JS smoke OK');\"" diff --git a/.github/scripts/coverage/config/tests_python.yml b/.github/scripts/coverage/config/tests_python.yml index f50573ca1ad83d..7a1d94e3f6d191 100644 --- a/.github/scripts/coverage/config/tests_python.yml +++ b/.github/scripts/coverage/config/tests_python.yml @@ -3,136 +3,149 @@ suite: python tests: - name: pyopenvino kind: pytest + profiles: [cpu, gpu] target: "${TESTS_DIR}/pyopenvino" - args: "-sv -k 'not cuda' --ignore=${TESTS_DIR}/pyopenvino/tests/test_utils/test_utils.py" + env: + cpu: "TEST_DEVICE=CPU" + gpu: "TEST_DEVICE=GPU" + args: + cpu: "-sv -k 'not gpu and not cuda and not npu_'" + gpu: "-sv -k 'not cuda and not npu_'" - name: onnx_python kind: pytest + profiles: [cpu, gpu] target: "${TESTS_DIR}/onnx" - args: "-sv -k 'not cuda' --ignore=${TESTS_DIR}/onnx/test_python/test_zoo_models.py" + env: + cpu: "TEST_DEVICE=CPU" + gpu: "TEST_DEVICE=GPU" + args: + cpu: "-sv --backend=CPU -k 'not gpu and not cuda and not npu_ and not zoo' --ignore=${TESTS_DIR}/onnx/test_python/test_zoo_models.py --ignore=${TESTS_DIR}/onnx/tests/tests_python/test_zoo_models.py" + gpu: "-sv --backend=GPU -k 'not cuda and not npu_ and not zoo' --ignore=${TESTS_DIR}/onnx/test_python/test_zoo_models.py --ignore=${TESTS_DIR}/onnx/tests/tests_python/test_zoo_models.py" + + - name: onnx_python_legacy_iterator + kind: pytest + profiles: [cpu, gpu] + target: "${TESTS_DIR}/onnx" + env: + cpu: "TEST_DEVICE=CPU ONNX_ITERATOR=0" + gpu: "TEST_DEVICE=GPU ONNX_ITERATOR=0" + args: + cpu: "-sv --backend=CPU -k 'not gpu and not cuda and not npu_ and not zoo' --ignore=${TESTS_DIR}/onnx/test_python/test_zoo_models.py --ignore=${TESTS_DIR}/onnx/tests/tests_python/test_zoo_models.py" + gpu: "-sv --backend=GPU -k 'not cuda and not npu_ and not zoo' --ignore=${TESTS_DIR}/onnx/test_python/test_zoo_models.py --ignore=${TESTS_DIR}/onnx/tests/tests_python/test_zoo_models.py" - name: ovc_unit kind: pytest + profiles: [cpu] target: "${TESTS_DIR}/ovc/unit_tests" - args: "-sv -k 'not cuda'" + args: "-sv -k 'not gpu and not cuda and not npu_'" - name: py_frontend - kind: pytest_if_dir + kind: pytest + profiles: [cpu] target: "${TESTS_DIR}/layer_tests/py_frontend_tests" - args: "-sv -v" + args: "-sv -k 'not gpu and not cuda and not npu_'" - name: tensorflow_lite_layers - kind: pytest_if_dir + kind: pytest + profiles: [cpu, gpu] target: "${TESTS_DIR}/layer_tests/tensorflow_lite_tests" - env: "TEST_DEVICE=CPU TEST_PRECISION=FP16" - args: "-sv -v" + env: + cpu: "TEST_DEVICE=CPU TEST_PRECISION=FP16" + gpu: "TEST_DEVICE=GPU TEST_PRECISION=FP16" + args: + cpu: "-sv -k 'not gpu and not cuda and not npu_'" + gpu: "-sv -k 'not cuda and not npu_'" - name: tensorflow_layers - kind: pytest_if_dir + kind: pytest + profiles: [cpu, gpu] target: "${TESTS_DIR}/layer_tests/tensorflow_tests" - env: "TEST_DEVICE=CPU TEST_PRECISION=FP16" - args: "-sv -m precommit -v" + env: + cpu: "TEST_DEVICE=CPU TEST_PRECISION=FP16" + gpu: "TEST_DEVICE=GPU TEST_PRECISION=FP16" + args: + cpu: "-sv -m precommit -k 'not gpu and not cuda and not npu_'" + gpu: "-sv -m precommit -k 'not cuda and not npu_'" - name: tensorflow2_keras_layers - kind: pytest_if_dir + kind: pytest + profiles: [cpu, gpu] target: "${TESTS_DIR}/layer_tests/tensorflow2_keras_tests" - env: "TEST_DEVICE=CPU" - args: "-sv -m precommit -v" + env: + cpu: "TEST_DEVICE=CPU TEST_PRECISION=" + gpu: "TEST_DEVICE=GPU TEST_PRECISION=FP16" + args: + cpu: "-sv -m precommit -k 'not gpu and not cuda and not npu_'" + gpu: "-sv -m precommit -k 'not cuda and not npu_'" - name: onnx_layers - kind: pytest_if_dir - profiles: [cpu, gpu, npu] + kind: pytest + profiles: [cpu, gpu] target: "${TESTS_DIR}/layer_tests/onnx_tests" env: cpu: "TEST_DEVICE=CPU TEST_PRECISION=FP16" gpu: "TEST_DEVICE=GPU TEST_PRECISION=FP16" - npu: "TEST_DEVICE=NPU TEST_PRECISION=FP16" - default: "TEST_DEVICE=CPU TEST_PRECISION=FP16" args: - cpu: "-sv -k 'not gpu and not cuda and not npu'" - gpu: "-sv -k 'not cuda and not npu'" - npu: "-sv -k 'not cuda and not gpu'" - default: "-sv -k 'not gpu and not cuda and not npu'" + cpu: "-sv -k 'not gpu and not cuda and not npu_'" + gpu: "-sv -k 'not cuda and not npu_'" - name: pytorch_layers - kind: pytest_if_dir + kind: pytest + profiles: [cpu, gpu] target: "${TESTS_DIR}/layer_tests/pytorch_tests" - env: "TEST_DEVICE=CPU TEST_PRECISION=FP32" - args: "-sv -m precommit -v" + env: + cpu: "TEST_DEVICE=CPU TEST_PRECISION=FP32" + gpu: "TEST_DEVICE=GPU TEST_PRECISION=FP32" + args: + cpu: "-sv -m precommit -k 'not gpu and not cuda and not npu_'" + gpu: "-sv -m precommit -k 'not cuda and not npu_'" - name: pytorch_layers_export - kind: pytest_if_dir + kind: pytest + profiles: [cpu, gpu] target: "${TESTS_DIR}/layer_tests/pytorch_tests" - env: "TEST_DEVICE=CPU TEST_PRECISION=FP32 PYTORCH_TRACING_MODE=EXPORT" - args: "-sv -m precommit_torch_export -v" + env: + cpu: "TEST_DEVICE=CPU TEST_PRECISION=FP32 PYTORCH_TRACING_MODE=EXPORT" + gpu: "TEST_DEVICE=GPU TEST_PRECISION=FP32 PYTORCH_TRACING_MODE=EXPORT" + args: + cpu: "-sv -m precommit_torch_export -k 'not gpu and not cuda and not npu_'" + gpu: "-sv -m precommit_torch_export -k 'not cuda and not npu_'" - name: pytorch_layers_fx_backend - kind: pytest_if_dir + kind: pytest + profiles: [cpu, gpu] target: "${TESTS_DIR}/layer_tests/pytorch_tests" - env: "TEST_DEVICE=CPU TEST_PRECISION=FP32 PYTORCH_TRACING_MODE=TORCHFX" - args: "-sv -m precommit_fx_backend -v" + env: + cpu: "TEST_DEVICE=CPU TEST_PRECISION=FP32 PYTORCH_TRACING_MODE=TORCHFX" + gpu: "TEST_DEVICE=GPU TEST_PRECISION=FP32 PYTORCH_TRACING_MODE=TORCHFX" + args: + cpu: "-sv -m precommit_fx_backend -k 'not gpu and not cuda and not npu_'" + gpu: "-sv -m precommit_fx_backend -k 'not cuda and not npu_'" - name: jax_layers_precommit - kind: pytest_if_dir - target: "${TESTS_DIR}/layer_tests/jax_tests" - env: "TEST_DEVICE=CPU TEST_PRECISION=FP16" - args: "-sv -m precommit_jax_fe -k 'not cuda'" - - - name: src_py_runtime - kind: pytest_if_dir - target: "${SRC_PY_TESTS_DIR}/test_runtime" - args: "-sv -k 'not gpu and not cuda and not npu'" - - - name: src_py_graph - kind: pytest_if_dir - target: "${SRC_PY_TESTS_DIR}/test_graph" - args: "-sv -k 'not gpu and not cuda and not npu'" - - - name: src_py_transformations - kind: pytest_if_dir - target: "${SRC_PY_TESTS_DIR}/test_transformations" - args: "-sv -k 'not gpu and not cuda and not npu'" - - - name: src_onnx_frontend_python - kind: pytest_if_dir - target: "${ONNX_PY_TESTS_DIR}" - args: "-sv -k 'not gpu and not cuda and not npu and not zoo' --ignore=${ONNX_PY_TESTS_DIR}/test_zoo_models.py" - - - name: src_py_runtime_strict - kind: pytest_if_dir - target: "${SRC_PY_TESTS_DIR}/test_runtime" - args: "-q --maxfail=1 -k 'not gpu and not cuda and not npu'" - - - name: src_py_utils - kind: pytest_if_dir - target: "${SRC_PY_TESTS_DIR}/test_utils" - args: "-sv -k 'not gpu and not cuda and not npu'" - - - name: src_py_package_versions kind: pytest - target: "${SRC_PY_TESTS_DIR}/test_package_versions.py" - args: "-sv -k 'not cuda'" + profiles: [cpu, gpu] + target: "${TESTS_DIR}/layer_tests/jax_tests" + env: + cpu: "TEST_DEVICE=CPU TEST_PRECISION=FP16 JAX_TRACE_MODE=JAXPR" + gpu: "TEST_DEVICE=GPU TEST_PRECISION=FP16 JAX_TRACE_MODE=JAXPR" + args: + cpu: "-sv -m precommit_jax_fe -k 'not gpu and not cuda and not npu_'" + gpu: "-sv -m precommit_jax_fe -k 'not cuda and not npu_'" - - name: src_py_runtime_remote_api_gpu + - name: ovc_python_api kind: pytest - profiles: [gpu] - profile_skip_reason: "GPU profile is OFF" - target: "${SRC_PY_TESTS_DIR}/test_runtime/test_remote_api.py" - env: "TEST_DEVICE=GPU TEST_PRECISION=FP16" - args: "-sv -k 'not cuda'" - - - name: tf_api_precommit_subset - kind: pytest_if_dir + profiles: [cpu, gpu] target: "${WORKSPACE_LAYER_TESTS_DIR}/ovc_python_api_tests" - env: "TEST_DEVICE=CPU TEST_PRECISION=FP16" - args: "-sv -m precommit_tf_fe -k 'test_tf and not cuda' -v" - - - name: ovc_python_api_paddle_precommit - kind: pytest - target: "${WORKSPACE_LAYER_TESTS_DIR}/ovc_python_api_tests/test_paddle.py" - env: "TEST_DEVICE=CPU TEST_PRECISION=FP16" - args: "-sv -m precommit -k 'not cuda'" + env: + cpu: "TEST_DEVICE=CPU TEST_PRECISION=FP16" + gpu: "TEST_DEVICE=GPU TEST_PRECISION=FP16" + args: + cpu: "-sv -k 'not gpu and not cuda and not npu_'" + gpu: "-sv -k 'not cuda and not npu_'" - - name: ovc_cli_help + - name: docs_python_snippets kind: command - command: "python3 -m coverage run -a -m openvino.tools.ovc --help" + profiles: [cpu] + command: "PYTHONPATH=docs/articles_en/assets:${PYTHONPATH} python3 -m coverage run -a docs/articles_en/assets/snippets/main.py" diff --git a/.github/scripts/coverage/coverage.py b/.github/scripts/coverage/coverage.py index 7aec77902ad6b2..80be466b733bb4 100644 --- a/.github/scripts/coverage/coverage.py +++ b/.github/scripts/coverage/coverage.py @@ -24,7 +24,7 @@ sys.modules.setdefault("coverage_workflow", sys.modules[__name__]) -SUPPORTED_PROFILES = {"cpu", "gpu", "npu"} +SUPPORTED_PROFILES = {"cpu", "gpu"} def _build_logger() -> logging.Logger: @@ -47,8 +47,6 @@ def _build_logger() -> logging.Logger: @dataclass(frozen=True) class CppTestCase: name: str - enabled: bool - skip_reason: str binary: str mode: str args: str @@ -58,8 +56,6 @@ class CppTestCase: @dataclass(frozen=True) class PythonTestCase: name: str - enabled: bool - skip_reason: str kind: str target: str args: str @@ -70,8 +66,6 @@ class PythonTestCase: @dataclass(frozen=True) class JsTestCase: name: str - enabled: bool - skip_reason: str kind: str command: str @@ -87,14 +81,6 @@ class Paths: model_path: Path -@dataclass(frozen=True) -class ProfileFlags: - run_gpu_tests: bool - run_npu_tests: bool - gpu_flags: tuple[str, ...] - npu_flags: tuple[str, ...] - - @dataclass(frozen=True) class ConfigValidationIssue: suite: str @@ -225,29 +211,14 @@ def _repo_root(default: Path) -> Path: return default -def _profile_flags(profile: str) -> ProfileFlags: - """Resolve build flags and runtime switches for a test profile.""" - if profile == "cpu": - return ProfileFlags(False, False, ("-DENABLE_INTEL_GPU=OFF", "-DENABLE_ONEDNN_FOR_GPU=OFF"), ("-DENABLE_INTEL_NPU=OFF",)) - if profile == "gpu": - return ProfileFlags(True, False, ("-DENABLE_INTEL_GPU=ON", "-DENABLE_ONEDNN_FOR_GPU=ON"), ("-DENABLE_INTEL_NPU=OFF",)) - if profile == "npu": - return ProfileFlags(False, True, ("-DENABLE_INTEL_GPU=OFF", "-DENABLE_ONEDNN_FOR_GPU=OFF"), ("-DENABLE_INTEL_NPU=ON",)) - raise ValueError(f"Unsupported TEST_PROFILE: {profile}. Use one of: {', '.join(sorted(SUPPORTED_PROFILES))}") - - @dataclass class CoverageContext: """Runtime configuration shared by coverage workflow steps.""" workspace: Path - build_type: str branch_coverage: bool test_profile: str - cc: str - cxx: str paths: Paths - profile_flags: ProfileFlags io: GithubIO @classmethod @@ -274,9 +245,6 @@ def _bool_env(name: str, fallback: bool) -> bool: if test_profile not in SUPPORTED_PROFILES: raise ValueError(f"Unsupported TEST_PROFILE: {test_profile}. Use one of: {', '.join(sorted(SUPPORTED_PROFILES))}") - cc = os.environ.get("CC", "gcc") - cxx = os.environ.get("CXX", "g++") - paths = Paths( workspace=workspace, build_dir=Path(os.environ.get("BUILD_DIR", str(workspace / "build"))), @@ -287,51 +255,28 @@ def _bool_env(name: str, fallback: bool) -> bool: model_path=Path(os.environ.get("MODEL_PATH", str(workspace / "src" / "core" / "tests" / "models" / "ir" / "add_abc.xml"))), ) - profile_flags = _profile_flags(test_profile) - os.environ["OV_WORKSPACE"] = str(workspace) os.environ["CMAKE_BUILD_TYPE"] = build_type os.environ["ENABLE_BRANCH_COVERAGE"] = "true" if branch_coverage else "false" os.environ["TEST_PROFILE"] = test_profile - os.environ["RUN_GPU_TESTS"] = "true" if profile_flags.run_gpu_tests else "false" - os.environ["RUN_NPU_TESTS"] = "true" if profile_flags.run_npu_tests else "false" return cls( workspace=workspace, - build_type=build_type, branch_coverage=branch_coverage, test_profile=test_profile, - cc=cc, - cxx=cxx, paths=paths, - profile_flags=profile_flags, io=io, ) @property def run_gpu_tests(self) -> bool: - return self.profile_flags.run_gpu_tests - - @property - def run_npu_tests(self) -> bool: - return self.profile_flags.run_npu_tests - - @property - def gpu_flags(self) -> tuple[str, ...]: - return self.profile_flags.gpu_flags - - @property - def npu_flags(self) -> tuple[str, ...]: - return self.profile_flags.npu_flags + return self.test_profile == "gpu" def log_profile(self) -> None: - """Print the resolved profile and accelerator flags.""" + """Print the resolved profile.""" LOGGER.info("TEST_PROFILE=%s", self.test_profile) LOGGER.info("ENABLE_BRANCH_COVERAGE=%s", "true" if self.branch_coverage else "false") LOGGER.info("RUN_GPU_TESTS=%s", "true" if self.run_gpu_tests else "false") - LOGGER.info("RUN_NPU_TESTS=%s", "true" if self.run_npu_tests else "false") - LOGGER.info("GPU_FLAGS=%s", " ".join(self.gpu_flags)) - LOGGER.info("NPU_FLAGS=%s", " ".join(self.npu_flags)) def _as_text(value: Any) -> str: @@ -344,38 +289,18 @@ def _as_text(value: Any) -> str: def _resolve_profile_value(value: Any, profile: str) -> str: - """Pick a profile-specific config value when a mapping is provided.""" + """Pick an explicit profile-specific config value when a mapping is provided.""" if isinstance(value, dict): - if profile in value: - return _as_text(value[profile]) - return _as_text(value.get("default", "")) + return _as_text(value.get(profile, "")) return _as_text(value) -def _resolve_enabled(test: dict[str, Any], profile: str) -> tuple[bool, str]: - """Decide whether a configured test is enabled for the active profile.""" - skip_reason = _as_text(test.get("skip_reason", "")).strip() +def _configured_profiles(test: dict[str, Any]) -> tuple[str, ...]: + """Return the explicit profiles configured for a test.""" profiles = test.get("profiles") - - # Accelerator-specific profiles execute only tests explicitly marked for them. - if profile in {"gpu", "npu"} and profiles is None: - reason = _as_text(test.get("profile_skip_reason", "")).strip() - if not reason: - reason = f"{profile.upper()} profile is OFF" - return False, reason - - if profiles is not None: - normalized = [str(p) for p in profiles] - if profile not in normalized: - reason = _as_text(test.get("profile_skip_reason", "")).strip() - if not reason: - reason = f"{profile.upper()} profile is OFF" - return False, reason - - if skip_reason: - return False, skip_reason - - return True, "" + if not isinstance(profiles, list): + return () + return tuple(_as_text(profile).strip() for profile in profiles) def _load_yaml(path: Path) -> dict[str, Any]: @@ -412,12 +337,11 @@ def load_cpp_tests(path: Path, profile: str) -> list[CppTestCase]: loaded: list[CppTestCase] = [] for test in _load_tests(path, "cpp"): - enabled, reason = _resolve_enabled(test, profile) + if profile not in _configured_profiles(test): + continue loaded.append( CppTestCase( name=_as_text(test.get("name", "")).strip(), - enabled=enabled, - skip_reason=reason, binary=_as_text(test.get("binary", "")).strip(), mode=_as_text(test.get("mode", "gtest_single")).strip() or "gtest_single", args=_resolve_profile_value(test.get("args", ""), profile).strip(), @@ -434,12 +358,11 @@ def load_python_tests(path: Path, profile: str) -> list[PythonTestCase]: loaded: list[PythonTestCase] = [] for test in _load_tests(path, "python"): - enabled, reason = _resolve_enabled(test, profile) + if profile not in _configured_profiles(test): + continue loaded.append( PythonTestCase( name=_as_text(test.get("name", "")).strip(), - enabled=enabled, - skip_reason=reason, kind=_as_text(test.get("kind", "pytest")).strip() or "pytest", target=_resolve_profile_value(test.get("target", ""), profile).strip(), args=_resolve_profile_value(test.get("args", ""), profile).strip(), @@ -457,12 +380,11 @@ def load_js_tests(path: Path, profile: str) -> list[JsTestCase]: loaded: list[JsTestCase] = [] for test in _load_tests(path, "js"): - enabled, reason = _resolve_enabled(test, profile) + if profile not in _configured_profiles(test): + continue loaded.append( JsTestCase( name=_as_text(test.get("name", "")).strip(), - enabled=enabled, - skip_reason=reason, kind=_as_text(test.get("kind", "command")).strip() or "command", command=_resolve_profile_value(test.get("command", ""), profile).strip(), ) @@ -470,6 +392,17 @@ def load_js_tests(path: Path, profile: str) -> list[JsTestCase]: return loaded +def _profile_value_fields(suite: str) -> tuple[str, ...]: + """Return config fields that may contain per-profile mappings.""" + if suite == "cpp": + return ("args", "extra_env") + if suite == "python": + return ("target", "args", "env", "command") + if suite == "js": + return ("command",) + return () + + def validate_configs(config_dir: Path) -> list[ConfigValidationIssue]: """Validate coverage YAML configs and return any issues found.""" issues: list[ConfigValidationIssue] = [] @@ -484,13 +417,29 @@ def validate_configs(config_dir: Path) -> list[ConfigValidationIssue]: tests = _load_tests(path, suite) for idx, test in enumerate(tests): name = _as_text(test.get("name", f"")) + profiles = test.get("profiles") + expected_profiles = _configured_profiles(test) if not _as_text(test.get("name", "")).strip(): issues.append(ConfigValidationIssue(suite, name, "missing 'name'")) + if not isinstance(profiles, list) or not profiles: + issues.append(ConfigValidationIssue(suite, name, "'profiles' must be a non-empty list")) + else: + for profile_name in expected_profiles: + if profile_name not in SUPPORTED_PROFILES: + issues.append(ConfigValidationIssue(suite, name, f"unsupported profile '{profile_name}'")) + for field in _profile_value_fields(suite): + value = test.get(field) + if not isinstance(value, dict): + continue + mapped_profiles = {_as_text(profile).strip() for profile in value} + for profile_name in expected_profiles: + if profile_name in SUPPORTED_PROFILES and profile_name not in mapped_profiles: + issues.append(ConfigValidationIssue(suite, name, f"'{field}' missing '{profile_name}' value")) if suite == "cpp" and not _as_text(test.get("binary", "")).strip(): issues.append(ConfigValidationIssue(suite, name, "missing 'binary'")) if suite == "python": kind = _as_text(test.get("kind", "pytest")).strip() or "pytest" - if kind not in {"pytest", "pytest_if_dir", "command"}: + if kind not in {"pytest", "command"}: issues.append(ConfigValidationIssue(suite, name, f"unsupported kind '{kind}'")) if suite == "js": kind = _as_text(test.get("kind", "command")).strip() or "command" @@ -558,19 +507,16 @@ def _command_step(args: argparse.Namespace) -> int: def _command_list_tests(args: argparse.Namespace) -> int: """Print resolved tests for a suite/profile pair.""" _apply_common_env(args) - workspace = Path(os.environ.get("OV_WORKSPACE") or os.environ.get("GITHUB_WORKSPACE") or Path.cwd()).resolve() config_dir = CONFIG_DIR if args.suite == "cpp": tests = load_cpp_tests(config_dir / "tests_cpp.yml", args.profile) - print("name\tenabled\tskip_reason\tbinary\tmode\targs\textra_env") + print("name\tbinary\tmode\targs\textra_env") for t in tests: print( "\t".join( [ t.name, - "1" if t.enabled else "0", - t.skip_reason, t.binary, t.mode, t.args, @@ -580,14 +526,12 @@ def _command_list_tests(args: argparse.Namespace) -> int: ) elif args.suite == "python": tests = load_python_tests(config_dir / "tests_python.yml", args.profile) - print("name\tenabled\tskip_reason\tkind\ttarget\targs\tenv\tcommand") + print("name\tkind\ttarget\targs\tenv\tcommand") for t in tests: print( "\t".join( [ t.name, - "1" if t.enabled else "0", - t.skip_reason, t.kind, t.target, t.args, @@ -598,14 +542,12 @@ def _command_list_tests(args: argparse.Namespace) -> int: ) else: tests = load_js_tests(config_dir / "tests_js.yml", args.profile) - print("name\tenabled\tskip_reason\tkind\tcommand") + print("name\tkind\tcommand") for t in tests: print( "\t".join( [ t.name, - "1" if t.enabled else "0", - t.skip_reason, t.kind, t.command, ] @@ -618,7 +560,6 @@ def _command_list_tests(args: argparse.Namespace) -> int: def _command_validate_config(args: argparse.Namespace) -> int: """Validate the coverage YAML configuration files.""" _apply_common_env(args) - workspace = Path(os.environ.get("OV_WORKSPACE") or os.environ.get("GITHUB_WORKSPACE") or Path.cwd()).resolve() issues = validate_configs(CONFIG_DIR) if issues: diff --git a/.github/scripts/coverage/run_tests.py b/.github/scripts/coverage/run_tests.py index 86aec212818998..8a1af4c023842f 100644 --- a/.github/scripts/coverage/run_tests.py +++ b/.github/scripts/coverage/run_tests.py @@ -222,7 +222,7 @@ def _append_summary( lines.extend(extra_lines) lines.extend( [ - f"{suite.label} test selection: {', '.join(selected_names) if selected_names else 'all enabled tests'}", + f"{suite.label} test selection: {', '.join(selected_names) if selected_names else 'all configured tests'}", f"{suite.label} tests executed: {executed}", f"{suite.label} tests passed: {passed}", f"{suite.label} tests failed: {len(failed)}", @@ -639,16 +639,11 @@ def run_cpp(ctx: CoverageContext) -> None: shutil.rmtree(ctx.paths.build_dir / "gcov", ignore_errors=True) results_by_name: dict[str, _TestRunResult] = {} - enabled_tests: list[tuple[CppTestCase, str]] = [] - for test in tests: - if not test.enabled: - results_by_name[test.name] = _TestRunResult(test.name, "skipped", f"{test.name} ({test.skip_reason})") - continue - enabled_tests.append((test, test.args.replace("__MODEL_PATH__", str(ctx.paths.model_path)))) + configured_tests = [(test, test.args.replace("__MODEL_PATH__", str(ctx.paths.model_path))) for test in tests] - if enabled_tests: + if configured_tests: runtime_ld_library_path = _runtime_ld_library_path(ctx) - for result in _run_cpp_tests_serial(ctx, enabled_tests, runtime_ld_library_path=runtime_ld_library_path): + for result in _run_cpp_tests_serial(ctx, configured_tests, runtime_ld_library_path=runtime_ld_library_path): results_by_name[result.name] = result results = [results_by_name[test.name] for test in tests if test.name in results_by_name] @@ -661,14 +656,9 @@ def run_cpp(ctx: CoverageContext) -> None: extra_lines=[ f"Test profile: {ctx.test_profile}", f"GPU mode: {'true' if ctx.run_gpu_tests else 'false'}", - f"NPU mode: {'true' if ctx.run_npu_tests else 'false'}", "", ], - no_execution_warning=( - f"No C++ tests were executed (all skipped). Check restored binaries under: {ctx.paths.bin_dir}" - if results - else None - ), + no_execution_warning=f"No C++ tests were executed. Check restored binaries under: {ctx.paths.bin_dir}", failure_warning="One or more C++ tests failed; continuing to coverage generation.", ) @@ -680,24 +670,19 @@ def run_python(ctx: CoverageContext) -> None: tests = _filter_selected_tests(load_python_tests(config, ctx.test_profile), selected_names, suite_label=PYTHON_SUITE.label) results: list[_TestRunResult] = [] - if not any(test.enabled for test in tests): - for test in tests: - if not test.enabled: - results.append(_TestRunResult(test.name, "skipped", f"{test.name} ({test.skip_reason})")) + if not tests: _finalize_suite( ctx, PYTHON_SUITE, selected_names=selected_names, results=results, - no_execution_warning=f"No Python tests are enabled for TEST_PROFILE={ctx.test_profile}; skipping Python suite.", + no_execution_warning=f"No Python tests are configured for TEST_PROFILE={ctx.test_profile}; skipping Python suite.", ) return tests_dir = ctx.paths.install_pkg_dir / "tests" py_source_root = ctx.workspace / "src" / "bindings" / "python" / "src" py_source_dir = py_source_root / "openvino" - src_py_tests = ctx.workspace / "src" / "bindings" / "python" / "tests" - onnx_py_tests = ctx.workspace / "src" / "frontends" / "onnx" / "tests" / "tests_python" layer_tests = ctx.workspace / "tests" / "layer_tests" py_cov_config = ctx.workspace / ".python_coverage_ci.rc" python_coverage_debug_dir = ctx.workspace / ".tmp" / "python-coverage" @@ -727,8 +712,6 @@ def run_python(ctx: CoverageContext) -> None: os.environ["PYTHONPATH"] = ":".join(entry for entry in python_path_entries if entry).rstrip(":") os.environ["PYTHONSAFEPATH"] = "1" os.environ["TESTS_DIR"] = str(tests_dir) - os.environ["SRC_PY_TESTS_DIR"] = str(src_py_tests) - os.environ["ONNX_PY_TESTS_DIR"] = str(onnx_py_tests) os.environ["WORKSPACE_LAYER_TESTS_DIR"] = str(layer_tests) os.environ["PY_COV_CONFIG"] = str(py_cov_config) @@ -746,10 +729,6 @@ def run_python(ctx: CoverageContext) -> None: run_cmd(["python3", "-m", "coverage", "erase"]) for test in tests: - if not test.enabled: - results.append(_TestRunResult(test.name, "skipped", f"{test.name} ({test.skip_reason})")) - continue - target = _expand(test.target) args = _expand(test.args) command = _expand(test.command) @@ -758,13 +737,6 @@ def run_python(ctx: CoverageContext) -> None: if test.kind == "pytest": cmd = _python_pytest_command(target, args, py_cov_source=py_cov_source, py_cov_config=py_cov_config) rc, duration_seconds = _timed_run(f"Python test: {test.name}", cmd, env=env) - elif test.kind == "pytest_if_dir": - if not Path(target).is_dir(): - LOGGER.warning("Skipping Python test group '%s' (missing: %s)", test.name, target) - results.append(_TestRunResult(test.name, "skipped", f"{test.name} (missing path)")) - continue - cmd = _python_pytest_command(target, args, py_cov_source=py_cov_source, py_cov_config=py_cov_config) - rc, duration_seconds = _timed_run(f"Python test: {test.name}", cmd, env=env) elif test.kind == "command": rc, duration_seconds = _timed_run( f"Python test: {test.name}", @@ -844,16 +816,13 @@ def run_js(ctx: CoverageContext) -> None: tests = _filter_selected_tests(load_js_tests(config, ctx.test_profile), selected_names, suite_label=JS_SUITE.label) results: list[_TestRunResult] = [] - if not any(test.enabled for test in tests): - for test in tests: - if not test.enabled: - results.append(_TestRunResult(test.name, "skipped", f"{test.name} ({test.skip_reason})")) + if not tests: _finalize_suite( ctx, JS_SUITE, selected_names=selected_names, results=results, - no_execution_warning=f"No JS tests are enabled for TEST_PROFILE={ctx.test_profile}; skipping JS suite.", + no_execution_warning=f"No JS tests are configured for TEST_PROFILE={ctx.test_profile}; skipping JS suite.", ) return @@ -863,9 +832,6 @@ def run_js(ctx: CoverageContext) -> None: ).rstrip(":") for test in tests: - if not test.enabled: - results.append(_TestRunResult(test.name, "skipped", f"{test.name} ({test.skip_reason})")) - continue if test.kind != "command": results.append(_TestRunResult(test.name, "skipped", f"{test.name} (unknown kind: {test.kind})")) continue diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d99457ffaca1c0..e3e938e56b5814 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -5,23 +5,47 @@ on: # run daily at 00:00 UTC - cron: '0 0 * * *' workflow_dispatch: + inputs: + run-cpu-tests: + description: 'Run the standard CPU coverage lanes' + type: boolean + required: false + default: true + run-gpu-tests: + description: 'Build with GPU support and run the GPU coverage lanes' + type: boolean + required: false + default: true concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: ${{ github.event_name == 'pull_request' && github.run_id || format('{0}-{1}', github.workflow, github.ref) }} + cancel-in-progress: false permissions: read-all env: PIP_CACHE_PATH: /mount/caches/pip/linux - TEST_PROFILE: cpu ENABLE_BRANCH_COVERAGE: 'false' LCOV_CAPTURE_TIMEOUT_SECONDS: '3600' NODE_VERSION: '21' NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/IntelProxyRootCA-Base64.crt + IGPU_DRIVER_PACKAGES: |- + https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.15985.7/intel-igc-core_1.0.15985.7_amd64.deb + https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.15985.7/intel-igc-opencl_1.0.15985.7_amd64.deb + https://github.com/intel/compute-runtime/releases/download/24.05.28454.6/intel-level-zero-gpu_1.3.28454.6_amd64.deb + https://github.com/intel/compute-runtime/releases/download/24.05.28454.6/intel-opencl-icd_24.05.28454.6_amd64.deb + https://github.com/intel/compute-runtime/releases/download/24.05.28454.6/libigdgmm12_22.3.11_amd64.deb + DGPU_DRIVER_PACKAGES: |- + https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17791.9/intel-igc-core_1.0.17791.9_amd64.deb + https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17791.9/intel-igc-opencl_1.0.17791.9_amd64.deb + https://github.com/intel/compute-runtime/releases/download/24.39.31294.12/intel-level-zero-gpu_1.6.31294.12_amd64.deb + https://github.com/intel/compute-runtime/releases/download/24.39.31294.12/intel-opencl-icd_24.39.31294.12_amd64.deb + https://github.com/intel/compute-runtime/releases/download/24.39.31294.12/libigdgmm12_22.5.2_amd64.deb jobs: Docker: + name: Coverage Docker Images + if: ${{ github.event_name == 'schedule' || github.event_name == 'pull_request' || github.event.inputs.run-cpu-tests == 'true' || github.event.inputs.run-gpu-tests == 'true' }} runs-on: aks-linux-4-cores-16gb-docker-build container: image: openvinogithubactions.azurecr.io/docker_build:0.2 @@ -45,8 +69,9 @@ jobs: changed_components: '{"docker_env":false,"dockerfiles":false}' Build: - name: Build coverage artifacts + name: Coverage Build Artifacts needs: Docker + if: ${{ github.event_name == 'schedule' || github.event_name == 'pull_request' || github.event.inputs.run-cpu-tests == 'true' || github.event.inputs.run-gpu-tests == 'true' }} uses: ./.github/workflows/job_build_linux.yml with: runner: 'aks-linux-16-cores-32gb' @@ -76,14 +101,15 @@ jobs: -DENABLE_OV_PYTORCH_FRONTEND=ON -DENABLE_OV_TF_FRONTEND=ON -DENABLE_OV_TF_LITE_FRONTEND=ON - -DENABLE_INTEL_GPU=OFF -DENABLE_INTEL_NPU=OFF - -DENABLE_ONEDNN_FOR_GPU=OFF + ${{ (github.event_name == 'schedule' || github.event_name == 'pull_request' || github.event.inputs.run-gpu-tests == 'true') && '-DENABLE_INTEL_GPU=ON' || '-DENABLE_INTEL_GPU=OFF' }} + ${{ (github.event_name == 'schedule' || github.event_name == 'pull_request' || github.event.inputs.run-gpu-tests == 'true') && '-DENABLE_ONEDNN_FOR_GPU=ON' || '-DENABLE_ONEDNN_FOR_GPU=OFF' }} CoverageCpp: - name: Coverage C++ + name: Coverage C++ (CPU) needs: [Docker, Build] - runs-on: aks-linux-8-cores-32gb + if: ${{ github.event_name == 'schedule' || github.event_name == 'pull_request' || github.event.inputs.run-cpu-tests == 'true' }} + runs-on: aks-linux-16-cores-32gb container: image: ${{ fromJSON(needs.Docker.outputs.images).ov_test.ubuntu_22_04_x64 }} volumes: @@ -95,6 +121,7 @@ jobs: shell: bash working-directory: ${{ github.workspace }} env: + TEST_PROFILE: cpu OV_WORKSPACE: ${{ github.workspace }} INSTALL_DIR: ${{ github.workspace }}/install INSTALL_TEST_DIR: ${{ github.workspace }}/install/tests @@ -179,34 +206,183 @@ jobs: --artifact-dir "${dir}" echo "dir=${dir}" >> "$GITHUB_OUTPUT" - - name: Upload Codecov (C++ runtime, CPU) - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ${{ env.OV_WORKSPACE }}/coverage.info - disable_search: true - plugins: noop - flags: cpp-runtime-cpp-cpu - override_commit: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - override_pr: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }} - fail_ci_if_error: true - verbose: true - - name: Upload C++ CPU artifact if: ${{ always() }} - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 We are not using newest version v6.0.0 due to very frequent issues with 'Error: Unable to make request: ECONNRESET' with: name: coverage-cpp-cpu path: ${{ steps.cpu_artifact.outputs.dir }} if-no-files-found: warn - retention-days: 14 + retention-days: 7 + compression-level: 0 + overwrite: true + + CoverageCppGpu: + name: Coverage C++ (${{ matrix.gpu.name }}, ${{ matrix.lane.name }}) + needs: Build + if: ${{ github.event_name == 'schedule' || github.event_name == 'pull_request' || github.event.inputs.run-gpu-tests == 'true' }} + runs-on: ${{ fromJSON(matrix.gpu.runs_on) }} + container: + image: ubuntu:22.04 + volumes: + - /usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro + - /mount:/mount + - ${{ github.workspace }}:${{ github.workspace }} + options: ${{ matrix.gpu.container_options }} + strategy: + fail-fast: false + max-parallel: 4 + matrix: + gpu: + - id: igpu + name: iGPU + runs_on: '["self-hosted","igpu"]' + container_options: "--group-add 44 --group-add 993 --device /dev/dri/card1:/dev/dri/card1 --device /dev/dri/renderD128:/dev/dri/renderD128" + - id: dgpu + name: dGPU Arc A770 + runs_on: '["self-hosted","dgpu","Arc-A770","Linux"]' + container_options: "--group-add 44 --group-add 993 --device /dev/dri/renderD129:/dev/dri/renderD129" + lane: + - id: unit + name: GPU Unit Tests + test_names: ov_onnx_frontend_tests,ov_onnx_frontend_tests (ONNX_ITERATOR=0),ov_gpu_unit_tests,test_inference_async,test_inference_sync + - id: func + name: GPU Functional Tests + test_names: ov_gpu_func_tests + defaults: + run: + shell: bash + working-directory: ${{ github.workspace }} + env: + TEST_PROFILE: gpu + CXX_TEST_NAMES: ${{ matrix.lane.test_names }} + OV_WORKSPACE: ${{ github.workspace }} + INSTALL_DIR: ${{ github.workspace }}/install + INSTALL_TEST_DIR: ${{ github.workspace }}/install/tests + INSTALL_PKG_DIR: ${{ github.workspace }}/install + BIN_DIR: ${{ github.workspace }}/install/tests + BUILD_DIR: ${{ github.workspace }}/openvino/openvino_build + BUILD_JS_DIR: ${{ github.workspace }}/openvino/openvino_build_js + COVERAGE_WRITE_STEP_SUMMARY: 'false' + steps: + - name: Prepare ${{ matrix.gpu.name }} test container + run: | + apt-get update && apt-get install -y pigz wget software-properties-common ca-certificates gpg-agent tzdata clinfo git lcov python3-yaml + env: + DEBIAN_FRONTEND: noninteractive + TZ: "Europe/London" + + - name: Checkout sources + uses: ababushk/checkout@dd591a6a2ac25618db4eda86e7e0d938f88cf01b # cherry_pick_retries + timeout-minutes: 15 + with: + submodules: 'true' + + - name: Download OpenVINO package + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + name: openvino_package + path: ${{ env.INSTALL_DIR }} + + - name: Download OpenVINO tests + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + name: openvino_tests + path: ${{ env.INSTALL_DIR }} + + - name: Download coverage build notes + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + name: coverage_build_notes + path: ${{ github.workspace }}/coverage_notes + + - name: Extract OpenVINO packages + run: | + pigz -dc openvino_package.tar.gz | tar -xf - -C "${INSTALL_DIR}" + pigz -dc openvino_tests.tar.gz | tar -xf - -C "${INSTALL_DIR}" + working-directory: ${{ env.INSTALL_DIR }} + + - name: Install GPU driver dependencies + run: | + set -euo pipefail + "${INSTALL_DIR}/install_dependencies/install_openvino_dependencies.sh" -c=core -c=dev -c=gpu -y + + case "${{ matrix.gpu.id }}" in + igpu) + driver_packages="${IGPU_DRIVER_PACKAGES}" + ;; + dgpu) + driver_packages="${DGPU_DRIVER_PACKAGES}" + ;; + *) + echo "Unsupported GPU device '${{ matrix.gpu.id }}'" + exit 1 + ;; + esac + + tmp_dir="$(mktemp -d)" + trap 'rm -rf "${tmp_dir}"' EXIT + pushd "${tmp_dir}" + while IFS= read -r package_url; do + [[ -n "${package_url}" ]] || continue + wget "${package_url}" + done <<< "${driver_packages}" + dpkg -i *.deb + popd + + - name: Verify GPU device availability + run: clinfo + + - name: Restore coverage notes + run: | + set -euo pipefail + notes_root="${{ github.workspace }}/coverage_notes" + if [[ -d "${notes_root}/coverage_build_notes" ]]; then + notes_root="${notes_root}/coverage_build_notes" + fi + rm -rf "${BUILD_DIR}" "${BUILD_JS_DIR}" + mkdir -p "${BUILD_DIR}" "${BUILD_JS_DIR}" + cp -a "${notes_root}/main-build/." "${BUILD_DIR}/" + if [[ -d "${notes_root}/js-build" ]]; then + cp -a "${notes_root}/js-build/." "${BUILD_JS_DIR}/" + fi + + - name: Run ${{ matrix.gpu.name }} ${{ matrix.lane.name }} C++ coverage tests + run: python3 .github/scripts/coverage/coverage.py step run-cpp-tests + + - name: Generate ${{ matrix.gpu.name }} ${{ matrix.lane.name }} native C/C++ coverage report + run: python3 .github/scripts/coverage/coverage.py step collect-cpp-coverage + + - name: Collect C++ ${{ matrix.gpu.name }} ${{ matrix.lane.name }} results + id: lane_artifact + if: ${{ always() }} + run: | + dir="${RUNNER_TEMP}/coverage-cpp-${{ matrix.gpu.id }}-${{ matrix.lane.id }}" + python3 .github/scripts/coverage/ci_reports.py collect-suite-results \ + --workspace "${{ env.OV_WORKSPACE }}" \ + --suite cpp \ + --profile gpu \ + --lane "${{ matrix.gpu.id }}-${{ matrix.lane.id }}" \ + --artifact-name "coverage-cpp-${{ matrix.gpu.id }}-${{ matrix.lane.id }}" \ + --artifact-dir "${dir}" + echo "dir=${dir}" >> "$GITHUB_OUTPUT" + + - name: Upload C++ ${{ matrix.gpu.name }} ${{ matrix.lane.name }} artifact + if: ${{ always() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 We are not using newest version v6.0.0 due to very frequent issues with 'Error: Unable to make request: ECONNRESET' + with: + name: coverage-cpp-${{ matrix.gpu.id }}-${{ matrix.lane.id }} + path: ${{ steps.lane_artifact.outputs.dir }} + if-no-files-found: warn + retention-days: 7 compression-level: 0 overwrite: true CoveragePython: - name: Coverage Python + name: Coverage Python (CPU) needs: [Docker, Build] - runs-on: aks-linux-4-cores-16gb + if: ${{ github.event_name == 'schedule' || github.event_name == 'pull_request' || github.event.inputs.run-cpu-tests == 'true' }} + runs-on: aks-linux-16-cores-32gb container: image: ${{ fromJSON(needs.Docker.outputs.images).ov_test.ubuntu_22_04_x64 }} volumes: @@ -218,6 +394,7 @@ jobs: shell: bash working-directory: ${{ github.workspace }} env: + TEST_PROFILE: cpu OV_WORKSPACE: ${{ github.workspace }} INSTALL_DIR: ${{ github.workspace }}/install INSTALL_TEST_DIR: ${{ github.workspace }}/install/tests @@ -323,46 +500,206 @@ jobs: --artifact-dir "${dir}" echo "dir=${dir}" >> "$GITHUB_OUTPUT" - - name: Upload Codecov (Python API, CPU) - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + - name: Upload Python CPU artifact + if: ${{ always() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 We are not using newest version v6.0.0 due to very frequent issues with 'Error: Unable to make request: ECONNRESET' with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ${{ env.OV_WORKSPACE }}/python-coverage.xml - disable_search: true - plugins: noop - flags: python-api-frontend-layer-ovc-cpu - override_commit: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - override_pr: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }} - fail_ci_if_error: true - verbose: true + name: coverage-python-cpu + path: ${{ steps.cpu_artifact.outputs.dir }} + if-no-files-found: warn + retention-days: 7 + compression-level: 0 + overwrite: true - - name: Upload Codecov (C++ runtime, Python suite, CPU) - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + CoveragePythonGpu: + name: Coverage Python (${{ matrix.gpu.name }}) + needs: Build + if: ${{ github.event_name == 'schedule' || github.event_name == 'pull_request' || github.event.inputs.run-gpu-tests == 'true' }} + runs-on: ${{ fromJSON(matrix.gpu.runs_on) }} + container: + image: ubuntu:22.04 + volumes: + - /usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro + - /mount:/mount + - ${{ github.workspace }}:${{ github.workspace }} + options: ${{ matrix.gpu.container_options }} + strategy: + fail-fast: false + matrix: + gpu: + - id: igpu + name: iGPU + runs_on: '["self-hosted","igpu"]' + container_options: "--group-add 44 --group-add 993 --device /dev/dri/card1:/dev/dri/card1 --device /dev/dri/renderD128:/dev/dri/renderD128" + - id: dgpu + name: dGPU Arc A770 + runs_on: '["self-hosted","dgpu","Arc-A770","Linux"]' + container_options: "--group-add 44 --group-add 993 --device /dev/dri/renderD129:/dev/dri/renderD129" + defaults: + run: + shell: bash + working-directory: ${{ github.workspace }} + env: + TEST_PROFILE: gpu + OV_WORKSPACE: ${{ github.workspace }} + INSTALL_DIR: ${{ github.workspace }}/install + INSTALL_TEST_DIR: ${{ github.workspace }}/install/tests + INSTALL_PKG_DIR: ${{ github.workspace }}/install + BUILD_DIR: ${{ github.workspace }}/openvino/openvino_build + BUILD_JS_DIR: ${{ github.workspace }}/openvino/openvino_build_js + COVERAGE_WRITE_STEP_SUMMARY: 'false' + REQUESTS_CA_BUNDLE: /usr/local/share/ca-certificates/IntelProxyRootCA-Base64.crt + steps: + - name: Prepare ${{ matrix.gpu.name }} test container + run: | + apt-get update && apt-get install -y pigz wget software-properties-common ca-certificates gpg-agent tzdata clinfo git lcov python3-yaml + env: + DEBIAN_FRONTEND: noninteractive + TZ: "Europe/London" + + - name: Checkout sources + uses: ababushk/checkout@dd591a6a2ac25618db4eda86e7e0d938f88cf01b # cherry_pick_retries + timeout-minutes: 15 with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ${{ env.OV_WORKSPACE }}/coverage.info - disable_search: true - plugins: noop - flags: cpp-runtime-python-cpu - override_commit: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - override_pr: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }} - fail_ci_if_error: true - verbose: true + submodules: 'true' - - name: Upload Python CPU artifact + - name: Download OpenVINO package + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + name: openvino_package + path: ${{ env.INSTALL_DIR }} + + - name: Download OpenVINO tests + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + name: openvino_tests + path: ${{ env.INSTALL_DIR }} + + - name: Download OpenVINO wheels + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + name: openvino_wheels + path: ${{ env.INSTALL_DIR }} + + - name: Download coverage build notes + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + name: coverage_build_notes + path: ${{ github.workspace }}/coverage_notes + + - name: Extract OpenVINO packages + run: | + pigz -dc openvino_package.tar.gz | tar -xf - -C "${INSTALL_DIR}" + pigz -dc openvino_tests.tar.gz | tar -xf - -C "${INSTALL_DIR}" + working-directory: ${{ env.INSTALL_DIR }} + + - name: Install GPU driver dependencies + run: | + set -euo pipefail + "${INSTALL_DIR}/install_dependencies/install_openvino_dependencies.sh" -c=core -c=dev -c=gpu -y + + case "${{ matrix.gpu.id }}" in + igpu) + driver_packages="${IGPU_DRIVER_PACKAGES}" + ;; + dgpu) + driver_packages="${DGPU_DRIVER_PACKAGES}" + ;; + *) + echo "Unsupported GPU device '${{ matrix.gpu.id }}'" + exit 1 + ;; + esac + + tmp_dir="$(mktemp -d)" + trap 'rm -rf "${tmp_dir}"' EXIT + pushd "${tmp_dir}" + while IFS= read -r package_url; do + [[ -n "${package_url}" ]] || continue + wget "${package_url}" + done <<< "${driver_packages}" + dpkg -i *.deb + popd + + - name: Verify GPU device availability + run: clinfo + + - name: Restore coverage notes + run: | + set -euo pipefail + notes_root="${{ github.workspace }}/coverage_notes" + if [[ -d "${notes_root}/coverage_build_notes" ]]; then + notes_root="${notes_root}/coverage_build_notes" + fi + rm -rf "${BUILD_DIR}" "${BUILD_JS_DIR}" + mkdir -p "${BUILD_DIR}" "${BUILD_JS_DIR}" + cp -a "${notes_root}/main-build/." "${BUILD_DIR}/" + if [[ -d "${notes_root}/js-build" ]]; then + cp -a "${notes_root}/js-build/." "${BUILD_JS_DIR}/" + fi + + - name: Setup Python 3.11 + uses: ./.github/actions/setup_python + with: + version: '3.11' + pip-cache-path: ${{ env.PIP_CACHE_PATH }} + should-setup-pip-paths: 'true' + self-hosted-runner: 'true' + + - name: Install coverage Python dependencies + run: python3 -m pip install -r .github/scripts/coverage/requirements-ci.txt + + - name: Install OpenVINO Python wheels + uses: ./.github/actions/install_ov_wheels + with: + wheels-dir-path: ${{ env.INSTALL_DIR }} + wheels-to-install: 'openvino' + + - name: Install Python test dependencies + run: | + python3 -m pip install -r "${INSTALL_DIR}/tests/bindings/python/requirements_test.txt" + python3 -m pip install -r "${INSTALL_DIR}/tests/python/preprocess/torchvision/requirements.txt" + python3 -m pip install -r "${INSTALL_DIR}/tests/layer_tests/requirements.txt" + python3 -m pip install -r "${INSTALL_DIR}/tests/requirements_onnx" + python3 -m pip install -r "${INSTALL_DIR}/tests/requirements_jax" + python3 -m pip install -r "${INSTALL_DIR}/tests/requirements_pytorch" + python3 -m pip install -r "${INSTALL_DIR}/tests/requirements_tensorflow" + + - name: Run ${{ matrix.gpu.name }} Python coverage tests + run: python3 .github/scripts/coverage/coverage.py step run-python-tests + + - name: Generate ${{ matrix.gpu.name }} native C/C++ coverage report + run: python3 .github/scripts/coverage/coverage.py step collect-cpp-coverage + + - name: Collect Python ${{ matrix.gpu.name }} results + id: gpu_artifact if: ${{ always() }} - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + run: | + dir="${RUNNER_TEMP}/coverage-python-${{ matrix.gpu.id }}" + python3 .github/scripts/coverage/ci_reports.py collect-suite-results \ + --workspace "${{ env.OV_WORKSPACE }}" \ + --suite python \ + --profile gpu \ + --lane "${{ matrix.gpu.id }}" \ + --artifact-name "coverage-python-${{ matrix.gpu.id }}" \ + --artifact-dir "${dir}" + echo "dir=${dir}" >> "$GITHUB_OUTPUT" + + - name: Upload Python ${{ matrix.gpu.name }} artifact + if: ${{ always() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 We are not using newest version v6.0.0 due to very frequent issues with 'Error: Unable to make request: ECONNRESET' with: - name: coverage-python-cpu - path: ${{ steps.cpu_artifact.outputs.dir }} + name: coverage-python-${{ matrix.gpu.id }} + path: ${{ steps.gpu_artifact.outputs.dir }} if-no-files-found: warn - retention-days: 14 + retention-days: 7 compression-level: 0 overwrite: true CoverageJS: - name: Coverage JS + name: Coverage JS (CPU) needs: [Docker, Build] + if: ${{ github.event_name == 'schedule' || github.event_name == 'pull_request' || github.event.inputs.run-cpu-tests == 'true' }} runs-on: aks-linux-4-cores-16gb container: image: ${{ fromJSON(needs.Docker.outputs.images).ov_test.ubuntu_22_04_x64 }} @@ -375,6 +712,7 @@ jobs: shell: bash working-directory: ${{ github.workspace }} env: + TEST_PROFILE: cpu OV_WORKSPACE: ${{ github.workspace }} INSTALL_DIR: ${{ github.workspace }}/install INSTALL_PKG_DIR: ${{ github.workspace }}/install @@ -481,83 +819,91 @@ jobs: --artifact-dir "${dir}" echo "dir=${dir}" >> "$GITHUB_OUTPUT" - - name: Upload Codecov (JS bindings, CPU) - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ${{ env.OV_WORKSPACE }}/js-lcov.info - disable_search: true - plugins: noop - flags: nodejs-bindings-unit-e2e-cpu - override_commit: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - override_pr: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }} - fail_ci_if_error: true - verbose: true - - - name: Upload Codecov (C++ runtime, JS suite, CPU) - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ${{ env.OV_WORKSPACE }}/coverage.info - disable_search: true - plugins: noop - flags: cpp-runtime-js-cpu - override_commit: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - override_pr: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }} - fail_ci_if_error: true - verbose: true - - name: Upload JS CPU artifact if: ${{ always() }} - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 We are not using newest version v6.0.0 due to very frequent issues with 'Error: Unable to make request: ECONNRESET' with: name: coverage-js-cpu path: ${{ steps.cpu_artifact.outputs.dir }} if-no-files-found: warn - retention-days: 14 + retention-days: 7 compression-level: 0 overwrite: true CoverageSummary: + name: Coverage Summary if: ${{ always() && needs.Build.result == 'success' }} - needs: [Build, CoverageCpp, CoveragePython, CoverageJS] + needs: [Build, CoverageCpp, CoverageCppGpu, CoveragePython, CoveragePythonGpu, CoverageJS] + outputs: + cpp_cpu: ${{ steps.coverage_files.outputs.cpp_cpu }} + cpp_igpu_unit: ${{ steps.coverage_files.outputs.cpp_igpu_unit }} + cpp_igpu_func: ${{ steps.coverage_files.outputs.cpp_igpu_func }} + cpp_dgpu_unit: ${{ steps.coverage_files.outputs.cpp_dgpu_unit }} + cpp_dgpu_func: ${{ steps.coverage_files.outputs.cpp_dgpu_func }} + python_cpu_xml: ${{ steps.coverage_files.outputs.python_cpu_xml }} + python_cpu_info: ${{ steps.coverage_files.outputs.python_cpu_info }} + python_igpu_xml: ${{ steps.coverage_files.outputs.python_igpu_xml }} + python_igpu_info: ${{ steps.coverage_files.outputs.python_igpu_info }} + python_dgpu_xml: ${{ steps.coverage_files.outputs.python_dgpu_xml }} + python_dgpu_info: ${{ steps.coverage_files.outputs.python_dgpu_info }} + js_cpu_lcov: ${{ steps.coverage_files.outputs.js_cpu_lcov }} + js_cpu_info: ${{ steps.coverage_files.outputs.js_cpu_info }} runs-on: ubuntu-latest steps: - name: Checkout sources uses: ababushk/checkout@dd591a6a2ac25618db4eda86e7e0d938f88cf01b # cherry_pick_retries timeout-minutes: 15 with: - sparse-checkout: | - .github/scripts/coverage submodules: 'false' - name: Download C++ artifacts - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 with: pattern: coverage-cpp-* path: ${{ github.workspace }}/artifacts/cpp - name: Download Python artifacts - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 with: pattern: coverage-python-* path: ${{ github.workspace }}/artifacts/python - name: Download JS artifacts - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 with: pattern: coverage-js-* path: ${{ github.workspace }}/artifacts/js + - name: Resolve selected lanes + id: lanes + env: + EVENT_NAME: ${{ github.event_name }} + RUN_CPU_TESTS: ${{ github.event.inputs.run-cpu-tests || '' }} + RUN_GPU_TESTS: ${{ github.event.inputs.run-gpu-tests || '' }} + run: | + selected_lanes='none' + if [[ "$EVENT_NAME" == 'schedule' || "$EVENT_NAME" == 'pull_request' ]]; then + selected_lanes='cpu,igpu,igpu-unit,igpu-func,dgpu,dgpu-unit,dgpu-func' + elif [[ "$RUN_CPU_TESTS" == 'true' && "$RUN_GPU_TESTS" == 'true' ]]; then + selected_lanes='cpu,igpu,igpu-unit,igpu-func,dgpu,dgpu-unit,dgpu-func' + elif [[ "$RUN_CPU_TESTS" == 'true' ]]; then + selected_lanes='cpu' + elif [[ "$RUN_GPU_TESTS" == 'true' ]]; then + selected_lanes='igpu,igpu-unit,igpu-func,dgpu,dgpu-unit,dgpu-func' + fi + echo "selected_lanes=${selected_lanes}" >> "$GITHUB_OUTPUT" + - name: Generate final summary + if: ${{ always() }} run: | python3 .github/scripts/coverage/ci_reports.py render-summary \ --workspace "${{ github.workspace }}" \ --summary-file "${GITHUB_STEP_SUMMARY}" \ - --selection "cpu" \ - --selected-lanes "cpu" + --selection "${{ steps.lanes.outputs.selected_lanes }}" \ + --selected-lanes "${{ steps.lanes.outputs.selected_lanes }}" - name: Merge duration reports + if: ${{ always() }} run: | python3 .github/scripts/coverage/ci_reports.py merge-durations \ --workspace "${{ github.workspace }}" \ @@ -565,11 +911,117 @@ jobs: - name: Upload merged duration artifact if: ${{ always() }} - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 We are not using newest version v6.0.0 due to very frequent issues with 'Error: Unable to make request: ECONNRESET' with: name: coverage-test-durations path: coverage-test-durations-all.csv if-no-files-found: warn - retention-days: 14 + retention-days: 7 compression-level: 0 overwrite: true + + - name: Resolve Codecov upload files + id: coverage_files + if: ${{ always() }} + run: | + python3 .github/scripts/coverage/ci_reports.py resolve-uploads \ + --workspace "${{ github.workspace }}" \ + --output-file "${GITHUB_OUTPUT}" + + CoverageCodecovUploads: + name: Coverage Codecov Upload (${{ matrix.upload.name }}) + if: ${{ always() && needs.CoverageSummary.result != 'skipped' && needs.CoverageSummary.result != 'cancelled' }} + needs: CoverageSummary + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + upload: + - name: C++ runtime, CPU + artifact_name: coverage-cpp-cpu + output_key: cpp_cpu + flag: cpp-runtime-cpp-cpu + - name: C++ runtime, iGPU Unit + artifact_name: coverage-cpp-igpu-unit + output_key: cpp_igpu_unit + flag: cpp-runtime-cpp-igpu-unit + - name: C++ runtime, iGPU Func + artifact_name: coverage-cpp-igpu-func + output_key: cpp_igpu_func + flag: cpp-runtime-cpp-igpu-func + - name: C++ runtime, dGPU Unit + artifact_name: coverage-cpp-dgpu-unit + output_key: cpp_dgpu_unit + flag: cpp-runtime-cpp-dgpu-unit + - name: C++ runtime, dGPU Func + artifact_name: coverage-cpp-dgpu-func + output_key: cpp_dgpu_func + flag: cpp-runtime-cpp-dgpu-func + - name: Python API, CPU + artifact_name: coverage-python-cpu + output_key: python_cpu_xml + flag: python-api-frontend-layer-ovc-cpu + - name: C++ runtime, Python suite, CPU + artifact_name: coverage-python-cpu + output_key: python_cpu_info + flag: cpp-runtime-python-cpu + - name: Python API, iGPU + artifact_name: coverage-python-igpu + output_key: python_igpu_xml + flag: python-api-frontend-layer-ovc-igpu + - name: C++ runtime, Python suite, iGPU + artifact_name: coverage-python-igpu + output_key: python_igpu_info + flag: cpp-runtime-python-igpu + - name: Python API, dGPU + artifact_name: coverage-python-dgpu + output_key: python_dgpu_xml + flag: python-api-frontend-layer-ovc-dgpu + - name: C++ runtime, Python suite, dGPU + artifact_name: coverage-python-dgpu + output_key: python_dgpu_info + flag: cpp-runtime-python-dgpu + - name: JS bindings, CPU + artifact_name: coverage-js-cpu + output_key: js_cpu_lcov + flag: nodejs-bindings-unit-e2e-cpu + - name: C++ runtime, JS suite, CPU + artifact_name: coverage-js-cpu + output_key: js_cpu_info + flag: cpp-runtime-js-cpu + steps: + - name: Checkout sources + if: ${{ needs.CoverageSummary.outputs[matrix.upload.output_key] != '' }} + uses: ababushk/checkout@dd591a6a2ac25618db4eda86e7e0d938f88cf01b # cherry_pick_retries + timeout-minutes: 15 + with: + submodules: 'false' + + - name: Download coverage artifact + if: ${{ needs.CoverageSummary.outputs[matrix.upload.output_key] != '' }} + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + name: ${{ matrix.upload.artifact_name }} + path: ${{ github.workspace }}/artifacts/${{ matrix.upload.artifact_name }} + + - name: Resolve Codecov upload file + id: coverage_files + if: ${{ needs.CoverageSummary.outputs[matrix.upload.output_key] != '' }} + run: | + python3 .github/scripts/coverage/ci_reports.py resolve-uploads \ + --workspace "${{ github.workspace }}" \ + --output-file "${GITHUB_OUTPUT}" + + - name: Upload Codecov (${{ matrix.upload.name }}) + if: ${{ steps.coverage_files.outputs[matrix.upload.output_key] != '' }} + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ${{ steps.coverage_files.outputs[matrix.upload.output_key] }} + disable_search: true + plugins: noop + flags: ${{ matrix.upload.flag }} + override_commit: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + override_pr: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }} + fail_ci_if_error: true + verbose: true From b62da294cf82f95fb42fd12728be3aab9130cc38 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Thu, 23 Apr 2026 00:07:54 +0800 Subject: [PATCH 027/545] [NPU] Create execution context for dynamic graph execution (#35380) ### Details: - *Create execution context for dynamic graph execution* ### Tickets: - *C182264* ### AI Assistance: - *AI assistance used: no / yes* - *If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).* --------- Signed-off-by: Xin Wang --- .../src/zero_dynamic_infer_request.cpp | 4 + .../src/backend/src/zero_dynamic_pipeline.cpp | 2 +- .../include/dynamic_graph.hpp | 9 +- .../compiler_adapter/src/dynamic_graph.cpp | 9 ++ .../infer_with_host_compile.hpp | 86 +++++++++++++------ 5 files changed, 82 insertions(+), 28 deletions(-) diff --git a/src/plugins/intel_npu/src/backend/src/zero_dynamic_infer_request.cpp b/src/plugins/intel_npu/src/backend/src/zero_dynamic_infer_request.cpp index 1c809482e6f6a9..de73337f90765b 100644 --- a/src/plugins/intel_npu/src/backend/src/zero_dynamic_infer_request.cpp +++ b/src/plugins/intel_npu/src/backend/src/zero_dynamic_infer_request.cpp @@ -269,6 +269,10 @@ void ZeroDynamicInferRequest::predict_shapes(std::vector& outputProps) { + if (outputProps.empty()) { + _logger.debug("check_tensor_and_predicted_shapes - no output props to check, skip check"); + return; + } // check_tensor in set_tensor already checked the input tensor and output tensor with metadata // Check again here to see if the shape is right compared with predicted shape // If user set output tensor, need check if the tensor is large enough diff --git a/src/plugins/intel_npu/src/backend/src/zero_dynamic_pipeline.cpp b/src/plugins/intel_npu/src/backend/src/zero_dynamic_pipeline.cpp index ff41fa8cf6c614..fcf8eeeedf26b0 100644 --- a/src/plugins/intel_npu/src/backend/src/zero_dynamic_pipeline.cpp +++ b/src/plugins/intel_npu/src/backend/src/zero_dynamic_pipeline.cpp @@ -220,7 +220,7 @@ void DynamicPipeline::push() { command_lists->resetCommandList(); dynamicGraph->execute(_init_structs, - command_lists->getBinding(), + graphArguments, command_lists->getHandles(), commandQueueHandle, fence, diff --git a/src/plugins/intel_npu/src/compiler_adapter/include/dynamic_graph.hpp b/src/plugins/intel_npu/src/compiler_adapter/include/dynamic_graph.hpp index 954f6a5f5889e9..896e6609e82962 100644 --- a/src/plugins/intel_npu/src/compiler_adapter/include/dynamic_graph.hpp +++ b/src/plugins/intel_npu/src/compiler_adapter/include/dynamic_graph.hpp @@ -77,10 +77,17 @@ class DynamicGraph final : public IDynamicGraph { } }; - struct GraphArgumentsImpl : public GraphArguments { + struct GraphArgumentsImpl { std::vector _inputMemRefs; std::vector _outputMemRefs; npu_vm_runtime_execute_params_t _executeParams = {}; + + ~GraphArgumentsImpl() { + if (_executeParams.executionContext != nullptr) { + npuVMRuntimeDestroyExecutionContext(_executeParams.executionContext); + _executeParams.executionContext = nullptr; + } + } }; class Impl { diff --git a/src/plugins/intel_npu/src/compiler_adapter/src/dynamic_graph.cpp b/src/plugins/intel_npu/src/compiler_adapter/src/dynamic_graph.cpp index 817666f24d6400..5535698d952054 100644 --- a/src/plugins/intel_npu/src/compiler_adapter/src/dynamic_graph.cpp +++ b/src/plugins/intel_npu/src/compiler_adapter/src/dynamic_graph.cpp @@ -337,6 +337,15 @@ void DynamicGraphImpl::executeGraph(const std::shared_ptr } } + // Prepare execution context for each graph arguments + if (params->executionContext == nullptr) { + if (npuVMRuntimeCreateExecutionContext(_engine, ¶ms->executionContext) != NPU_VM_RUNTIME_RESULT_SUCCESS) { + OPENVINO_THROW("Failed to create a VM execution context"); + } else { + _logger.debug("Execution context is created successfully."); + } + } + params->pInputs = argsImpl->_inputMemRefs.data(); params->numOfInputs = static_cast(argsImpl->_inputMemRefs.size()); params->pOutputs = argsImpl->_outputMemRefs.data(); diff --git a/src/plugins/intel_npu/tests/functional/behavior/dynamic_host_pipeline/infer_with_host_compile.hpp b/src/plugins/intel_npu/tests/functional/behavior/dynamic_host_pipeline/infer_with_host_compile.hpp index 582b4ff1c46113..e3d40de92f5723 100644 --- a/src/plugins/intel_npu/tests/functional/behavior/dynamic_host_pipeline/infer_with_host_compile.hpp +++ b/src/plugins/intel_npu/tests/functional/behavior/dynamic_host_pipeline/infer_with_host_compile.hpp @@ -71,31 +71,26 @@ class InferWithHostCompileTests : public testing::WithParamInterfaceGetParam(); - APIBaseTest::SetUp(); - } - - bool isLLVMFormat(std::stringstream& modelStream) { - auto pos = modelStream.tellg(); - if (pos == std::streampos(-1)) { - return false; + std::vector deviceNames = + core->get_property("NPU", ov::available_devices.name()).as>(); + for (auto name : deviceNames) { + if (target_device.find(name) != std::string::npos) { + isTargetDevice = true; + break; + } } - modelStream.seekg(0, std::ios::beg); - - // Assume the first 20 char of LLVM blob shall have key word 'llvm' - std::string region(20, '\0'); - modelStream.read(®ion[0], 20); - region.resize(modelStream.gcount()); - - modelStream.clear(); - modelStream.seekg(pos); + APIBaseTest::SetUp(); + } - return region.find("llvm") != std::string::npos; + void TearDown() { + core->set_property("NPU", ov::log::level(ov::log::Level::ERR)); } protected: std::shared_ptr core = utils::PluginCache::get().core(); ov::AnyMap configuration; + bool isTargetDevice = false; }; TEST_P(InferWithHostCompileTests, Compile) { @@ -110,12 +105,15 @@ TEST_P(InferWithHostCompileTests, Compile) { std::stringstream modelStream; OV_ASSERT_NO_THROW(compiledModel.export_model(modelStream)); - // With HostCompile, the modelStream shall contain "llvm.func" - ASSERT_TRUE(isLLVMFormat(modelStream)) << "CompiledStream from HostCompile mode shall has 'llvm.func' inside it"; - ov::InferRequest reqDynamic; - // Add shape check once npu_mlir_runtime is inside test package - EXPECT_THROW(reqDynamic = compiledModel.create_infer_request(), ov::Exception); + + try { + reqDynamic = compiledModel.create_infer_request(); + } catch (const ov::Exception& e) { + if (std::string(e.what()).find("Cannot load library") == std::string::npos) { + FAIL() << "Expected exception message to contain 'Cannot load library', but got: " << e.what(); + } + } } TEST_P(InferWithHostCompileTests, CompileAndImport) { @@ -130,15 +128,51 @@ TEST_P(InferWithHostCompileTests, CompileAndImport) { std::stringstream modelStream; OV_ASSERT_NO_THROW(compiledModel.export_model(modelStream)); - // With HostCompile, the modelStream shall contain "llvm.func" - ASSERT_TRUE(isLLVMFormat(modelStream)) << "CompiledStream from HostCompile mode shall has 'llvm.func' inside it"; + ov::CompiledModel importedModel; + OV_ASSERT_NO_THROW(core->import_model(modelStream, target_device, configuration)); + + ov::InferRequest reqDynamic; + + try { + reqDynamic = compiledModel.create_infer_request(); + } catch (const ov::Exception& e) { + if (std::string(e.what()).find("Cannot load library") == std::string::npos) { + FAIL() << "Expected exception message to contain 'Cannot load library', but got: " << e.what(); + } + } +} + +TEST_P(InferWithHostCompileTests, CompileAndImportAndInfer) { + // Skip test according to plugin specific disabledTestPatterns() (if any) + SKIP_IF_CURRENT_TEST_IS_DISABLED() + if (!isTargetDevice) { + GTEST_SKIP() << "Skip test for current device"; + } + auto model = createMaxPoolModel(); + + ov::CompiledModel compiledModel; + // Compilation shall pass since load of npu_mlir_runtime is deffered with NPU_CREATE_EXECUTOR=0 + OV_ASSERT_NO_THROW(compiledModel = core->compile_model(model, target_device, configuration)); + + std::stringstream modelStream; + OV_ASSERT_NO_THROW(compiledModel.export_model(modelStream)); ov::CompiledModel importedModel; OV_ASSERT_NO_THROW(core->import_model(modelStream, target_device, configuration)); ov::InferRequest reqDynamic; - // Add shape check once npu_mlir_runtime is inside test package - EXPECT_THROW(reqDynamic = importedModel.create_infer_request(), ov::Exception); + + try { + reqDynamic = compiledModel.create_infer_request(); + } catch (const ov::Exception& e) { + if (std::string(e.what()).find("Cannot load library") == std::string::npos) { + FAIL() << "Expected exception message to contain 'Cannot load library', but got: " << e.what(); + } else { + GTEST_SKIP() << "Cannot load library, skip test."; + } + } + + OV_ASSERT_NO_THROW(reqDynamic.infer()); } } // namespace behavior From 6ed00030756f519f6ad9a2b9eb8aefba074b275b Mon Sep 17 00:00:00 2001 From: intelgaoxiong Date: Thu, 23 Apr 2026 00:09:26 +0800 Subject: [PATCH 028/545] [NPUW]Optimize pipeline initialize time in online partitioner. (#34958) ### Details: `getPartitioning` was running very slowly when the graph contained a large number of nodes. This PR optimized several hotspots to improve performance at scale. - `hasCycle()`: add O(1) fast-path for single-consumer producers; fix DFS to mark visited at push-time to avoid redundant stack pushes - `mergeUniques()`: replace per-group O(V) getRepGroups() scan with a pre-built rep-tag->GPtrSet index, reducing total cost from O(V^2) to O(V) - `interconnect()`: use const ref for ports_map to avoid deep copy on each call - `metaInterconnect()`: cache MetaInterconnectIO; invalidate on layer changes - `getMetaDesc()`: add cache held by snapshot, to avoid re-stringifying same node ~30x improvement for 256 chunk size: - 16K context: 200s -> 7s - 8K context: 58s ->2.5s ### Tickets: - *[EISW-209691](https://jira.devtools.intel.com/browse/EISW-209691)* ### AI Assistance: - *AI assistance used: no / yes* - *If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).* --------- Signed-off-by: intelgaoxiong --- .../plugin/npuw/partitioning/online/group.cpp | 44 +++++++--- .../plugin/npuw/partitioning/online/group.hpp | 6 ++ .../npuw/partitioning/online/snapshot.cpp | 88 ++++++++++++++----- .../npuw/partitioning/online/snapshot.hpp | 2 + 4 files changed, 105 insertions(+), 35 deletions(-) diff --git a/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/group.cpp b/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/group.cpp index 76c46973e76d33..6331686027fe9b 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/group.cpp +++ b/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/group.cpp @@ -145,10 +145,12 @@ const std::unordered_set>& Group::getOutputs() const { void Group::addInput(const std::shared_ptr& node) { m_input_layers.insert(node); + m_mic_io_valid = false; } void Group::addOutput(const std::shared_ptr& node) { m_output_layers.insert(node); + m_mic_io_valid = false; } void Group::addContent(const std::shared_ptr& node) { @@ -173,6 +175,7 @@ own::ade::NodeHandle Group::getHandle() const { // Not every input should be included - those layers which contained inside merging group are not inputs anymore void Group::updateInputLayers(const Group::GPtr& gptr_other) { + m_mic_io_valid = false; detail::OVNodeSet combined_input; combined_input.insert(m_input_layers.begin(), m_input_layers.end()); combined_input.insert(gptr_other->m_input_layers.begin(), gptr_other->m_input_layers.end()); @@ -199,6 +202,7 @@ void Group::updateInputLayers(const Group::GPtr& gptr_other) { // Not every output should be included - those layers which are consumed _ONLY_ by the merged group are not outputs // anymore void Group::updateOutputLayers(const Group::GPtr& gptr_other) { + m_mic_io_valid = false; detail::OVNodeSet combined_output; combined_output.insert(m_output_layers.begin(), m_output_layers.end()); combined_output.insert(gptr_other->m_output_layers.begin(), gptr_other->m_output_layers.end()); @@ -333,21 +337,26 @@ void Group::takeFlags(const Group::GPtr& gptr_other) { // Check if there is indirect path from this to gptr_cons bool Group::hasCycle(const Group::GPtr& gptr_cons) const { - std::unordered_set visited; + // Fast-path: if this group has at most 1 consumer, there is no alternative path + // to gptr_cons and therefore no cycle. + if (m_nh->dstNodes().size() <= 1) { + return false; + } + std::unordered_set visited; std::stack st; for (const auto& prod : gptr_cons->srcNodes()) { // skip self during this iter if (!(m_nh == prod)) { st.push(prod); + visited.insert(prod); // mark at push time to avoid duplicate pushes } } while (!st.empty()) { auto nh = st.top(); st.pop(); - visited.insert(nh); if (nh == m_nh) { // Found another path from self to gptr_cons @@ -355,8 +364,9 @@ bool Group::hasCycle(const Group::GPtr& gptr_cons) const { } for (const auto& prod : nh->srcNodes()) { - if (visited.find(prod) == visited.end()) { + if (!visited.count(prod)) { st.push(prod); + visited.insert(prod); // mark at push time } } } @@ -415,33 +425,39 @@ void Group::setRepeated(const std::shared_ptr& rep) { PairMICSetIO Group::metaInterconnect(const Group::GPtr& gptr_prod) const { MICSet mics; + auto locked_snapshot = m_snapshot.lock(); auto ics = interconnect(gptr_prod); for (const auto& ic : ics) { - mics.insert({ov::npuw::online::util::getMetaDesc(ic.input_node), + mics.insert({locked_snapshot->getMetaDesc(ic.input_node), gptr_prod->m_reptrack.at(ic.input_node), ic.input_port, - ov::npuw::online::util::getMetaDesc(ic.output_node), + locked_snapshot->getMetaDesc(ic.output_node), m_reptrack.at(ic.output_node), ic.output_port}); } - MetaInterconnectIO mic_io; - auto locked_snapshot = m_snapshot.lock(); - for (const auto& oi : m_input_layers) { - mic_io.output_imeta.insert(ov::npuw::online::util::getMetaDesc(oi)); - } - for (const auto& oo : m_output_layers) { - mic_io.output_ometa.insert(ov::npuw::online::util::getMetaDesc(oo)); + // Cache the MetaInterconnectIO part — it only depends on m_input_layers / + // m_output_layers which are invalidated (m_mic_io_valid = false) whenever + // those sets change (addInput / addOutput / updateInputLayers / updateOutputLayers). + if (!m_mic_io_valid) { + m_cached_mic_io = MetaInterconnectIO{}; + for (const auto& oi : m_input_layers) { + m_cached_mic_io.output_imeta.insert(locked_snapshot->getMetaDesc(oi)); + } + for (const auto& oo : m_output_layers) { + m_cached_mic_io.output_ometa.insert(locked_snapshot->getMetaDesc(oo)); + } + m_mic_io_valid = true; } - return {mics, mic_io}; + return {mics, m_cached_mic_io}; } std::unordered_set Group::interconnect(const Group::GPtr& gptr_prod) const { std::unordered_set ics; auto locked_snapshot = m_snapshot.lock(); - auto ports_map = locked_snapshot->getPortsMap(); + const auto& ports_map = locked_snapshot->getPortsMap(); for (const auto& layer : m_content) { for (const auto& input_layer : locked_snapshot->getNodeProducers(layer)) { diff --git a/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/group.hpp b/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/group.hpp index 1fa7935364555a..899b382fd8b5ec 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/group.hpp +++ b/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/group.hpp @@ -11,6 +11,7 @@ #include "graph.hpp" #include "openvino/openvino.hpp" +#include "repeated.hpp" #include "utils/utils.hpp" namespace ov { @@ -119,6 +120,11 @@ class Group : public std::enable_shared_from_this { std::shared_ptr m_repeated = nullptr; // For each layer inside group, store it's history of repeated groups detail::ReptrackMap m_reptrack; + + // Cache for the MetaInterconnectIO part of metaInterconnect(). + // Invalidated whenever m_input_layers or m_output_layers change. + mutable bool m_mic_io_valid = false; + mutable MetaInterconnectIO m_cached_mic_io; }; } // namespace online diff --git a/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/snapshot.cpp b/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/snapshot.cpp index 0c6cc941758e93..7fc42289b139de 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/snapshot.cpp +++ b/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/snapshot.cpp @@ -822,7 +822,7 @@ void Snapshot::identifyUniques() { // This pass should only be called at the very beginning, // thus check and use only the single initial layer auto ov_node = group->getInitialNode(); - auto metadesc = ov::npuw::online::util::getMetaDesc(ov_node); + auto metadesc = getMetaDesc(ov_node); const auto& avoids = group->avoidedTargets(); const auto& special_tags = group->specialTags(); uniques[{metadesc, avoids, special_tags}].insert(group); @@ -1082,6 +1082,28 @@ void Snapshot::mergeUniques() { LOG_INFO("Online partitioning: executing mergeUniques pass..."); LOG_BLOCK(); + // Pre-build a rep-tag → GPtrSet index to replace the O(V) scan inside + // getRepGroups(). Without this, getRepGroups() is called once per distinct rep + // tag and each call scans all V graph nodes → O(V × nRepTags) = O(V²) total. + // With small chunk sizes (many KV blocks → large V), this dominates compilation. + // + // Correctness: each rep tag is visited at most once (merged_this_time guard). + // During the loop, tryMergeRepeating() may: + // - re-tag consumer groups (they get a new_rep) → stale entries in the index + // - remove producer groups from the graph + // Both cases are handled by the staleness filter below (rep-tag and graph checks). + std::unordered_map rep_index; + for (const auto& nh_i : m_graph->sorted()) { + if (!m_graph->contains(nh_i)) { + continue; + } + const Group::GPtr& g = m_graph->meta(nh_i).get(); + const auto& rep_i = g->repeated(); + if (rep_i && !g->isFrozen()) { + rep_index[rep_i.get()].insert(g); + } + } + std::unordered_set> merged_this_time; for (const auto& nh : m_graph->sorted()) { @@ -1094,7 +1116,17 @@ void Snapshot::mergeUniques() { GPtrSet repeating_groups; if (rep && rep->openForMerge() && merged_this_time.count(rep) == 0) { - repeating_groups = getRepGroups(group); + // Use the prebuilt index (O(|rep_set|)) instead of the O(V) graph scan. + // Apply a staleness filter: a group's rep tag may have changed if it was + // merged in an earlier iteration of this same mergeUniques() call. + auto it = rep_index.find(rep.get()); + if (it != rep_index.end()) { + for (const auto& g : it->second) { + if (m_graph->contains(g->getHandle()) && g->repeated().get() == rep.get() && !g->isFrozen()) { + repeating_groups.insert(g); + } + } + } } if (!repeating_groups.empty()) { @@ -1147,26 +1179,29 @@ std::shared_ptr Snapshot::tryGrowRepeatingGroups(const GPtrSet& repeat Group::GPtr prod_group = m_graph->meta(prod_nh).get(); LOG_DEBUG("prod_group:"); prod_group->dump(); - if (prod_group->repeated() && !prod_group->hasCycle(group) && prod_group->repeated() != this_rep_tag && - prod_group->avoidedTargets() == this_avoided && prod_group->specialTags() == this_special) { - auto meta_interconnect = group->metaInterconnect(prod_group); - - // FIXME: find a better way to reduce time complexity - // Need to align interconnects in the same format via sort, so they could be compared later - MICVec mic_sorted_key(meta_interconnect.first.begin(), meta_interconnect.first.end()); - std::sort(mic_sorted_key.begin(), mic_sorted_key.end()); - mics[{mic_sorted_key, meta_interconnect.second}].push_back({prod_group, group}); - LOG_DEBUG("Add the pair to the merge vector!"); - } else if (ov::npuw::debug_groups()) { - LOG_DEBUG("Couldn't add the pair to the merge vector due to failed checks:"); - LOG_BLOCK(); + if (prod_group->repeated()) { + bool cycle = prod_group->hasCycle(group); + if (!cycle && prod_group->repeated() != this_rep_tag && prod_group->avoidedTargets() == this_avoided && + prod_group->specialTags() == this_special) { + auto meta_interconnect = group->metaInterconnect(prod_group); + + // FIXME: find a better way to reduce time complexity + // Need to align interconnects in the same format via sort, so they could be compared later + MICVec mic_sorted_key(meta_interconnect.first.begin(), meta_interconnect.first.end()); + std::sort(mic_sorted_key.begin(), mic_sorted_key.end()); + mics[{mic_sorted_key, meta_interconnect.second}].push_back({prod_group, group}); + LOG_DEBUG("Add the pair to the merge vector!"); + } else if (ov::npuw::debug_groups()) { + LOG_DEBUG("Couldn't add the pair to the merge vector due to failed checks:"); + LOG_BLOCK(); #define INSPECT(x) LOG_DEBUG(#x " = " << (x)) - INSPECT(prod_group->specialTags()); - INSPECT(prod_group->repeated() != this_rep_tag); - INSPECT(prod_group->hasCycle(group)); - INSPECT(prod_group->avoidedTargets() == this_avoided); - INSPECT(prod_group->specialTags() == this_special); + INSPECT(prod_group->specialTags()); + INSPECT(prod_group->repeated() != this_rep_tag); + INSPECT(cycle); + INSPECT(prod_group->avoidedTargets() == this_avoided); + INSPECT(prod_group->specialTags() == this_special); #undef INSPECT + } } } } @@ -1380,7 +1415,7 @@ void Snapshot::completeRepeating(const std::shared_ptr& reptag, const for (const auto& gptr : gset) { for (const auto& layer : gptr->getContent()) { // FIXME: should it be a part of group's API instead? - const auto& metadesc = ov::npuw::online::util::getMetaDesc(layer); + const auto& metadesc = getMetaDesc(layer); const auto& archetype = gptr->getReptrack(layer); matches[{std::move(metadesc), std::move(archetype)}].insert(layer); } @@ -1465,6 +1500,17 @@ size_t Snapshot::graphSize() const { return m_graph->nodes().size(); } +std::string Snapshot::getMetaDesc(const std::shared_ptr& node) const { + auto id = node->get_instance_id(); + auto it = m_metadesc_cache.find(id); + if (it != m_metadesc_cache.end()) { + return it->second; + } + auto result = util::getMetaDesc(node); + m_metadesc_cache.emplace(id, result); + return result; +} + const OVPortsMap& Snapshot::getPortsMap() const { return m_ports_map; } diff --git a/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/snapshot.hpp b/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/snapshot.hpp index ecb5e4ef68b6ac..395b673ab06a3d 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/snapshot.hpp +++ b/src/plugins/intel_npu/src/plugin/npuw/partitioning/online/snapshot.hpp @@ -67,6 +67,7 @@ class Snapshot : public std::enable_shared_from_this { void repeat(detail::Pass&& pass); void setCtx(const PassContext& ctx); size_t graphSize() const; + std::string getMetaDesc(const std::shared_ptr& node) const; private: detail::GPtrSet getRepGroups(const std::shared_ptr& group) const; @@ -100,6 +101,7 @@ class Snapshot : public std::enable_shared_from_this { detail::OVPortsMap m_ports_map; std::map>> m_layer_matches; + mutable std::unordered_map m_metadesc_cache; }; } // namespace online From e0ce2ec5326345f61f1522ceb0f26af99cd0ec81 Mon Sep 17 00:00:00 2001 From: Mingyu Kim Date: Thu, 23 Apr 2026 02:31:36 +0900 Subject: [PATCH 029/545] [GPU] minor fix in log message (#35467) --- src/plugins/intel_gpu/src/runtime/ze/ze_stream.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/intel_gpu/src/runtime/ze/ze_stream.cpp b/src/plugins/intel_gpu/src/runtime/ze/ze_stream.cpp index 9602c480d54902..998ce3efd75fb8 100644 --- a/src/plugins/intel_gpu/src/runtime/ze/ze_stream.cpp +++ b/src/plugins/intel_gpu/src/runtime/ze/ze_stream.cpp @@ -348,7 +348,7 @@ std::unique_ptr ze_stream::create_surfaces_lock(const std::vector } void ze_stream::flush() const { - GPU_DEBUG_INFO << "Immediate Command List submits commands immediately - no flush impl"; + GPU_DEBUG_TRACE << "Immediate Command List submits commands immediately - no flush impl" << std::endl; } void ze_stream::finish() const { From 26c3c8f94d415c5ead6166040e4ddd8caf5d8226 Mon Sep 17 00:00:00 2001 From: zlma Date: Thu, 23 Apr 2026 13:44:44 +0800 Subject: [PATCH 030/545] [GPU] Fix RMS bfyx_opt kernel producing wrong results with static padding (#35350) The RMS bfyx_opt kernel uses flat addressing (data_idx * data_size) by default, which assumes contiguous memory. When in-place crop introduces static padding, gaps appear between slices and the flat offset calculation reads incorrect data, producing wrong inference results. Fix by detecting static padding on input dimensions and switching to the per-dimension indexed addressing path (HAS_DYNAMIC_PADDING), which correctly accounts for pitches and padding. ### Tickets: [CVS-183841](https://jira.devtools.intel.com/browse/CVS-183841) Co-authored-by: Pavel Durandin --- .../cl_kernels/rms_gpu_bfyx_opt.cl | 2 +- .../kernels/rms/rms_kernel_bfyx_opt.cpp | 18 +- .../tests/unit/test_cases/rms_gpu_test.cpp | 161 +++++++++++++++--- 3 files changed, 148 insertions(+), 33 deletions(-) diff --git a/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/rms_gpu_bfyx_opt.cl b/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/rms_gpu_bfyx_opt.cl index 53efd93775e8e0..771df857aeb7a1 100644 --- a/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/rms_gpu_bfyx_opt.cl +++ b/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/rms_gpu_bfyx_opt.cl @@ -43,7 +43,7 @@ KERNEL(rms_gpu_bfyx_opt)( const uint items_num = data_size / workers_per_data; const uint leftovers = data_size % workers_per_data; - #if HAS_DYNAMIC_PADDING + #if HAS_PADDING uint b_idx = 0; uint f_idx = 0; uint z_idx = 0; diff --git a/src/plugins/intel_gpu/src/kernel_selector/kernels/rms/rms_kernel_bfyx_opt.cpp b/src/plugins/intel_gpu/src/kernel_selector/kernels/rms/rms_kernel_bfyx_opt.cpp index 56513be1772dea..ffb52b9a8fe5ed 100644 --- a/src/plugins/intel_gpu/src/kernel_selector/kernels/rms/rms_kernel_bfyx_opt.cpp +++ b/src/plugins/intel_gpu/src/kernel_selector/kernels/rms/rms_kernel_bfyx_opt.cpp @@ -54,12 +54,20 @@ DeviceFeaturesKey RMSKernelBfyxOpt::get_required_device_features_key(const Param JitConstants RMSKernelBfyxOpt::GetJitConstants(const rms_params& params, DispatchData dispatchData) const { auto jit = Parent::GetJitConstants(params, dispatchData); - bool has_dynamic_padding = false; - for (const auto& dim : params.inputs[0].GetDims()) - has_dynamic_padding |= dim.pad.is_dynamic; + // Check for any padding (dynamic or static) on input dimensions. + // The flat addressing path (data_idx * data_size) assumes contiguous memory, + // which breaks when padding introduces gaps between slices (e.g., from in-place crop). + // Switch to per-dimension indexed addressing via get_input_index() in that case. + bool has_padding = false; + for (const auto& dim : params.inputs[0].GetDims()) { + if (dim.pad.is_dynamic || dim.pad.before != 0 || dim.pad.after != 0) { + has_padding = true; + break; + } + } - if (has_dynamic_padding) - jit.AddConstant(MakeJitConstant("HAS_DYNAMIC_PADDING", 1)); + if (has_padding) + jit.AddConstant(MakeJitConstant("HAS_PADDING", 1)); if (params.has_dynamic_tensors()) { const auto& input = params.inputs[0]; diff --git a/src/plugins/intel_gpu/tests/unit/test_cases/rms_gpu_test.cpp b/src/plugins/intel_gpu/tests/unit/test_cases/rms_gpu_test.cpp index 490db4b4a7906c..d2cac9452996db 100644 --- a/src/plugins/intel_gpu/tests/unit/test_cases/rms_gpu_test.cpp +++ b/src/plugins/intel_gpu/tests/unit/test_cases/rms_gpu_test.cpp @@ -4,7 +4,9 @@ #include "test_utils.h" +#include #include +#include #include #include "rms_inst.h" @@ -30,36 +32,36 @@ void rms_ref(const memory::ptr input, const memory::ptr gamma, memory::ptr outpu weight = std::make_unique>(gamma, get_test_stream()); } + // RMS normalization across the last (innermost populated) dimension. + // For bfyx with x_size > 1 (rank 4): normalize across X per (b, f, y). + // For bfyx with x_size == 1 (rank 3 mapped to bfyx): normalize across Y per (b, f). + // This matches the kernel behavior based on ov_input_rank. + bool norm_over_x = (x_size > 1); + uint32_t outer_y = norm_over_x ? y_size : 1; + uint32_t norm_size = norm_over_x ? x_size : y_size; + for (uint32_t b = 0; b < batch_size; ++b) { for (uint32_t f = 0; f < feature_size; ++f) { - float rms = 0.f; - for (uint32_t y = 0; y < y_size; ++y) { - for (uint32_t x = 0; x < x_size; ++x) { - auto tensor_src = tensor(batch(b), feature(f), spatial(x, y, 0, 0)); - size_t src_offset = input_layout.get_linear_offset(tensor_src); - rms += std::pow(static_cast(src[src_offset]), 2); + for (uint32_t oy = 0; oy < outer_y; ++oy) { + float rms = 0.f; + for (uint32_t n = 0; n < norm_size; ++n) { + uint32_t y = norm_over_x ? oy : n; + uint32_t x = norm_over_x ? n : 0; + auto t = tensor(batch(b), feature(f), spatial(x, y, 0, 0)); + rms += std::pow(static_cast(src[input_layout.get_linear_offset(t)]), 2); } - } - rms /= y_size * x_size; - rms += epsilon; - rms = std::pow(std::sqrt(rms), -1); - - for (uint32_t y = 0; y < y_size; ++y) { - for (uint32_t x = 0; x < x_size; ++x) { - auto tensor_src = tensor(batch(b), feature(f), spatial(x, y, 0, 0)); - auto tensor_dst = tensor(batch(b), feature(f), spatial(x, y, 0, 0)); - size_t src_offset = input_layout.get_linear_offset(tensor_src); - size_t dst_offset = input_layout.get_linear_offset(tensor_dst); - - float gamma_val = 1.0f; - if (weight) { - auto tensor_weight = tensor(batch(0), feature(0), spatial(x, y, 0, 0)); - size_t weight_offset = input_layout.get_linear_offset(tensor_weight); - gamma_val = static_cast((*weight)[weight_offset]); - } - - float result = rms * static_cast(src[src_offset]) * gamma_val; - dst[dst_offset] = static_cast(result); + rms /= norm_size; + rms += epsilon; + rms = std::pow(std::sqrt(rms), -1); + + for (uint32_t n = 0; n < norm_size; ++n) { + uint32_t y = norm_over_x ? oy : n; + uint32_t x = norm_over_x ? n : 0; + auto t = tensor(batch(b), feature(f), spatial(x, y, 0, 0)); + size_t offset = input_layout.get_linear_offset(t); + + float gamma_val = weight ? static_cast((*weight)[n]) : 1.0f; + dst[offset] = static_cast(rms * static_cast(src[offset]) * gamma_val); } } } @@ -538,3 +540,108 @@ TEST(rms_gpu_test, rms_test_without_gamma_dyn) { EXPECT_NEAR(output_ptr[i], output_ref_ptr[i], 1e-3); } } + +// Regression test for in-place crop on spatial axis followed by RMS normalization. +// RMS kernels may not correctly handle spatial padding introduced by in-place crop. +TEST(rms_gpu_test, in_place_crop_rms_spatial_split) { + auto& engine = get_test_engine(); + + // Input [1,2,3,16] bfyx. VariadicSplit on axis 2 (spatial Y) into [2, 1]. + // crop_0 covers y=[0..1] (size 2), crop_1 covers y=2 (size 1). + // Each crop feeds RMS normalization. + // dim_x = 16 is the minimum for the bfyx_opt kernel (requires gamma >= subgroup_size(16)). + // force_implementations ensures bfyx_opt is selected regardless of other heuristics. + const int64_t dim_b = 1, dim_f = 2, dim_y = 3, dim_x = 16; + const int64_t split0 = 2, split1 = 1; + const int64_t off_y0 = 0, off_y1 = 2; + const float epsilon = 1e-6f; + + auto input_mem = engine.allocate_memory({ov::PartialShape{dim_b, dim_f, dim_y, dim_x}, + data_types::f32, format::bfyx}); + auto axis_mem = engine.allocate_memory({ov::PartialShape{}, data_types::i64, format::bfyx}); + auto splits_length_mem = engine.allocate_memory({ov::PartialShape{2}, data_types::i64, format::bfyx}); + // Gamma weights for RMS: shape [1, 1, 1, dim_x] — per-element scale on X axis + auto gamma_mem = engine.allocate_memory({ov::PartialShape{1, 1, 1, dim_x}, data_types::f32, format::bfyx}); + + const int64_t axis = 2; + const size_t total = static_cast(dim_b * dim_f * dim_y * dim_x); + + // Deterministic input with small values + std::vector input_data(total); + for (size_t i = 0; i < total; i++) + input_data[i] = static_cast(i + 1); + set_values(input_mem, input_data); + set_values(axis_mem, {axis}); + set_values(splits_length_mem, {split0, split1}); + // Gamma = 1.0 (identity scale) for easy reference computation + std::vector gamma_data(dim_x, 1.0f); + set_values(gamma_mem, gamma_data); + + // Prepare crop slices and compute reference outputs using rms_ref + auto make_crop_ref = [&](int64_t split_size, int64_t y_offset) { + auto crop_mem = engine.allocate_memory({ov::PartialShape{dim_b, dim_f, split_size, dim_x}, data_types::f32, format::bfyx}); + auto out_mem = engine.allocate_memory({ov::PartialShape{dim_b, dim_f, split_size, dim_x}, data_types::f32, format::bfyx}); + std::vector crop_data; + for (int64_t f = 0; f < dim_f; f++) + for (int64_t y = 0; y < split_size; y++) + for (int64_t x = 0; x < dim_x; x++) + crop_data.push_back(input_data[static_cast(f * dim_y * dim_x + (y_offset + y) * dim_x + x)]); + set_values(crop_mem, crop_data); + rms_ref(crop_mem, gamma_mem, out_mem, epsilon); + return out_mem; + }; + auto output_ref_0 = make_crop_ref(split0, off_y0); + auto output_ref_1 = make_crop_ref(split1, off_y1); + + cldnn::crop_ngraph_op_mode op_mode = cldnn::crop_ngraph_op_mode::variadic_split; + topology topology; + topology.add(input_layout("input", input_mem->get_layout())); + topology.add(data("axis", axis_mem)); + topology.add(data("splits_length", splits_length_mem)); + topology.add(data("gamma", gamma_mem)); + // Branch 0: crop [1,2,2,16] -> RMS normalization + topology.add(crop("crop_0", {input_info("input"), input_info("axis"), input_info("splits_length")}, + tensor(1), tensor(0, 0, 0, off_y0), op_mode, 0, axis)); + topology.add(rms("rms_0", input_info("crop_0"), input_info("gamma"), epsilon)); + topology.add(reorder("output_0", input_info("rms_0"), format::bfyx, data_types::f32, + std::vector(), reorder_mean_mode::subtract, padding(), true)); + // Branch 1: crop [1,2,1,16] -> RMS normalization + topology.add(crop("crop_1", {input_info("input"), input_info("axis"), input_info("splits_length")}, + tensor(1), tensor(0, 0, 0, off_y1), op_mode, 1, axis)); + topology.add(rms("rms_1", input_info("crop_1"), input_info("gamma"), epsilon)); + topology.add(reorder("output_1", input_info("rms_1"), format::bfyx, data_types::f32, + std::vector(), reorder_mean_mode::subtract, padding(), true)); + + auto config = get_test_default_config(engine); + config.set_property(ov::intel_gpu::allow_new_shape_infer(true)); + config.set_property(ov::intel_gpu::optimize_data(true)); + config.set_property(ov::intel_gpu::force_implementations(ov::intel_gpu::ImplForcingMap{ + {"rms_0", {format::bfyx, "rms_gpu_bfyx_opt"}}, + {"rms_1", {format::bfyx, "rms_gpu_bfyx_opt"}} + })); + network network(engine, topology, config); + network.set_input_data("input", input_mem); + + auto outputs = network.execute(); + + ASSERT_TRUE(network.get_primitive("crop_0")->can_be_optimized()); + ASSERT_TRUE(network.get_primitive("crop_1")->can_be_optimized()); + + // Verify branch 0: crop [1,2,2,16], y-offset = 0 + auto out0_mem = outputs.at("output_0").get_memory(); + cldnn::mem_lock out0(out0_mem, get_test_stream()); + cldnn::mem_lock ref0(output_ref_0, get_test_stream()); + ASSERT_EQ(out0.size(), ref0.size()); + for (size_t i = 0; i < ref0.size(); i++) { + ASSERT_NEAR(out0[i], ref0[i], 1e-4f) << "Branch 0 mismatch at index=" << i; + } + + // Verify branch 1: crop [1,2,1,16], y-offset = 2 + auto out1_mem = outputs.at("output_1").get_memory(); + cldnn::mem_lock out1(out1_mem, get_test_stream()); + cldnn::mem_lock ref1(output_ref_1, get_test_stream()); + ASSERT_EQ(out1.size(), ref1.size()); + for (size_t i = 0; i < ref1.size(); i++) { + ASSERT_NEAR(out1[i], ref1[i], 1e-4f) << "Branch 1 mismatch at index=" << i; + } +} From e68f65896669dfe7814eefcc211d29826b592d38 Mon Sep 17 00:00:00 2001 From: Jade Cho Date: Thu, 23 Apr 2026 15:13:00 +0900 Subject: [PATCH 031/545] [GPU] Add Power(-0.5) pattern to RMSFusion (#35460) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description of the issue(symptom, root-cause, how it was resolved) - RMSNorm fusion (`ov::pass::RMSFusion`) fails to match on models (e.g., Gemma 4 E2B/E4B) that express the reciprocal square root as a single `Power(-0.5)` node instead of the two-node `Sqrt → Power(-1)` sequence. - Root cause: the pattern in `rms_fusion.cpp` only matches `Sqrt` → `Power(-1)` or `1/Sqrt` paths; there is no branch for `Power(add_eps, -0.5)`. - Resolved by adding a `Power(-0.5)` direct path as a third alternative in the `Or` node, with an optional `Convert` to handle mixed-precision constants. #### The code and line that caused this issue (if it is not changed directly) - `src/common/transformations/src/transformations/common_optimizations/rms_fusion.cpp:59-69` — pattern only defines `Sqrt`-based paths #### Reproduction step and snapshot (if applicable. Do not attach for customer model) - Any model converted from PyTorch that lowers `rsqrt(x)` to `Power(x, -0.5)` instead of `Sqrt(x) → Power(x, -1)` will miss RMSNorm fusion. #### Problematic graph - Existing (matched): `x → Power(2) → ReduceMean → Add(eps) → Sqrt → Power(-1) → Multiply(x) → Multiply(gamma)` - Missing (unmatched): `x → Power(2) → ReduceMean → Add(eps) → Power(-0.5) → Multiply(x) → Multiply(gamma)` #### Checklist - [x] Is it a proper fix? (not a workaround) - [x] Did you include test case for this fix, if necessary? - Added 4 new tests (Test13–Test16) covering `Power(-0.5)` with gamma+Convert, gamma without Convert, without gamma with dynamic scale, and f16 constants with Convert. - [x] Did you review existing test that can be extended to cover this scenario? Which test did you review? - Reviewed `rms_norm_decomposition_test.cpp` (Test1–Test12). Existing tests only cover `Sqrt`-based paths; new tests mirror their structure for the `Power(-0.5)` variant. --- .../common_optimizations/rms_fusion.cpp | 8 +- .../rms_norm_decomposition_test.cpp | 145 ++++++++++++++++++ 2 files changed, 152 insertions(+), 1 deletion(-) diff --git a/src/common/transformations/src/transformations/common_optimizations/rms_fusion.cpp b/src/common/transformations/src/transformations/common_optimizations/rms_fusion.cpp index 5ee4a646eb00bd..e2179c16f5598e 100644 --- a/src/common/transformations/src/transformations/common_optimizations/rms_fusion.cpp +++ b/src/common/transformations/src/transformations/common_optimizations/rms_fusion.cpp @@ -66,7 +66,13 @@ RMSFusion::RMSFusion(bool force_tail_convert, bool enable_div_x, bool enable_wit auto const_div = pattern::wrap_type(pattern::value_matches("1")); auto const_div_convert = pattern::optional(const_div); auto div = pattern::wrap_type({const_div_convert, sqrt}); - auto div_or_pow = std::make_shared(OutputVector{div, pow}); + + // Power(ReduceMean(x^2,axes)+eps, -0.5) — direct rsqrt without Sqrt node + auto const_neg_half = pattern::wrap_type(pattern::value_matches("-0.5")); + auto const_neg_half_convert = pattern::optional(const_neg_half); + auto pow_direct = pattern::wrap_type({add_eps, const_neg_half_convert}); + + std::shared_ptr div_or_pow = std::make_shared(OutputVector{div, pow, pow_direct}); // x * 1/Sqrt(ReduceMean(x^2,axes)+eps) auto mul1 = pattern::wrap_type({x, div_or_pow}); diff --git a/src/common/transformations/tests/common_optimizations/rms_norm_decomposition_test.cpp b/src/common/transformations/tests/common_optimizations/rms_norm_decomposition_test.cpp index 2b319bdc753c9e..3ce950117f1e24 100644 --- a/src/common/transformations/tests/common_optimizations/rms_norm_decomposition_test.cpp +++ b/src/common/transformations/tests/common_optimizations/rms_norm_decomposition_test.cpp @@ -424,3 +424,148 @@ TEST_F(TransformationTestsF, RMSNormFusionTest12) { comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES); comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES); } + +// --- Power(-0.5) direct rsqrt path tests --- + +// Power(-0.5) direct path with gamma and tail convert +TEST_F(TransformationTestsF, RMSNormFusionTest13_PowerNegHalf_GammaConvert) { + { + auto input = std::make_shared(ov::element::f32, ov::Shape{1, 2, 6}); + auto power_const = ov::op::v0::Constant::create(ov::element::f32, {}, {2.f}); + auto power = std::make_shared(input, power_const); + auto mean_axes = ov::op::v0::Constant::create(ov::element::i64, ov::Shape{1}, {-1}); + auto mean = std::make_shared(power, mean_axes, true); + auto eps = ov::op::v0::Constant::create(ov::element::f32, {}, {1e-6f}); + auto add_eps = std::make_shared(mean, eps); + auto neg_half = ov::op::v0::Constant::create(ov::element::f32, {}, {-0.5f}); + auto rsqrt = std::make_shared(add_eps, neg_half); + auto mul1 = std::make_shared(input, rsqrt); + auto gamma = ov::op::v0::Constant::create(ov::element::f32, + ov::Shape{6}, + {0.029f, 0.014f, 0.003f, 0.013f, 0.015f, 0.009f}); + auto mul2 = std::make_shared(gamma, mul1); + auto comp = std::make_shared(mul2, ov::element::f16); + + model = std::make_shared(ov::OutputVector{comp}, ov::ParameterVector{input}); + manager.register_pass(); + } + { + auto input = std::make_shared(ov::element::f32, ov::Shape{1, 2, 6}); + auto rms_const = ov::op::v0::Constant::create(ov::element::f32, + ov::Shape{6}, + {0.029f, 0.014f, 0.003f, 0.013f, 0.015f, 0.009f}); + auto rms = std::make_shared(input, rms_const, 1e-6f, ov::element::f16); + + model_ref = std::make_shared(ov::OutputVector{rms}, ov::ParameterVector{input}); + } + comparator.enable(FunctionsComparator::CmpValues::ACCURACY); + comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES); + comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES); +} + +// Power(-0.5) direct path with gamma, no tail convert +TEST_F(TransformationTestsF, RMSNormFusionTest14_PowerNegHalf_GammaNoConvert) { + { + auto input = std::make_shared(ov::element::f32, ov::PartialShape{-1, -1, 6}); + auto power_const = ov::op::v0::Constant::create(ov::element::f32, {}, {2.f}); + auto power = std::make_shared(input, power_const); + auto mean_axes = ov::op::v0::Constant::create(ov::element::i64, ov::Shape{1}, {-1}); + auto mean = std::make_shared(power, mean_axes, true); + auto eps = ov::op::v0::Constant::create(ov::element::f32, {}, {1e-6f}); + auto add_eps = std::make_shared(mean, eps); + auto neg_half = ov::op::v0::Constant::create(ov::element::f32, {}, {-0.5f}); + auto rsqrt = std::make_shared(add_eps, neg_half); + auto mul1 = std::make_shared(input, rsqrt); + auto gamma = ov::op::v0::Constant::create(ov::element::f32, + ov::Shape{6}, + {0.029f, 0.014f, 0.003f, 0.013f, 0.015f, 0.009f}); + auto mul2 = std::make_shared(gamma, mul1); + + model = std::make_shared(ov::OutputVector{mul2}, ov::ParameterVector{input}); + manager.register_pass(false); + } + { + auto input = std::make_shared(ov::element::f32, ov::PartialShape{-1, -1, 6}); + auto rms_const = ov::op::v0::Constant::create(ov::element::f32, + ov::Shape{6}, + {0.029f, 0.014f, 0.003f, 0.013f, 0.015f, 0.009f}); + auto rms = std::make_shared(input, rms_const, 1e-6f, ov::element::f32); + + model_ref = std::make_shared(ov::OutputVector{rms}, ov::ParameterVector{input}); + } + comparator.enable(FunctionsComparator::CmpValues::ACCURACY); + comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES); + comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES); +} + +// Power(-0.5) direct path without gamma, with dynamic scale +TEST_F(TransformationTestsF, RMSNormFusionTest15_PowerNegHalf_NoGammaScale) { + { + auto input = std::make_shared(ov::element::f32, ov::PartialShape{-1, -1, 6}); + auto scale = std::make_shared(ov::element::f32, ov::PartialShape{-1, -1, 6}); + + auto power_const = ov::op::v0::Constant::create(ov::element::f32, {}, {2.f}); + auto power = std::make_shared(input, power_const); + auto mean_axes = ov::op::v0::Constant::create(ov::element::i64, ov::Shape{1}, {-1}); + auto mean = std::make_shared(power, mean_axes, true); + auto eps = ov::op::v0::Constant::create(ov::element::f32, {}, {1e-6f}); + auto add_eps = std::make_shared(mean, eps); + auto neg_half = ov::op::v0::Constant::create(ov::element::f32, {}, {-0.5f}); + auto rsqrt = std::make_shared(add_eps, neg_half); + auto mul1 = std::make_shared(input, rsqrt); + auto mul2 = std::make_shared(mul1, scale); + + model = std::make_shared(ov::OutputVector{mul2}, ov::ParameterVector{input, scale}); + manager.register_pass(false, false, true); + } + { + auto input = std::make_shared(ov::element::f32, ov::PartialShape{-1, -1, 6}); + auto scale = std::make_shared(ov::element::f32, ov::PartialShape{-1, -1, 6}); + + auto rms = std::make_shared(input, 1e-6f, ov::element::f32); + auto mul = std::make_shared(rms, scale); + + model_ref = std::make_shared(ov::OutputVector{mul}, ov::ParameterVector{input, scale}); + } + comparator.enable(FunctionsComparator::CmpValues::ACCURACY); +} + +// Power(-0.5) direct path with f16 constants and Convert +TEST_F(TransformationTestsF, RMSNormFusionTest16_PowerNegHalf_F16Convert) { + { + auto input = std::make_shared(ov::element::f32, ov::PartialShape{-1, -1, 6}); + auto power_const = ov::op::v0::Constant::create(ov::element::f16, {}, {2.f}); + auto power_const_convert = std::make_shared(power_const, ov::element::f32); + auto power = std::make_shared(input, power_const_convert); + auto mean_axes = ov::op::v0::Constant::create(ov::element::i64, ov::Shape{1}, {-1}); + auto mean = std::make_shared(power, mean_axes, true); + auto eps = ov::op::v0::Constant::create(ov::element::f16, {}, {1e-5f}); + auto eps_convert = std::make_shared(eps, ov::element::f32); + auto add_eps = std::make_shared(mean, eps_convert); + auto neg_half = ov::op::v0::Constant::create(ov::element::f16, {}, {-0.5f}); + auto neg_half_convert = std::make_shared(neg_half, ov::element::f32); + auto rsqrt = std::make_shared(add_eps, neg_half_convert); + auto mul1 = std::make_shared(input, rsqrt); + auto gamma = ov::op::v0::Constant::create(ov::element::f16, + ov::Shape{6}, + {0.029f, 0.014f, 0.003f, 0.013f, 0.015f, 0.009f}); + auto gamma_convert = std::make_shared(gamma, ov::element::f32); + auto mul2 = std::make_shared(gamma_convert, mul1); + + model = std::make_shared(ov::OutputVector{mul2}, ov::ParameterVector{input}); + manager.register_pass(false); + } + { + auto input = std::make_shared(ov::element::f32, ov::PartialShape{-1, -1, 6}); + auto gamma = ov::op::v0::Constant::create(ov::element::f16, + ov::Shape{6}, + {0.029f, 0.014f, 0.003f, 0.013f, 0.015f, 0.009f}); + auto gamma_convert = std::make_shared(gamma, ov::element::f32); + auto rms = std::make_shared(input, gamma_convert, 1e-5f, ov::element::f32); + + model_ref = std::make_shared(ov::OutputVector{rms}, ov::ParameterVector{input}); + } + comparator.enable(FunctionsComparator::CmpValues::ACCURACY); + comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES); + comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES); +} From 5f388e7daaabc0bc649818739ccf4cbe233a6498 Mon Sep 17 00:00:00 2001 From: Jeevaka Prabu Badrappan Date: Thu, 23 Apr 2026 12:30:18 +0530 Subject: [PATCH 032/545] Rtti fix (#35443) ### Details: - *Fix cross-library RTTI failure on Android* ### Tickets: - *CVS-185381* ### AI Assistance: - *AI assistance used: yes* - *Commit message and review* --------- Signed-off-by: Jeevaka Prabu Badrappan --- src/core/include/openvino/core/type.hpp | 4 ++++ src/core/include/openvino/op/one_hot.hpp | 4 ++-- src/core/include/openvino/op/squeeze.hpp | 4 ++-- .../intel_npu/src/al/include/intel_npu/config/config.hpp | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/core/include/openvino/core/type.hpp b/src/core/include/openvino/core/type.hpp index 09e5be9cb64983..1e40044dd479cd 100644 --- a/src/core/include/openvino/core/type.hpp +++ b/src/core/include/openvino/core/type.hpp @@ -83,7 +83,11 @@ class ConversionExtensionBase; template constexpr bool use_ov_dynamic_cast() { +#if defined(__ANDROID__) + return true; +#else return std::is_base_of_v; +#endif } /// \brief Tests if value is a pointer/shared_ptr that can be statically cast to a diff --git a/src/core/include/openvino/op/one_hot.hpp b/src/core/include/openvino/op/one_hot.hpp index 6f740c5724fd10..f6f994e4b92a45 100644 --- a/src/core/include/openvino/op/one_hot.hpp +++ b/src/core/include/openvino/op/one_hot.hpp @@ -14,7 +14,7 @@ namespace v1 { /// \ingroup ov_ops_cpp_api class OPENVINO_API OneHot : public util::OneHotBase { public: - OPENVINO_OP("OneHot", "opset1", op::Op); + OPENVINO_OP("OneHot", "opset1", util::OneHotBase); /// \brief Constructs a one-hot operation. OneHot() = default; @@ -48,7 +48,7 @@ namespace v16 { /// \ingroup ov_ops_cpp_api class OPENVINO_API OneHot : public util::OneHotBase { public: - OPENVINO_OP("OneHot", "opset16", op::Op); + OPENVINO_OP("OneHot", "opset16", util::OneHotBase); /// \brief Lists the supported negative indices modes for this version of the operator. /// See the specification for the description of how negative indices are handled. diff --git a/src/core/include/openvino/op/squeeze.hpp b/src/core/include/openvino/op/squeeze.hpp index aa940e009e36cf..19ca742b1ccda1 100644 --- a/src/core/include/openvino/op/squeeze.hpp +++ b/src/core/include/openvino/op/squeeze.hpp @@ -14,7 +14,7 @@ namespace v0 { /// \ingroup ov_ops_cpp_api class OPENVINO_API Squeeze : public util::SqueezeBase { public: - OPENVINO_OP("Squeeze", "opset1"); + OPENVINO_OP("Squeeze", "opset1", util::SqueezeBase); Squeeze(); /// \brief Constructs a squeeze v0 operation. @@ -43,7 +43,7 @@ namespace v15 { /// \ingroup ov_ops_cpp_api class OPENVINO_API Squeeze : public util::SqueezeBase { public: - OPENVINO_OP("Squeeze", "opset15"); + OPENVINO_OP("Squeeze", "opset15", util::SqueezeBase); Squeeze(); /// \brief Constructs a squeeze v15 operation. diff --git a/src/plugins/intel_npu/src/al/include/intel_npu/config/config.hpp b/src/plugins/intel_npu/src/al/include/intel_npu/config/config.hpp index 8204217ed27e79..92f9bb3975447c 100644 --- a/src/plugins/intel_npu/src/al/include/intel_npu/config/config.hpp +++ b/src/plugins/intel_npu/src/al/include/intel_npu/config/config.hpp @@ -528,7 +528,7 @@ typename Opt::ValueType Config::get() const { OPENVINO_ASSERT(it->second != nullptr, "Got NULL OptionValue for :", Opt::key().data()); const auto optVal = std::dynamic_pointer_cast>(it->second); -#if defined(__CHROMIUMOS__) +#if defined(__CHROMIUMOS__) || defined(__ANDROID__) if (optVal == nullptr) { if (Opt::getTypeName() == it->second->getTypeName()) { const auto val = std::static_pointer_cast>(it->second); From 5ef1deb27055aab2310a8370432bce57c6c7dfaa Mon Sep 17 00:00:00 2001 From: Dmitry Matveev Date: Thu, 23 Apr 2026 08:29:31 +0100 Subject: [PATCH 033/545] NPUW: Serialization refactoring (#35441) ### Details: - Switch to a boost.serialize-like approach ### Tickets: - EISW-213306 ### AI Assistance: - *AI assistance used: yes* - Implemented according to spec + new tests added --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/plugin/npuw/compiled_model.cpp | 604 ++++++++-------- .../src/plugin/npuw/compiled_model.hpp | 7 +- .../intel_npu/src/plugin/npuw/lazy_tensor.cpp | 394 +++++------ .../intel_npu/src/plugin/npuw/lazy_tensor.hpp | 22 +- .../src/plugin/npuw/llm_compiled_model.cpp | 98 +-- .../src/plugin/npuw/serialization.cpp | 649 ++++++------------ .../src/plugin/npuw/serialization.hpp | 364 ++++++---- .../src/plugin/npuw/weights_bank.cpp | 108 ++- .../src/plugin/npuw/weights_bank.hpp | 9 +- .../tests/unit/npuw/import_non_llm_blob.cpp | 71 +- .../tests/unit/npuw/serialization.cpp | 640 +++++++++++++++++ 11 files changed, 1677 insertions(+), 1289 deletions(-) diff --git a/src/plugins/intel_npu/src/plugin/npuw/compiled_model.cpp b/src/plugins/intel_npu/src/plugin/npuw/compiled_model.cpp index d8fc99affb526e..f4c2f9c21405cb 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/compiled_model.cpp +++ b/src/plugins/intel_npu/src/plugin/npuw/compiled_model.cpp @@ -41,6 +41,20 @@ #include "transformations/convert_precision.hpp" namespace { +std::string canonical_device_name(const std::string& device_name) { + const auto dot_pos = device_name.find('.'); + return dot_pos == std::string::npos ? device_name : device_name.substr(0, dot_pos); +} + +std::size_t find_device_index(const std::vector& devices, const std::string& device_name) { + const auto canonical_name = canonical_device_name(device_name); + const auto it = std::find_if(devices.begin(), devices.end(), [&](const std::string& candidate) { + return canonical_device_name(candidate) == canonical_name; + }); + NPUW_ASSERT(it != devices.end()); + return static_cast(it - devices.begin()); +} + void split_properties(const ov::AnyMap& properties, ov::AnyMap& npu_plugin_properties, ov::AnyMap& npuw_path_properties) { @@ -729,311 +743,228 @@ bool ov::npuw::CompiledModel::should_use_quantized_host_gather(const std::shared return false; } -void ov::npuw::CompiledModel::CompiledModelDesc::serialize(std::ostream& stream, - const ov::npuw::s11n::WeightsContext& ctx) const { +void ov::npuw::CompiledModel::CompiledModelDesc::serialize(ov::npuw::s11n::Stream& stream, + const ov::npuw::s11n::WeightsContext& ctx, + const ov::npuw::s11n::SubmodelDeserializeCtx* submodel_ctx) { using namespace ov::npuw::s11n; - LOG_DEBUG("Serializing CompiledModelDesc..."); - LOG_BLOCK(); - - write(stream, replaced_by); - - write(stream, param_base); - write(stream, forced_to_fcall); - - write(stream, host_gather.dst_idx); - write(stream, host_gather.src_idx); - write(stream, host_gather.idx_idx); - - write(stream, quant_unpack_gather.dst_idx); - write(stream, quant_unpack_gather.src_w_idx); - write(stream, quant_unpack_gather.src_z_idx); - write(stream, quant_unpack_gather.src_s_idx); - write(stream, quant_unpack_gather.idx_idx); - - write(stream, spatial); - write(stream, attention); - - // Serialize compiled submodels for pyramid attention, except the last one - write(stream, pyramid_attention); - if (pyramid_attention.has_value()) { - size_t num_models = pyramid_attention.value()._compiled_models.size(); - write(stream, num_models); - - for (size_t i = 0; i < num_models - 1; ++i) { - std::stringstream ss; - auto compiled_model = pyramid_attention.value()._compiled_models[i]; - compiled_model->export_model(ss); - write(stream, ss.str()); - } - } - - // Serialize MoE experts - write(stream, moe_experts); - if (moe_experts.has_value()) { - const auto& moe = moe_experts.value(); - size_t num_compiled_models = moe._compiled_models.size(); - write(stream, num_compiled_models); - - for (const auto& [chunk_size, compiled_model] : moe._compiled_models) { - write(stream, chunk_size); - if (compiled_model) { - write(stream, true); - std::stringstream ss; - compiled_model->export_model(ss); - write(stream, ss.str()); - } else { - write(stream, false); - } - } - } - - // Serialize MoE experts downstream - write(stream, moe_experts_downstream); - if (moe_experts_downstream.has_value()) { - const auto& moe_downstream = moe_experts_downstream.value(); - if (moe_downstream._compiled_model) { - write(stream, true); - std::stringstream ss; - moe_downstream._compiled_model->export_model(ss); - write(stream, ss.str()); - } else { - write(stream, false); - } - } - - // Serialize host flash attention - write(stream, host_flash_attention); - if (host_flash_attention.has_value()) { - // Serialize compiled tile model - if (host_flash_attention.value()._compiled_tile_model) { - write(stream, true); - std::stringstream ss; - host_flash_attention.value()._compiled_tile_model->export_model(ss); - write(stream, ss.str()); - } else { - write(stream, false); - } - } - - auto& closure_desc = closure.get(); - - write(stream, closure_desc.is_remote); - write(stream, closure_desc.closure_uid); - - if (ctx.is_weightless) { - write_weightless(stream, scales, ctx); - write_weightless(stream, zerops, ctx); - - write(stream, closure_desc.closure.size()); - std::vector cpu_closures; - std::vector cpu_closure_ids; - std::vector non_cpu_tensors; - std::vector non_cpu_tensors_ids; - for (std::size_t cidx = 0; cidx < closure_desc.closure.size(); ++cidx) { - if (closure_desc.closure_uid[cidx] == -1) { // CPU closure - cpu_closure_ids.push_back(cidx); - cpu_closures.push_back(closure_desc.closure[cidx]); - } else { - non_cpu_tensors_ids.push_back(cidx); - non_cpu_tensors.push_back(lazy_closure[cidx]); // must be there - } - } - - write(stream, cpu_closure_ids); - write_weightless(stream, cpu_closures, ctx); - write(stream, non_cpu_tensors_ids); - write(stream, non_cpu_tensors); + if (stream.output()) { + LOG_DEBUG("Serializing CompiledModelDesc..."); } else { - write(stream, scales); - write(stream, zerops); - - write(stream, closure_desc.closure.size()); - std::vector cpu_closures; - std::vector cpu_closure_ids; - for (std::size_t cidx = 0; cidx < closure_desc.closure.size(); ++cidx) { - if (closure_desc.closure_uid[cidx] == -1) { // CPU closure, not in the bank - cpu_closure_ids.push_back(cidx); - cpu_closures.push_back(closure_desc.closure[cidx]); - } - } - - write(stream, cpu_closure_ids); - - for (const auto& tensor : cpu_closures) { - write(stream, tensor); - } + LOG_DEBUG("Deserializing CompiledModelDesc..."); } - - LOG_DEBUG("DONE."); -} - -void ov::npuw::CompiledModel::CompiledModelDesc::deserialize( - std::istream& stream, - const ov::npuw::s11n::WeightsContext& ctx, - const ov::npuw::s11n::SubmodelDeserializeCtx& submodel_ctx) { - using namespace ov::npuw::s11n; - - LOG_DEBUG("Deserializing CompiledModelDesc..."); LOG_BLOCK(); - read(stream, replaced_by); - - read(stream, param_base); - read(stream, forced_to_fcall); - - read(stream, host_gather.dst_idx); - read(stream, host_gather.src_idx); - read(stream, host_gather.idx_idx); + stream & replaced_by & param_base & forced_to_fcall & host_gather.dst_idx & host_gather.src_idx & + host_gather.idx_idx & quant_unpack_gather.dst_idx & quant_unpack_gather.src_w_idx & + quant_unpack_gather.src_z_idx & quant_unpack_gather.src_s_idx & quant_unpack_gather.idx_idx & spatial & + attention & pyramid_attention; - read(stream, quant_unpack_gather.dst_idx); - read(stream, quant_unpack_gather.src_w_idx); - read(stream, quant_unpack_gather.src_z_idx); - read(stream, quant_unpack_gather.src_s_idx); - read(stream, quant_unpack_gather.idx_idx); - - read(stream, spatial); - read(stream, attention); - - read(stream, pyramid_attention); if (pyramid_attention.has_value()) { size_t num_models = 0; - read(stream, num_models); - pyramid_attention.value()._compiled_models.resize(num_models); + if (stream.output()) { + num_models = pyramid_attention.value()._compiled_models.size(); + } + stream & num_models; - if (num_models > 0) { - // Import all pyramid models except the last one + if (stream.output()) { + for (size_t i = 0; i < num_models - 1; ++i) { + std::stringstream ss; + pyramid_attention.value()._compiled_models[i]->export_model(ss); + std::string model_str = ss.str(); + stream & model_str; + } + } else if (num_models > 0) { + pyramid_attention.value()._compiled_models.resize(num_models); + NPUW_ASSERT(submodel_ctx != nullptr); for (size_t i = 0; i < num_models - 1; ++i) { std::string model_str; - read(stream, model_str); + stream & model_str; std::stringstream ss(model_str); pyramid_attention->_compiled_models[i] = - submodel_ctx.plugin->get_core()->import_model(ss, submodel_ctx.device, submodel_ctx.import_config); + submodel_ctx->plugin->get_core()->import_model(ss, + submodel_ctx->device, + submodel_ctx->import_config); } - - // Reuse the already compiled model for the last pyramid attention model - if (submodel_ctx.compiled_model) { - pyramid_attention->_compiled_models[num_models - 1] = submodel_ctx.compiled_model; + if (submodel_ctx->compiled_model) { + pyramid_attention->_compiled_models[num_models - 1] = submodel_ctx->compiled_model; LOG_DEBUG("Reused compiled_model for the last pyramid attention model"); } } } - // Deserialize MoE experts - read(stream, moe_experts); + stream & moe_experts; if (moe_experts.has_value()) { size_t num_compiled_models = 0; - read(stream, num_compiled_models); - - for (size_t i = 0; i < num_compiled_models; ++i) { - size_t chunk_size = 0; - read(stream, chunk_size); - - bool has_model = false; - read(stream, has_model); - - if (has_model) { - std::string model_str; - read(stream, model_str); - std::stringstream ss(model_str); - auto compiled_model = - submodel_ctx.plugin->get_core()->import_model(ss, submodel_ctx.device, submodel_ctx.import_config); - moe_experts->_compiled_models[chunk_size] = compiled_model; - LOG_DEBUG("Imported MoE compiled model for chunk_size=" << chunk_size); + if (stream.output()) { + num_compiled_models = moe_experts.value()._compiled_models.size(); + } + stream & num_compiled_models; + + if (stream.output()) { + for (const auto& [chunk_size, compiled_model] : moe_experts.value()._compiled_models) { + stream & chunk_size; + bool has_model = compiled_model != nullptr; + stream & has_model; + if (has_model) { + std::stringstream ss; + compiled_model->export_model(ss); + std::string model_str = ss.str(); + stream & model_str; + } + } + } else { + NPUW_ASSERT(submodel_ctx != nullptr); + for (size_t i = 0; i < num_compiled_models; ++i) { + size_t chunk_size = 0; + stream & chunk_size; + bool has_model = false; + stream & has_model; + if (has_model) { + std::string model_str; + stream & model_str; + std::stringstream ss(model_str); + moe_experts->_compiled_models[chunk_size] = + submodel_ctx->plugin->get_core()->import_model(ss, + submodel_ctx->device, + submodel_ctx->import_config); + LOG_DEBUG("Imported MoE compiled model for chunk_size=" << chunk_size); + } } + LOG_DEBUG("Deserialized " << moe_experts->_compiled_models.size() << " MoE expert models"); } - - LOG_DEBUG("Deserialized " << moe_experts->_compiled_models.size() << " MoE expert models"); } - // Deserialize MoE experts downstream - read(stream, moe_experts_downstream); + stream & moe_experts_downstream; if (moe_experts_downstream.has_value()) { bool has_model = false; - read(stream, has_model); - + if (stream.output()) { + has_model = moe_experts_downstream.value()._compiled_model != nullptr; + } + stream & has_model; if (has_model) { - std::string model_str; - read(stream, model_str); - std::stringstream ss(model_str); - auto compiled_model = - submodel_ctx.plugin->get_core()->import_model(ss, submodel_ctx.device, submodel_ctx.import_config); - moe_experts_downstream->_compiled_model = compiled_model; - LOG_DEBUG("Imported MoE downstream compiled model"); + if (stream.output()) { + std::stringstream ss; + moe_experts_downstream.value()._compiled_model->export_model(ss); + std::string model_str = ss.str(); + stream & model_str; + } else { + NPUW_ASSERT(submodel_ctx != nullptr); + std::string model_str; + stream & model_str; + std::stringstream ss(model_str); + moe_experts_downstream->_compiled_model = + submodel_ctx->plugin->get_core()->import_model(ss, + submodel_ctx->device, + submodel_ctx->import_config); + LOG_DEBUG("Imported MoE downstream compiled model"); + } } } - // Deserialize host flash attention - read(stream, host_flash_attention); + stream & host_flash_attention; if (host_flash_attention.has_value()) { bool has_compiled_model = false; - read(stream, has_compiled_model); + if (stream.output()) { + has_compiled_model = host_flash_attention.value()._compiled_tile_model != nullptr; + } + stream & has_compiled_model; if (has_compiled_model) { - std::string model_str; - read(stream, model_str); - std::stringstream ss(model_str); - host_flash_attention->_compiled_tile_model = - submodel_ctx.plugin->get_core()->import_model(ss, submodel_ctx.device, submodel_ctx.import_config); - LOG_DEBUG("Imported compiled tile model for host flash attention"); + if (stream.output()) { + std::stringstream ss; + host_flash_attention.value()._compiled_tile_model->export_model(ss); + std::string model_str = ss.str(); + stream & model_str; + } else { + NPUW_ASSERT(submodel_ctx != nullptr); + std::string model_str; + stream & model_str; + std::stringstream ss(model_str); + host_flash_attention->_compiled_tile_model = + submodel_ctx->plugin->get_core()->import_model(ss, + submodel_ctx->device, + submodel_ctx->import_config); + LOG_DEBUG("Imported compiled tile model for host flash attention"); + } + } + if (stream.input()) { + NPUW_ASSERT(submodel_ctx != nullptr); + host_flash_attention->_compiled_final_tile_model = submodel_ctx->compiled_model; + LOG_DEBUG("Set compiled final tile model reference for host flash attention"); } - - // Set reference to the final tile model (which is the main compiled_model for HFA) - host_flash_attention->_compiled_final_tile_model = submodel_ctx.compiled_model; - LOG_DEBUG("Set compiled final tile model reference for host flash attention"); } auto& closure_desc = closure.get(); - read(stream, closure_desc.is_remote); - read(stream, closure_desc.closure_uid); + stream & closure_desc.is_remote & closure_desc.closure_uid; - if (ctx.weights || !ctx.consts_cache.empty()) { - read_weightless(stream, scales, ctx); - read_weightless(stream, zerops, ctx); - - std::size_t closure_size = 0; - read(stream, closure_size); - closure_desc.closure.resize(closure_size); - lazy_closure.resize(closure_size); - - std::vector cpu_closure_ids; - read(stream, cpu_closure_ids); + if (ctx.is_weightless) { + serialize_weightless(stream, scales, ctx); + serialize_weightless(stream, zerops, ctx); + std::size_t closure_size = closure_desc.closure.size(); + stream & closure_size; std::vector cpu_closures; - read_weightless(stream, cpu_closures, ctx); - std::size_t tidx = 0; - for (const auto& idx : cpu_closure_ids) { - closure_desc.closure[idx] = std::move(cpu_closures[tidx++]); - } - - std::vector non_cpu_tensors_ids; - read(stream, non_cpu_tensors_ids); - + std::vector cpu_closure_ids; std::vector non_cpu_tensors; - read(stream, non_cpu_tensors); - std::size_t ltidx = 0; - for (const auto& idx : non_cpu_tensors_ids) { - lazy_closure[idx] = std::move(non_cpu_tensors[ltidx++]); - } - - // Also read weights into LazyTensors - for (std::size_t cidx = 0; cidx < closure_desc.closure.size(); ++cidx) { - if (closure_desc.closure_uid[cidx] != -1 && - lazy_closure[cidx]) { // previously registered before serialization - lazy_closure[cidx].read_weight(ctx); + std::vector non_cpu_tensors_ids; + if (stream.output()) { + for (std::size_t cidx = 0; cidx < closure_desc.closure.size(); ++cidx) { + if (closure_desc.closure_uid[cidx] == -1) { + cpu_closure_ids.push_back(cidx); + cpu_closures.push_back(closure_desc.closure[cidx]); + } else { + non_cpu_tensors_ids.push_back(cidx); + non_cpu_tensors.push_back(lazy_closure[cidx]); + } + } + stream & cpu_closure_ids; + serialize_weightless(stream, cpu_closures, ctx); + stream & non_cpu_tensors_ids & non_cpu_tensors; + } else { + closure_desc.closure.resize(closure_size); + lazy_closure.resize(closure_size); + stream & cpu_closure_ids; + serialize_weightless(stream, cpu_closures, ctx); + std::size_t tidx = 0; + for (const auto& idx : cpu_closure_ids) { + closure_desc.closure[idx] = std::move(cpu_closures[tidx++]); + } + stream & non_cpu_tensors_ids & non_cpu_tensors; + std::size_t ltidx = 0; + for (const auto& idx : non_cpu_tensors_ids) { + lazy_closure[idx] = std::move(non_cpu_tensors[ltidx++]); + } + for (std::size_t cidx = 0; cidx < closure_desc.closure.size(); ++cidx) { + if (closure_desc.closure_uid[cidx] != -1 && lazy_closure[cidx]) { + lazy_closure[cidx].read_weight(ctx); + } } } } else { - read(stream, scales); - read(stream, zerops); + stream & scales & zerops; - std::size_t closure_size = 0; - read(stream, closure_size); + std::size_t closure_size = closure_desc.closure.size(); + stream & closure_size; std::vector cpu_closure_ids; - read(stream, cpu_closure_ids); - closure_desc.closure.resize(closure_size); - for (const auto& cidx : cpu_closure_ids) { - read(stream, closure_desc.closure[cidx]); + if (stream.output()) { + std::vector cpu_closures; + for (std::size_t cidx = 0; cidx < closure_desc.closure.size(); ++cidx) { + if (closure_desc.closure_uid[cidx] == -1) { + cpu_closure_ids.push_back(cidx); + cpu_closures.push_back(closure_desc.closure[cidx]); + } + } + stream & cpu_closure_ids; + for (auto& tensor : cpu_closures) { + stream & tensor; + } + } else { + stream & cpu_closure_ids; + closure_desc.closure.resize(closure_size); + for (const auto& cidx : cpu_closure_ids) { + stream & closure_desc.closure[cidx]; + } } } @@ -1046,7 +977,7 @@ ov::npuw::CompiledModel::~CompiledModel() { } } -void ov::npuw::CompiledModel::export_model(std::ostream& stream) const { +void ov::npuw::CompiledModel::export_model(std::ostream& raw_stream) const { using namespace ov::npuw::s11n; // Identify encryption flow @@ -1067,28 +998,28 @@ void ov::npuw::CompiledModel::export_model(std::ostream& stream) const { // Write header regardless of encryption requirement - to identify NPUW serializated blobs // Serialize magic number first - write(stream, NPUW_SERIALIZATION_INDICATOR); + write(raw_stream, NPUW_SERIALIZATION_INDICATOR); // Serilize CompiledModel identifier - write(stream, NPUW_COMPILED_MODEL_INDICATOR); + write(raw_stream, NPUW_COMPILED_MODEL_INDICATOR); // Serialize general meta info - write(stream, OPENVINO_VERSION_MAJOR); - write(stream, OPENVINO_VERSION_MINOR); - write(stream, OPENVINO_VERSION_PATCH); - write(stream, std::string(NPUW_SERIALIZATION_VERSION)); + write(raw_stream, OPENVINO_VERSION_MAJOR); + write(raw_stream, OPENVINO_VERSION_MINOR); + write(raw_stream, OPENVINO_VERSION_PATCH); + write(raw_stream, std::string(NPUW_SERIALIZATION_VERSION)); // Serialize encrypted flag - write(stream, encryption_required); + write(raw_stream, encryption_required); // Write flow identifier - write(stream, is_weightless); + write(raw_stream, is_weightless); if (!encryption_required) { CompiledContext ctx(false, nullptr, nullptr, m_bf16_consts); - serialize(stream, ctx); + serialize(raw_stream, ctx); - write(stream, m_weights_bank->get_name()); + auto stream = Stream::writer(raw_stream); + auto bank_name = m_weights_bank->get_name(); + stream & bank_name; if (!is_weightless) { - // Serialize weights bank - // Note: no need to encrypt weights in full flow - m_weights_bank->serialize(stream); + ov::npuw::s11n::serialize(stream, *m_weights_bank); } return; } @@ -1096,22 +1027,22 @@ void ov::npuw::CompiledModel::export_model(std::ostream& stream) const { // In case of weightless flow the whole blob will be encrypted on NPUW side. std::stringstream non_encrypted_stream; if (is_weightless) { - non_encrypted_stream.copyfmt(stream); + non_encrypted_stream.copyfmt(raw_stream); CompiledContext ctx(false, nullptr, nullptr, m_bf16_consts); serialize(non_encrypted_stream, ctx); std::string encrypted = enc_callbacks.encrypt(non_encrypted_stream.str()); - write(stream, encrypted); + write(raw_stream, encrypted); } else { // In case of blob with weights only encrypt XML part of the model CompiledContext ctx(true, enc_callbacks.encrypt, nullptr, m_bf16_consts); - serialize(stream, ctx); + serialize(raw_stream, ctx); } - write(stream, m_weights_bank->get_name()); + auto stream = Stream::writer(raw_stream); + auto bank_name = m_weights_bank->get_name(); + stream & bank_name; if (!is_weightless) { - // Serialize weights bank - // Note: no need to encrypt weights in full flow - m_weights_bank->serialize(stream); + ov::npuw::s11n::serialize(stream, *m_weights_bank); } } @@ -1170,16 +1101,16 @@ std::shared_ptr ov::npuw::CompiledModel::import_model( auto read_and_finalize_bank = [&](std::istream& model_stream, const std::shared_ptr& compiled) { - // Deserialize weights bank name + auto stream = Stream::reader(model_stream); std::string bank_name; - read(model_stream, bank_name); + stream & bank_name; if (is_weightless) { compiled->m_weights_bank = ov::npuw::weights::bank(bank_name, compiled->get_plugin()->get_core(), ""); compiled->finalize_weights_bank(); } else { - compiled->m_weights_bank = - ov::npuw::weights::Bank::deserialize(model_stream, compiled->get_plugin()->get_core(), bank_name); + compiled->m_weights_bank = ov::npuw::weights::bank(bank_name, compiled->get_plugin()->get_core(), ""); + ov::npuw::s11n::serialize(stream, *compiled->m_weights_bank); compiled->reconstruct_closure(); } }; @@ -1229,73 +1160,76 @@ void ov::npuw::CompiledModel::serialize(std::ostream& stream, const ov::npuw::s1 using namespace ov::npuw::s11n; auto write_model = [&](std::ostream& model_stream) { + auto stream = Stream::writer(model_stream); // Serialize name - write(model_stream, m_name); + stream & m_name; // Serialize inputs and outputs - write(model_stream, inputs()); - write(model_stream, outputs()); + stream& inputs() & outputs(); // Serialize meta - write(model_stream, m_inputs_to_submodels_inputs); - write(model_stream, m_outputs_to_submodels_outputs); - write(model_stream, m_param_subscribers); - write(model_stream, m_submodels_input_to_prev_output); + stream & m_inputs_to_submodels_inputs & m_outputs_to_submodels_outputs & m_param_subscribers & + m_submodels_input_to_prev_output; // Write device list - write(model_stream, m_dev_list); + stream & m_dev_list; // Write config - write(model_stream, m_cfg); + stream & m_cfg; // FIXME: utilize overload instead - write(model_stream, m_non_npuw_props.size()); + auto props_size = m_non_npuw_props.size(); + stream & props_size; for (const auto& p : m_non_npuw_props) { // Skip properties which don't need to/can't be serialized // FIXME: extend the logic if (p.first == ov::cache_encryption_callbacks.name()) { - write(model_stream, false); + bool should_write = false; + stream & should_write; continue; } - write(model_stream, true); - write(model_stream, p.first); + bool should_write = true; + stream & should_write & p.first; write_any(model_stream, p.second); } // Write flow identifier bool is_weightless = should_use_weightless_flow(m_non_npuw_props, m_cfg, m_const_to_offset); - write(model_stream, is_weightless); + stream & is_weightless; // Write bf16 consts cache - write(model_stream, enc_ctx.bf16_consts); + stream & enc_ctx.bf16_consts; // Create weightless context WeightsContext ctx(is_weightless, m_const_to_offset); // Serialize compiled submodels - write(model_stream, m_compiled_submodels.size()); + auto submodel_count = m_compiled_submodels.size(); + stream & submodel_count; for (std::size_t i = 0; i < m_compiled_submodels.size(); ++i) { - auto& subm = m_compiled_submodels[i]; + auto& subm = const_cast(m_compiled_submodels[i]); auto real_idx = subm.replaced_by.value_or(i); // Write device idx // FIXME: if there is no compiled submodel, device_it is not set. auto dev_idx = [&]() { - auto it = std::find(m_dev_list.begin(), m_dev_list.end(), submodel_device(real_idx)); - NPUW_ASSERT(it != m_dev_list.end()); - return it - m_dev_list.begin(); + return find_device_index(m_dev_list, submodel_device(real_idx)); }; - write(model_stream, real_idx == i ? dev_idx() : 0); + auto device_index = real_idx == i ? dev_idx() : 0; + stream & device_index; // Write ICompiledModel if it's there if (subm.compiled_model) { - write(model_stream, true); + bool has_compiled_model = true; + stream & has_compiled_model; // FIXME: workaround for import/export model since import model seem to reset the file pointer std::stringstream ss; subm.compiled_model->export_model(ss); - write(model_stream, ss.str()); + auto exported_model = ss.str(); + stream & exported_model; } else { - write(model_stream, false); + bool has_compiled_model = false; + stream & has_compiled_model; } // Write the rest of the submodel desc - subm.serialize(model_stream, ctx); + subm.serialize(stream, ctx); } }; @@ -1324,17 +1258,17 @@ std::shared_ptr ov::npuw::CompiledModel::deserialize( using namespace ov::npuw::s11n; auto read_model = [&](std::istream& model_stream) { + auto stream = Stream::reader(model_stream); // Deserialize model name first std::string model_name; - read(stream, model_name); + stream & model_name; // Create a dummy CompiledModel with an empty ov::Model - this will skip the constructor flow // to continue deserialization ov::ParameterVector parameters; ov::NodeVector results; - read(stream, parameters); - read(stream, results); + stream & parameters & results; auto ov_model = std::make_shared(ov::as_output_vector(results), parameters, model_name); @@ -1342,42 +1276,40 @@ std::shared_ptr ov::npuw::CompiledModel::deserialize( // Deserialize meta compiled->m_name = model_name; - read(stream, compiled->m_inputs_to_submodels_inputs); - read(stream, compiled->m_outputs_to_submodels_outputs); - read(stream, compiled->m_param_subscribers); - read(stream, compiled->m_submodels_input_to_prev_output); + stream & compiled->m_inputs_to_submodels_inputs & compiled->m_outputs_to_submodels_outputs & + compiled->m_param_subscribers & compiled->m_submodels_input_to_prev_output; // Deserialize device list - read(stream, compiled->m_dev_list); + stream & compiled->m_dev_list; // Deserialize config - read(stream, compiled->m_cfg); + stream & compiled->m_cfg; compiled->m_cfg.parseEnvVars(); // FIXME: utilize overload instead std::size_t props_size; - read(stream, props_size); + stream & props_size; for (std::size_t i = 0; i < props_size; ++i) { bool should_read = true; - read(stream, should_read); + stream & should_read; // Skip properties which don't need to/can't be deserialized // FIXME: extend the logic if (!should_read) { continue; } std::string key; - read(stream, key); + stream & key; ov::Any val; - read_any(stream, val); + read_any(model_stream, val); compiled->m_non_npuw_props[key] = std::move(val); } compiled->implement_properties(); // Read flow identifier bool is_weightless = false; - read(stream, is_weightless); + stream & is_weightless; // Read bf16 consts cache - read(stream, compiled->m_bf16_consts); + stream & compiled->m_bf16_consts; // Initialize weights stream if weightless flow std::string weights_path; @@ -1453,14 +1385,14 @@ std::shared_ptr ov::npuw::CompiledModel::deserialize( // Deserialize compiled submodels std::size_t subm_size = 0; - read(stream, subm_size); + stream & subm_size; compiled->m_compiled_submodels.resize(subm_size); for (std::size_t i = 0; i < subm_size; ++i) { std::size_t device_idx = 0; - read(stream, device_idx); + stream & device_idx; bool has_compiled_model = false; - read(stream, has_compiled_model); + stream & has_compiled_model; // Build import config for NPU device ov::AnyMap import_config; @@ -1476,7 +1408,7 @@ std::shared_ptr ov::npuw::CompiledModel::deserialize( // Import model from the plugin // FIXME: workaround for import/export model since import model seems to reset the file pointer std::string buf; - read(stream, buf); + stream & buf; std::stringstream buffer(buf); compiled->m_compiled_submodels[i].compiled_model = plugin->get_core()->import_model(buffer, device, import_config); @@ -1488,7 +1420,7 @@ std::shared_ptr ov::npuw::CompiledModel::deserialize( device, compiled->m_compiled_submodels[i].compiled_model, import_config); - compiled->m_compiled_submodels[i].deserialize(stream, compiled->m_import_weights_ctx, submodel_ctx); + compiled->m_compiled_submodels[i].serialize(stream, compiled->m_import_weights_ctx, &submodel_ctx); } compiled->implement_properties(); @@ -1839,6 +1771,26 @@ bool ov::npuw::CompiledModel::compile_for_success(std::size_t id, const std::vec auto make_wrapped = [&](const std::shared_ptr& model, const std::string& profile_suffix, const std::vector& devs) -> ov::SoPtr { + if (!m_cfg.get<::intel_npu::NPUW_FALLBACK_EXEC>()) { + std::exception_ptr last_failure; + auto factory = make_factory(model, profile_suffix); + for (const auto& device : devs) { + try { + auto compiled = factory(device); + OPENVINO_ASSERT(compiled._ptr != nullptr, + "Failsafe factory returned null compiled model for device ", + device); + return compiled; + } catch (...) { + last_failure = std::current_exception(); + } + } + if (last_failure) { + std::rethrow_exception(last_failure); + } + OPENVINO_THROW("No candidate devices available for compilation"); + } + return ov::npuw::failsafe::CompiledModel::create(model, get_plugin(), devs, diff --git a/src/plugins/intel_npu/src/plugin/npuw/compiled_model.hpp b/src/plugins/intel_npu/src/plugin/npuw/compiled_model.hpp index 3ec230f52079b3..3a2f042f5ccfa5 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/compiled_model.hpp +++ b/src/plugins/intel_npu/src/plugin/npuw/compiled_model.hpp @@ -286,10 +286,9 @@ class CompiledModel : public ov::npuw::ICompiledModel_v0 { // Metrics execution_stats stat; - void serialize(std::ostream& stream, const ov::npuw::s11n::WeightsContext& ctx) const; - void deserialize(std::istream& stream, - const ov::npuw::s11n::WeightsContext& ctx, - const ov::npuw::s11n::SubmodelDeserializeCtx& submodel_ctx); + void serialize(ov::npuw::s11n::Stream& stream, + const ov::npuw::s11n::WeightsContext& ctx, + const ov::npuw::s11n::SubmodelDeserializeCtx* submodel_ctx = nullptr); }; std::vector m_compiled_submodels; diff --git a/src/plugins/intel_npu/src/plugin/npuw/lazy_tensor.cpp b/src/plugins/intel_npu/src/plugin/npuw/lazy_tensor.cpp index cd59bfe04b720b..502ce66e5899a8 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/lazy_tensor.cpp +++ b/src/plugins/intel_npu/src/plugin/npuw/lazy_tensor.cpp @@ -144,51 +144,6 @@ void Const::detach() { m_mmaped_weights.reset(); } -void Const::serialize(std::ostream& stream) const { - using namespace ov::npuw::s11n; - write(stream, m_cached_type.to_string()); - write(stream, m_cached_shape); - write(stream, m_offset); - write(stream, m_byte_size); - - // FIXME: handle a special case: - // 1) We added a Constant to the model before compilation (e.g. int RoPE patterns) - // 2) This Constant became a parameter during folding - // 3) Thus it became a LazyTensor, but there is no original data in weights file, - // so it will fail in read_weight() during weightless deserialization. - // In this case we need to include Constant's data into the blob. - if (m_copied_if_not_in_model) { - LOG_WARN("Some pattern introduced a new Constant node not present in the original weights file. This will " - "increase the blob size by " - << m_byte_size << " bytes."); - write(stream, true); - write(stream, m_copied_if_not_in_model); - // detach the tensor - m_copied_if_not_in_model = ov::Tensor(); - } else { - write(stream, false); - } -} - -Const Const::deserialize(std::istream& stream) { - using namespace ov::npuw::s11n; - Const c; - std::string type_str; - read(stream, type_str); - c.m_cached_type = ov::element::Type(type_str); - read(stream, c.m_cached_shape); - read(stream, c.m_offset); - read(stream, c.m_byte_size); - - bool contains_weight = false; - read(stream, contains_weight); - if (contains_weight) { - read(stream, c.m_read_from_bin); - } - - return c; -} - std::size_t Concat::hash() const { std::size_t seed = std::hash()(axis) + 0x9e3779b9; for (auto& lt : tensors) { @@ -230,20 +185,6 @@ void Concat::detach() { } } -void Concat::serialize(std::ostream& stream) const { - using namespace ov::npuw::s11n; - write(stream, axis); - write(stream, tensors); -} - -Concat Concat::deserialize(std::istream& stream) { - using namespace ov::npuw::s11n; - Concat c; - read(stream, c.axis); - read(stream, c.tensors); - return c; -} - std::size_t Unpack::hash() const { std::size_t seed = w.get_hash() + 0x9e3779b9; seed ^= z.get_hash() + 0x9e3779b9; @@ -294,28 +235,6 @@ void Unpack::detach() { s.detach(); } -void Unpack::serialize(std::ostream& stream) const { - using namespace ov::npuw::s11n; - write(stream, type.to_string()); - write(stream, shape); - write(stream, w); - write(stream, z); - write(stream, s); -} - -Unpack Unpack::deserialize(std::istream& stream) { - using namespace ov::npuw::s11n; - Unpack u; - std::string type_str; - read(stream, type_str); - u.type = ov::element::Type(type_str); - read(stream, u.shape); - read(stream, u.w); - read(stream, u.z); - read(stream, u.s); - return u; -} - std::size_t Permute::hash() const { std::size_t seed = tensor.get_hash() + 0x9e3779b9; for (const auto& axis : axes) { @@ -350,20 +269,6 @@ void Permute::detach() { tensor.detach(); } -void Permute::serialize(std::ostream& stream) const { - using namespace ov::npuw::s11n; - write(stream, axes); - write(stream, tensor); -} - -Permute Permute::deserialize(std::istream& stream) { - using namespace ov::npuw::s11n; - Permute p; - read(stream, p.axes); - read(stream, p.tensor); - return p; -} - std::size_t Convert::hash() const { std::size_t seed = type.hash() + 0x9e3779b9; seed ^= tensor.get_hash() + 0x9e3779b9; @@ -391,22 +296,6 @@ void Convert::detach() { tensor.detach(); } -void Convert::serialize(std::ostream& stream) const { - using namespace ov::npuw::s11n; - write(stream, type.to_string()); - write(stream, tensor); -} - -Convert Convert::deserialize(std::istream& stream) { - using namespace ov::npuw::s11n; - Convert c; - std::string type_str; - read(stream, type_str); - c.type = ov::element::Type(type_str); - read(stream, c.tensor); - return c; -} - std::size_t Gather::hash() const { std::size_t seed = w.get_hash() + 0x9e3779b9; seed ^= t.get_element_type().hash() + 0x9e3779b9; @@ -466,25 +355,6 @@ void Gather::detach() { w.detach(); } -void Gather::serialize(std::ostream& stream) const { - using namespace ov::npuw::s11n; - write(stream, dst_type.to_string()); - write(stream, dst_shape); - write(stream, w); - write(stream, t); -} - -Gather Gather::deserialize(std::istream& stream) { - using namespace ov::npuw::s11n; - Gather g; - std::string type_str; - read(stream, type_str); - g.dst_type = ov::element::Type(type_str); - read(stream, g.dst_shape); - read(stream, g.w); - read(stream, g.t); - return g; -} } // namespace op enum class TransformType : int { CONST = 0, CONCAT, UNPACK, PERMUTE, CONVERT, GATHER }; @@ -501,9 +371,8 @@ struct LazyTensorImpl { void detach(); - void serialize(std::ostream& stream) const; - static std::shared_ptr deserialize(std::istream& stream); void read_weight(const ov::npuw::s11n::WeightsContext& ctx); + void serialize(ov::npuw::s11n::Stream& stream); LazyTensor::Transform m_transform; std::size_t m_hash = 0; @@ -524,6 +393,180 @@ struct overloaded : Ts... { template overloaded(Ts...) -> overloaded; +namespace { + +ov::npuw::weights::TransformType get_transform_type(const ov::npuw::weights::op::Const&) { + return ov::npuw::weights::TransformType::CONST; +} + +ov::npuw::weights::TransformType get_transform_type(const ov::npuw::weights::op::Concat&) { + return ov::npuw::weights::TransformType::CONCAT; +} + +ov::npuw::weights::TransformType get_transform_type(const ov::npuw::weights::op::Unpack&) { + return ov::npuw::weights::TransformType::UNPACK; +} + +ov::npuw::weights::TransformType get_transform_type(const ov::npuw::weights::op::Permute&) { + return ov::npuw::weights::TransformType::PERMUTE; +} + +ov::npuw::weights::TransformType get_transform_type(const ov::npuw::weights::op::Convert&) { + return ov::npuw::weights::TransformType::CONVERT; +} + +ov::npuw::weights::TransformType get_transform_type(const ov::npuw::weights::op::Gather&) { + return ov::npuw::weights::TransformType::GATHER; +} + +} // namespace + +namespace ov { +namespace npuw { +namespace weights { +namespace op { + +void Const::serialize(ov::npuw::s11n::Stream& stream) { + std::string type_str; + if (stream.output()) { + type_str = m_cached_type.to_string(); + } + stream & type_str & m_cached_shape & m_offset & m_byte_size; + if (stream.input()) { + m_cached_type = ov::element::Type(type_str); + } + + bool contains_weight = static_cast(m_copied_if_not_in_model); + stream & contains_weight; + if (contains_weight) { + if (stream.output()) { + stream & m_copied_if_not_in_model; + m_copied_if_not_in_model = ov::Tensor(); + } else { + stream & m_read_from_bin; + } + } +} + +void Concat::serialize(ov::npuw::s11n::Stream& stream) { + stream & axis & tensors; +} + +void Unpack::serialize(ov::npuw::s11n::Stream& stream) { + std::string type_str; + if (stream.output()) { + type_str = type.to_string(); + } + stream & type_str & shape & w & z & s; + if (stream.input()) { + type = ov::element::Type(type_str); + } +} + +void Permute::serialize(ov::npuw::s11n::Stream& stream) { + stream & axes & tensor; +} + +void Convert::serialize(ov::npuw::s11n::Stream& stream) { + std::string type_str; + if (stream.output()) { + type_str = type.to_string(); + } + stream & type_str & tensor; + if (stream.input()) { + type = ov::element::Type(type_str); + } +} + +void Gather::serialize(ov::npuw::s11n::Stream& stream) { + std::string type_str; + if (stream.output()) { + type_str = dst_type.to_string(); + } + stream & type_str & dst_shape & w & t; + if (stream.input()) { + dst_type = ov::element::Type(type_str); + } +} + +} // namespace op + +void LazyTensorImpl::serialize(ov::npuw::s11n::Stream& stream) { + stream & m_hash; + + int op_type = 0; + if (stream.output()) { + std::visit( + [&](auto& op) { + op_type = static_cast(get_transform_type(op)); + stream & op_type; + op.serialize(stream); + }, + m_transform); + return; + } + + stream & op_type; + switch (TransformType(op_type)) { + case TransformType::CONCAT: + m_transform.emplace(); + std::get(m_transform).serialize(stream); + break; + case TransformType::CONST: + m_transform.emplace(); + std::get(m_transform).serialize(stream); + break; + case TransformType::CONVERT: + m_transform.emplace(); + std::get(m_transform).serialize(stream); + break; + case TransformType::PERMUTE: + m_transform.emplace(); + std::get(m_transform).serialize(stream); + break; + case TransformType::UNPACK: + m_transform.emplace(); + std::get(m_transform).serialize(stream); + break; + case TransformType::GATHER: + m_transform.emplace(); + std::get(m_transform).serialize(stream); + break; + default: + NPUW_ASSERT(false && "Unsupported type"); + break; + } +} + +void LazyTensor::serialize(ov::npuw::s11n::Stream& stream) { + bool is_initialized = static_cast(m_impl); + stream & is_initialized; + if (!is_initialized) { + if (stream.input()) { + m_impl.reset(); + } + return; + } + + if (stream.output()) { + m_impl->serialize(stream); + } else { + m_impl = std::make_shared(); + m_impl->serialize(stream); + } +} + +} // namespace weights + +namespace s11n { +void serialize(ov::npuw::s11n::Stream& stream, ov::npuw::weights::LazyTensor& var) { + var.serialize(stream); +} +} // namespace s11n + +} // namespace npuw +} // namespace ov + LazyTensorImpl::LazyTensorImpl(LazyTensor::Transform&& t) : m_transform(std::move(t)), m_hash(std::visit(overloaded{[](const auto& op) { @@ -612,71 +655,6 @@ void LazyTensorImpl::detach() { m_transform); } -void LazyTensorImpl::serialize(std::ostream& stream) const { - using namespace ov::npuw::s11n; - write(stream, m_hash); - // FIXME: create proper op identificators instead of int - std::visit(overloaded{ - [&stream](const op::Concat& op) { - write(stream, static_cast(TransformType::CONCAT)); - op.serialize(stream); - }, - [&stream](const op::Const& op) { - write(stream, static_cast(TransformType::CONST)); - op.serialize(stream); - }, - [&stream](const op::Convert& op) { - write(stream, static_cast(TransformType::CONVERT)); - op.serialize(stream); - }, - [&stream](const op::Permute& op) { - write(stream, static_cast(TransformType::PERMUTE)); - op.serialize(stream); - }, - [&stream](const op::Unpack& op) { - write(stream, static_cast(TransformType::UNPACK)); - op.serialize(stream); - }, - [&stream](const op::Gather& op) { - write(stream, static_cast(TransformType::GATHER)); - op.serialize(stream); - }, - }, - m_transform); -} - -std::shared_ptr LazyTensorImpl::deserialize(std::istream& stream) { - using namespace ov::npuw::s11n; - auto lt_impl = std::make_shared(); - read(stream, lt_impl->m_hash); - int op_type; - read(stream, op_type); - switch (TransformType(op_type)) { - case TransformType::CONCAT: - lt_impl->m_transform = op::Concat::deserialize(stream); - break; - case TransformType::CONST: - lt_impl->m_transform = op::Const::deserialize(stream); - break; - case TransformType::CONVERT: - lt_impl->m_transform = op::Convert::deserialize(stream); - break; - case TransformType::PERMUTE: - lt_impl->m_transform = op::Permute::deserialize(stream); - break; - case TransformType::UNPACK: - lt_impl->m_transform = op::Unpack::deserialize(stream); - break; - case TransformType::GATHER: - lt_impl->m_transform = op::Gather::deserialize(stream); - break; - default: - NPUW_ASSERT(false && "Unsupported type"); - break; - } - return lt_impl; -} - LazyTensor::LazyTensor(const std::shared_ptr& const_ptr) : m_impl(std::make_shared(op::Const(const_ptr))) {} LazyTensor::LazyTensor(const std::vector& to_concat, const std::size_t axis) @@ -765,28 +743,6 @@ void LazyTensor::detach() { } } -void LazyTensor::serialize(std::ostream& stream) const { - using namespace ov::npuw::s11n; - if (!m_impl) { - write(stream, false); - return; - } - write(stream, true); - m_impl->serialize(stream); -} - -LazyTensor LazyTensor::deserialize(std::istream& stream) { - using namespace ov::npuw::s11n; - bool is_initialized; - read(stream, is_initialized); - LazyTensor lt; - if (!is_initialized) { - return lt; - } - lt.m_impl = LazyTensorImpl::deserialize(stream); - return lt; -} - std::size_t LazyTensor::Hash::operator()(const LazyTensor& lt) const { return lt.get_hash(); } diff --git a/src/plugins/intel_npu/src/plugin/npuw/lazy_tensor.hpp b/src/plugins/intel_npu/src/plugin/npuw/lazy_tensor.hpp index 66ab1bef45bf58..e7b71cfc2cdafc 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/lazy_tensor.hpp +++ b/src/plugins/intel_npu/src/plugin/npuw/lazy_tensor.hpp @@ -73,12 +73,12 @@ class LazyTensor { }; Meta eval_meta() const; - void serialize(std::ostream& stream) const; - static LazyTensor deserialize(std::istream& stream); void read_weight(const ov::npuw::s11n::WeightsContext& ctx); operator bool() const; private: + friend void ov::npuw::s11n::serialize(ov::npuw::s11n::Stream& stream, LazyTensor& var); + void serialize(ov::npuw::s11n::Stream& stream); std::shared_ptr m_impl = nullptr; }; @@ -96,10 +96,9 @@ class Const { LazyTensor::Meta eval_meta() const; void read_weight(const ov::npuw::s11n::WeightsContext& ctx); void detach(); - void serialize(std::ostream& stream) const; - static Const deserialize(std::istream& stream); private: + void serialize(ov::npuw::s11n::Stream& stream); std::shared_ptr m_node = nullptr; ov::element::Type m_cached_type; ov::Shape m_cached_shape; @@ -129,10 +128,9 @@ class Concat { LazyTensor::Meta eval_meta() const; void read_weight(const ov::npuw::s11n::WeightsContext& ctx); void detach(); - void serialize(std::ostream& stream) const; - static Concat deserialize(std::istream& stream); private: + void serialize(ov::npuw::s11n::Stream& stream); std::vector tensors; std::size_t axis = 0; }; @@ -155,10 +153,9 @@ class Unpack { LazyTensor::Meta eval_meta() const; void read_weight(const ov::npuw::s11n::WeightsContext& ctx); void detach(); - void serialize(std::ostream& stream) const; - static Unpack deserialize(std::istream& stream); private: + void serialize(ov::npuw::s11n::Stream& stream); LazyTensor w, z, s; ov::element::Type type; ov::Shape shape; @@ -177,10 +174,9 @@ class Permute { LazyTensor::Meta eval_meta() const; void read_weight(const ov::npuw::s11n::WeightsContext& ctx); void detach(); - void serialize(std::ostream& stream) const; - static Permute deserialize(std::istream& stream); private: + void serialize(ov::npuw::s11n::Stream& stream); LazyTensor tensor; std::vector axes; }; @@ -198,10 +194,9 @@ class Convert { LazyTensor::Meta eval_meta() const; void read_weight(const ov::npuw::s11n::WeightsContext& ctx); void detach(); - void serialize(std::ostream& stream) const; - static Convert deserialize(std::istream& stream); private: + void serialize(ov::npuw::s11n::Stream& stream); LazyTensor tensor; ov::element::Type type; }; @@ -223,10 +218,9 @@ class Gather { LazyTensor::Meta eval_meta() const; void read_weight(const ov::npuw::s11n::WeightsContext& ctx); void detach(); - void serialize(std::ostream& stream) const; - static Gather deserialize(std::istream& stream); private: + void serialize(ov::npuw::s11n::Stream& stream); LazyTensor w; ov::Tensor t; ov::element::Type dst_type; diff --git a/src/plugins/intel_npu/src/plugin/npuw/llm_compiled_model.cpp b/src/plugins/intel_npu/src/plugin/npuw/llm_compiled_model.cpp index 92ef105aea2f56..d819a653042980 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/llm_compiled_model.cpp +++ b/src/plugins/intel_npu/src/plugin/npuw/llm_compiled_model.cpp @@ -1143,7 +1143,7 @@ void ov::npuw::LLMCompiledModel::export_model(std::ostream& stream) const { } } -void ov::npuw::LLMCompiledModel::serialize(std::ostream& stream, const ov::npuw::s11n::CompiledContext& ctx) const { +void ov::npuw::LLMCompiledModel::serialize(std::ostream& raw_stream, const ov::npuw::s11n::CompiledContext& ctx) const { LOG_INFO("Serializing LLMCompiledModel..."); LOG_BLOCK(); @@ -1158,38 +1158,26 @@ void ov::npuw::LLMCompiledModel::serialize(std::ostream& stream, const ov::npuw: } auto write_model_meta = [&](std::ostream& model_stream) { + auto stream = Stream::writer(model_stream); // Serialize name - write(model_stream, m_name); + stream & m_name; // Serialize inputs and outputs - write(model_stream, inputs()); - write(model_stream, outputs()); + stream& inputs() & outputs(); // Serialize LLMCompiledModel-specific data - write(model_stream, m_kvcache_desc.max_prompt_size); - write(model_stream, m_kvcache_desc.total_size); - write(model_stream, m_kvcache_desc.num_stored_tokens); - write(model_stream, m_kvcache_desc.dim); - write(model_stream, m_kvcache_desc.max_generation_token_len); - write(model_stream, m_kvcache_desc.v_tensors_transposed_pre); - write(model_stream, m_kvcache_desc.v_tensors_transposed_gen); - write(model_stream, m_prefill_chunk_size); - write(model_stream, m_use_chunk_prefill); - write(model_stream, m_max_lora_rank); - write(model_stream, m_enable_prefix_caching); - write(model_stream, m_prefix_caching_block_size); - write(model_stream, m_prefix_caching_max_num_blocks); - write(model_stream, m_is_whisper); - write(model_stream, m_eos_token_id); - write(model_stream, m_is_eagle); - write(model_stream, m_is_embedding); + stream & m_kvcache_desc.max_prompt_size & m_kvcache_desc.total_size & m_kvcache_desc.num_stored_tokens & + m_kvcache_desc.dim & m_kvcache_desc.max_generation_token_len & m_kvcache_desc.v_tensors_transposed_pre & + m_kvcache_desc.v_tensors_transposed_gen & m_prefill_chunk_size & m_use_chunk_prefill & m_max_lora_rank & + m_enable_prefix_caching & m_prefix_caching_block_size & m_prefix_caching_max_num_blocks & m_is_whisper & + m_eos_token_id & m_is_eagle & m_is_embedding; // Write config - write(model_stream, m_cfg); + stream & m_cfg; // Serialize KV cache model variants - write(model_stream, m_kvcache_sizes); - write(model_stream, static_cast(m_generate_compiled_variants.size())); + auto variant_count = static_cast(m_generate_compiled_variants.size()); + stream & m_kvcache_sizes & variant_count; // Serialize CompiledModels // Note: no need to pass any encryption here as it's done in export_model() @@ -1202,7 +1190,7 @@ void ov::npuw::LLMCompiledModel::serialize(std::ostream& stream, const ov::npuw: m_prefill_compiled->serialize(model_stream, enc_ctx); const bool is_shared_lm_head = m_lm_head_compiled != nullptr; - write(model_stream, is_shared_lm_head); + stream & is_shared_lm_head; if (is_shared_lm_head) { m_lm_head_compiled->serialize(model_stream, enc_ctx); } @@ -1211,24 +1199,24 @@ void ov::npuw::LLMCompiledModel::serialize(std::ostream& stream, const ov::npuw: std::stringstream non_encrypted_stream; if (ctx.encrypted) { NPUW_ASSERT(ctx.encrypt && "Encryption function isn't provided!"); - non_encrypted_stream.copyfmt(stream); + non_encrypted_stream.copyfmt(raw_stream); write_model_meta(non_encrypted_stream); std::string encrypted_str = ctx.encrypt(non_encrypted_stream.str()); - write(stream, encrypted_str); + write(raw_stream, encrypted_str); } else { - write_model_meta(stream); + write_model_meta(raw_stream); } // Serialize bank name const auto& kv_bank = m_kvcache_compiled->get_weights_bank(); const auto& p_bank = m_prefill_compiled->get_weights_bank(); NPUW_ASSERT(kv_bank && p_bank && kv_bank == p_bank && "Prefill and KVCache models' weight bank should be shared!"); - write(stream, kv_bank->get_name()); + auto stream = Stream::writer(raw_stream); + auto bank_name = kv_bank->get_name(); + stream & bank_name; if (!is_weightless) { - // Serialize weights bank - // Note: no need to encrypt weights in full flow - kv_bank->serialize(stream); + ov::npuw::s11n::serialize(stream, *kv_bank); } LOG_INFO("Done."); @@ -1289,9 +1277,9 @@ std::shared_ptr ov::npuw::LLMCompiledModel::import_m auto read_and_finalize_banks = [&](std::istream& model_stream, const std::shared_ptr& compiled) { - // Deserialize weights bank name + auto stream = Stream::reader(model_stream); std::string bank_name; - read(model_stream, bank_name); + stream & bank_name; if (is_weightless) { auto bank = ov::npuw::weights::bank(bank_name, compiled->get_plugin()->get_core(), ""); @@ -1309,8 +1297,8 @@ std::shared_ptr ov::npuw::LLMCompiledModel::import_m compiled->m_lm_head_compiled->finalize_weights_bank(); } } else { - auto bank = - ov::npuw::weights::Bank::deserialize(model_stream, compiled->get_plugin()->get_core(), bank_name); + auto bank = ov::npuw::weights::bank(bank_name, compiled->get_plugin()->get_core(), ""); + ov::npuw::s11n::serialize(stream, *bank); compiled->m_kvcache_compiled->set_weights_bank(bank); for (const auto& compiled_variant : compiled->m_generate_compiled_variants) { @@ -1375,49 +1363,39 @@ std::shared_ptr ov::npuw::LLMCompiledModel::deserial using namespace ov::npuw::s11n; auto read_model_meta = [&](std::istream& model_stream) { + auto stream = Stream::reader(model_stream); // Deserialize model name first std::string model_name; - read(model_stream, model_name); + stream & model_name; // Create a dummy CompiledModel with an empty ov::Model - this will skip the constructor flow // to continue deserialization ov::ParameterVector parameters; ov::NodeVector results; - read(model_stream, parameters); - read(model_stream, results); + stream & parameters & results; auto ov_model = std::make_shared(ov::as_output_vector(results), parameters, model_name); auto compiled = std::make_shared(ov_model, plugin, true); // Deserialize LLMCompiledModel-specific data - read(model_stream, compiled->m_kvcache_desc.max_prompt_size); - read(model_stream, compiled->m_kvcache_desc.total_size); - read(model_stream, compiled->m_kvcache_desc.num_stored_tokens); - read(model_stream, compiled->m_kvcache_desc.dim); - read(model_stream, compiled->m_kvcache_desc.max_generation_token_len); - read(model_stream, compiled->m_kvcache_desc.v_tensors_transposed_pre); - read(model_stream, compiled->m_kvcache_desc.v_tensors_transposed_gen); - read(model_stream, compiled->m_prefill_chunk_size); - read(model_stream, compiled->m_use_chunk_prefill); - read(model_stream, compiled->m_max_lora_rank); - read(model_stream, compiled->m_enable_prefix_caching); - read(model_stream, compiled->m_prefix_caching_block_size); - read(model_stream, compiled->m_prefix_caching_max_num_blocks); - read(model_stream, compiled->m_is_whisper); - read(model_stream, compiled->m_eos_token_id); - read(model_stream, compiled->m_is_eagle); - read(model_stream, compiled->m_is_embedding); + stream & compiled->m_kvcache_desc.max_prompt_size & compiled->m_kvcache_desc.total_size & + compiled->m_kvcache_desc.num_stored_tokens & compiled->m_kvcache_desc.dim & + compiled->m_kvcache_desc.max_generation_token_len & compiled->m_kvcache_desc.v_tensors_transposed_pre & + compiled->m_kvcache_desc.v_tensors_transposed_gen & compiled->m_prefill_chunk_size & + compiled->m_use_chunk_prefill & compiled->m_max_lora_rank & compiled->m_enable_prefix_caching & + compiled->m_prefix_caching_block_size & compiled->m_prefix_caching_max_num_blocks & compiled->m_is_whisper & + compiled->m_eos_token_id & compiled->m_is_eagle & compiled->m_is_embedding; // Deserialize config - read(model_stream, compiled->m_cfg); + stream & compiled->m_cfg; compiled->implement_properties(); // Deserialize KV cache model variants - read(model_stream, compiled->m_kvcache_sizes); + stream & compiled->m_kvcache_sizes; uint32_t num_variants = 0; - read(model_stream, num_variants); + stream & num_variants; compiled->m_generate_compiled_variants.reserve(num_variants); @@ -1438,7 +1416,7 @@ std::shared_ptr ov::npuw::LLMCompiledModel::deserial compiled->m_prefill_compiled = ov::npuw::CompiledModel::deserialize(model_stream, plugin, properties, enc_ctx); bool is_shared_lm_head = false; - read(model_stream, is_shared_lm_head); + stream & is_shared_lm_head; if (is_shared_lm_head) { compiled->m_lm_head_compiled = ov::npuw::CompiledModel::deserialize(model_stream, plugin, properties, enc_ctx); diff --git a/src/plugins/intel_npu/src/plugin/npuw/serialization.cpp b/src/plugins/intel_npu/src/plugin/npuw/serialization.cpp index e194b568394520..39cc31bfdfa42f 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/serialization.cpp +++ b/src/plugins/intel_npu/src/plugin/npuw/serialization.cpp @@ -21,6 +21,17 @@ #include "spatial.hpp" #include "util.hpp" +namespace { + +std::streamsize checked_stream_size(const std::size_t size) { + if (size > static_cast(std::numeric_limits::max())) { + OPENVINO_THROW("Blob size is too large to be represented on a std::streamsize!"); + } + return static_cast(size); +} + +} // namespace + // NOTE: This construtor should only be used when exporting blobs ov::npuw::s11n::WeightsContext::WeightsContext(bool _is_weightless, const std::unordered_map& _const_to_offset) @@ -60,539 +71,295 @@ ov::npuw::s11n::BF16Cache ov::npuw::s11n::get_bf16_consts(const std::shared_ptr< return bf16_cache; } -void ov::npuw::s11n::write(std::ostream& stream, const std::streampos& var) { - stream.write(reinterpret_cast(&var), sizeof var); +void ov::npuw::s11n::serialize(Stream& stream, std::streampos& var) { + stream.bytes(&var, sizeof var); } -void ov::npuw::s11n::write(std::ostream& stream, const std::string& var) { - auto var_size = var.size(); - stream.write(reinterpret_cast(&var_size), sizeof var_size); - stream.write(&var[0], var.size()); -} - -void ov::npuw::s11n::write(std::ostream& stream, const bool& var) { - stream.write(reinterpret_cast(&var), sizeof var); -} - -void ov::npuw::s11n::write(std::ostream& stream, const float& var) { - stream.write(reinterpret_cast(&var), sizeof var); -} - -void ov::npuw::s11n::write(std::ostream& stream, const ov::npuw::compiled::Spatial& var) { - using ov::npuw::s11n::write; - - write(stream, var.params.size()); - for (const auto& p : var.params) { - write(stream, p.idx); - write(stream, p.dim); - } - write(stream, var.range); - write(stream, var.nway); - write(stream, var.out_dim); - write(stream, var.nway_iters); - write(stream, var.tail_size); -} - -void ov::npuw::s11n::write(std::ostream& stream, const ov::npuw::compiled::Attention& var) { - using ov::npuw::s11n::write; - - write(stream, var.query_size); - write(stream, var.context_size); - - // NB: This should've been done through a generic vector write! - write(stream, var.params.size()); - for (const auto& p : var.params) { - write(stream, p.idx); - write(stream, p.dim); - } - - write(stream, var.mask_idx); - write(stream, var.attend_all); -} - -void ov::npuw::s11n::write(std::ostream& stream, const ov::npuw::compiled::PyramidAttention& var) { - using ov::npuw::s11n::write; - - write(stream, var.query_size); - write(stream, var.full_context_size); - write(stream, var._context_lengths); - - // Serialize attention infos - write(stream, var._attention_infos.size()); - for (const auto& info : var._attention_infos) { - write(stream, info.params.size()); - for (const auto& p : info.params) { - write(stream, p.idx); - write(stream, p.dim); +void ov::npuw::s11n::serialize(Stream& stream, std::string& var) { + if (stream.output()) { + auto var_size = var.size(); + stream.bytes(&var_size, sizeof var_size); + if (!var.empty()) { + stream.bytes(var.data(), checked_stream_size(var.size())); } - write(stream, info.mask_idx); - write(stream, info.query_size); - write(stream, info.context_length); - } -} - -void ov::npuw::s11n::write(std::ostream& stream, const ov::npuw::compiled::HostFlashAttention& var) { - using ov::npuw::s11n::write; - - // Serialize basic info from _sdpa_attention_info - write(stream, var._sdpa_attention_info._query_size); - write(stream, var._sdpa_attention_info._context_size); - write(stream, var._sdpa_attention_info._k_seq_dim); - write(stream, var._sdpa_attention_info._v_seq_dim); - - // Serialize SDPA indices from _sdpa_attention_info - write(stream, var._sdpa_attention_info._sdpa_indices.query); - write(stream, var._sdpa_attention_info._sdpa_indices.past_key); - write(stream, var._sdpa_attention_info._sdpa_indices.past_value); - write(stream, var._sdpa_attention_info._sdpa_indices.present_key); - write(stream, var._sdpa_attention_info._sdpa_indices.present_value); - write(stream, var._sdpa_attention_info._sdpa_indices.attention_mask); - - // Serialize tile input indices from _sdpa_attention_info - write(stream, var._sdpa_attention_info._tile_input_indices.q); - write(stream, var._sdpa_attention_info._tile_input_indices.k); - write(stream, var._sdpa_attention_info._tile_input_indices.v); - write(stream, var._sdpa_attention_info._tile_input_indices.mask); - write(stream, var._sdpa_attention_info._tile_input_indices.acc); - write(stream, var._sdpa_attention_info._tile_input_indices.max); - write(stream, var._sdpa_attention_info._tile_input_indices.d); - - // Serialize tile output indices from _sdpa_attention_info - write(stream, var._sdpa_attention_info._tile_output_indices.acc); - write(stream, var._sdpa_attention_info._tile_output_indices.max); - write(stream, var._sdpa_attention_info._tile_output_indices.d); - - // Serialize tile_size - write(stream, var._tile_size); - - // Serialize can_use_tensor_view - write(stream, var._can_use_tensor_view); - - // Note: _tile_model_to_compile and _compiled_tile_model are not serialized here - // They are handled separately in CompiledModelDesc::serialize() -} - -void ov::npuw::s11n::write(std::ostream& stream, const ov::npuw::compiled::MoEExperts& var) { - using ov::npuw::s11n::write; - - // Serialize basic MoE metadata - write(stream, var.num_experts); - write(stream, var.expert_hidden_dim); - write(stream, var.num_active_experts); - write(stream, var.input_token_count); - - // Serialize router scores index - write(stream, var._router_scores_idx.has_value()); - if (var._router_scores_idx.has_value()) { - write(stream, var._router_scores_idx.value()); - } - - // Serialize expert input parameter index - write(stream, var._expert_input_param_idx.has_value()); - if (var._expert_input_param_idx.has_value()) { - write(stream, var._expert_input_param_idx.value()); - } - - // Serialize parameter mapping - write(stream, var._param_mapping); - - // Note: _compiled_models and _models_to_compile are not serialized here - // They are handled separately in CompiledModelDesc::serialize() -} - -void ov::npuw::s11n::write(std::ostream& stream, const ov::npuw::compiled::MoEDownstream& var) { - using ov::npuw::s11n::write; - - // Serialize MoE downstream metadata - write(stream, var.total_experts_num); - write(stream, var.active_experts_num); - write(stream, var.expert_output_param_idx); - - // Note: _compiled_model and _model_to_compile are not serialized here - // They are handled separately in CompiledModelDesc::serialize() -} - -void ov::npuw::s11n::write(std::ostream& stream, const ov::Tensor& var) { - using ov::npuw::s11n::write; - - if (!var) { - write(stream, false); - return; - } - write(stream, true); - - auto type_str = var.get_element_type().to_string(); - write(stream, type_str); - write(stream, var.get_shape()); - write(stream, var.get_byte_size()); - - ov::Tensor tensor; - if (var.is_continuous()) { - tensor = var; } else { - // Just copy strided tensor to a non-strided one - tensor = ov::Tensor(var.get_element_type(), var.get_shape()); - var.copy_to(tensor); - } - NPUW_ASSERT(tensor); - size_t blob_size = var.get_byte_size(); - if (blob_size > static_cast(std::numeric_limits::max())) { - OPENVINO_THROW("Blob size is too large to be represented on a std::streamsize!"); + std::size_t var_size = 0; + stream.bytes(&var_size, sizeof var_size); + var.resize(var_size); + if (var_size != 0) { + stream.bytes(var.data(), checked_stream_size(var_size)); + } } - stream.write(reinterpret_cast(var.data()), static_cast(blob_size)); } -void ov::npuw::s11n::write(std::ostream& stream, const ::intel_npu::Config& var) { - write(stream, var.toString()); +void ov::npuw::s11n::serialize(Stream& stream, bool& var) { + stream.bytes(&var, sizeof var); } -void ov::npuw::s11n::write(std::ostream& stream, const ov::Output& var) { - write(stream, var.get_element_type().to_string()); - write(stream, var.get_partial_shape().to_string()); - write(stream, var.get_names()); +void ov::npuw::s11n::serialize(Stream& stream, float& var) { + stream.bytes(&var, sizeof var); } -void ov::npuw::s11n::write_any(std::ostream& stream, const ov::Any& var) { - auto str = ov::npuw::s11n::anyToString(var); - write(stream, str); +void ov::npuw::s11n::serialize(Stream& stream, ov::npuw::compiled::Spatial& var) { + stream & var.params & var.range & var.nway & var.out_dim & var.nway_iters & var.tail_size; } -void ov::npuw::s11n::write(std::ostream& stream, const ov::npuw::weights::LazyTensor& var) { - var.serialize(stream); +void ov::npuw::s11n::serialize(Stream& stream, ov::npuw::compiled::Spatial::Param& var) { + stream & var.idx & var.dim; } -void ov::npuw::s11n::write(std::ostream& stream, const ov::CacheMode& var) { - stream.write(reinterpret_cast(&var), sizeof var); +void ov::npuw::s11n::serialize(Stream& stream, ov::npuw::compiled::Attention& var) { + stream & var.query_size & var.context_size & var.params & var.mask_idx & var.attend_all; } -void ov::npuw::s11n::write(std::ostream& stream, const ov::element::Type& var) { - stream.write(reinterpret_cast(&var), sizeof var); +void ov::npuw::s11n::serialize(Stream& stream, ov::npuw::compiled::Attention::Param& var) { + stream & var.idx & var.dim; } -void ov::npuw::s11n::write(std::ostream& stream, const ov::hint::PerformanceMode& var) { - stream.write(reinterpret_cast(&var), sizeof var); +void ov::npuw::s11n::serialize(Stream& stream, ov::npuw::compiled::PyramidAttention& var) { + stream & var.query_size & var.full_context_size & var._context_lengths & var._attention_infos; } -void ov::npuw::s11n::write(std::ostream& stream, const ov::AnyMap& var) { - auto str = ov::npuw::s11n::anyMapToString(var); - write(stream, str); +void ov::npuw::s11n::serialize(Stream& stream, ov::npuw::compiled::PyramidAttentionInfo& var) { + stream & var.params & var.mask_idx & var.query_size & var.context_length; } -void ov::npuw::s11n::read(std::istream& stream, std::streampos& var) { - stream.read(reinterpret_cast(&var), sizeof var); +void ov::npuw::s11n::serialize(Stream& stream, ov::npuw::compiled::PyramidAttentionInfo::Param& var) { + stream & var.idx & var.dim; } -void ov::npuw::s11n::read(std::istream& stream, std::string& var) { - std::size_t var_size = 0; - stream.read(reinterpret_cast(&var_size), sizeof var_size); - var.resize(var_size); - stream.read(&var[0], var_size); +void ov::npuw::s11n::serialize(Stream& stream, ov::npuw::compiled::HostFlashAttention& var) { + auto& info = var._sdpa_attention_info; + stream & info._query_size & info._context_size & info._k_seq_dim & info._v_seq_dim & info._sdpa_indices.query & + info._sdpa_indices.past_key & info._sdpa_indices.past_value & info._sdpa_indices.present_key & + info._sdpa_indices.present_value & info._sdpa_indices.attention_mask & info._tile_input_indices.q & + info._tile_input_indices.k & info._tile_input_indices.v & info._tile_input_indices.mask & + info._tile_input_indices.acc & info._tile_input_indices.max & info._tile_input_indices.d & + info._tile_output_indices.acc & info._tile_output_indices.max & info._tile_output_indices.d & var._tile_size & + var._can_use_tensor_view; } -void ov::npuw::s11n::read(std::istream& stream, bool& var) { - stream.read(reinterpret_cast(&var), sizeof var); +void ov::npuw::s11n::serialize(Stream& stream, ov::npuw::compiled::MoEExperts& var) { + stream & var.num_experts & var.expert_hidden_dim & var.num_active_experts & var.input_token_count & + var._router_scores_idx & var._expert_input_param_idx & var._param_mapping; } -void ov::npuw::s11n::read(std::istream& stream, float& var) { - stream.read(reinterpret_cast(&var), sizeof var); +void ov::npuw::s11n::serialize(Stream& stream, ov::npuw::compiled::MoEDownstream& var) { + stream & var.total_experts_num & var.active_experts_num & var.expert_output_param_idx; } -void ov::npuw::s11n::read(std::istream& stream, ov::npuw::compiled::Spatial& var) { - using ov::npuw::s11n::read; - - std::size_t params_size = 0; - read(stream, params_size); - for (std::size_t i = 0; i < params_size; ++i) { - ov::npuw::compiled::Spatial::Param p; - read(stream, p.idx); - read(stream, p.dim); - var.params.push_back(p); - } - read(stream, var.range); - read(stream, var.nway); - read(stream, var.out_dim); - read(stream, var.nway_iters); - read(stream, var.tail_size); +void ov::npuw::s11n::serialize(Stream& stream, ov::Tensor& var) { + transfer_tensor(stream, var); } -void ov::npuw::s11n::read(std::istream& stream, ov::npuw::compiled::Attention& var) { - using ov::npuw::s11n::read; - - read(stream, var.query_size); - read(stream, var.context_size); - - std::size_t params_size = 0; - read(stream, params_size); - for (std::size_t i = 0; i < params_size; ++i) { - ov::npuw::compiled::Attention::Param p; - read(stream, p.idx); - read(stream, p.dim); - var.params.push_back(p); +void ov::npuw::s11n::serialize(Stream& stream, ::intel_npu::Config& var) { + std::string str; + if (stream.output()) { + str = var.toString(); } - - read(stream, var.mask_idx); - read(stream, var.attend_all); -} - -void ov::npuw::s11n::read(std::istream& stream, ov::npuw::compiled::PyramidAttention& var) { - using ov::npuw::s11n::read; - - read(stream, var.query_size); - read(stream, var.full_context_size); - read(stream, var._context_lengths); - - // Deserialize attention infos - std::size_t attention_infos_size = 0; - read(stream, attention_infos_size); - var._attention_infos.resize(attention_infos_size); - - for (auto& info : var._attention_infos) { - std::size_t params_size = 0; - read(stream, params_size); - info.params.resize(params_size); - for (auto& p : info.params) { - read(stream, p.idx); - read(stream, p.dim); - } - read(stream, info.mask_idx); - read(stream, info.query_size); - read(stream, info.context_length); + stream & str; + if (stream.input()) { + var.fromString(str); } } -void ov::npuw::s11n::read(std::istream& stream, ov::npuw::compiled::HostFlashAttention& var) { - using ov::npuw::s11n::read; - - // Deserialize basic info into _sdpa_attention_info - read(stream, var._sdpa_attention_info._query_size); - read(stream, var._sdpa_attention_info._context_size); - read(stream, var._sdpa_attention_info._k_seq_dim); - read(stream, var._sdpa_attention_info._v_seq_dim); - - // Deserialize SDPA indices into _sdpa_attention_info - read(stream, var._sdpa_attention_info._sdpa_indices.query); - read(stream, var._sdpa_attention_info._sdpa_indices.past_key); - read(stream, var._sdpa_attention_info._sdpa_indices.past_value); - read(stream, var._sdpa_attention_info._sdpa_indices.present_key); - read(stream, var._sdpa_attention_info._sdpa_indices.present_value); - read(stream, var._sdpa_attention_info._sdpa_indices.attention_mask); - - // Deserialize tile input indices into _sdpa_attention_info - read(stream, var._sdpa_attention_info._tile_input_indices.q); - read(stream, var._sdpa_attention_info._tile_input_indices.k); - read(stream, var._sdpa_attention_info._tile_input_indices.v); - read(stream, var._sdpa_attention_info._tile_input_indices.mask); - read(stream, var._sdpa_attention_info._tile_input_indices.acc); - read(stream, var._sdpa_attention_info._tile_input_indices.max); - read(stream, var._sdpa_attention_info._tile_input_indices.d); - - // Deserialize tile output indices into _sdpa_attention_info - read(stream, var._sdpa_attention_info._tile_output_indices.acc); - read(stream, var._sdpa_attention_info._tile_output_indices.max); - read(stream, var._sdpa_attention_info._tile_output_indices.d); - - // Deserialize tile_size - read(stream, var._tile_size); - - // Deserialize can_use_tensor_view - read(stream, var._can_use_tensor_view); - - // Note: _tile_model_to_compile and _compiled_tile_model are not deserialized here - // They are handled separately in CompiledModelDesc::deserialize() -} - -void ov::npuw::s11n::read(std::istream& stream, ov::npuw::compiled::MoEExperts& var) { - using ov::npuw::s11n::read; - - // Deserialize basic MoE metadata - read(stream, var.num_experts); - read(stream, var.expert_hidden_dim); - read(stream, var.num_active_experts); - read(stream, var.input_token_count); - - // Deserialize router scores index - bool has_router_scores_idx = false; - read(stream, has_router_scores_idx); - if (has_router_scores_idx) { - size_t value = 0; - read(stream, value); - var._router_scores_idx = value; - } - - // Deserialize expert input parameter index - bool has_expert_input_param_idx = false; - read(stream, has_expert_input_param_idx); - if (has_expert_input_param_idx) { - size_t value = 0; - read(stream, value); - var._expert_input_param_idx = value; +void ov::npuw::s11n::serialize(Stream& stream, ov::Output& var) { + if (stream.output()) { + auto elem_type = var.get_element_type().to_string(); + auto shape = var.get_partial_shape().to_string(); + auto names = var.get_names(); + stream & elem_type & shape & names; + } else { + OPENVINO_THROW("ov::Output is write-only in NPUW serialization"); } - - // Deserialize parameter mapping - read(stream, var._param_mapping); - - // Note: _compiled_models and _models_to_compile are not deserialized here - // They are handled separately in CompiledModelDesc::deserialize() } -void ov::npuw::s11n::read(std::istream& stream, ov::npuw::compiled::MoEDownstream& var) { - using ov::npuw::s11n::read; +void ov::npuw::s11n::transfer_tensor(Stream& stream, ov::Tensor& var, const TensorAllocator& allocator) { + if (stream.output()) { + bool is_initialized = static_cast(var); + stream & is_initialized; + if (!is_initialized) { + return; + } - // Deserialize MoE downstream metadata - read(stream, var.total_experts_num); - read(stream, var.active_experts_num); - read(stream, var.expert_output_param_idx); + auto type_str = var.get_element_type().to_string(); + auto shape = var.get_shape(); + auto byte_size = var.get_byte_size(); + stream & type_str & shape & byte_size; - // Note: _compiled_model and _model_to_compile are not deserialized here - // They are handled separately in CompiledModelDesc::deserialize() -} + ov::Tensor tensor = var; + if (!var.is_continuous()) { + tensor = ov::Tensor(var.get_element_type(), var.get_shape()); + var.copy_to(tensor); + } + NPUW_ASSERT(tensor); + stream.bytes(tensor.data(), tensor.get_byte_size()); + return; + } -void ov::npuw::s11n::read(std::istream& stream, ov::Tensor& var) { bool is_initialized = false; - read(stream, is_initialized); - + stream & is_initialized; if (!is_initialized) { + var = ov::Tensor(); return; } std::string type_str; - read(stream, type_str); + stream & type_str; ov::element::Type type(type_str); ov::Shape shape; - read(stream, shape); + stream & shape; std::size_t byte_size = 0; - read(stream, byte_size); - - var = ov::Tensor(type, shape); - - stream.read(reinterpret_cast(var.data()), byte_size); -} - -void ov::npuw::s11n::read(std::istream& stream, ::intel_npu::Config& var) { - std::string str; - read(stream, str); - var.fromString(str); -} + stream & byte_size; -void ov::npuw::s11n::read(std::istream& stream, std::shared_ptr& var) { - std::string elem_type_str; - std::string part_shape_str; - std::unordered_set names; - read(stream, elem_type_str); - read(stream, part_shape_str); - read(stream, names); - // NOTE: the code below is taken from NPU plugin's create_dummy_model() - var = std::make_shared(ov::element::Type(elem_type_str), ov::PartialShape(part_shape_str)); - if (!names.empty()) { - var->set_friendly_name(*names.begin()); // FIXME: any_name ? + if (allocator) { + var = allocator(type, shape); + } else { + var = ov::Tensor(type, shape); + } + NPUW_ASSERT(var && "Tensor allocator returned an empty tensor"); + NPUW_ASSERT(var.get_element_type() == type && var.get_shape() == shape && + "Tensor allocator returned tensor with unexpected type or shape"); + NPUW_ASSERT(var.get_byte_size() == byte_size && "Tensor allocator returned tensor with unexpected byte size"); + + stream.bytes(var.data(), byte_size); +} + +void ov::npuw::s11n::serialize(Stream& stream, std::shared_ptr& var) { + if (stream.input()) { + std::string elem_type_str; + std::string part_shape_str; + std::unordered_set names; + stream & elem_type_str & part_shape_str & names; + var = std::make_shared(ov::element::Type(elem_type_str), ov::PartialShape(part_shape_str)); + if (!names.empty()) { + var->set_friendly_name(*names.begin()); + } + var->output(0).get_tensor().set_names(names); + } else { + OPENVINO_THROW("Parameter pointer is read-only in NPUW serialization"); } - var->output(0).get_tensor().set_names(names); } -void ov::npuw::s11n::read(std::istream& stream, std::shared_ptr& var) { - std::string elem_type_str; - std::string part_shape_str; - std::unordered_set names; - read(stream, elem_type_str); - read(stream, part_shape_str); - read(stream, names); - // NOTE: the code below is taken from NPU plugin's create_dummy_model() - std::shared_ptr res = - std::make_shared(ov::element::Type(elem_type_str), std::vector{1}); - // FIXME: serialize names as well? - const std::shared_ptr& tensor_dummy = - std::make_shared(ov::element::Type(elem_type_str), - ov::PartialShape(part_shape_str), - names); - var = std::make_shared(res); - var->output(0).set_tensor_ptr(tensor_dummy); - if (!names.empty()) { - var->set_friendly_name(*names.begin()); // any_name ? +void ov::npuw::s11n::serialize(Stream& stream, std::shared_ptr& var) { + if (stream.input()) { + std::string elem_type_str; + std::string part_shape_str; + std::unordered_set names; + stream & elem_type_str & part_shape_str & names; + std::shared_ptr res = + std::make_shared(ov::element::Type(elem_type_str), std::vector{1}); + const std::shared_ptr& tensor_dummy = + std::make_shared(ov::element::Type(elem_type_str), + ov::PartialShape(part_shape_str), + names); + var = std::make_shared(res); + var->output(0).set_tensor_ptr(tensor_dummy); + if (!names.empty()) { + var->set_friendly_name(*names.begin()); + } + } else { + OPENVINO_THROW("Node pointer is read-only in NPUW serialization"); } } -void ov::npuw::s11n::read_any(std::istream& stream, ov::Any& var) { +void ov::npuw::s11n::serialize(Stream& stream, ov::Any& var) { std::string str; - read(stream, str); - var = ov::npuw::s11n::stringToAny(str); -} - -void ov::npuw::s11n::read(std::istream& stream, ov::npuw::weights::LazyTensor& var) { - var = ov::npuw::weights::LazyTensor::deserialize(stream); + if (stream.output()) { + str = ov::npuw::s11n::anyToString(var); + } + stream & str; + if (stream.input()) { + var = ov::npuw::s11n::stringToAny(str); + } } -void ov::npuw::s11n::read(std::istream& stream, ov::CacheMode& var) { - stream.read(reinterpret_cast(&var), sizeof var); +void ov::npuw::s11n::serialize(Stream& stream, ov::CacheMode& var) { + stream.bytes(&var, sizeof var); } -void ov::npuw::s11n::read(std::istream& stream, ov::element::Type& var) { - stream.read(reinterpret_cast(&var), sizeof var); +void ov::npuw::s11n::serialize(Stream& stream, ov::element::Type& var) { + stream.bytes(&var, sizeof var); } -void ov::npuw::s11n::read(std::istream& stream, ov::hint::PerformanceMode& var) { - stream.read(reinterpret_cast(&var), sizeof var); +void ov::npuw::s11n::serialize(Stream& stream, ov::hint::PerformanceMode& var) { + stream.bytes(&var, sizeof var); } -void ov::npuw::s11n::read(std::istream& stream, ov::AnyMap& var) { +void ov::npuw::s11n::serialize(Stream& stream, ov::AnyMap& var) { std::string str; - read(stream, str); - var = ov::npuw::s11n::stringToAnyMap(str); + if (stream.output()) { + str = ov::npuw::s11n::anyMapToString(var); + } + stream & str; + if (stream.input()) { + var = ov::npuw::s11n::stringToAnyMap(str); + } } // Weightless // FIXME: all serialization needs a good rewriting -void ov::npuw::s11n::write_weightless(std::ostream& stream, - const std::vector& var, - const ov::npuw::s11n::WeightsContext& ctx) { - write(stream, var.size()); - for (const auto& t : var) { - if (!t) { - write(stream, false); - continue; - } - write(stream, true); - auto data = t.data(); - auto iter = ctx.const_to_offset.find(data); - if (iter == ctx.const_to_offset.end()) { - write(stream, false); - write(stream, t); - } else { - write(stream, true); - write(stream, t.get_element_type().to_string()); - write(stream, t.get_shape()); - write(stream, t.get_byte_size()); - write(stream, iter->second); // offset in weights file +void ov::npuw::s11n::serialize_weightless(Stream& stream, + std::vector& var, + const ov::npuw::s11n::WeightsContext& ctx) { + if (stream.output()) { + auto size = var.size(); + serialize(stream, size); + for (auto& t : var) { + if (!t) { + bool is_initialized = false; + serialize(stream, is_initialized); + continue; + } + bool is_initialized = true; + serialize(stream, is_initialized); + auto data = t.data(); + auto iter = ctx.const_to_offset.find(data); + if (iter == ctx.const_to_offset.end()) { + bool is_weightless = false; + serialize(stream, is_weightless); + auto tensor = t; + serialize(stream, tensor); + } else { + bool is_weightless = true; + serialize(stream, is_weightless); + auto elem_type = t.get_element_type().to_string(); + serialize(stream, elem_type); + auto shape = t.get_shape(); + serialize(stream, shape); + auto byte_size = t.get_byte_size(); + serialize(stream, byte_size); + auto offset = iter->second; + serialize(stream, offset); + } } + return; } -} -void ov::npuw::s11n::read_weightless(std::istream& stream, - std::vector& var, - const ov::npuw::s11n::WeightsContext& ctx) { var.clear(); std::size_t size; - read(stream, size); + serialize(stream, size); for (std::size_t i = 0; i < size; ++i) { bool is_initialized = false; - read(stream, is_initialized); + serialize(stream, is_initialized); if (!is_initialized) { var.push_back(ov::Tensor()); continue; } bool is_weightless = false; - read(stream, is_weightless); + serialize(stream, is_weightless); if (is_weightless) { std::string type_str; - read(stream, type_str); + serialize(stream, type_str); ov::element::Type type(type_str); ov::Shape shape; - read(stream, shape); + serialize(stream, shape); std::size_t byte_size = 0; - read(stream, byte_size); + serialize(stream, byte_size); std::size_t offset = 0; - read(stream, offset); + serialize(stream, offset); ov::Tensor t(type, shape); if (ctx.weights) { @@ -623,7 +390,7 @@ void ov::npuw::s11n::read_weightless(std::istream& stream, var.push_back(t); } else { ov::Tensor t; - read(stream, t); + serialize(stream, t); var.push_back(t); } } diff --git a/src/plugins/intel_npu/src/plugin/npuw/serialization.hpp b/src/plugins/intel_npu/src/plugin/npuw/serialization.hpp index 99bd1003b87141..0ffbc79c0ca341 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/serialization.hpp +++ b/src/plugins/intel_npu/src/plugin/npuw/serialization.hpp @@ -8,16 +8,23 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include +#include "attention.hpp" +#include "host_flash_attention.hpp" +#include "openvino/core/shape.hpp" #include "openvino/runtime/file_handle.hpp" +#include "pyramid_attention.hpp" +#include "spatial.hpp" namespace ov { namespace npuw { @@ -36,7 +43,7 @@ const constexpr ov::npuw::s11n::IndicatorType NPUW_COMPILED_MODEL_INDICATOR = const constexpr ov::npuw::s11n::IndicatorType NPUW_LLM_COMPILED_MODEL_INDICATOR = {char{0x4c}, char{0x4c}, char{0x4d}, char{0x43}, char{0x4d}, char{0x4f}}; -const constexpr char* NPUW_SERIALIZATION_VERSION = "0.21"; +const constexpr char* NPUW_SERIALIZATION_VERSION = "0.22"; // Forward declaration namespace intel_npu { @@ -87,6 +94,7 @@ struct MoEDownstream; } // namespace compiled namespace weights { class LazyTensor; +class Bank; } // namespace weights namespace s11n { @@ -176,209 +184,261 @@ struct SubmodelDeserializeCtx { BF16Cache get_bf16_consts(const std::shared_ptr& model); -// Specific type overloads -void write(std::ostream& stream, const std::streampos& var); -void write(std::ostream& stream, const std::string& var); -void write(std::ostream& stream, const bool& var); -void write(std::ostream& stream, const float& var); -void write(std::ostream& stream, const ov::npuw::compiled::Spatial& var); -void write(std::ostream& stream, const ov::npuw::compiled::Attention& var); -void write(std::ostream& stream, const ov::npuw::compiled::PyramidAttention& var); -void write(std::ostream& stream, const ov::npuw::compiled::HostFlashAttention& var); -void write(std::ostream& stream, const ov::npuw::compiled::MoEExperts& var); -void write(std::ostream& stream, const ov::npuw::compiled::MoEDownstream& var); -void write(std::ostream& stream, const ov::Tensor& var); -void write(std::ostream& stream, const ::intel_npu::Config& var); -void write(std::ostream& stream, const ov::Output& var); -void write_any(std::ostream& stream, const ov::Any& var); -void write(std::ostream& stream, const ov::npuw::weights::LazyTensor& var); -void write(std::ostream& stream, const ov::CacheMode& var); -void write(std::ostream& stream, const ov::element::Type& var); -void write(std::ostream& stream, const std::map& var); -void write(std::ostream& stream, const ov::hint::PerformanceMode& var); - -void read(std::istream& stream, std::streampos& var); -void read(std::istream& stream, std::string& var); -void read(std::istream& stream, bool& var); -void read(std::istream& stream, float& var); -void read(std::istream& stream, ov::npuw::compiled::Spatial& var); -void read(std::istream& stream, ov::npuw::compiled::Attention& var); -void read(std::istream& stream, ov::npuw::compiled::PyramidAttention& var); -void read(std::istream& stream, ov::npuw::compiled::HostFlashAttention& var); -void read(std::istream& stream, ov::npuw::compiled::MoEExperts& var); -void read(std::istream& stream, ov::npuw::compiled::MoEDownstream& var); -void read(std::istream& stream, ov::Tensor& var); -void read(std::istream& stream, ::intel_npu::Config& var); -void read(std::istream& stream, std::shared_ptr& var); -void read(std::istream& stream, std::shared_ptr& var); -void read_any(std::istream& stream, ov::Any& var); -void read(std::istream& stream, ov::npuw::weights::LazyTensor& var); -void read(std::istream& stream, ov::CacheMode& var); -void read(std::istream& stream, ov::element::Type& var); -void read(std::istream& stream, std::map& var); -void read(std::istream& stream, ov::hint::PerformanceMode& var); +class Stream { +public: + static Stream reader(std::istream& stream) { + return Stream(&stream, nullptr); + } + + static Stream writer(std::ostream& stream) { + return Stream(nullptr, &stream); + } + + bool input() const { + return m_input != nullptr; + } + + bool output() const { + return m_output != nullptr; + } + + template + Stream& operator&(T&& value) { + using plain_type = std::remove_const_t>; + serialize(*this, const_cast(value)); + return *this; + } + + void operator()() {} + + template + void operator()(T&& value, Ts&&... values) { + (*this) & std::forward(value); + (*this)(std::forward(values)...); + } + + void bytes(void* data, std::size_t size) { + const auto stream_size = to_streamsize(size); + if (output()) { + m_output->write(reinterpret_cast(data), stream_size); + } else { + m_input->read(reinterpret_cast(data), stream_size); + } + } + +private: + static std::streamsize to_streamsize(std::size_t size) { + if (size > static_cast(std::numeric_limits::max())) { + throw std::overflow_error("Stream::bytes() size exceeds std::streamsize range"); + } + return static_cast(size); + } + + Stream(std::istream* input, std::ostream* output) : m_input(input), m_output(output) {} + + std::istream* m_input = nullptr; + std::ostream* m_output = nullptr; +}; + +void serialize(Stream& stream, std::streampos& var); +void serialize(Stream& stream, std::string& var); +void serialize(Stream& stream, bool& var); +void serialize(Stream& stream, float& var); +void serialize(Stream& stream, ov::npuw::compiled::Spatial& var); +void serialize(Stream& stream, ov::npuw::compiled::Spatial::Param& var); +void serialize(Stream& stream, ov::npuw::compiled::Attention& var); +void serialize(Stream& stream, ov::npuw::compiled::Attention::Param& var); +void serialize(Stream& stream, ov::npuw::compiled::PyramidAttention& var); +void serialize(Stream& stream, ov::npuw::compiled::PyramidAttentionInfo& var); +void serialize(Stream& stream, ov::npuw::compiled::PyramidAttentionInfo::Param& var); +void serialize(Stream& stream, ov::npuw::compiled::HostFlashAttention& var); +void serialize(Stream& stream, ov::npuw::compiled::MoEExperts& var); +void serialize(Stream& stream, ov::npuw::compiled::MoEDownstream& var); +void serialize(Stream& stream, ov::Tensor& var); +void serialize(Stream& stream, ::intel_npu::Config& var); +void serialize(Stream& stream, ov::Any& var); +void serialize(Stream& stream, ov::npuw::weights::LazyTensor& var); +void serialize(Stream& stream, ov::npuw::weights::Bank& var); +void serialize(Stream& stream, ov::CacheMode& var); +void serialize(Stream& stream, ov::element::Type& var); +void serialize(Stream& stream, ov::hint::PerformanceMode& var); +void serialize(Stream& stream, std::map& var); +void serialize(Stream& stream, ov::Output& var); +void serialize(Stream& stream, std::shared_ptr& var); +void serialize(Stream& stream, std::shared_ptr& var); // Weightless utils -void write_weightless(std::ostream& stream, const std::vector& var, const WeightsContext& ctx); -// No allocation needed -void read_weightless(std::istream& stream, std::vector& var, const WeightsContext& ctx); +void serialize_weightless(Stream& stream, std::vector& var, const WeightsContext& ctx); // Forward declaration template -void write(std::ostream& stream, const std::pair& var); +void serialize(Stream& stream, std::pair& var); template -void write(std::ostream& stream, const std::vector& var); +void serialize(Stream& stream, std::vector& var); template -void write(std::ostream& stream, const std::array& var); -template -void read(std::istream& stream, std::pair& var); -template -void read(std::istream& stream, std::vector& var); -template -void read(std::istream& stream, std::array& var); +void serialize(Stream& stream, std::array& var); -// Serialization -template ::value, bool> = true> +template void write(std::ostream& stream, const T& var) { - stream.write(reinterpret_cast(&var), sizeof var); -} - -template -void write(std::ostream& stream, const std::pair& var) { - write(stream, var.first); - write(stream, var.second); + auto stream_io = Stream::writer(stream); + auto& mutable_var = const_cast&>(var); + serialize(stream_io, mutable_var); } template -void write(std::ostream& stream, const std::vector& var) { - write(stream, var.size()); - for (const auto& el : var) { - write(stream, el); - } -} - -template -void write(std::ostream& stream, const std::array& var) { - for (const auto& el : var) { - write(stream, el); - } +void read(std::istream& stream, T& var) { + auto stream_io = Stream::reader(stream); + serialize(stream_io, var); } -template -void write(std::ostream& stream, const std::unordered_set& var) { - write(stream, var.size()); - for (const auto& el : var) { - write(stream, el); - } +inline void write_any(std::ostream& stream, const ov::Any& var) { + write(stream, var); } -template -void write(std::ostream& stream, const std::unordered_set& var) { - write(stream, var.size()); - for (const auto& el : var) { - write(stream, el); - } +inline void read_any(std::istream& stream, ov::Any& var) { + read(stream, var); } -template -void write(std::ostream& stream, const std::map& var) { - write(stream, var.size()); - for (const auto& el : var) { - write(stream, el); - } +inline void write_weightless(std::ostream& stream, const std::vector& var, const WeightsContext& ctx) { + auto stream_io = Stream::writer(stream); + auto& mutable_var = const_cast&>(var); + serialize_weightless(stream_io, mutable_var, ctx); } -template -void write(std::ostream& stream, const std::optional& var) { - if (var) { - write(stream, true); - write(stream, var.value()); - } else { - write(stream, false); - } +inline void read_weightless(std::istream& stream, std::vector& var, const WeightsContext& ctx) { + auto stream_io = Stream::reader(stream); + serialize_weightless(stream_io, var, ctx); } -// Deserialization +// Serialization template ::value, bool> = true> -void read(std::istream& stream, T& var) { - stream.read(reinterpret_cast(&var), sizeof var); +void serialize(Stream& stream, T& var) { + stream.bytes(&var, sizeof var); } template -void read(std::istream& stream, std::pair& var) { - read(stream, var.first); - read(stream, var.second); +void serialize(Stream& stream, std::pair& var) { + stream & var.first & var.second; } template -void read(std::istream& stream, std::vector& var) { - var.clear(); - std::size_t var_size = 0; - stream.read(reinterpret_cast(&var_size), sizeof var_size); - var.reserve(var_size); - for (std::size_t i = 0; i < var_size; ++i) { - T elem; - read(stream, elem); - var.push_back(std::move(elem)); +void serialize(Stream& stream, std::vector& var) { + if (stream.output()) { + auto size = var.size(); + stream & size; + for (std::size_t i = 0; i < var.size(); ++i) { + if constexpr (std::is_same_v) { + bool el = var[i]; + stream & el; + } else { + auto& el = var[i]; + stream & el; + } + } + } else { + var.clear(); + std::size_t var_size = 0; + stream & var_size; + var.reserve(var_size); + for (std::size_t i = 0; i < var_size; ++i) { + T elem; + stream & elem; + var.push_back(std::move(elem)); + } } } -template -void read(std::istream& stream, std::array& var) { - for (std::size_t i = 0; i < N; ++i) { - T elem; - read(stream, elem); - var[i] = elem; +template +void serialize(Stream& stream, std::array& var) { + for (auto& el : var) { + stream & el; } } template -void read(std::istream& stream, std::unordered_set& var) { - var.clear(); - std::size_t var_size = 0; - stream.read(reinterpret_cast(&var_size), sizeof var_size); - for (std::size_t i = 0; i < var_size; ++i) { - T elem; - read(stream, elem); - var.insert(std::move(elem)); +void serialize(Stream& stream, std::unordered_set& var) { + if (stream.output()) { + auto size = var.size(); + stream & size; + for (const auto& el : var) { + auto value = el; + stream & value; + } + } else { + var.clear(); + std::size_t var_size = 0; + stream & var_size; + for (std::size_t i = 0; i < var_size; ++i) { + T elem; + stream & elem; + var.insert(std::move(elem)); + } } } template -void read(std::istream& stream, std::unordered_set& var) { - var.clear(); - std::size_t var_size = 0; - stream.read(reinterpret_cast(&var_size), sizeof var_size); - for (std::size_t i = 0; i < var_size; ++i) { - T elem; - read(stream, elem); - var.insert(std::move(elem)); +void serialize(Stream& stream, std::unordered_set& var) { + if (stream.output()) { + auto size = var.size(); + stream & size; + for (const auto& el : var) { + auto value = el; + stream & value; + } + } else { + var.clear(); + std::size_t var_size = 0; + stream & var_size; + for (std::size_t i = 0; i < var_size; ++i) { + T elem; + stream & elem; + var.insert(std::move(elem)); + } } } template -void read(std::istream& stream, std::map& var) { - var.clear(); - std::size_t var_size = 0; - stream.read(reinterpret_cast(&var_size), sizeof var_size); - for (std::size_t i = 0; i < var_size; ++i) { - std::pair elem; - read(stream, elem); - var[elem.first] = elem.second; +void serialize(Stream& stream, std::map& var) { + if (stream.output()) { + auto size = var.size(); + stream & size; + for (auto& el : var) { + auto pair = el; + stream & pair; + } + } else { + var.clear(); + std::size_t var_size = 0; + stream & var_size; + for (std::size_t i = 0; i < var_size; ++i) { + std::pair elem; + stream & elem; + var[elem.first] = elem.second; + } } } template -void read(std::istream& stream, std::optional& var) { - bool has_value = false; - read(stream, has_value); +void serialize(Stream& stream, std::optional& var) { + bool has_value = var.has_value(); + stream & has_value; if (has_value) { - T val; - read(stream, val); - var = val; + if (stream.output()) { + auto& value = var.value(); + stream & value; + } else { + T value; + stream & value; + var = std::move(value); + } + } else { + var.reset(); } } +using TensorAllocator = std::function; +void transfer_tensor(Stream& stream, ov::Tensor& var, const TensorAllocator& allocator = {}); + } // namespace s11n } // namespace npuw } // namespace ov diff --git a/src/plugins/intel_npu/src/plugin/npuw/weights_bank.cpp b/src/plugins/intel_npu/src/plugin/npuw/weights_bank.cpp index b43753c360a748..9823a467ae9a76 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/weights_bank.cpp +++ b/src/plugins/intel_npu/src/plugin/npuw/weights_bank.cpp @@ -196,21 +196,20 @@ bool Bank::is_remote(int64_t uid) const { return false; } -void Bank::serialize(std::ostream& stream) const { - using namespace ov::npuw::s11n; - +void Bank::serialize(ov::npuw::s11n::Stream& stream) { LOG_INFO("Serializing weights bank..."); LOG_BLOCK(); std::unique_lock guard(m_mutex); - write(stream, m_device_banks.size()); + std::size_t bank_size = m_device_banks.size(); + stream & bank_size; for (const auto& elem : m_device_banks) { const auto& device = elem.first; const auto& device_bank = elem.second; - write(stream, device); - write(stream, device_bank.storage.size()); + auto storage_size = device_bank.storage.size(); + stream & device & storage_size; // Write tensors sequentially according to sorted uids for better memory allocation and utilization std::set uids; for (const auto& t_pair : device_bank.storage) { @@ -218,47 +217,16 @@ void Bank::serialize(std::ostream& stream) const { } for (const auto& uid : uids) { - write(stream, uid); - write(stream, device_bank.storage.at(uid).tensor); - } - } - - LOG_INFO("DONE."); -} - -std::shared_ptr Bank::deserialize(std::istream& stream, - const std::shared_ptr& core, - const std::string& name) { - using namespace ov::npuw::s11n; - - LOG_INFO("Deserializing weights bank..."); - LOG_BLOCK(); - - auto bank = ov::npuw::weights::bank(name, core, ""); - - std::size_t bank_size = 0; - read(stream, bank_size); - - for (std::size_t i = 0; i < bank_size; ++i) { - std::string device; - read(stream, device); - std::size_t storage_size = 0; - read(stream, storage_size); - for (std::size_t j = 0; j < storage_size; ++j) { - int64_t uid = -1; - read(stream, uid); - bank->read_and_add_tensor(stream, uid, device); + stream & uid; + auto tensor = device_bank.storage.at(uid).tensor; + transfer_tensor(stream, tensor); } } LOG_INFO("DONE."); - - return bank; } -void Bank::read_and_add_tensor(std::istream& stream, int64_t uid, const std::string& device) { - using namespace ov::npuw::s11n; - +void Bank::read_and_add_tensor(ov::npuw::s11n::Stream& stream, int64_t uid, const std::string& device) { // This method is supposed to be used only during deserialization std::unique_lock guard(m_mutex); @@ -273,40 +241,52 @@ void Bank::read_and_add_tensor(std::istream& stream, int64_t uid, const std::str if (device == "CPU") { // Just read deserialized tensor into the bank - read(stream, device_bank.storage[uid].tensor); + transfer_tensor(stream, device_bank.storage[uid].tensor); return; } // Need to allocate on device and copy deserialized tensor to that memory - ov::SoPtr remote_tensor; - ov::Tensor allocated_tensor; - - // FIXME: reading not via a dedicated function - bool is_intialized = false; - read(stream, is_intialized); - NPUW_ASSERT(is_intialized); - - std::string type_str; - read(stream, type_str); - ov::element::Type type(type_str); - - ov::Shape shape; - read(stream, shape); - - std::size_t byte_size = 0; - read(stream, byte_size); - auto remote_ctx = m_core->get_default_context(device)._ptr; - remote_tensor = remote_ctx->create_host_tensor(type, shape); - allocated_tensor = ov::make_tensor(remote_tensor); - device_bank.storage[uid] = {LazyTensor(), allocated_tensor}; - stream.read(reinterpret_cast(allocated_tensor.data()), byte_size); + transfer_tensor(stream, + device_bank.storage[uid].tensor, + [&remote_ctx](const ov::element::Type& type, const ov::Shape& shape) { + ov::SoPtr remote_tensor = remote_ctx->create_host_tensor(type, shape); + return ov::make_tensor(remote_tensor); + }); + NPUW_ASSERT(device_bank.storage[uid].tensor && "Remote tensor should be initialized during bank deserialize"); + device_bank.storage[uid].lt = LazyTensor(); } std::string Bank::get_name() const { return m_bank_name; } +void ov::npuw::s11n::serialize(Stream& stream, ov::npuw::weights::Bank& var) { + if (stream.output()) { + var.serialize(stream); + } else { + LOG_INFO("Deserializing weights bank..."); + LOG_BLOCK(); + + std::size_t bank_size = 0; + stream & bank_size; + + for (std::size_t i = 0; i < bank_size; ++i) { + std::string device; + stream & device; + std::size_t storage_size = 0; + stream & storage_size; + for (std::size_t j = 0; j < storage_size; ++j) { + int64_t uid = -1; + stream & uid; + var.read_and_add_tensor(stream, uid, device); + } + } + + LOG_INFO("DONE."); + } +} + std::shared_ptr BankManager::getBank(const std::string& bank_name, const std::shared_ptr& core, const std::string& alloc_device) { diff --git a/src/plugins/intel_npu/src/plugin/npuw/weights_bank.hpp b/src/plugins/intel_npu/src/plugin/npuw/weights_bank.hpp index 10efad349b2c6d..2375b644565cd6 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/weights_bank.hpp +++ b/src/plugins/intel_npu/src/plugin/npuw/weights_bank.hpp @@ -43,6 +43,7 @@ class Bank { private: friend class ov::npuw::LLMCompiledModel; friend class ov::npuw::CompiledModel; + friend void ov::npuw::s11n::serialize(ov::npuw::s11n::Stream& stream, ov::npuw::weights::Bank& var); struct StoredTensor { LazyTensor lt; @@ -60,12 +61,8 @@ class Bank { const std::vector& to_process, const std::string& device); - void serialize(std::ostream& stream) const; - static std::shared_ptr deserialize(std::istream& stream, - const std::shared_ptr& core, - const std::string& name); - // Used during deserialization - void read_and_add_tensor(std::istream& stream, int64_t uid, const std::string& device); + void serialize(ov::npuw::s11n::Stream& stream); + void read_and_add_tensor(ov::npuw::s11n::Stream& stream, int64_t uid, const std::string& device); mutable std::mutex m_mutex; std::shared_ptr m_core = nullptr; diff --git a/src/plugins/intel_npu/tests/unit/npuw/import_non_llm_blob.cpp b/src/plugins/intel_npu/tests/unit/npuw/import_non_llm_blob.cpp index e0bbd93a9cb002..1a2b3544442571 100644 --- a/src/plugins/intel_npu/tests/unit/npuw/import_non_llm_blob.cpp +++ b/src/plugins/intel_npu/tests/unit/npuw/import_non_llm_blob.cpp @@ -11,6 +11,7 @@ #include "intel_npu/config/npuw.hpp" #include "model_builder.hpp" #include "openvino/openvino.hpp" +#include "serialization.hpp" using ov::test::npuw::ModelBuilder; @@ -23,6 +24,64 @@ class ImportNonLLMBlobTestNPUW : public ::testing::TestWithParam { } protected: + static ov::Tensor make_input_tensor(const ov::Output& input) { + ov::Tensor tensor(input.get_element_type(), input.get_shape()); + + if (input.get_element_type() == ov::element::i32) { + auto* data = tensor.data(); + for (std::size_t i = 0; i < tensor.get_size(); ++i) { + data[i] = static_cast(i % 17); + } + } else if (input.get_element_type() == ov::element::i64) { + auto* data = tensor.data(); + for (std::size_t i = 0; i < tensor.get_size(); ++i) { + data[i] = static_cast(i % 17); + } + } else if (input.get_element_type() == ov::element::f32) { + auto* data = tensor.data(); + for (std::size_t i = 0; i < tensor.get_size(); ++i) { + data[i] = static_cast(i) * 0.25f; + } + } else if (input.get_element_type() == ov::element::f16) { + auto* data = tensor.data(); + for (std::size_t i = 0; i < tensor.get_size(); ++i) { + data[i] = ov::float16(static_cast(i) * 0.25f); + } + } else { + std::memset(tensor.data(), 0, tensor.get_byte_size()); + } + + return tensor; + } + + static std::vector infer_outputs(ov::CompiledModel& model) { + auto request = model.create_infer_request(); + for (const auto& input : model.inputs()) { + request.set_tensor(input, make_input_tensor(input)); + } + request.infer(); + + std::vector outputs; + outputs.reserve(model.outputs().size()); + for (const auto& output : model.outputs()) { + auto result = request.get_tensor(output); + ov::Tensor copy(result.get_element_type(), result.get_shape()); + result.copy_to(copy); + outputs.push_back(copy); + } + return outputs; + } + + static void expect_outputs_equal(const std::vector& expected, const std::vector& actual) { + ASSERT_EQ(expected.size(), actual.size()); + for (std::size_t i = 0; i < expected.size(); ++i) { + EXPECT_EQ(expected[i].get_element_type(), actual[i].get_element_type()); + EXPECT_EQ(expected[i].get_shape(), actual[i].get_shape()); + ASSERT_EQ(expected[i].get_byte_size(), actual[i].get_byte_size()); + EXPECT_EQ(std::memcmp(expected[i].data(), actual[i].data(), expected[i].get_byte_size()), 0); + } + } + ModelBuilder model_builder; std::shared_ptr m_ov_model; ov::AnyMap m_props; @@ -35,13 +94,15 @@ TEST_P(ImportNonLLMBlobTestNPUW, CacheModeOptimizeSpeed) { m_props["CACHE_MODE"] = "OPTIMIZE_SPEED"; auto compiled = m_core.compile_model(m_ov_model, "NPU", m_props); + auto compiled_outputs = infer_outputs(compiled); std::stringstream blob; compiled.export_model(blob); EXPECT_NO_THROW({ auto imported = m_core.import_model(blob, "NPU", m_props); - imported.create_infer_request(); + auto imported_outputs = infer_outputs(imported); + expect_outputs_equal(compiled_outputs, imported_outputs); }); } @@ -51,6 +112,7 @@ TEST_P(ImportNonLLMBlobTestNPUW, CacheModeOptimizeSizeWithModelPtr) { m_props["CACHE_MODE"] = "OPTIMIZE_SIZE"; auto compiled = m_core.compile_model(m_ov_model, "NPU", m_props); + auto compiled_outputs = infer_outputs(compiled); std::stringstream blob; compiled.export_model(blob); @@ -59,7 +121,8 @@ TEST_P(ImportNonLLMBlobTestNPUW, CacheModeOptimizeSizeWithModelPtr) { auto import_props = m_props; import_props[ov::hint::model.name()] = std::static_pointer_cast(m_ov_model); auto imported = m_core.import_model(blob, "NPU", import_props); - imported.create_infer_request(); + auto imported_outputs = infer_outputs(imported); + expect_outputs_equal(compiled_outputs, imported_outputs); }); } @@ -70,13 +133,15 @@ TEST_P(ImportNonLLMNonWAIBlobTestNPUW, CacheModeOptimizeSizeNoModelPtr) { m_props["CACHE_MODE"] = "OPTIMIZE_SIZE"; auto compiled = m_core.compile_model(m_ov_model, "NPU", m_props); + auto compiled_outputs = infer_outputs(compiled); std::stringstream blob; compiled.export_model(blob); EXPECT_NO_THROW({ auto imported = m_core.import_model(blob, "NPU", m_props); - imported.create_infer_request(); + auto imported_outputs = infer_outputs(imported); + expect_outputs_equal(compiled_outputs, imported_outputs); }); } diff --git a/src/plugins/intel_npu/tests/unit/npuw/serialization.cpp b/src/plugins/intel_npu/tests/unit/npuw/serialization.cpp index f5d062106073b9..82f63beb08c56c 100644 --- a/src/plugins/intel_npu/tests/unit/npuw/serialization.cpp +++ b/src/plugins/intel_npu/tests/unit/npuw/serialization.cpp @@ -6,18 +6,158 @@ #include +#include +#include #include +#include "attention.hpp" +#include "common_test_utils/file_utils.hpp" #include "compiled_model.hpp" +#include "host_flash_attention.hpp" #include "intel_npu/config/config.hpp" #include "intel_npu/config/npuw.hpp" +#include "lazy_tensor.hpp" +#include "moe_transformations/moe_transformation.hpp" #include "model_builder.hpp" #include "openvino/core/parallel.hpp" +#include "openvino/core/rt_info/weightless_caching_attributes.hpp" +#include "openvino/op/constant.hpp" #include "openvino/openvino.hpp" +#include "openvino/runtime/shared_buffer.hpp" +#include "openvino/util/mmap_object.hpp" +#include "pyramid_attention.hpp" #include "spatial.hpp" +#include "weights_bank.hpp" using ov::test::npuw::ModelBuilder; +namespace { + +void expect_tensors_equal(const ov::Tensor& expected, const ov::Tensor& actual) { + ASSERT_EQ(static_cast(expected), static_cast(actual)); + if (!expected) { + return; + } + + EXPECT_EQ(expected.get_element_type(), actual.get_element_type()); + EXPECT_EQ(expected.get_shape(), actual.get_shape()); + ASSERT_EQ(expected.get_byte_size(), actual.get_byte_size()); + EXPECT_EQ(std::memcmp(expected.data(), actual.data(), expected.get_byte_size()), 0); +} + +template +std::shared_ptr make_weightless_constant(const ov::element::Type& type, + const ov::Shape& shape, + const std::vector& data, + std::size_t offset) { + auto constant = std::make_shared(type, shape, data); + constant->get_rt_info()[ov::WeightlessCacheAttribute::get_type_info_static()] = + ov::WeightlessCacheAttribute(constant->get_byte_size(), offset, type); + return constant; +} + +ov::npuw::s11n::WeightsContext::ConstsCache make_consts_cache( + const std::vector>& constants) { + ov::npuw::s11n::WeightsContext::ConstsCache cache; + for (const auto& constant : constants) { + const auto attr = + constant->get_rt_info().at(ov::WeightlessCacheAttribute::get_type_info_static()) + .as(); + cache[{attr.bin_offset, constant->get_byte_size()}] = constant; + } + return cache; +} + +void expect_attention_equal(const ov::npuw::compiled::Attention& expected, const ov::npuw::compiled::Attention& actual) { + EXPECT_EQ(expected.query_size, actual.query_size); + EXPECT_EQ(expected.context_size, actual.context_size); + EXPECT_EQ(expected.params.size(), actual.params.size()); + for (std::size_t i = 0; i < expected.params.size(); ++i) { + EXPECT_EQ(expected.params[i].idx, actual.params[i].idx); + EXPECT_EQ(expected.params[i].dim, actual.params[i].dim); + } + EXPECT_EQ(expected.mask_idx, actual.mask_idx); + expect_tensors_equal(expected.attend_all, actual.attend_all); +} + +void expect_pyramid_attention_equal(const ov::npuw::compiled::PyramidAttention& expected, + const ov::npuw::compiled::PyramidAttention& actual) { + EXPECT_EQ(expected.query_size, actual.query_size); + EXPECT_EQ(expected.full_context_size, actual.full_context_size); + EXPECT_EQ(expected._context_lengths, actual._context_lengths); + ASSERT_EQ(expected._attention_infos.size(), actual._attention_infos.size()); + for (std::size_t i = 0; i < expected._attention_infos.size(); ++i) { + const auto& lhs = expected._attention_infos[i]; + const auto& rhs = actual._attention_infos[i]; + EXPECT_EQ(lhs.mask_idx, rhs.mask_idx); + EXPECT_EQ(lhs.query_size, rhs.query_size); + EXPECT_EQ(lhs.context_length, rhs.context_length); + EXPECT_EQ(lhs.params.size(), rhs.params.size()); + for (std::size_t j = 0; j < lhs.params.size(); ++j) { + EXPECT_EQ(lhs.params[j].idx, rhs.params[j].idx); + EXPECT_EQ(lhs.params[j].dim, rhs.params[j].dim); + } + } +} + +void expect_host_flash_attention_equal(const ov::npuw::compiled::HostFlashAttention& expected, + const ov::npuw::compiled::HostFlashAttention& actual) { + const auto& lhs = expected._sdpa_attention_info; + const auto& rhs = actual._sdpa_attention_info; + EXPECT_EQ(lhs._query_size, rhs._query_size); + EXPECT_EQ(lhs._context_size, rhs._context_size); + EXPECT_EQ(lhs._k_seq_dim, rhs._k_seq_dim); + EXPECT_EQ(lhs._v_seq_dim, rhs._v_seq_dim); + EXPECT_EQ(lhs._sdpa_indices.query, rhs._sdpa_indices.query); + EXPECT_EQ(lhs._sdpa_indices.past_key, rhs._sdpa_indices.past_key); + EXPECT_EQ(lhs._sdpa_indices.past_value, rhs._sdpa_indices.past_value); + EXPECT_EQ(lhs._sdpa_indices.present_key, rhs._sdpa_indices.present_key); + EXPECT_EQ(lhs._sdpa_indices.present_value, rhs._sdpa_indices.present_value); + EXPECT_EQ(lhs._sdpa_indices.attention_mask, rhs._sdpa_indices.attention_mask); + EXPECT_EQ(lhs._tile_input_indices.q, rhs._tile_input_indices.q); + EXPECT_EQ(lhs._tile_input_indices.k, rhs._tile_input_indices.k); + EXPECT_EQ(lhs._tile_input_indices.v, rhs._tile_input_indices.v); + EXPECT_EQ(lhs._tile_input_indices.mask, rhs._tile_input_indices.mask); + EXPECT_EQ(lhs._tile_input_indices.acc, rhs._tile_input_indices.acc); + EXPECT_EQ(lhs._tile_input_indices.max, rhs._tile_input_indices.max); + EXPECT_EQ(lhs._tile_input_indices.d, rhs._tile_input_indices.d); + EXPECT_EQ(lhs._tile_output_indices.acc, rhs._tile_output_indices.acc); + EXPECT_EQ(lhs._tile_output_indices.max, rhs._tile_output_indices.max); + EXPECT_EQ(lhs._tile_output_indices.d, rhs._tile_output_indices.d); + EXPECT_EQ(expected._tile_size, actual._tile_size); + EXPECT_EQ(expected._can_use_tensor_view, actual._can_use_tensor_view); +} + +void expect_moe_experts_equal(const ov::npuw::compiled::MoEExperts& expected, + const ov::npuw::compiled::MoEExperts& actual) { + EXPECT_EQ(expected.num_experts, actual.num_experts); + EXPECT_EQ(expected.expert_hidden_dim, actual.expert_hidden_dim); + EXPECT_EQ(expected.num_active_experts, actual.num_active_experts); + EXPECT_EQ(expected.input_token_count, actual.input_token_count); + EXPECT_EQ(expected._router_scores_idx, actual._router_scores_idx); + EXPECT_EQ(expected._expert_input_param_idx, actual._expert_input_param_idx); + EXPECT_EQ(expected._param_mapping, actual._param_mapping); +} + +void expect_moe_downstream_equal(const ov::npuw::compiled::MoEDownstream& expected, + const ov::npuw::compiled::MoEDownstream& actual) { + EXPECT_EQ(expected.total_experts_num, actual.total_experts_num); + EXPECT_EQ(expected.active_experts_num, actual.active_experts_num); + EXPECT_EQ(expected.expert_output_param_idx, actual.expert_output_param_idx); +} + +void expect_lazy_tensor_transform_types_equal(const ov::npuw::weights::LazyTensor& expected, + const ov::npuw::weights::LazyTensor& actual) { + const auto expected_transforms = expected.get_transformations(); + const auto actual_transforms = actual.get_transformations(); + ASSERT_EQ(expected_transforms.size(), actual_transforms.size()); + for (std::size_t i = 0; i < expected_transforms.size(); ++i) { + EXPECT_EQ(expected_transforms[i].index(), actual_transforms[i].index()); + } +} + +} // namespace + // FIXME: parametrize all the tests below TEST(SerializationTest, BasicTypes_string) { @@ -210,6 +350,34 @@ TEST(SerializationTest, BasicTypes_vector) { EXPECT_EQ(var, res); } +TEST(SerializationTest, BasicTypes_array) { + using namespace ov::npuw::s11n; + + std::array var{1, 2, 3, 4}; + std::array res{}; + + std::stringstream ss; + + write(ss, var); + read(ss, res); + + EXPECT_EQ(var, res); +} + +TEST(SerializationTest, BasicTypes_vector_bool) { + using namespace ov::npuw::s11n; + + std::vector var{true, false, true, true, false}; + std::vector res; + + std::stringstream ss; + + write(ss, var); + read(ss, res); + + EXPECT_EQ(var, res); +} + TEST(SerializationTest, BasicTypes_map) { using namespace ov::npuw::s11n; @@ -258,6 +426,175 @@ TEST(SerializationTest, BasicTypes_optional) { EXPECT_EQ(var2, res2); } +TEST(SerializationTest, OVTypes_AnyMap) { + using namespace ov::npuw::s11n; + + ov::AnyMap var = {{"NPU_USE_NPUW", std::string("YES")}, + {"NPUW_FUNCALL_FOR_ALL", true}, + {"NPUW_ACC_THRESH", 0.125f}, + {"NPUW_LLM_BATCH_DIM", 7}}; + ov::AnyMap res; + + std::stringstream ss; + + write(ss, var); + read(ss, res); + + ASSERT_EQ(var.size(), res.size()); + EXPECT_EQ(res.at("NPU_USE_NPUW").as(), "YES"); + EXPECT_EQ(res.at("NPUW_FUNCALL_FOR_ALL").as(), true); + EXPECT_FLOAT_EQ(res.at("NPUW_ACC_THRESH").as(), 0.125f); + EXPECT_EQ(res.at("NPUW_LLM_BATCH_DIM").as(), 7); +} + +TEST(SerializationTest, OVTypes_ElementType) { + using namespace ov::npuw::s11n; + + ov::element::Type var = ov::element::f16; + ov::element::Type res; + + std::stringstream ss; + + write(ss, var); + read(ss, res); + + EXPECT_EQ(var, res); +} + +TEST(SerializationTest, OVTypes_CacheMode) { + using namespace ov::npuw::s11n; + + ov::CacheMode var = ov::CacheMode::OPTIMIZE_SPEED; + ov::CacheMode res = ov::CacheMode::OPTIMIZE_SIZE; + + std::stringstream ss; + + write(ss, var); + read(ss, res); + + EXPECT_EQ(var, res); +} + +TEST(SerializationTest, OVTypes_PerformanceMode) { + using namespace ov::npuw::s11n; + + ov::hint::PerformanceMode var = ov::hint::PerformanceMode::LATENCY; + ov::hint::PerformanceMode res = ov::hint::PerformanceMode::THROUGHPUT; + + std::stringstream ss; + + write(ss, var); + read(ss, res); + + EXPECT_EQ(var, res); +} + +TEST(SerializationTest, OVTypes_Attention) { + using namespace ov::npuw::s11n; + + ov::npuw::compiled::Attention var; + var.query_size = 3; + var.context_size = 5; + var.params = {{1, 2}, {3, 4}}; + var.mask_idx = 7; + var.attend_all = ov::Tensor(ov::element::f32, ov::Shape{1, 1, 3, 5}); + std::vector mask_data(var.attend_all.get_size()); + std::iota(mask_data.begin(), mask_data.end(), -2.0f); + std::memcpy(var.attend_all.data(), mask_data.data(), var.attend_all.get_byte_size()); + + ov::npuw::compiled::Attention res; + + std::stringstream ss; + + write(ss, var); + read(ss, res); + + expect_attention_equal(var, res); +} + +TEST(SerializationTest, OVTypes_PyramidAttention) { + using namespace ov::npuw::s11n; + + ov::npuw::compiled::PyramidAttention var; + var.query_size = 16; + var.full_context_size = 128; + var._context_lengths = {16, 32, 64, 128}; + var._attention_infos = {{{{0, 2}, {1, 3}}, 4, 16, 32}, {{{2, 1}}, 5, 16, 64}}; + + ov::npuw::compiled::PyramidAttention res; + + std::stringstream ss; + + write(ss, var); + read(ss, res); + + expect_pyramid_attention_equal(var, res); +} + +TEST(SerializationTest, OVTypes_HostFlashAttention) { + using namespace ov::npuw::s11n; + + ov::npuw::compiled::HostFlashAttention var; + var._sdpa_attention_info._query_size = 8; + var._sdpa_attention_info._context_size = 32; + var._sdpa_attention_info._k_seq_dim = 1; + var._sdpa_attention_info._v_seq_dim = 2; + var._sdpa_attention_info._sdpa_indices = {3, 4, 5, 6, 7, 8}; + var._sdpa_attention_info._tile_input_indices = {9, 10, 11, 12, 13, 14, 15}; + var._sdpa_attention_info._tile_output_indices = {16, 17, 18}; + var._tile_size = 64; + var._can_use_tensor_view = true; + + ov::npuw::compiled::HostFlashAttention res; + + std::stringstream ss; + + write(ss, var); + read(ss, res); + + expect_host_flash_attention_equal(var, res); +} + +TEST(SerializationTest, OVTypes_MoEExperts) { + using namespace ov::npuw::s11n; + + ov::npuw::compiled::MoEExperts var; + var.num_experts = 16; + var.expert_hidden_dim = 128; + var.num_active_experts = 2; + var.input_token_count = 64; + var._router_scores_idx = 5; + var._expert_input_param_idx = 3; + var._param_mapping = {{0, {1, 2}}, {4, {6, 7, 8}}}; + + ov::npuw::compiled::MoEExperts res; + + std::stringstream ss; + + write(ss, var); + read(ss, res); + + expect_moe_experts_equal(var, res); +} + +TEST(SerializationTest, OVTypes_MoEDownstream) { + using namespace ov::npuw::s11n; + + ov::npuw::compiled::MoEDownstream var; + var.total_experts_num = 16; + var.active_experts_num = 2; + var.expert_output_param_idx = 9; + + ov::npuw::compiled::MoEDownstream res; + + std::stringstream ss; + + write(ss, var); + read(ss, res); + + expect_moe_downstream_equal(var, res); +} + // "with_weights" is an option for read/write_weightless() when Constant in our model is not present in the original // it reads/writes the whole ov::Tensor TEST(SerializationTest, OVTypes_Tensor_with_weights) { @@ -289,4 +626,307 @@ TEST(SerializationTest, OVTypes_Tensor_with_weights) { EXPECT_EQ(data, data_res); } +TEST(SerializationTest, OVTypes_Tensor_empty) { + using namespace ov::npuw::s11n; + + ov::Tensor var; + ov::Tensor res(ov::element::u8, ov::Shape{1}); + + std::stringstream ss; + + write(ss, var); + read(ss, res); + + EXPECT_FALSE(res); +} + +TEST(SerializationTest, OVTypes_Tensor_non_contiguous) { + using namespace ov::npuw::s11n; + + std::vector storage{1.f, 2.f, 100.f, 3.f, 4.f, 200.f}; + ov::Strides strides{3 * sizeof(float), sizeof(float)}; + ov::Tensor var(ov::element::f32, ov::Shape{2, 2}, storage.data(), strides); + ASSERT_FALSE(var.is_continuous()); + + ov::Tensor res; + std::stringstream ss; + + write(ss, var); + read(ss, res); + + ASSERT_TRUE(res.is_continuous()); + std::vector expected_values{1.f, 2.f, 3.f, 4.f}; + ov::Tensor expected(ov::element::f32, ov::Shape{2, 2}, expected_values.data()); + expect_tensors_equal(expected, res); +} + +TEST(SerializationTest, OVTypes_Tensor_allocator) { + using namespace ov::npuw::s11n; + + ov::Tensor var(ov::element::i32, ov::Shape{2, 2}); + std::vector values{1, 2, 3, 4}; + std::memcpy(var.data(), values.data(), var.get_byte_size()); + + std::stringstream ss; + auto output_stream = Stream::writer(ss); + transfer_tensor(output_stream, var); + + bool allocator_called = false; + ov::Tensor res; + auto input_stream = Stream::reader(ss); + transfer_tensor(input_stream, + res, + [&](const ov::element::Type& type, const ov::Shape& shape) { + allocator_called = true; + return ov::Tensor(type, shape); + }); + + EXPECT_TRUE(allocator_called); + expect_tensors_equal(var, res); +} + +TEST(SerializationTest, OVTypes_Tensor_weightless_bf16_to_f16) { + using namespace ov::npuw::s11n; + + std::vector expected_values = {ov::float16(1.0f), ov::float16(-2.5f), ov::float16(3.25f)}; + ov::Tensor var(ov::element::f16, ov::Shape{3}, expected_values.data()); + + std::stringstream ss; + WeightsContext export_ctx(true, {{var.data(), 0}}); + write_weightless(ss, {var}, export_ctx); + + std::filesystem::path file_path = ov::test::utils::generateTestFilePrefix() + "_npuw_bf16_weights.bin"; + { + std::ofstream os(file_path, std::ios::binary); + std::vector bf16_values = {ov::bfloat16(1.0f), ov::bfloat16(-2.5f), ov::bfloat16(3.25f)}; + os.write(reinterpret_cast(bf16_values.data()), + static_cast(bf16_values.size() * sizeof(ov::bfloat16))); + } + + std::vector res; + { + auto mapped = ov::load_mmap_object(file_path); + ASSERT_NE(mapped, nullptr); + auto weights = std::make_shared(reinterpret_cast(mapped->data()), mapped->size(), mapped); + + WeightsContext import_ctx(weights, file_path.string(), {}, {{0, var.get_byte_size()}}); + read_weightless(ss, res, import_ctx); + } // mapped + weights released here, file handle closed on Windows + + ASSERT_EQ(res.size(), 1); + expect_tensors_equal(var, res.front()); + std::filesystem::remove(file_path); +} + +TEST(SerializationTest, OVTypes_OutputPort_roundtrips_into_parameter_pointer) { + using namespace ov::npuw::s11n; + + auto param = std::make_shared(ov::element::f32, ov::PartialShape{1, 3}); + param->set_friendly_name("input"); + param->output(0).get_tensor().set_names({"input", "alias"}); + + std::shared_ptr res; + std::stringstream ss; + ov::Output output = param->output(0); + + write(ss, output); + read(ss, res); + + ASSERT_NE(res, nullptr); + EXPECT_EQ(res->get_element_type(), param->get_element_type()); + EXPECT_EQ(res->get_partial_shape(), param->get_partial_shape()); + EXPECT_EQ(res->output(0).get_tensor().get_names(), param->output(0).get_tensor().get_names()); +} + +TEST(SerializationTest, OVTypes_OutputPort_roundtrips_into_node_pointer) { + using namespace ov::npuw::s11n; + + auto param = std::make_shared(ov::element::i32, ov::PartialShape{2, 4}); + param->set_friendly_name("node_input"); + param->output(0).get_tensor().set_names({"node_input"}); + + std::shared_ptr res; + std::stringstream ss; + ov::Output output = param->output(0); + + write(ss, output); + read(ss, res); + + ASSERT_NE(res, nullptr); + EXPECT_EQ(res->get_friendly_name(), "node_input"); + EXPECT_EQ(res->output(0).get_tensor().get_names(), param->output(0).get_tensor().get_names()); +} + +TEST(SerializationTest, OVTypes_OutputPort_throws_on_read) { + using namespace ov::npuw::s11n; + + auto param = std::make_shared(ov::element::f32, ov::PartialShape{1}); + std::stringstream ss; + ov::Output output = param->output(0); + write(ss, output); + + EXPECT_THROW(read(ss, output), ov::Exception); +} + +TEST(SerializationTest, OVTypes_ParameterPointer_throws_on_write) { + using namespace ov::npuw::s11n; + + auto param = std::make_shared(ov::element::f32, ov::PartialShape{1}); + std::stringstream ss; + + EXPECT_THROW(write(ss, param), ov::Exception); +} + +TEST(SerializationTest, OVTypes_NodePointer_throws_on_write) { + using namespace ov::npuw::s11n; + + std::shared_ptr node = + std::make_shared(ov::element::f32, ov::PartialShape{1}); + std::stringstream ss; + + EXPECT_THROW(write(ss, node), ov::Exception); +} + +TEST(SerializationTest, OVTypes_LazyTensor_uninitialized) { + using namespace ov::npuw::s11n; + + ov::npuw::weights::LazyTensor var; + ov::npuw::weights::LazyTensor res; + + std::stringstream ss; + + write(ss, var); + read(ss, res); + + EXPECT_FALSE(var); + EXPECT_FALSE(res); + EXPECT_EQ(var, res); +} + +TEST(SerializationTest, OVTypes_LazyTensor_const_roundtrip) { + using namespace ov::npuw::s11n; + + auto constant = make_weightless_constant(ov::element::f32, ov::Shape{2}, {1.0f, 2.0f}, 0); + ov::npuw::weights::LazyTensor var(constant); + ov::npuw::weights::LazyTensor res; + + std::stringstream ss; + write(ss, var); + read(ss, res); + res.read_weight(WeightsContext(nullptr, "", make_consts_cache({constant}), {})); + + expect_lazy_tensor_transform_types_equal(var, res); + EXPECT_EQ(var.eval_meta().shape, res.eval_meta().shape); + EXPECT_EQ(var.eval_meta().type, res.eval_meta().type); + expect_tensors_equal(var.eval(), res.eval()); +} + +TEST(SerializationTest, OVTypes_LazyTensor_const_with_embedded_weight_roundtrip) { + using namespace ov::npuw::s11n; + + auto constant = std::make_shared(ov::element::f32, ov::Shape{2}, std::vector{1.0f, 2.0f}); + ov::npuw::weights::LazyTensor var(constant); + ov::npuw::weights::LazyTensor res; + + std::stringstream ss; + write(ss, var); + read(ss, res); + + expect_lazy_tensor_transform_types_equal(var, res); + EXPECT_EQ(var.eval_meta().shape, res.eval_meta().shape); + EXPECT_EQ(var.eval_meta().type, res.eval_meta().type); + expect_tensors_equal(var.eval(), res.eval()); +} + +TEST(SerializationTest, OVTypes_LazyTensor_concat_permute_convert_roundtrip) { + using namespace ov::npuw::s11n; + + auto first = make_weightless_constant(ov::element::f32, ov::Shape{1, 2}, {1.0f, 2.0f}, 0); + auto second = make_weightless_constant(ov::element::f32, ov::Shape{1, 2}, {3.0f, 4.0f}, first->get_byte_size()); + ov::npuw::weights::LazyTensor concat(std::vector{ + ov::npuw::weights::LazyTensor(first), + ov::npuw::weights::LazyTensor(second)}, + 0); + auto var = concat.permute({1, 0}).convert(ov::element::f16); + ov::npuw::weights::LazyTensor res; + + std::stringstream ss; + write(ss, var); + read(ss, res); + res.read_weight(WeightsContext(nullptr, "", make_consts_cache({first, second}), {})); + + expect_lazy_tensor_transform_types_equal(var, res); + EXPECT_EQ(var.get_hash(), res.get_hash()); + EXPECT_EQ(var.eval_meta().shape, res.eval_meta().shape); + EXPECT_EQ(var.eval_meta().type, res.eval_meta().type); +} + +TEST(SerializationTest, OVTypes_LazyTensor_unpack_roundtrip) { + using namespace ov::npuw::s11n; + + auto w = make_weightless_constant(ov::element::u8, ov::Shape{8}, {1, 2, 3, 4, 5, 6, 7, 8}, 0); + auto s = make_weightless_constant(ov::element::f16, ov::Shape{8}, {1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f}, w->get_byte_size()); + ov::npuw::weights::LazyTensor var(ov::npuw::weights::LazyTensor(w), + ov::npuw::weights::LazyTensor(), + ov::npuw::weights::LazyTensor(s), + ov::element::f16, + ov::Shape{8}); + ov::npuw::weights::LazyTensor res; + + std::stringstream ss; + write(ss, var); + read(ss, res); + res.read_weight(WeightsContext(nullptr, "", make_consts_cache({w, s}), {})); + + expect_lazy_tensor_transform_types_equal(var, res); + EXPECT_EQ(var.eval_meta().shape, res.eval_meta().shape); + EXPECT_EQ(var.eval_meta().type, res.eval_meta().type); +} + +TEST(SerializationTest, OVTypes_LazyTensor_gather_roundtrip) { + using namespace ov::npuw::s11n; + + auto w = make_weightless_constant(ov::element::u8, ov::Shape{16}, {0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15}, + 0); + std::vector indices{0, 1, 2, 3}; + ov::Tensor lut(ov::element::f8e4m3, ov::Shape{4}, indices.data()); + ov::npuw::weights::LazyTensor var(ov::npuw::weights::LazyTensor(w), lut, ov::element::f16, ov::Shape{8}); + ov::npuw::weights::LazyTensor res; + + std::stringstream ss; + write(ss, var); + read(ss, res); + res.read_weight(WeightsContext(nullptr, "", make_consts_cache({w}), {})); + + expect_lazy_tensor_transform_types_equal(var, res); + EXPECT_EQ(var.eval_meta().shape, res.eval_meta().shape); + EXPECT_EQ(var.eval_meta().type, res.eval_meta().type); +} + +TEST(SerializationTest, OVTypes_WeightsBank_cpu_roundtrip) { + using namespace ov::npuw::s11n; + + auto first = make_weightless_constant(ov::element::f32, ov::Shape{2}, {1.0f, 2.0f}, 0); + auto second = make_weightless_constant(ov::element::f32, ov::Shape{2}, {3.0f, 4.0f}, first->get_byte_size()); + + ov::npuw::weights::Bank var(nullptr, "CPU", "test-bank"); + const auto uid0 = var.registerLT(ov::npuw::weights::LazyTensor(first), "CPU"); + const auto uid0_dup = var.registerLT(ov::npuw::weights::LazyTensor(first), "CPU"); + const auto uid1 = var.registerLT(ov::npuw::weights::LazyTensor(second), "CPU"); + EXPECT_EQ(uid0, uid0_dup); + EXPECT_NE(uid0, uid1); + + var.evaluate_and_allocate(); + + ov::npuw::weights::Bank res(nullptr, "CPU", "restored-bank"); + std::stringstream ss; + + write(ss, var); + read(ss, res); + + expect_tensors_equal(var.get(uid0, "CPU"), res.get(uid0, "CPU")); + expect_tensors_equal(var.get(uid1, "CPU"), res.get(uid1, "CPU")); +} + // TODO: add tests on CompiledModel and LLMCompiledModel once tests have access to any model to test on From d2f02370885b9a737a68a10373a05a79f718a102 Mon Sep 17 00:00:00 2001 From: Oleg Pipikin Date: Thu, 23 Apr 2026 10:34:12 +0200 Subject: [PATCH 034/545] Fix in lto support check (#35354) ### Details: - Fix in lto support check. Align check with https://github.com/openvinotoolkit/openvino.genai/pull/3712 ### Tickets: - *ticket-id* ### AI Assistance: - *AI assistance used: no / yes* - *If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).* --- cmake/developer_package/OpenVINODeveloperScriptsConfig.cmake | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmake/developer_package/OpenVINODeveloperScriptsConfig.cmake b/cmake/developer_package/OpenVINODeveloperScriptsConfig.cmake index 7d7d089d6433f9..0f013b7a385890 100644 --- a/cmake/developer_package/OpenVINODeveloperScriptsConfig.cmake +++ b/cmake/developer_package/OpenVINODeveloperScriptsConfig.cmake @@ -253,8 +253,7 @@ if(ENABLE_LTO) LANGUAGES C CXX) if(NOT IPO_SUPPORTED) - set(ENABLE_LTO "OFF" CACHE STRING "Enable Link Time Optimization" FORCE) - message(WARNING "IPO / LTO is not supported: ${OUTPUT_MESSAGE}") + message(FATAL_ERROR "ENABLE_LTO is ON but IPO / LTO is not supported: ${OUTPUT_MESSAGE}") endif() endif() From e1dde008fc7783060f9c86c89c4b3e36b765cc6f Mon Sep 17 00:00:00 2001 From: Paul Youngsoo Ahn Date: Thu, 23 Apr 2026 18:02:22 +0900 Subject: [PATCH 035/545] [GPU] Fix implicit narrowing conversion warnings (C4244) in GPU plugin - Step 2 (#35444) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Details: ### Details: - Continue fixing implicit narrowing conversions (C4244) in GPU plugin for `/wd4244` removal (BinSkim BA2007 compliance) - Widen local variables to match source types (`int64_t`, `ov::Dimension::value_type`, `tensor::value_type`) instead of narrowing with `static_cast`; use `static_cast` only at API boundaries (kernel_selector `uint32_t`, oneDNN `int`, OpenCL API) - Fix `double` → `float` narrowing with `sqrtf()`, float literals (`0.5f`), or `static_cast()` in kernel param calculations - Deduplicate conversion patterns: `append_from_lock` lambda in `memory.hpp`, `to_i32_vec`/`to_u8_vec` helpers in `strided_slice.cpp` - Fix type mismatches in debug/runtime infra: `m_iter` (`size_t` → `int64_t`), `dump()` signature (`uint32_t` → `uint64_t`) - Remove dead code in `border.cpp` and `detection_output.cpp` ### Tickets: - [CVS-185007](https://jira.devtools.intel.com/browse/CVS-185007) ### AI Assistance: - AI assistance used: yes - GitHub Copilot used for code analysis (identifying root causes of each warning, evaluating fix options for type safety), generating fix code, and verifying build results with `CMAKE_COMPILE_WARNING_AS_ERROR=ON` (`/WX`). All changes were human-reviewed for correctness, validated via local `/WX` clean build with `ENABLE_TESTS=ON`, and confirmed to have no performance or accuracy impact. --- .../include/intel_gpu/runtime/memory.hpp | 13 +- .../include/intel_gpu/runtime/memory_pool.hpp | 6 +- .../intel_gpu/src/graph/batch_to_space.cpp | 2 +- .../intel_gpu/src/graph/debug_helper.cpp | 2 +- .../intel_gpu/src/graph/debug_helper.hpp | 4 +- .../intel_gpu/src/graph/deconvolution.cpp | 4 +- .../intel_gpu/src/graph/detection_output.cpp | 29 +---- ...mental_detectron_roi_feature_extractor.cpp | 4 +- src/plugins/intel_gpu/src/graph/gather_nd.cpp | 2 +- .../graph_optimizer/concat_input_order.cpp | 9 +- .../graph_optimizer/prepare_buffer_fusing.cpp | 8 +- .../graph_optimizer/prepare_quantization.cpp | 8 +- .../remove_redundant_reorders.cpp | 2 +- .../graph/impls/cm/paged_attention_gen.cpp | 4 +- .../src/graph/impls/cm/vl_sdpa_opt.cpp | 2 +- .../src/graph/impls/cpu/detection_output.cpp | 120 +++++++++--------- .../graph/impls/cpu/non_max_suppression.cpp | 6 +- .../src/graph/impls/cpu/proposal.cpp | 22 ++-- .../src/graph/impls/ocl/arg_max_min.cpp | 2 +- .../intel_gpu/src/graph/impls/ocl/border.cpp | 3 - .../src/graph/impls/ocl/convolution.cpp | 6 +- .../graph/impls/ocl/ctc_greedy_decoder.cpp | 2 +- .../src/graph/impls/ocl/custom_primitive.cpp | 2 +- .../src/graph/impls/ocl/deconvolution.cpp | 6 +- .../src/graph/impls/ocl/detection_output.cpp | 8 +- .../impls/ocl/kernel_selector_helper.cpp | 4 +- .../intel_gpu/src/graph/impls/ocl/one_hot.cpp | 4 +- .../intel_gpu/src/graph/impls/ocl/pooling.cpp | 6 +- .../src/graph/impls/ocl/prior_box.cpp | 8 +- .../intel_gpu/src/graph/impls/ocl/reduce.cpp | 2 +- .../intel_gpu/src/graph/impls/ocl/reorder.cpp | 2 +- .../src/graph/impls/ocl/strided_slice.cpp | 28 ++-- .../intel_gpu/src/graph/impls/ocl/swiglu.cpp | 4 +- .../impls/ocl_v2/gated_delta_net_ref.cpp | 2 +- .../impls/ocl_v2/moe/moe_3gemm_gen_micro.cpp | 4 +- .../impls/ocl_v2/moe/moe_gemm_gen_opt.hpp | 2 +- .../impls/ocl_v2/sdpa/sdpa_gen_micro.cpp | 28 ++-- .../impls/onednn/concatenation_onednn.cpp | 2 +- .../impls/onednn/fully_connected_onednn.cpp | 10 +- .../src/graph/impls/onednn/utils.cpp | 3 +- .../intel_gpu/src/graph/layout_optimizer.cpp | 8 +- .../intel_gpu/src/graph/multiclass_nms.cpp | 2 +- src/plugins/intel_gpu/src/graph/one_hot.cpp | 2 +- src/plugins/intel_gpu/src/graph/permute.cpp | 2 +- .../intel_gpu/src/graph/primitive_inst.cpp | 8 +- src/plugins/intel_gpu/src/graph/prior_box.cpp | 20 +-- .../src/graph/program_dump_graph.cpp | 2 +- .../intel_gpu/src/graph/program_node.cpp | 6 +- src/plugins/intel_gpu/src/graph/reduce.cpp | 2 +- src/plugins/intel_gpu/src/graph/reshape.cpp | 4 +- .../intel_gpu/src/graph/roi_pooling.cpp | 4 +- .../src/graph/scatter_elements_update.cpp | 6 +- .../intel_gpu/src/graph/space_to_batch.cpp | 2 +- .../multiclass_nms_kernel_ref.cpp | 4 +- .../resample/resample_kernel_pil_ref.cpp | 14 +- src/plugins/intel_gpu/src/plugin/graph.cpp | 8 +- .../intel_gpu/src/plugin/ops/convolution.cpp | 6 +- ...mental_detectron_roi_feature_extractor.cpp | 8 +- src/plugins/intel_gpu/src/plugin/ops/eye.cpp | 2 +- .../intel_gpu/src/plugin/ops/interpolate.cpp | 14 +- src/plugins/intel_gpu/src/plugin/ops/loop.cpp | 4 +- src/plugins/intel_gpu/src/plugin/ops/mvn.cpp | 4 +- .../src/plugin/ops/paged_attention.cpp | 2 +- src/plugins/intel_gpu/src/plugin/ops/rms.cpp | 5 +- .../intel_gpu/src/plugin/ops/roi_pooling.cpp | 14 +- .../src/plugin/ops/shuffle_channels.cpp | 4 +- src/plugins/intel_gpu/src/plugin/plugin.cpp | 4 +- .../optimize_subsequent_reshapes.cpp | 2 +- .../swiglu_fusion_with_clamp.cpp | 4 +- ...nsqueeze_broadcast_reshape_sdpa_fusion.cpp | 5 +- .../intel_gpu/src/runtime/memory_pool.cpp | 6 +- .../intel_gpu/src/runtime/ocl/ocl_stream.cpp | 2 +- 72 files changed, 276 insertions(+), 289 deletions(-) diff --git a/src/plugins/intel_gpu/include/intel_gpu/runtime/memory.hpp b/src/plugins/intel_gpu/include/intel_gpu/runtime/memory.hpp index 1e9ef900a148fc..a31e5eeecf95b2 100644 --- a/src/plugins/intel_gpu/include/intel_gpu/runtime/memory.hpp +++ b/src/plugins/intel_gpu/include/intel_gpu/runtime/memory.hpp @@ -274,25 +274,30 @@ inline std::vector read_vector(cldnn::memory::ptr mem, const cldnn::stream& s default: OPENVINO_ASSERT(false, "[GPU] read_vector: unsupported data type"); } } else { + auto append_from_lock = [](auto& lock, std::vector& out) { + out.reserve(lock.end() - lock.begin()); + for (auto it = lock.begin(); it != lock.end(); ++it) + out.push_back(static_cast(*it)); + }; switch (mem_dtype) { case data_types::i32: { mem_lock lock{mem, stream}; - out_vecs = std::move(std::vector(lock.begin(), lock.end())); + append_from_lock(lock, out_vecs); break; } case data_types::i64: { mem_lock lock{mem, stream}; - out_vecs = std::move(std::vector(lock.begin(), lock.end())); + append_from_lock(lock, out_vecs); break; } case data_types::f16: { mem_lock lock{mem, stream}; - out_vecs = std::move(std::vector(lock.begin(), lock.end())); + append_from_lock(lock, out_vecs); break; } case data_types::f32: { mem_lock lock{mem, stream}; - out_vecs = std::move(std::vector(lock.begin(), lock.end())); + append_from_lock(lock, out_vecs); break; } default: OPENVINO_ASSERT(false, "[GPU] read_vector: unsupported data type"); diff --git a/src/plugins/intel_gpu/include/intel_gpu/runtime/memory_pool.hpp b/src/plugins/intel_gpu/include/intel_gpu/runtime/memory_pool.hpp index f6e42f716d1a6d..5295c97913f4f3 100644 --- a/src/plugins/intel_gpu/include/intel_gpu/runtime/memory_pool.hpp +++ b/src/plugins/intel_gpu/include/intel_gpu/runtime/memory_pool.hpp @@ -195,12 +195,12 @@ class memory_pool { return _non_padded_pool.size(); } - void dump(uint32_t id, uint32_t iter, std::string dump_dir_path = ""); + void dump(uint32_t id, int64_t iter, std::string dump_dir_path = ""); size_t get_total_mem_pool_size(allocation_type type); private: - void dump_to_screen(uint32_t id, uint32_t iter); - void dump_to_file(uint32_t id, uint32_t iter, std::string dump_dir_path); + void dump_to_screen(uint32_t id, int64_t iter); + void dump_to_file(uint32_t id, int64_t iter, std::string dump_dir_path); #ifdef GPU_DEBUG_CONFIG std::vector _no_reusable_mems; diff --git a/src/plugins/intel_gpu/src/graph/batch_to_space.cpp b/src/plugins/intel_gpu/src/graph/batch_to_space.cpp index 1ea760782f7f8d..e4c3ada4028d8c 100644 --- a/src/plugins/intel_gpu/src/graph/batch_to_space.cpp +++ b/src/plugins/intel_gpu/src/graph/batch_to_space.cpp @@ -71,7 +71,7 @@ layout batch_to_space_inst::calc_output_layout(batch_to_space_node const& node, static std::vector tensor_to_vec(const tensor& t, const format f) { std::vector vec(cldnn::format::dimension(f)); for (size_t i = 0; i < vec.size(); ++i) { - vec[i] = t.sizes()[i]; + vec[i] = static_cast(t.sizes()[i]); } std::reverse(vec.begin() + 2, vec.end()); return vec; diff --git a/src/plugins/intel_gpu/src/graph/debug_helper.cpp b/src/plugins/intel_gpu/src/graph/debug_helper.cpp index 129ad9469bf80e..e7fb9443d617ba 100644 --- a/src/plugins/intel_gpu/src/graph/debug_helper.cpp +++ b/src/plugins/intel_gpu/src/graph/debug_helper.cpp @@ -691,7 +691,7 @@ NetworkDebugHelper::~NetworkDebugHelper() { } if (!config.get_dump_graphs_path().empty() && is_target_iteration(m_iter, config.get_dump_iterations())) { - auto get_fixed_str = [](size_t value, int length = 2) -> std::string { + auto get_fixed_str = [](int64_t value, int length = 2) -> std::string { std::ostringstream ss; ss << std::setw(length) << std::setfill('0') << std::to_string(value); return ss.str(); diff --git a/src/plugins/intel_gpu/src/graph/debug_helper.hpp b/src/plugins/intel_gpu/src/graph/debug_helper.hpp index 12883c0de14f2f..3ded63d39223c3 100644 --- a/src/plugins/intel_gpu/src/graph/debug_helper.hpp +++ b/src/plugins/intel_gpu/src/graph/debug_helper.hpp @@ -60,7 +60,7 @@ class NodeDebugHelper { stream& m_stream; const network& m_network; const program* m_program; - const size_t m_iter; + const int64_t m_iter; }; class NetworkDebugHelper { @@ -71,7 +71,7 @@ class NetworkDebugHelper { private: void dump_memory_pool(std::string dump_path, int64_t curr_iter) const; network& m_network; - const size_t m_iter; + const int64_t m_iter; }; #define NETWORK_DEBUG(net) NetworkDebugHelper __network_debug_helper(net) diff --git a/src/plugins/intel_gpu/src/graph/deconvolution.cpp b/src/plugins/intel_gpu/src/graph/deconvolution.cpp index a0c8e351e7a089..0e8ac831d6bd17 100644 --- a/src/plugins/intel_gpu/src/graph/deconvolution.cpp +++ b/src/plugins/intel_gpu/src/graph/deconvolution.cpp @@ -37,7 +37,7 @@ layout deconvolution_inst::calc_output_layout(deconvolution_node const& node, ke auto pad = desc->pad; auto strd = desc->stride; - int32_t number_of_features = weights_layout.group() * weights_layout.ofm(); + auto number_of_features = weights_layout.group() * weights_layout.ofm(); format out_fmt = input_layout.format; if (node.get_preferred_impl_type() == impl_types::onednn && node.get_preferred_output_fmt() != format::any) { @@ -127,7 +127,7 @@ std::vector deconvolution_inst::calc_output_layouts(deconvolution_node c auto output_padding = desc->out_padding; auto output_partial_shape = desc->output_partial_shape; - int32_t number_of_features = weights_layout.group() * weights_layout.ofm(); + auto number_of_features = weights_layout.group() * weights_layout.ofm(); format out_fmt = input_layout.format; if (node.get_preferred_impl_type() == impl_types::onednn && node.get_preferred_output_fmt() != format::any) { diff --git a/src/plugins/intel_gpu/src/graph/detection_output.cpp b/src/plugins/intel_gpu/src/graph/detection_output.cpp index 58369ab429e67d..16482400cc1da3 100644 --- a/src/plugins/intel_gpu/src/graph/detection_output.cpp +++ b/src/plugins/intel_gpu/src/graph/detection_output.cpp @@ -27,31 +27,6 @@ layout detection_output_inst::calc_output_layout(detection_output_node const& no auto input_layout = impl_param.get_input_layout(); - // Batch size and feature size are 1. - // Number of bounding boxes to be kept is set to keep_top_k*batch size. - // If number of detections is lower than top_k, will write dummy results at the end with image_id=-1. - // Each row is a 7 dimension vector, which stores: - // [image_id, label, confidence, xmin, ymin, xmax, ymax] - int output_size = static_cast(input_layout.get_linear_size()) / PRIOR_BOX_SIZE; - int num_classes = desc->num_classes; - - if (desc->share_location) { - num_classes = (desc->background_label_id == 0) ? desc->num_classes - 1 - : desc->num_classes; - output_size *= num_classes; - } - - if (desc->top_k != -1) { - int top_k = desc->top_k * num_classes * input_layout.batch(); - if (top_k < output_size) { - output_size = top_k; - } - } - - output_size *= DETECTION_OUTPUT_ROW_SIZE; - // Add space for number of output results per image - needed in the next detection output step - output_size += ((input_layout.batch() + 15) / 16) * 16; - return {input_layout.data_type, cldnn::format::bfyx, cldnn::tensor(1, 1, DETECTION_OUTPUT_ROW_SIZE, desc->keep_top_k * input_layout.batch())}; } @@ -198,14 +173,14 @@ detection_output_inst::typed_primitive_inst(network& network, detection_output_n "Location input dimensions", (location_layout.feature() * location_layout.batch()), "detection output layer dimensions", - static_cast(location_layout.count()), + location_layout.count(), "Location input/ detection output dims mismatch"); CLDNN_ERROR_NOT_EQUAL(node.id(), "Confidence input dimensions", (confidence_layout.feature() * confidence_layout.batch()), "detection output layer dimensions", - static_cast(confidence_layout.count()), + confidence_layout.count(), "Confidence input/detection output dims mistmach"); CLDNN_ERROR_NOT_EQUAL(node.id(), diff --git a/src/plugins/intel_gpu/src/graph/experimental_detectron_roi_feature_extractor.cpp b/src/plugins/intel_gpu/src/graph/experimental_detectron_roi_feature_extractor.cpp index a52dc870cd3c62..913f37fd9c7b29 100644 --- a/src/plugins/intel_gpu/src/graph/experimental_detectron_roi_feature_extractor.cpp +++ b/src/plugins/intel_gpu/src/graph/experimental_detectron_roi_feature_extractor.cpp @@ -55,8 +55,8 @@ layout experimental_detectron_roi_feature_extractor_inst::calc_output_layout( "Output data type forcing is not supported for roi_pooling_node!"); layout rois_layout = impl_param.get_input_layout(0); layout data_layout = impl_param.get_input_layout(1); - int num_rois = rois_layout.batch(); - int num_channels = data_layout.feature(); + int64_t num_rois = rois_layout.batch(); + int64_t num_channels = data_layout.feature(); auto desc = impl_param.typed_desc(); return layout(data_layout.data_type, format::bfyx, {num_rois, num_channels, desc->output_dim, desc->output_dim}); diff --git a/src/plugins/intel_gpu/src/graph/gather_nd.cpp b/src/plugins/intel_gpu/src/graph/gather_nd.cpp index b372480844a02b..dd69ac4cef8442 100644 --- a/src/plugins/intel_gpu/src/graph/gather_nd.cpp +++ b/src/plugins/intel_gpu/src/graph/gather_nd.cpp @@ -42,7 +42,7 @@ layout gather_nd_inst::calc_output_layout(gather_nd_node const& node, kernel_imp if (op->batch_merged_output) { // calculate batch_size by batch_dims - int batch_size = 1; + tensor::value_type batch_size = 1; for (uint8_t x = 0; x < batch_dims; x++) { batch_size *= output_sizes[x]; } diff --git a/src/plugins/intel_gpu/src/graph/graph_optimizer/concat_input_order.cpp b/src/plugins/intel_gpu/src/graph/graph_optimizer/concat_input_order.cpp index 60211902892363..c1edc869961637 100644 --- a/src/plugins/intel_gpu/src/graph/graph_optimizer/concat_input_order.cpp +++ b/src/plugins/intel_gpu/src/graph/graph_optimizer/concat_input_order.cpp @@ -17,7 +17,7 @@ using namespace cldnn; namespace { -using shuffle_range = std::pair; +using shuffle_range = std::pair; bool can_shuffle_features(program_node& node, program_node& concat_node, stream& stream) { if (node.is_type()) { @@ -70,10 +70,13 @@ void shuffle_weights(data_node& node, const std::vector& ranges, mem_lock new_weights_memory_lock{new_weights_memory, stream}; auto old_ptr = old_weights_memory_lock.data(); auto new_ptr = new_weights_memory_lock.data(); + for (int32_t ofi = 0; ofi < wei_layout.batch(); ++ofi) { int32_t new_ifi = 0; for (auto& range : ranges) { - for (int32_t ifi = range.first; ifi < range.second; ++ifi, ++new_ifi) { + const int32_t range_begin = static_cast(range.first); + const int32_t range_end = static_cast(range.second); + for (int32_t ifi = range_begin; ifi < range_end; ++ifi, ++new_ifi) { for (int32_t wi = 0; wi < wei_layout.spatial(3); ++wi) { for (int32_t zi = 0; zi < wei_layout.spatial(2); ++zi) { for (int32_t yi = 0; yi < wei_layout.spatial(1); ++yi) { @@ -179,7 +182,7 @@ void concat_input_order::run(program& p) { new_order.push_back(i); } // Calculate new ranges - int32_t current_offset = 0; + tensor::value_type current_offset = 0; std::vector original_ranges; original_ranges.reserve(inputs_count); for (auto& feature_size : feature_sizes) { diff --git a/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_buffer_fusing.cpp b/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_buffer_fusing.cpp index 710dfa6d1c85f6..a165c86ef0b998 100644 --- a/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_buffer_fusing.cpp +++ b/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_buffer_fusing.cpp @@ -141,7 +141,7 @@ bool concat_in_place_optimization::match(const program_node& concat_node, // which would affect a form of its output (unless debug flag is set), // we also need to restrict input types to those which support padding on all axis if (!pred.first->is_dynamic() || is_runtime) { - if (!pred.first->is_padding_supported(static_cast(concat_axis), lower_padd_in_axis)) + if (!pred.first->is_padding_supported(static_cast(concat_axis), static_cast(lower_padd_in_axis))) return false; } // TODO: handle optimized reshape @@ -454,7 +454,7 @@ static bool can_read_value_be_optimize(const read_value_node& node) { return true; // following pattern should be optimized, otherwise it could lead to corruptted data. - // readvalue's users eventually need to pass kvcache before assign, which makes kvcache node the dominator of assign node, + // readvalue's users eventually need to pass kvcache before assign, which makes kvcache node the dominator of assign node, // it could be safely treated as if readvalue is directly connecting to kvcache. // readvalue --> any // | | @@ -706,7 +706,7 @@ void crop_in_place_optimization::update_in_place_crop_padding_simple_data_format // output_pattern[0] == -1 means the batch dim is absorbed (squeezed). reshape_axis = 0; } else { - auto mul = 1; + ov::Dimension::value_type mul = 1; auto reshape_ps = user_info.second.get_partial_shape(); reshape_axis = reshape_ps.size() - 1; auto crop_dim_val = crop_layout.get_partial_shape()[crop_axis].get_length(); @@ -778,7 +778,7 @@ void crop_in_place_optimization::update_in_place_crop_padding_simple_data_format reshape_upper_sizes[0] = upper_sizes[0] * batch_stride_factor; reshape_dyn_pad_mask[0] = 1; } else { - auto divider = 1; + ov::Dimension::value_type divider = 1; auto reshape_axis = reshape_ps.size(); for (size_t i = reshape_ps.size(); i > 1; i--) { const auto& dim_value = reshape_ps[i - 1].get_length(); diff --git a/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_quantization.cpp b/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_quantization.cpp index 60581a20bd843e..52afa3ef664706 100644 --- a/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_quantization.cpp +++ b/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_quantization.cpp @@ -83,14 +83,14 @@ void prepare_quantization::prepare_scale_shift_opt(program &p, quantize_node& qu auto mem_output_scale = p.get_engine().allocate_memory(scales_layout, false); auto mem_output_shift = p.get_engine().allocate_memory(scales_layout, false); - auto get_offset_safe = [](const layout& l, const tensor& idx) -> int { + auto get_offset_safe = [](const layout& l, const tensor& idx) -> tensor::value_type { auto sizes = l.get_tensor(); auto pitches = l.get_pitches(); return (idx.batch[0] % sizes.batch[0])*pitches[0] - + (idx.feature[0] % sizes.feature[0])*pitches[1] - + (idx.spatial[1] % sizes.spatial[1])*pitches[2 + 0] // y - + (idx.spatial[0] % sizes.spatial[0])*pitches[2 + 1]; // x + + (idx.feature[0] % sizes.feature[0])*pitches[1] + + (idx.spatial[1] % sizes.spatial[1])*pitches[2 + 0] // y + + (idx.spatial[0] % sizes.spatial[0])*pitches[2 + 1]; // x }; auto lock_memory = [&stream] (memory::ptr memory, std::function& set_data, diff --git a/src/plugins/intel_gpu/src/graph/graph_optimizer/remove_redundant_reorders.cpp b/src/plugins/intel_gpu/src/graph/graph_optimizer/remove_redundant_reorders.cpp index 90fcd59571b992..e089bd8240cfa1 100644 --- a/src/plugins/intel_gpu/src/graph/graph_optimizer/remove_redundant_reorders.cpp +++ b/src/plugins/intel_gpu/src/graph/graph_optimizer/remove_redundant_reorders.cpp @@ -573,7 +573,7 @@ void remove_redundant_reorders::run(program& p) { auto sizes_in_format = layout::format_sizes(node->get_input_layout(0).data_padding._lower_size, node_format); for (size_t axis = 0; axis < sizes_in_format.size(); axis++) { if (!user->is_padding_supported(static_cast(axis), - sizes_in_format[axis])) + static_cast(sizes_in_format[axis]))) return false; } } diff --git a/src/plugins/intel_gpu/src/graph/impls/cm/paged_attention_gen.cpp b/src/plugins/intel_gpu/src/graph/impls/cm/paged_attention_gen.cpp index 1b195f566fec4b..14ba0c04bcd980 100644 --- a/src/plugins/intel_gpu/src/graph/impls/cm/paged_attention_gen.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/cm/paged_attention_gen.cpp @@ -731,8 +731,8 @@ DispatchDataFunc XAttentionEstimateFindBlock::get_dispatch_data_func() const { const size_t q_len = out_shape[0]; const size_t sum_per_n_token_in_block = static_cast(rtp->xattn_block_size / STRIDE); - const uint32_t q_block = ceil_div(rtp->M, sum_per_n_token_in_block); - const uint32_t k_block = ceil_div(rtp->N, sum_per_n_token_in_block); + const size_t q_block = static_cast(ceil_div(rtp->M, sum_per_n_token_in_block)); + const size_t k_block = static_cast(ceil_div(rtp->N, sum_per_n_token_in_block)); const float xattn_thresh = get_xattn_thresh(params); diff --git a/src/plugins/intel_gpu/src/graph/impls/cm/vl_sdpa_opt.cpp b/src/plugins/intel_gpu/src/graph/impls/cm/vl_sdpa_opt.cpp index cf8ff57ac02970..e4c12f182d0044 100644 --- a/src/plugins/intel_gpu/src/graph/impls/cm/vl_sdpa_opt.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/cm/vl_sdpa_opt.cpp @@ -55,7 +55,7 @@ class VLSDPAGenerator : public KernelGenerator { const size_t head_size = key_shape[query_shape.size() - 1].get_length(); const size_t num_q_heads = query_shape[query_shape.size() - 3].get_length(); const size_t num_kv_heads = key_shape[key_shape.size() - 3].get_length(); - const float scale_factor = 1.0 / std::sqrt(static_cast(head_size)); + const float scale_factor = 1.0f / std::sqrt(static_cast(head_size)); GPU_DEBUG_TRACE_DETAIL << "VLSDPA query_shape " << query_shape << ", q_transpose_order " << PartialShape(desc->input_q_transpose_order) << ", key_shape " << key_shape << ", k_transpose_order " << PartialShape(desc->input_k_transpose_order) diff --git a/src/plugins/intel_gpu/src/graph/impls/cpu/detection_output.cpp b/src/plugins/intel_gpu/src/graph/impls/cpu/detection_output.cpp index 485dd89759cdb9..53b308500590d4 100644 --- a/src/plugins/intel_gpu/src/graph/impls/cpu/detection_output.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/cpu/detection_output.cpp @@ -276,7 +276,7 @@ struct detection_output_impl : typed_primitive_impl { template void generate_detections(stream& stream, const detection_output_inst& instance, - const int num_of_images, + const int64_t num_of_images, const std::vector>>& all_bboxes, std::vector>>>& confidences, std::vector>>>& scoreIndexPairs) { @@ -288,11 +288,11 @@ struct detection_output_impl : typed_primitive_impl { auto confidence_layout = instance.confidence_memory()->get_layout(); auto priors_layout = instance.prior_box_memory()->get_layout(); - const int num_of_priors = priors_layout.spatial(1) / args->prior_info_size; - const int num_classes = (args->num_classes == -1) ? confidence_layout.feature() / num_of_priors : args->num_classes; + const int64_t num_of_priors = priors_layout.spatial(1) / args->prior_info_size; + const int64_t num_classes = (args->num_classes == -1) ? confidence_layout.feature() / num_of_priors : args->num_classes; // Per image -> For each label: Pair (score, prior index) std::vector>>> final_detections; - for (int image = 0; image < num_of_images; ++image) { + for (int64_t image = 0; image < num_of_images; ++image) { const std::vector>& bboxes_per_image = all_bboxes[image]; std::vector>>& conf_per_image = confidences[image]; std::map> indices; @@ -411,18 +411,18 @@ struct detection_output_impl : typed_primitive_impl { } // Compute the linear index taking the padding into account. - static inline int get_linear_feature_index(const int batch_id, - const int feature_id, - const int input_buffer_size_f, - const int input_buffer_size_y, - const int input_buffer_size_x, - const int input_padding_lower_y, - const int input_padding_lower_x) { + static inline int64_t get_linear_feature_index(const int64_t batch_id, + const int64_t feature_id, + const int64_t input_buffer_size_f, + const int64_t input_buffer_size_y, + const int64_t input_buffer_size_x, + const int64_t input_padding_lower_y, + const int64_t input_padding_lower_x) { // This helper function assumes input layout with x_size = 1 and y_size = 1; // Location and confidence inputs should be tensors with size {b,f,1,1}. // This is validated in detection output primitive instance creation. - int input_idx = (batch_id * input_buffer_size_f + feature_id) * input_buffer_size_y * input_buffer_size_x; + int64_t input_idx = (batch_id * input_buffer_size_f + feature_id) * input_buffer_size_y * input_buffer_size_x; input_idx += input_padding_lower_y * input_buffer_size_x + input_padding_lower_x; return input_idx; @@ -431,33 +431,33 @@ struct detection_output_impl : typed_primitive_impl { template void extract_locations_per_image(stream& stream, const detection_output_inst& instance, std::vector>>& locations, - const int num_of_priors, - const int num_loc_classes) { + const int64_t num_of_priors, + const int64_t num_loc_classes) { const bool share_location = instance.argument->share_location; auto input_location = instance.location_memory(); auto location_layout = input_location->get_layout(); - const int num_of_images = static_cast(locations.size()); + const int64_t num_of_images = static_cast(locations.size()); mem_lock lock{input_location, stream}; auto location_data = lock.begin(); assert(num_of_priors * num_loc_classes * PRIOR_BOX_SIZE == input_location->get_layout().feature()); const auto& input_buffer_size = location_layout.get_padded_dims(); - const int input_buffer_size_x = input_buffer_size[3]; - const int input_buffer_size_y = input_buffer_size[2]; - const int input_buffer_size_f = input_buffer_size[1]; + const int64_t input_buffer_size_x = input_buffer_size[3]; + const int64_t input_buffer_size_y = input_buffer_size[2]; + const int64_t input_buffer_size_f = input_buffer_size[1]; const auto& input_padding = location_layout.data_padding; - const int input_padding_lower_x = input_padding._lower_size[2]; - const int input_padding_lower_y = input_padding._lower_size[3]; + const int64_t input_padding_lower_x = input_padding._lower_size[2]; + const int64_t input_padding_lower_y = input_padding._lower_size[3]; - for (int image = 0; image < num_of_images; ++image) { + for (int64_t image = 0; image < num_of_images; ++image) { std::vector>& label_to_bbox = locations[image]; label_to_bbox.resize(num_loc_classes); - for (int cls = 0; cls < num_loc_classes; ++cls) { - int label = share_location ? 0 : cls; + for (int64_t cls = 0; cls < num_loc_classes; ++cls) { + int64_t label = share_location ? 0 : cls; auto& bboxes = label_to_bbox[label]; bboxes.resize(num_of_priors); - for (int prior = 0; prior < num_of_priors; ++prior) { - int idx = prior * num_loc_classes * PRIOR_BOX_SIZE; + for (int64_t prior = 0; prior < num_of_priors; ++prior) { + int64_t idx = prior * num_loc_classes * PRIOR_BOX_SIZE; bboxes[prior].xmin = static_cast((location_data[get_linear_feature_index(image, idx + cls * PRIOR_BOX_SIZE, input_buffer_size_f, @@ -496,18 +496,18 @@ struct detection_output_impl : typed_primitive_impl { const bool variance_encoded_in_target, const int32_t prior_info_size, const int32_t prior_coordinates_offset, - const int32_t images_count, + const int64_t images_count, std::vector& prior_bboxes, std::vector>& prior_variances) { auto input_prior_box = instance.prior_box_memory(); - const int num_of_priors = static_cast(prior_bboxes.size()) / images_count; + const int64_t num_of_priors = static_cast(prior_bboxes.size()) / images_count; mem_lock lock{std::move(input_prior_box), stream}; - for (int i = 0; i < images_count; i++) { + for (int64_t i = 0; i < images_count; i++) { auto prior_box_data = lock.begin() + i * num_of_priors * prior_info_size * (variance_encoded_in_target ? 1 : 2); - for (int prior = 0; prior < num_of_priors; ++prior) { - int idx = prior * prior_info_size + prior_coordinates_offset; + for (int64_t prior = 0; prior < num_of_priors; ++prior) { + int64_t idx = prior * prior_info_size + prior_coordinates_offset; prior_bboxes[i * num_of_priors + prior] = bounding_box(static_cast(prior_box_data[idx]), static_cast(prior_box_data[idx + 1]), static_cast(prior_box_data[idx + 2]), @@ -515,8 +515,8 @@ struct detection_output_impl : typed_primitive_impl { idx += num_of_priors * prior_info_size; } if (!variance_encoded_in_target) { - for (int prior = 0; prior < num_of_priors; ++prior) { - int start_idx = prior * 4; + for (int64_t prior = 0; prior < num_of_priors; ++prior) { + int64_t start_idx = prior * 4; std::array var = {0.f, 0.f, 0.f, 0.f}; for (int j = 0; j < PRIOR_BOX_SIZE; ++j) { var[j] = (prior_box_data[start_idx + j + num_of_priors * prior_info_size]); @@ -530,8 +530,8 @@ struct detection_output_impl : typed_primitive_impl { template void extract_confidences_per_image_caffe(stream& stream, const detection_output_inst& instance, std::vector>>>& confidences, - const int num_of_priors, const int num_classes) { - const int num_of_images = static_cast(confidences.size()); + const int64_t num_of_priors, const int64_t num_classes) { + const int64_t num_of_images = static_cast(confidences.size()); auto input_confidence = instance.confidence_memory(); const float confidence_threshold = instance.argument->confidence_threshold; @@ -541,19 +541,19 @@ struct detection_output_impl : typed_primitive_impl { assert(num_of_priors * num_classes == input_confidence->get_layout().feature()); const auto& input_buffer_layout = input_confidence->get_layout(); - const int input_buffer_size_x = input_buffer_layout.spatial(0); - const int input_buffer_size_y = input_buffer_layout.spatial(1); - const int input_buffer_size_f = input_buffer_layout.feature(); + const int64_t input_buffer_size_x = input_buffer_layout.spatial(0); + const int64_t input_buffer_size_y = input_buffer_layout.spatial(1); + const int64_t input_buffer_size_f = input_buffer_layout.feature(); const auto& input_padding = input_confidence->get_layout().data_padding; - const int input_padding_lower_x = input_padding._lower_size[2]; - const int input_padding_lower_y = input_padding._lower_size[3]; - const int stride = input_buffer_size_y * input_buffer_size_x; + const int64_t input_padding_lower_x = input_padding._lower_size[2]; + const int64_t input_padding_lower_y = input_padding._lower_size[3]; + const int64_t stride = input_buffer_size_y * input_buffer_size_x; - for (int image = 0; image < num_of_images; ++image) { + for (int64_t image = 0; image < num_of_images; ++image) { std::vector>>& label_to_scores = confidences[image]; std::vector>> score_index_per_prior; label_to_scores.resize(num_classes); - int idx = get_linear_feature_index(image, + int64_t idx = get_linear_feature_index(image, 0, input_buffer_size_f, input_buffer_size_y, @@ -622,10 +622,10 @@ struct detection_output_impl : typed_primitive_impl { template void extract_confidences_per_image_mxnet(stream& stream, const detection_output_inst& instance, std::vector>>>& confidences, - const int num_of_priors, const int num_classes, + const int64_t num_of_priors, const int64_t num_classes, std::vector>>>& scoreIndexPairs) { const int background_label_id = instance.argument->background_label_id; - const int num_of_images = static_cast(confidences.size()); + const int64_t num_of_images = static_cast(confidences.size()); auto input_confidence = instance.confidence_memory(); const float confidence_threshold = instance.argument->confidence_threshold; auto confidence_layout = input_confidence->get_layout(); @@ -635,19 +635,19 @@ struct detection_output_impl : typed_primitive_impl { assert(num_of_priors * num_classes == confidence_layout.feature()); - const int input_buffer_size_x = confidence_layout.spatial(0); - const int input_buffer_size_y = confidence_layout.spatial(1); - const int input_buffer_size_f = confidence_layout.feature(); + const int64_t input_buffer_size_x = confidence_layout.spatial(0); + const int64_t input_buffer_size_y = confidence_layout.spatial(1); + const int64_t input_buffer_size_f = confidence_layout.feature(); const auto& input_padding = confidence_layout.data_padding; - const int input_padding_lower_x = input_padding._lower_size[2]; - const int input_padding_lower_y = input_padding._lower_size[3]; - const int stride = input_buffer_size_y * input_buffer_size_x; + const int64_t input_padding_lower_x = input_padding._lower_size[2]; + const int64_t input_padding_lower_y = input_padding._lower_size[3]; + const int64_t stride = input_buffer_size_y * input_buffer_size_x; - for (int image = 0; image < num_of_images; ++image) { + for (int64_t image = 0; image < num_of_images; ++image) { std::vector>>& label_to_scores = confidences[image]; std::vector>> score_index_per_prior; label_to_scores.resize(num_classes); - int idx = get_linear_feature_index(image, + int64_t idx = get_linear_feature_index(image, 0, input_buffer_size_f, input_buffer_size_y, @@ -757,17 +757,17 @@ struct detection_output_impl : typed_primitive_impl { auto confidence_layout = instance.confidence_memory()->get_layout(); auto priors_layout = instance.prior_box_memory()->get_layout(); - const int num_of_images = static_cast(bboxes.size()); - const int num_of_priors = priors_layout.spatial(1) / args->prior_info_size; - const int num_classes = (args->num_classes == -1) ? confidence_layout.feature() / num_of_priors : args->num_classes; - const int num_loc_classes = args->share_location ? 1 : num_classes; + const int64_t num_of_images = static_cast(bboxes.size()); + const int64_t num_of_priors = priors_layout.spatial(1) / args->prior_info_size; + const int64_t num_classes = (args->num_classes == -1) ? confidence_layout.feature() / num_of_priors : args->num_classes; + const int64_t num_loc_classes = args->share_location ? 1 : num_classes; // Extract locations per image. std::vector>> locations( num_of_images); // Per image : label -> bounding boxes. extract_locations_per_image(stream, instance, locations, num_of_priors, num_loc_classes); - int32_t batches_in_prior_boxes = priors_layout.batch(); + int64_t batches_in_prior_boxes = priors_layout.batch(); std::vector prior_bboxes(batches_in_prior_boxes * num_of_priors); // Prior-Boxes (identical for all images since we assume // all images in a batch are of same dimension). @@ -800,8 +800,8 @@ struct detection_output_impl : typed_primitive_impl { for (int i = 0; i < label_loc_preds_size; ++i) { bounding_box decoded_bbox; - int32_t pb_offset = (batches_in_prior_boxes > 1) ? (image * num_of_priors + i) : i; - int32_t var_offset = (batches_in_prior_boxes > 1) ? (image * num_of_priors + i) : i; + int64_t pb_offset = (batches_in_prior_boxes > 1) ? (image * num_of_priors + i) : i; + int64_t var_offset = (batches_in_prior_boxes > 1) ? (image * num_of_priors + i) : i; decode_bounding_box(prior_bboxes[pb_offset], prior_variances[var_offset], args->code_type, @@ -833,7 +833,7 @@ struct detection_output_impl : typed_primitive_impl { stream.wait_for_events(events); } - const int num_of_images = instance.location_memory()->get_layout().batch(); // batch size + const int64_t num_of_images = instance.location_memory()->get_layout().batch(); // batch size // Per image : label -> decoded bounding boxes. std::vector>> bboxes(num_of_images); // Per image : class -> confidences per bounding box. diff --git a/src/plugins/intel_gpu/src/graph/impls/cpu/non_max_suppression.cpp b/src/plugins/intel_gpu/src/graph/impls/cpu/non_max_suppression.cpp index 80acac7f3d77cc..e3d8dcdaea55be 100644 --- a/src/plugins/intel_gpu/src/graph/impls/cpu/non_max_suppression.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/cpu/non_max_suppression.cpp @@ -121,10 +121,10 @@ vector2D load_boxes_impl(stream& stream, memory::ptr mem, bool cen mem_lock boxes_lock(mem, stream); auto ptr = boxes_lock.data(); - for (int bi = 0; bi < batch_size; ++bi) { + for (int64_t bi = 0; bi < batch_size; ++bi) { result[bi].reserve(boxes_num); - for (int bxi = 0; bxi < boxes_num; ++bxi) { - int offset = bi * boxes_num * 4 + bxi * 4; + for (int64_t bxi = 0; bxi < boxes_num; ++bxi) { + int64_t offset = bi * boxes_num * 4 + bxi * 4; if (center_point) { result[bi].emplace_back(static_cast(ptr[offset + 0]), static_cast(ptr[offset + 1]), diff --git a/src/plugins/intel_gpu/src/graph/impls/cpu/proposal.cpp b/src/plugins/intel_gpu/src/graph/impls/cpu/proposal.cpp index f9b968bdb34a02..6f643040b0ca3e 100644 --- a/src/plugins/intel_gpu/src/graph/impls/cpu/proposal.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/cpu/proposal.cpp @@ -74,8 +74,8 @@ void sort_and_keep_n_items(std::vector& proposals, size_t n) { roi_t gen_bbox(const proposal_inst::anchor& box, const delta_t& delta, - int anchor_shift_x, - int anchor_shift_y, + int64_t anchor_shift_x, + int64_t anchor_shift_y, int img_w, int img_h, float coordinates_offset, @@ -279,25 +279,25 @@ struct proposal_impl : typed_primitive_impl { // feat map sizes const auto& score_layout = cls_scores->get_layout(); - int fm_h = score_layout.spatial(1); - int fm_w = score_layout.spatial(0); + int64_t fm_h = score_layout.spatial(1); + int64_t fm_w = score_layout.spatial(0); - int fm_sz = fm_w * fm_h; + int64_t fm_sz = fm_w * fm_h; mem_lock cls_scores_ptr{cls_scores, stream}; mem_lock bbox_pred_ptr{std::move(bbox_pred), stream}; const dtype* cls_scores_mem = cls_scores_ptr.data(); const dtype* bbox_pred_mem = bbox_pred_ptr.data(); - for (int n = 0; n < score_layout.batch(); n++) { + for (int64_t n = 0; n < score_layout.batch(); n++) { std::vector sorted_proposals_confidence; size_t num_proposals = fm_h * fm_w * anchors_num; sorted_proposals_confidence.reserve(num_proposals); - for (int y = 0; y < fm_h; ++y) { - for (int x = 0; x < fm_w; ++x) { - const int anchor_shift_x = (swap_xy ? y : x) * primitive->feature_stride; - const int anchor_shift_y = (swap_xy ? x : y) * primitive->feature_stride; - const int location_index = y * fm_w + x; + for (int64_t y = 0; y < fm_h; ++y) { + for (int64_t x = 0; x < fm_w; ++x) { + const int64_t anchor_shift_x = (swap_xy ? y : x) * primitive->feature_stride; + const int64_t anchor_shift_y = (swap_xy ? x : y) * primitive->feature_stride; + const int64_t location_index = y * fm_w + x; // we assume proposals are grouped by window location for (unsigned int anchor_index = 0; anchor_index < anchors_num; anchor_index++) { diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/arg_max_min.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/arg_max_min.cpp index 5fe837bed63028..1a20df2aa77441 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/arg_max_min.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/arg_max_min.cpp @@ -88,7 +88,7 @@ struct arg_max_min_impl : typed_primitive_impl_ocl { // However, here we utilize output_layout and axis information to minimize mem_lock. auto output_layout = impl_param.get_output_layout(0); auto out_dims = output_layout.get_dims(); - argm_params.topK = out_dims[axis]; + argm_params.topK = static_cast(out_dims[axis]); } else { argm_params.topK = top_k; } diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/border.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/border.cpp index e56c35c26e07c2..32e197c7ffeaf1 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/border.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/border.cpp @@ -31,9 +31,6 @@ struct border_impl : typed_primitive_impl_ocl { size_t rank = impl_param.get_input_layout(0).get_rank(); format pads_format = format::adjust_to_rank(format::bfyx, rank); - std::vector begin(primitive->pads_begin.begin(), primitive->pads_begin.end()); - std::vector end(primitive->pads_end.begin(), primitive->pads_end.end()); - size_t input_offset = 1; if (!(primitive->non_constant_input_mask & border::PAD_NON_CONST_INPUT::BEGIN)) { params.begin_type = kernel_selector::base_params::ArgType::Constant; diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/convolution.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/convolution.cpp index 9fbdd13b3ef8e7..d81a8d22470131 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/convolution.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/convolution.cpp @@ -130,9 +130,9 @@ struct convolution_impl : typed_primitive_impl_ocl { std::fill(pads_end.begin(), pads_end.end(), 0); } - uint32_t kx = weights_layout.spatial(0); - uint32_t ky = weights_layout.spatial(1); - uint32_t kz = weights_layout.spatial(2); + uint32_t kx = static_cast(weights_layout.spatial(0)); + uint32_t ky = static_cast(weights_layout.spatial(1)); + uint32_t kz = static_cast(weights_layout.spatial(2)); conv_params.filterSize = { kx, ky, kz }; uint32_t pad_begin_x, pad_begin_y, pad_begin_z; diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/ctc_greedy_decoder.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/ctc_greedy_decoder.cpp index d3d2a3fab27fa4..44142fe2357d6b 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/ctc_greedy_decoder.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/ctc_greedy_decoder.cpp @@ -43,7 +43,7 @@ struct ctc_greedy_decoder_impl : typed_primitive_impl_ocl { params.inputs.push_back(convert_data_tensor(impl_param.input_layouts[1])); params.merge_repeated = primitive->ctc_merge_repeated; if (primitive->blank_index == UINT32_MAX) { - params.blank_index = impl_param.get_input_layout(0).spatial(1) - 1; + params.blank_index = static_cast(impl_param.get_input_layout(0).spatial(1) - 1); } else { params.blank_index = primitive->blank_index; } diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/custom_primitive.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/custom_primitive.cpp index 03c23ba1088689..6a7df62e83c1ca 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/custom_primitive.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/custom_primitive.cpp @@ -224,7 +224,7 @@ static void add_layout_to_jit(kernel_selector::jit_constants& mem_consts, const // Offset (in elements) // #define INPUT0_OFFSET 0 - int32_t offset = + auto offset = (pitches[0] * l.data_padding._lower_size[0]) + (pitches[1] * l.data_padding._lower_size[1]) + (pitches[2] * l.data_padding._lower_size[3]) + (pitches[3] * l.data_padding._lower_size[2]); mem_consts.AddConstant(kernel_selector::MakeJitConstant(name + "_OFFSET", std::to_string(offset))); diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/deconvolution.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/deconvolution.cpp index 84f1c17b8ba13c..342d33e8c275ad 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/deconvolution.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/deconvolution.cpp @@ -49,9 +49,9 @@ struct deconvolution_impl : typed_primitive_impl_ocl { const auto weights_idx = 1 + 0; const auto& weights_layout = impl_param.input_layouts[weights_idx].convert_to_weights_layout(primitive->grouped_weights_shape); - uint32_t kx = weights_layout.spatial(0); - uint32_t ky = weights_layout.spatial(1); - uint32_t kz = weights_layout.spatial(2); + uint32_t kx = static_cast(weights_layout.spatial(0)); + uint32_t ky = static_cast(weights_layout.spatial(1)); + uint32_t kz = static_cast(weights_layout.spatial(2)); params.filterSize = { kx, ky, kz }; diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/detection_output.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/detection_output.cpp index 7a747c32d6c402..39bb35e1c39138 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/detection_output.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/detection_output.cpp @@ -54,10 +54,10 @@ struct detection_output_impl : typed_primitive_impl_ocl { detectOutParams.decrease_label_id = primitive->decrease_label_id; detectOutParams.clip_before_nms = primitive->clip_before_nms; detectOutParams.clip_after_nms = primitive->clip_after_nms; - detectOutParams.conf_size_x = confidence_layout.get_padded_dims()[2]; - detectOutParams.conf_size_y = confidence_layout.get_padded_dims()[3]; - detectOutParams.conf_padding_x = confidence_layout.data_padding._lower_size[2]; - detectOutParams.conf_padding_y = confidence_layout.data_padding._lower_size[3]; + detectOutParams.conf_size_x = static_cast(confidence_layout.get_padded_dims()[2]); + detectOutParams.conf_size_y = static_cast(confidence_layout.get_padded_dims()[3]); + detectOutParams.conf_padding_x = static_cast(confidence_layout.data_padding._lower_size[2]); + detectOutParams.conf_padding_y = static_cast(confidence_layout.data_padding._lower_size[3]); return params; } diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/kernel_selector_helper.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/kernel_selector_helper.cpp index a96f9b25e626f5..4ae968f8bd5d01 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/kernel_selector_helper.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/kernel_selector_helper.cpp @@ -1078,8 +1078,8 @@ std::shared_ptr convert_fuse_params(std::shared_pt auto clamp_max = casted->_desc->clamp_max; auto swish_beta = casted->_desc->swish_beta; auto up_add_val = casted->_desc->up_add_val; - return std::make_shared(axis, - glu_stride, + return std::make_shared(static_cast(axis), + static_cast(glu_stride), gate_idx, clamp_min, clamp_max, diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/one_hot.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/one_hot.cpp index 00ae1867d5b87f..b661aae72211bc 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/one_hot.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/one_hot.cpp @@ -27,14 +27,14 @@ struct one_hot_impl : typed_primitive_impl_ocl { const auto& primitive = impl_param.typed_desc(); auto params = get_default_params(impl_param); - params.one_hot_axis = primitive->one_hot_axis; + params.one_hot_axis = static_cast(primitive->one_hot_axis); params.on_value = primitive->on_value; params.off_value = primitive->off_value; params.indices_normalize_mode = primitive->indices_normalize_mode; auto output_sizes = impl_param.get_output_layout().get_dims(); - params.one_hot_limit = output_sizes[params.one_hot_axis]; + params.one_hot_limit = static_cast(output_sizes[params.one_hot_axis]); return params; } }; diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/pooling.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/pooling.cpp index 74776da02cedb9..f0157a34b9f8e6 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/pooling.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/pooling.cpp @@ -138,9 +138,9 @@ struct pooling_impl : typed_primitive_impl_ocl { uint32_t kernel_x = kernel.size() >= 1 ? static_cast(kernel[kernel.size() - 1]) : 1; pp.poolSize = {kernel_x, kernel_y, kernel_z}; - uint32_t pad_z = std::max(pads_begin.size() >= 3 ? pads_begin[pads_begin.size() - 3] : 0, 0); - uint32_t pad_y = std::max(pads_begin.size() >= 2 ? pads_begin[pads_begin.size() - 2] : 0, 0); - uint32_t pad_x = std::max(pads_begin.size() >= 1 ? pads_begin[pads_begin.size() - 1] : 0, 0); + uint32_t pad_z = static_cast(std::max(pads_begin.size() >= 3 ? pads_begin[pads_begin.size() - 3] : 0, 0)); + uint32_t pad_y = static_cast(std::max(pads_begin.size() >= 2 ? pads_begin[pads_begin.size() - 2] : 0, 0)); + uint32_t pad_x = static_cast(std::max(pads_begin.size() >= 1 ? pads_begin[pads_begin.size() - 1] : 0, 0)); pp.poolPad = {pad_x, pad_y, pad_z}; uint32_t stride_z = stride.size() >= 3 ? static_cast(stride[stride.size() - 3]) : 1; diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/prior_box.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/prior_box.cpp index da928565fbc902..f614a1d7a53652 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/prior_box.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/prior_box.cpp @@ -65,11 +65,11 @@ struct prior_box_impl : typed_primitive_impl_ocl { params.variance = primitive->variance; params.reverse_image_width = 1.0f / image_width; params.reverse_image_height = 1.0f / image_height; - params.width = width; - params.height = height; + params.width = static_cast(width); + params.height = static_cast(height); if (step == 0) { - params.step_x = image_width / width; - params.step_y = image_height / height; + params.step_x = static_cast(image_width) / static_cast(width); + params.step_y = static_cast(image_height) / static_cast(height); } else { params.step_x = step; params.step_y = step; diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/reduce.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/reduce.cpp index 45db8db0bc7f06..023f8dca35cfe2 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/reduce.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/reduce.cpp @@ -15,7 +15,7 @@ static std::vector convert_axes(std::vector axes, size_t rank std::vector converted_axes; for (auto axis : axes) { if (axis == 0 || axis == 1) { - converted_axes.push_back(axis); + converted_axes.push_back(static_cast(axis)); continue; } diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/reorder.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/reorder.cpp index 59cefa75adcb1d..b793c27022d1be 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/reorder.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/reorder.cpp @@ -106,7 +106,7 @@ struct reorder_impl : typed_primitive_impl_ocl { if (output_layout.format == format::winograd_2x3_s1_data) { params.winograd_input_offset_x = 0; params.winograd_input_offset_y = 0; - params.winograd_nr_tiles_x = ceil_div(output_layout.spatial(0), 4); + params.winograd_nr_tiles_x = static_cast(ceil_div(output_layout.spatial(0), 4)); } params.winograd = impl_param.input_layouts[0].format.is_winograd() || output_layout.format.is_winograd(); diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/strided_slice.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/strided_slice.cpp index 6583a256001cc2..6ccc1fd6e5f44c 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/strided_slice.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/strided_slice.cpp @@ -93,9 +93,15 @@ struct strided_slice_impl : typed_primitive_impl_ocl { auto params = get_default_params(impl_param, is_shape_agnostic); const size_t dims_num = params.inputs[0].Dimentions(); - std::vector begin(prim->begin.begin(), prim->begin.end()); - std::vector end(prim->end.begin(), prim->end.end()); - std::vector strides(prim->strides.begin(), prim->strides.end()); + auto to_i32_vec = [](const auto& src) { + std::vector dst; + dst.reserve(src.size()); + for (const auto& v : src) dst.push_back(static_cast(v)); + return dst; + }; + std::vector begin = to_i32_vec(prim->begin); + std::vector end = to_i32_vec(prim->end); + std::vector strides = to_i32_vec(prim->strides); // Getting data from constant inputs. There are 3 args: Begin, End, Stride if (!begin.empty() && !params.has_dynamic_tensors()) { @@ -149,11 +155,17 @@ struct strided_slice_impl : typed_primitive_impl_ocl { auto shrink_axis_mask_ = prim->shrink_axis_mask; auto ellipsis_mask_ = prim->ellipsis_mask; - std::vector begin_mask(begin_mask_.begin(), begin_mask_.end()); - std::vector end_mask(end_mask_.begin(), end_mask_.end()); - std::vector new_axis_mask(new_axis_mask_.begin(), new_axis_mask_.end()); - std::vector shrink_axis_mask(shrink_axis_mask_.begin(), shrink_axis_mask_.end()); - std::vector ellipsis_mask(ellipsis_mask_.begin(), ellipsis_mask_.end()); + auto to_u8_vec = [](const auto& src) { + std::vector dst; + dst.reserve(src.size()); + for (const auto& v : src) dst.push_back(static_cast(v)); + return dst; + }; + std::vector begin_mask = to_u8_vec(begin_mask_); + std::vector end_mask = to_u8_vec(end_mask_); + std::vector new_axis_mask = to_u8_vec(new_axis_mask_); + std::vector shrink_axis_mask = to_u8_vec(shrink_axis_mask_); + std::vector ellipsis_mask = to_u8_vec(ellipsis_mask_); params.end_mask = std::move(end_mask); pad_vector_to_size(params.end_mask, dims_num, 0, prim->ellipsis_mask); params.begin_mask = std::move(begin_mask); diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/swiglu.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/swiglu.cpp index cf2efe9484dac6..6601362110bb74 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/swiglu.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/swiglu.cpp @@ -37,8 +37,8 @@ struct swiglu_impl : typed_primitive_impl_ocl { auto params = get_default_params(impl_param, is_shape_agnostic); auto rank = impl_param.get_input_layout(0).get_partial_shape().rank(); - params.axis = ov::util::normalize(primitive->axis, rank.get_length()); - params.glu_stride = primitive->glu_stride; + params.axis = static_cast(ov::util::normalize(primitive->axis, rank.get_length())); + params.glu_stride = static_cast(primitive->glu_stride); params.glu_type = primitive->glu_type; params.gate_idx = static_cast(primitive->gate_idx); params.clamp_min = primitive->clamp_min; diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl_v2/gated_delta_net_ref.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl_v2/gated_delta_net_ref.cpp index 8dcf4aa21d6d23..237039c4d5bc92 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl_v2/gated_delta_net_ref.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl_v2/gated_delta_net_ref.cpp @@ -43,7 +43,7 @@ class GatedDeltaNetRefGenerator : public KernelGenerator { const auto& v_shape = params.get_input_layout(2).get_partial_shape(); const size_t v_head_nums = v_shape[2].get_length(); const size_t v_head_dims = v_shape[3].get_length(); - const float scale_factor = 1.0f / std::sqrt(static_cast(k_head_dims)); + const float scale_factor = 1.0f / std::sqrt(static_cast(k_head_dims)); const auto output_state = params.output_layouts.size() > 1 ? 1 : 0; jit.make("K_HEAD_NUM", q_head_nums); diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_3gemm_gen_micro.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_3gemm_gen_micro.cpp index dd8f9bd3cb1cca..d2484a162748c9 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_3gemm_gen_micro.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_3gemm_gen_micro.cpp @@ -237,7 +237,7 @@ void MoE3GemmMicroGenerator::init_microkernels(const kernel_impl_params& params, if (is_weight_quantized) { problem_moe.Ta = micro::Type::f16; problem_moe.Ta_ext = convert_type(params.get_input_layout(wei_idx).data_type); - problem_moe.A.setAlignment(micro::alignment_for_ld(k * problem_moe.Ta_ext)); + problem_moe.A.setAlignment(micro::alignment_for_ld(static_cast(k * problem_moe.Ta_ext))); // scale layout example: f16:bfyx:4x8x3072:nopad const auto& scale_layout = params.get_input_layout(scale_idx); @@ -272,7 +272,7 @@ void MoE3GemmMicroGenerator::init_microkernels(const kernel_impl_params& params, problem_moe.A.layout = micro::MatrixLayout::T; problem_moe.B.layout = micro::MatrixLayout::N; problem_moe.C.layout = micro::MatrixLayout::N; - problem_moe.B.setAlignment(micro::alignment_for_ld(k * problem_moe.Tb)); + problem_moe.B.setAlignment(micro::alignment_for_ld(static_cast(k * problem_moe.Tb))); problem_moe.C.setAlignment(static_cast(problem_moe.Tc.size())); /* Set up problem_moe size information */ diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_gemm_gen_opt.hpp b/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_gemm_gen_opt.hpp index 5ab850b2f2b51e..4be2f61de4193a 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_gemm_gen_opt.hpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl_v2/moe/moe_gemm_gen_opt.hpp @@ -53,7 +53,7 @@ class MoEGemmOptGeneratorBase : public MoEGemmBase { auto k = (weight_shape.size() == 4) ? weight_shape[2] * weight_shape[3] : weight_shape[2]; auto scale_group_dim = params.input_layouts[moe_cfg.weight_scale_idx].get_shape().size() - 2; auto num_scale_groups = (weight_shape.size() == 4) ? params.input_layouts[moe_cfg.weight_scale_idx].get_shape()[scale_group_dim] : 1; - moe_cfg.weight_group_size = k / num_scale_groups; + moe_cfg.weight_group_size = static_cast(k / num_scale_groups); if (static_cast(params.input_layouts.size()) > moe_cfg.weight_zp_idx) { moe_cfg.is_weight_symmetric_quantized = false; } else { diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl_v2/sdpa/sdpa_gen_micro.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl_v2/sdpa/sdpa_gen_micro.cpp index e127493b4699f9..5890d81f8728bc 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl_v2/sdpa/sdpa_gen_micro.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl_v2/sdpa/sdpa_gen_micro.cpp @@ -1471,32 +1471,20 @@ void SDPAMicroGenerator::init_microkernels(const kernel_impl_params& params, bool is_quantized = (K.data_type == ov::element::u8 || K.data_type == ov::element::i8) || (V.data_type == ov::element::u8 || V.data_type == ov::element::i8); - int32_t nkeys_v = n_keys.is_dynamic() ? 0 : n_keys.get_length(); + int32_t nkeys_v = static_cast(n_keys.is_dynamic() ? 0 : n_keys.get_length()); GPU_DEBUG_TRACE_DETAIL << "k_head_size = " << k_head_size << ", nkeys_v = " << nkeys_v << "\n"; GPU_DEBUG_TRACE_DETAIL << "thin_q = " << thin_q << ", is_quantized = " << is_quantized << "\n"; switch (device_info.arch) { case gpu_arch::xe_hpg: { - config = choose_config_xehpg(static_cast(k_head_size), static_cast(nkeys_v), thin_q, is_quantized, is_paged_attention, is_prefill); + config = choose_config_xehpg(static_cast(k_head_size), nkeys_v, thin_q, is_quantized, is_paged_attention, is_prefill); break; } case gpu_arch::xe_hpc: - config = choose_config_xehpc(static_cast(k_head_size), - static_cast(nkeys_v), - thin_q, - is_quantized, - is_integrated, - is_paged_attention, - is_prefill); + config = choose_config_xehpc(static_cast(k_head_size), nkeys_v, thin_q, is_quantized, is_integrated, is_paged_attention, is_prefill); break; default: { - config = choose_config_xe2(static_cast(k_head_size), - static_cast(nkeys_v), - thin_q, - is_quantized, - is_integrated, - is_paged_attention, - is_prefill); + config = choose_config_xe2(static_cast(k_head_size), nkeys_v, thin_q, is_quantized, is_integrated, is_paged_attention, is_prefill); break; } } @@ -1588,7 +1576,7 @@ void SDPAMicroGenerator::init_microkernels(const kernel_impl_params& params, problem_kq.B.layout = micro::MatrixLayout::Pr; problem_kq.C.layout = micro::MatrixLayout::T; - problem_kq.A.setAlignment(micro::alignment_for_ld(k_head_size * problem.Ta)); + problem_kq.A.setAlignment(micro::alignment_for_ld(static_cast(k_head_size * problem.Ta))); if (is_paged_attention && !is_prefill) { auto pa_desc = params.typed_desc(); const auto paged_attention_block_size = static_cast(paged_attention::block_size); @@ -1638,7 +1626,7 @@ void SDPAMicroGenerator::init_microkernels(const kernel_impl_params& params, problem_kcq.A.layout = micro::MatrixLayout::T; problem_kcq.B.layout = micro::MatrixLayout::Pr; problem_kcq.C.layout = micro::MatrixLayout::T; - problem_kcq.A.setAlignment(micro::alignment_for_ld(k_head_size * problem.Ta)); + problem_kcq.A.setAlignment(micro::alignment_for_ld(static_cast(k_head_size * problem.Ta))); problem_kcq.B.setAlignment(64); // Q is packed in VNNI format in SLM problem_kcq.B.crosspack = 2; problem_kcq.B.tileR = static_cast(d_max); @@ -1717,7 +1705,7 @@ void SDPAMicroGenerator::init_microkernels(const kernel_impl_params& params, problem_vs.B.layout = micro::MatrixLayout::Pr; problem_vs.C.layout = micro::MatrixLayout::N; - problem_vs.A.setAlignment(micro::alignment_for_ld(v_head_size * problem.Ta)); + problem_vs.A.setAlignment(micro::alignment_for_ld(static_cast(v_head_size * problem.Ta))); problem_vs.B.setAlignment(64); // S is packed in SLM problem_vs.B.crosspack = 16; sizes.m = n_values.is_dynamic() ? -1 : n_values.get_length(); @@ -1756,7 +1744,7 @@ void SDPAMicroGenerator::init_microkernels(const kernel_impl_params& params, problem_vcs.B.layout = micro::MatrixLayout::Pr; problem_vcs.C.layout = micro::MatrixLayout::N; - problem_vcs.A.setAlignment(micro::alignment_for_ld(v_head_size * problem.Ta)); + problem_vcs.A.setAlignment(micro::alignment_for_ld(static_cast(v_head_size * problem.Ta))); problem_vcs.B.setAlignment(64); // S is packed in SLM problem_vcs.B.crosspack = 16; sizes.m = n_values.is_dynamic() ? -1 : n_values.get_length(); diff --git a/src/plugins/intel_gpu/src/graph/impls/onednn/concatenation_onednn.cpp b/src/plugins/intel_gpu/src/graph/impls/onednn/concatenation_onednn.cpp index ba5c8774e90196..5bd5071247ee20 100644 --- a/src/plugins/intel_gpu/src/graph/impls/onednn/concatenation_onednn.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/onednn/concatenation_onednn.cpp @@ -70,7 +70,7 @@ struct concatenation_onednn : typed_primitive_onednn_impl( engine.get_onednn_engine(), output_md, - axis, + static_cast(axis), input_mds, attr); } diff --git a/src/plugins/intel_gpu/src/graph/impls/onednn/fully_connected_onednn.cpp b/src/plugins/intel_gpu/src/graph/impls/onednn/fully_connected_onednn.cpp index 19601d38a86521..244f18aeda9e11 100644 --- a/src/plugins/intel_gpu/src/graph/impls/onednn/fully_connected_onednn.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/onednn/fully_connected_onednn.cpp @@ -310,8 +310,8 @@ struct fully_connected_onednn : typed_primitive_onednn_impl { auto partial_shape = impl_params->get_input_layout(0).get_partial_shape(); auto innermost_len = partial_shape[partial_shape.size() - 1].get_length(); auto& src_scale_shape = impl_params->input_layouts[src_scale_idx].get_partial_shape(); - int src_scale_ngroups = src_scale_shape[src_scale_shape.size() - 1].get_length(); - int src_group_size = innermost_len / src_scale_ngroups; + int64_t src_scale_ngroups = src_scale_shape[src_scale_shape.size() - 1].get_length(); + int64_t src_group_size = innermost_len / src_scale_ngroups; auto act_scale_data_type = convert_data_type(impl_params->get_input_layout(src_scale_idx).data_type); _attrs->set_scales(DNNL_ARG_SRC, grouped, dnnl::memory::dims{1, src_group_size}, act_scale_data_type); @@ -373,7 +373,7 @@ struct fully_connected_onednn : typed_primitive_onednn_impl { ds_data_type = convert_data_type(scale_layout.data_type); auto ifm = arg.get_dependency(1).get_output_layout().get_dim(weight_rank - 1); auto ngroups = scale_layout.get_dim(weight_rank - 1); - group_size = ifm / ngroups; + group_size = static_cast(ifm / ngroups); OPENVINO_ASSERT((group_size == 1 || ngroups == 1 || group_size % 16 == 0), "[GPU] group_size should be aligned to 16 if it is not a single scale group or the group_size is not one."); if (scale_layout.count() == 1) { @@ -416,8 +416,8 @@ struct fully_connected_onednn : typed_primitive_onednn_impl { auto& partial_shape = impl_params.input_layouts[0].get_partial_shape(); auto innermost_len = partial_shape[partial_shape.size() - 1].get_length(); auto& src_scale_shape = impl_params.input_layouts[src_scale_idx].get_partial_shape(); - int src_scale_ngroups = src_scale_shape[src_scale_shape.size() - 1].get_length(); - int src_group_size = innermost_len / src_scale_ngroups; + int64_t src_scale_ngroups = src_scale_shape[src_scale_shape.size() - 1].get_length(); + int64_t src_group_size = innermost_len / src_scale_ngroups; auto act_scale_data_type = convert_data_type(impl_params.input_layouts[src_scale_idx].data_type); attr->set_scales(DNNL_ARG_SRC, grouped, dnnl::memory::dims{1, src_group_size}, act_scale_data_type); diff --git a/src/plugins/intel_gpu/src/graph/impls/onednn/utils.cpp b/src/plugins/intel_gpu/src/graph/impls/onednn/utils.cpp index ce1104785ae7e7..d5f92061e8956c 100644 --- a/src/plugins/intel_gpu/src/graph/impls/onednn/utils.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/onednn/utils.cpp @@ -806,7 +806,8 @@ cldnn::format_traits convert_memory_desc_to_traits(const dnnl::memory::desc& des std::vector> block_sizes(inner_nblks); for (int i = 0; i < inner_nblks; i++) { - block_sizes[i] = std::make_pair(inner_idxs[i] + (is_grouped && inner_idxs[i] == 0 ? 9 : 0) + (is_grouped ? -1 : 0), inner_blks[i]); + const auto idx = inner_idxs[i] + (is_grouped && inner_idxs[i] == 0 ? 9 : 0) + (is_grouped ? -1 : 0); + block_sizes[i] = {static_cast(idx), static_cast(inner_blks[i])}; } // all fmts has at least batch and feature dim for now diff --git a/src/plugins/intel_gpu/src/graph/layout_optimizer.cpp b/src/plugins/intel_gpu/src/graph/layout_optimizer.cpp index 88a91690bd9fb7..1398523a6de50d 100644 --- a/src/plugins/intel_gpu/src/graph/layout_optimizer.cpp +++ b/src/plugins/intel_gpu/src/graph/layout_optimizer.cpp @@ -544,8 +544,8 @@ layout_optimizer::layout_optimizer(bool output_size_handling_enabled) } bool layout_optimizer::is_depthwise(const convolution_node& node) const { - const int32_t output_channels = node.get_output_layout(0).feature(); - const int32_t input_channels = node.get_input_layout(0).feature(); + const auto output_channels = node.get_output_layout(0).feature(); + const auto input_channels = node.get_input_layout(0).feature(); return node.get_groups() == static_cast(input_channels) && input_channels == output_channels; } @@ -656,8 +656,8 @@ bool layout_optimizer::convolution_b_fs_yx_fsv16_opt(const layout& input_layout, int32_t required_feature_num = weak_restrictions ? feature_block_size / 2 : feature_block_size; bool correct_in_feature = (input_layout.feature() >= required_feature_num && output_layout.feature() >= required_feature_num); - int32_t in_features_per_group = input_layout.feature() / conv->groups; - int32_t out_features_per_group = output_layout.feature() / conv->groups; + auto in_features_per_group = input_layout.feature() / conv->groups; + auto out_features_per_group = output_layout.feature() / conv->groups; if (!correct_in_feature && input_layout.feature() <= 4 && out_features_per_group >= feature_block_size) correct_in_feature = true; bool depthwise = conv->groups == static_cast(input_layout.feature()); // depthwise conv diff --git a/src/plugins/intel_gpu/src/graph/multiclass_nms.cpp b/src/plugins/intel_gpu/src/graph/multiclass_nms.cpp index 0b709f8f530d06..2e3a3db4dc711e 100644 --- a/src/plugins/intel_gpu/src/graph/multiclass_nms.cpp +++ b/src/plugins/intel_gpu/src/graph/multiclass_nms.cpp @@ -57,7 +57,7 @@ layout multiclass_nms_inst::calc_output_layout(const multiclass_nms_node& node, num_classes = std::max(1, num_classes - 1); } - int max_output_boxes_per_class = 0; + ov::Dimension::value_type max_output_boxes_per_class = 0; if (attrs.nms_top_k >= 0) { max_output_boxes_per_class = std::min(num_boxes, attrs.nms_top_k); } else { diff --git a/src/plugins/intel_gpu/src/graph/one_hot.cpp b/src/plugins/intel_gpu/src/graph/one_hot.cpp index 834d9f9871fc02..34e9c1a513c6c0 100644 --- a/src/plugins/intel_gpu/src/graph/one_hot.cpp +++ b/src/plugins/intel_gpu/src/graph/one_hot.cpp @@ -15,7 +15,7 @@ namespace cldnn { GPU_DEFINE_PRIMITIVE_TYPE_ID(one_hot) -static bool is_output_bfzyx(const layout& input, int32_t axis) { +static bool is_output_bfzyx(const layout& input, int64_t axis) { if (input.format == format::bfzyx) return true; if (axis == 4) diff --git a/src/plugins/intel_gpu/src/graph/permute.cpp b/src/plugins/intel_gpu/src/graph/permute.cpp index 0d218d45c8c3fd..05580279d4e45e 100644 --- a/src/plugins/intel_gpu/src/graph/permute.cpp +++ b/src/plugins/intel_gpu/src/graph/permute.cpp @@ -80,7 +80,7 @@ std::vector permute_inst::calc_output_layouts(permute_node const& node, auto permute_order = desc->permute_order; if (permute_order.empty()) { for (int64_t i = 1; i <= input_static_rank; ++i) { - permute_order.emplace_back(input_static_rank - i); + permute_order.emplace_back(static_cast(input_static_rank - i)); } } diff --git a/src/plugins/intel_gpu/src/graph/primitive_inst.cpp b/src/plugins/intel_gpu/src/graph/primitive_inst.cpp index 9f68ae9603b7c8..4ac03d728e3f3c 100644 --- a/src/plugins/intel_gpu/src/graph/primitive_inst.cpp +++ b/src/plugins/intel_gpu/src/graph/primitive_inst.cpp @@ -1077,8 +1077,8 @@ void primitive_inst::realloc_outputs(bool prev_execution_skipped) { if (get_node().is_type() && i != 1) { const auto& desc = get_node().as().get_primitive(); const auto shape_rank = updated_layouts[i].get_shape().size(); - const auto seq_axis = i == 0 ? kv_cache_inst::get_sequence_axis(desc->concat_axis, shape_rank) - : kv_cache_inst::get_scale_zp_sequence_axis(); + const int32_t seq_axis = static_cast(i == 0 ? kv_cache_inst::get_sequence_axis(desc->concat_axis, shape_rank) + : kv_cache_inst::get_scale_zp_sequence_axis()); prealloc_info = sp.predict_preallocation_shape(id(), updated_layouts[i], false, i, tmp_prealloc_count, seq_axis); } else { @@ -1332,10 +1332,10 @@ void primitive_inst::fill_shape_info_data(const layout& runtime_layout, const la if (dynamic_pad[j] == 1) { GPU_DEBUG_TRACE_DETAIL << " shape_info[" << offset << "] = " << lower_pads[j] << "(pad_before for " << j << "-th dim)" << std::endl; - shape_info_ptr[offset++] = lower_pads[j]; // pad_before + shape_info_ptr[offset++] = static_cast(lower_pads[j]); // pad_before GPU_DEBUG_TRACE_DETAIL << " shape_info[" << offset << "] = " << upper_pads[j] << "(pad_after for " << j << "-th dim)" << std::endl; - shape_info_ptr[offset++] = upper_pads[j]; // pad_after + shape_info_ptr[offset++] = static_cast(upper_pads[j]); // pad_after } } } diff --git a/src/plugins/intel_gpu/src/graph/prior_box.cpp b/src/plugins/intel_gpu/src/graph/prior_box.cpp index 60b29ee4c2a002..c33e9e0244b0cf 100644 --- a/src/plugins/intel_gpu/src/graph/prior_box.cpp +++ b/src/plugins/intel_gpu/src/graph/prior_box.cpp @@ -26,10 +26,10 @@ void calculate_prior_box_output(memory::ptr output_mem, stream& stream, layout c // All the inputs for this layer are known at this point, // so the output buffer is written here and not in execute(). - const int layer_width = input_layout.spatial(0); - const int layer_height = input_layout.spatial(1); - const int img_width = argument.img_size.spatial[0]; - const int img_height = argument.img_size.spatial[1]; + const int64_t layer_width = input_layout.spatial(0); + const int64_t layer_height = input_layout.spatial(1); + const int64_t img_width = argument.img_size.spatial[0]; + const int64_t img_height = argument.img_size.spatial[1]; float step_w = argument.step_width; float step_h = argument.step_height; if (!argument.is_clustered() && (step_w == 0 || step_h == 0)) { @@ -124,8 +124,8 @@ void calculate_prior_box_output(memory::ptr output_mem, stream& stream, layout c continue; } - box_width = fixed_size * sqrt(ar); - box_height = fixed_size / sqrt(ar); + box_width = fixed_size * sqrtf(ar); + box_height = fixed_size / sqrtf(ar); for (size_t r = 0; r < density; ++r) { for (size_t c = 0; c < density; ++c) { @@ -161,7 +161,7 @@ void calculate_prior_box_output(memory::ptr output_mem, stream& stream, layout c if (argument.max_sizes.size() > 0) { float max_size_ = argument.max_sizes[s]; // second prior: aspect_ratio = 1, size = sqrt(min_size * max_size) - box_width = box_height = sqrt(min_size * max_size_); + box_width = box_height = sqrtf(min_size * max_size_); // xmin out_ptr[idx++] = (dtype)((center_x - box_width / 2.f) / img_width); // ymin @@ -180,8 +180,8 @@ void calculate_prior_box_output(memory::ptr output_mem, stream& stream, layout c if (fabs(ar - 1.) < 1e-6) { continue; } - box_width = min_size * sqrt(ar); - box_height = min_size / sqrt(ar); + box_width = min_size * sqrtf(ar); + box_height = min_size / sqrtf(ar); // xmin out_ptr[idx++] = (dtype)((center_x - box_width / 2.f) / img_width); // ymin @@ -204,7 +204,7 @@ void calculate_prior_box_output(memory::ptr output_mem, stream& stream, layout c } // set the variance. - int count = output_mem->get_layout().spatial(0) * output_mem->get_layout().spatial(1); + int64_t count = output_mem->get_layout().spatial(0) * output_mem->get_layout().spatial(1); int var_loop_count = argument.is_clustered() ? var_size : 4; for (int h = 0; h < layer_height; ++h) { for (int w = 0; w < layer_width; ++w) { diff --git a/src/plugins/intel_gpu/src/graph/program_dump_graph.cpp b/src/plugins/intel_gpu/src/graph/program_dump_graph.cpp index a46f516783ed8e..daf3f768f48da9 100644 --- a/src/plugins/intel_gpu/src/graph/program_dump_graph.cpp +++ b/src/plugins/intel_gpu/src/graph/program_dump_graph.cpp @@ -288,7 +288,7 @@ void dump_graph_init(std::ofstream& graph, bool doubled = true; auto it = user->get_dependencies().begin(); while (it != user->get_dependencies().end()) { - int input_port = it - user->get_dependencies().begin(); + int input_port = static_cast(it - user->get_dependencies().begin()); if (it->first == node && marked_connection.find({node, input_port}) == marked_connection.end()) { marked_connection.emplace(user, input_port); break; diff --git a/src/plugins/intel_gpu/src/graph/program_node.cpp b/src/plugins/intel_gpu/src/graph/program_node.cpp index 8661757bdda3fa..6b23a623c17995 100644 --- a/src/plugins/intel_gpu/src/graph/program_node.cpp +++ b/src/plugins/intel_gpu/src/graph/program_node.cpp @@ -1224,8 +1224,8 @@ dnnl::post_ops program_node::try_optimize_post_ops(std::vector>>>>>>>>>>>>" << std::endl; // Get post-ops size for current node - int64_t post_ops_size = cur_post_ops.size(); + int post_ops_size = static_cast(cur_post_ops.size()); auto get_optimized_eltwise_type = [](onednn_post_op_type type) { switch (type) { diff --git a/src/plugins/intel_gpu/src/graph/reduce.cpp b/src/plugins/intel_gpu/src/graph/reduce.cpp index 6566c9366c4c8c..9cc8d227dae0bd 100644 --- a/src/plugins/intel_gpu/src/graph/reduce.cpp +++ b/src/plugins/intel_gpu/src/graph/reduce.cpp @@ -27,7 +27,7 @@ static std::vector convert_axes(std::vector axes, size_t rank std::vector converted_axes; for (auto axis : axes) { if (axis == 0 || axis == 1) { - converted_axes.push_back(axis); + converted_axes.push_back(static_cast(axis)); continue; } diff --git a/src/plugins/intel_gpu/src/graph/reshape.cpp b/src/plugins/intel_gpu/src/graph/reshape.cpp index 4310ec4700c2bf..9cc1378be7fd20 100644 --- a/src/plugins/intel_gpu/src/graph/reshape.cpp +++ b/src/plugins/intel_gpu/src/graph/reshape.cpp @@ -121,7 +121,7 @@ layout reshape_inst::calc_output_layout(reshape_node const& node, kernel_impl_pa auto sizes = desc->output_shape.sizes(); auto input_sizes = input_layout.get_tensor().sizes(); size_t need_recalc = 0; - uint32_t shape_count = 1; + int64_t shape_count = 1; for (size_t i = 0; i < sizes.size(); i++) { if (sizes[i] == -1) { @@ -137,7 +137,7 @@ layout reshape_inst::calc_output_layout(reshape_node const& node, kernel_impl_pa shape_count *= sizes[i]; } if (need_recalc) - sizes[need_recalc] = static_cast(input_layout.count()) / shape_count; + sizes[need_recalc] = static_cast(input_layout.count()) / shape_count; return layout{input_layout.data_type, input_layout.format, tensor(sizes)}; } diff --git a/src/plugins/intel_gpu/src/graph/roi_pooling.cpp b/src/plugins/intel_gpu/src/graph/roi_pooling.cpp index dc1e49270c8327..8e201ddeb14fa0 100644 --- a/src/plugins/intel_gpu/src/graph/roi_pooling.cpp +++ b/src/plugins/intel_gpu/src/graph/roi_pooling.cpp @@ -19,8 +19,8 @@ layout roi_pooling_inst::calc_output_layout(roi_pooling_node const& node, kernel auto desc = impl_param.typed_desc(); layout data_layout = impl_param.get_input_layout(0); layout rois_layout = impl_param.get_input_layout(1); - int num_rois = rois_layout.batch(); - int out_fm = desc->position_sensitive ? desc->output_dim : data_layout.feature(); + int64_t num_rois = rois_layout.batch(); + int64_t out_fm = desc->position_sensitive ? desc->output_dim : data_layout.feature(); return layout(data_layout.data_type, data_layout.format, diff --git a/src/plugins/intel_gpu/src/graph/scatter_elements_update.cpp b/src/plugins/intel_gpu/src/graph/scatter_elements_update.cpp index 8f580840f692d7..7efb5d5c92b1e2 100644 --- a/src/plugins/intel_gpu/src/graph/scatter_elements_update.cpp +++ b/src/plugins/intel_gpu/src/graph/scatter_elements_update.cpp @@ -17,8 +17,8 @@ GPU_DEFINE_PRIMITIVE_TYPE_ID(scatter_elements_update) layout scatter_elements_update_inst::calc_output_layout(scatter_elements_update_node const& node, kernel_impl_params const& impl_param) { auto desc = impl_param.typed_desc(); - const int32_t axis = desc->axis; - const size_t input_number_of_dims = impl_param.get_input_layout().get_partial_shape().size(); + const int64_t axis = desc->axis; + const int64_t input_number_of_dims = static_cast(impl_param.get_input_layout().get_partial_shape().size()); auto input_layout = impl_param.get_input_layout(); @@ -30,7 +30,7 @@ layout scatter_elements_update_inst::calc_output_layout(scatter_elements_update_ output_type = impl_param.get_output_element_type(); } - if (static_cast(axis) < 0 || static_cast(axis) >= input_number_of_dims) + if (axis < 0 || axis >= input_number_of_dims) CLDNN_ERROR_MESSAGE(desc->id, "Incorrect axis value for ScatterElementsUpdate: Axis must be positive and less than the input tensor dimension."); return layout{output_shape, output_type, input_format}; diff --git a/src/plugins/intel_gpu/src/graph/space_to_batch.cpp b/src/plugins/intel_gpu/src/graph/space_to_batch.cpp index a7b226bd6d84a6..e09d4801beb5da 100644 --- a/src/plugins/intel_gpu/src/graph/space_to_batch.cpp +++ b/src/plugins/intel_gpu/src/graph/space_to_batch.cpp @@ -63,7 +63,7 @@ layout space_to_batch_inst::calc_output_layout(space_to_batch_node const& node, static std::vector tensor_to_vec(const tensor& t, const format f) { std::vector vec(cldnn::format::dimension(f)); for (size_t i = 0; i < vec.size(); ++i) { - vec[i] = t.sizes()[i]; + vec[i] = static_cast(t.sizes()[i]); } std::reverse(vec.begin() + 2, vec.end()); return vec; diff --git a/src/plugins/intel_gpu/src/kernel_selector/kernels/multiclass_nms/multiclass_nms_kernel_ref.cpp b/src/plugins/intel_gpu/src/kernel_selector/kernels/multiclass_nms/multiclass_nms_kernel_ref.cpp index c426fd070a1704..b2c2490f07a23b 100644 --- a/src/plugins/intel_gpu/src/kernel_selector/kernels/multiclass_nms/multiclass_nms_kernel_ref.cpp +++ b/src/plugins/intel_gpu/src/kernel_selector/kernels/multiclass_nms/multiclass_nms_kernel_ref.cpp @@ -89,14 +89,14 @@ JitConstants MulticlassNmsKernelRef::GetJitConstants(const multiclass_nms_params int64_t max_output_boxes_per_class = 0; if (params.nms_top_k >= 0) { - max_output_boxes_per_class = std::min(static_cast(num_boxes), params.nms_top_k); + max_output_boxes_per_class = std::min(static_cast(num_boxes), params.nms_top_k); } else { max_output_boxes_per_class = num_boxes; } auto max_output_boxes_per_batch = max_output_boxes_per_class * real_num_classes; if (params.keep_top_k >= 0) - max_output_boxes_per_batch = std::min(max_output_boxes_per_batch, params.keep_top_k); + max_output_boxes_per_batch = std::min(max_output_boxes_per_batch, params.keep_top_k); jit.AddConstants({ MakeJitConstant("SORT_RESULT_TYPE", static_cast(params.sort_result_type)), diff --git a/src/plugins/intel_gpu/src/kernel_selector/kernels/resample/resample_kernel_pil_ref.cpp b/src/plugins/intel_gpu/src/kernel_selector/kernels/resample/resample_kernel_pil_ref.cpp index d47a9dc01e7898..07d61e435408c6 100644 --- a/src/plugins/intel_gpu/src/kernel_selector/kernels/resample/resample_kernel_pil_ref.cpp +++ b/src/plugins/intel_gpu/src/kernel_selector/kernels/resample/resample_kernel_pil_ref.cpp @@ -93,22 +93,22 @@ std::size_t GetKernelsNum(const resample_params& params) { } std::size_t GetFirstRow(const resample_params& params) { - float scale = static_cast(getInputVerticalSize(params, true)) / getOutputVerticalSize(params); + float scale = static_cast(getInputVerticalSize(params, true)) / static_cast(getOutputVerticalSize(params)); float filter_scale = std::max(1.f, scale); float support = params.resampleType == ResampleType::BILINEAR_PILLOW ? 1.f : 2.f * filter_scale; - float center = 0.5 * scale; - auto xmin = std::max(0, static_cast(center - support + 0.5)); + float center = 0.5f * scale; + auto xmin = std::max(0, static_cast(center - support + 0.5f)); return xmin; } std::size_t GetLastRow(const resample_params& params) { auto inputVerticalSize = getInputVerticalSize(params, true); auto outputVerticalSize = getOutputVerticalSize(params); - float scale = static_cast(inputVerticalSize) / outputVerticalSize; + float scale = static_cast(inputVerticalSize) / static_cast(outputVerticalSize); float filter_scale = std::max(1.f, scale); float support = params.resampleType == ResampleType::BILINEAR_PILLOW ? 1.f : 2.f * filter_scale; - float center = (outputVerticalSize - 0.5) * scale; - auto xmax = std::min(inputVerticalSize, static_cast(center + support + 0.5)); + float center = (outputVerticalSize - 0.5f) * scale; + auto xmax = std::min(inputVerticalSize, static_cast(center + support + 0.5f)); return xmax; } @@ -379,7 +379,7 @@ JitConstants ResampleKernelPilRef::GetJitConstantsForKernel(KernelId id, const r MakeJitConstant("ENABLE_VERTICAL_PASS", NeedVerticalPass(params)), MakeJitConstant("ENABLE_HORIZONTAL_PASS", needHorizontalPass), MakeJitConstant("KSIZE", ksize), - }); + }); if (needHorizontalPass) { jit_constants.AddConstant(MakeJitConstant("RESAMPLE_VERTICAL_INPUT_TYPE", "INTERMEDIATE_BUF_TYPE")); } else { diff --git a/src/plugins/intel_gpu/src/plugin/graph.cpp b/src/plugins/intel_gpu/src/plugin/graph.cpp index 09619554284e26..fba4a77ac9847d 100644 --- a/src/plugins/intel_gpu/src/plugin/graph.cpp +++ b/src/plugins/intel_gpu/src/plugin/graph.cpp @@ -160,13 +160,13 @@ Graph::~Graph() { const auto begin = std::begin(host_exec_times) + 1; const auto end = std::end(host_exec_times); - avg.inputs_processing = std::accumulate(begin, end, 0, + avg.inputs_processing = std::accumulate(begin, end, int64_t{0}, [](int64_t sum, const HostTimeProfilingEntry& entry) { return sum + entry.inputs_processing; }); - avg.enqueue = std::accumulate(begin, end, 0, + avg.enqueue = std::accumulate(begin, end, int64_t{0}, [](int64_t sum, const HostTimeProfilingEntry& entry) { return sum + entry.enqueue; }); - avg.wait = std::accumulate(begin, end, 0, + avg.wait = std::accumulate(begin, end, int64_t{0}, [](int64_t sum, const HostTimeProfilingEntry& entry) { return sum + entry.wait; }); - avg.outputs_processing = std::accumulate(begin, end, 0, + avg.outputs_processing = std::accumulate(begin, end, int64_t{0}, [](int64_t sum, const HostTimeProfilingEntry& entry) { return sum + entry.outputs_processing; }); const auto iters_num = host_exec_times.size() - 1; diff --git a/src/plugins/intel_gpu/src/plugin/ops/convolution.cpp b/src/plugins/intel_gpu/src/plugin/ops/convolution.cpp index 9748c30fd189bf..2e8d3aa5f369df 100644 --- a/src/plugins/intel_gpu/src/plugin/ops/convolution.cpp +++ b/src/plugins/intel_gpu/src/plugin/ops/convolution.cpp @@ -34,7 +34,7 @@ static void CreateConvolutionOp(ProgramBuilder& p, const std::shared_ptrget_output_partial_shape(0); cldnn::primitive_id weights = inputs[op::Convolution::Args::WEIGHTS].pid; - const uint32_t groups = std::max(op->get_groups(), 1); + const uint32_t groups = static_cast(std::max(op->get_groups(), 1)); const bool weights_have_group_dim = op->get_groups() > 0; auto strides = op->get_strides(); @@ -288,8 +288,8 @@ static void DeformableConvolutionImpl(ProgramBuilder& p, weights, "", true, - groups, - deformable_groups_num, + static_cast(groups), + static_cast(deformable_groups_num), strides, dilations, padding, diff --git a/src/plugins/intel_gpu/src/plugin/ops/experimental_detectron_roi_feature_extractor.cpp b/src/plugins/intel_gpu/src/plugin/ops/experimental_detectron_roi_feature_extractor.cpp index dbf7fb6e5306e5..9bd96647e9881b 100644 --- a/src/plugins/intel_gpu/src/plugin/ops/experimental_detectron_roi_feature_extractor.cpp +++ b/src/plugins/intel_gpu/src/plugin/ops/experimental_detectron_roi_feature_extractor.cpp @@ -20,9 +20,9 @@ static void CreateExperimentalDetectronROIFeatureExtractorOp(ProgramBuilder& p, if (p.use_new_shape_infer()) { cldnn::experimental_detectron_roi_feature_extractor prim(layer_type_name_ID(op), inputs, - operation_attributes.output_size, + static_cast(operation_attributes.output_size), operation_attributes.pyramid_scales, - operation_attributes.sampling_ratio, + static_cast(operation_attributes.sampling_ratio), operation_attributes.aligned); prim.num_outputs = op->get_output_size(); prim.output_data_types = get_output_data_types(op, {{ov::element::i64, ov::element::i32}}); @@ -46,9 +46,9 @@ static void CreateExperimentalDetectronROIFeatureExtractorOp(ProgramBuilder& p, cldnn::experimental_detectron_roi_feature_extractor experimentalDetectronPrim(layerName, inputs, - operation_attributes.output_size, + static_cast(operation_attributes.output_size), operation_attributes.pyramid_scales, - operation_attributes.sampling_ratio, + static_cast(operation_attributes.sampling_ratio), operation_attributes.aligned); p.add_primitive(*op, experimentalDetectronPrim); diff --git a/src/plugins/intel_gpu/src/plugin/ops/eye.cpp b/src/plugins/intel_gpu/src/plugin/ops/eye.cpp index c5afa207ab2e75..e6a60da7a77005 100644 --- a/src/plugins/intel_gpu/src/plugin/ops/eye.cpp +++ b/src/plugins/intel_gpu/src/plugin/ops/eye.cpp @@ -37,7 +37,7 @@ static void CreateEyeOp(ProgramBuilder& p, const std::shared_ptrget_data_ptr(); break; case ov::element::Type_t::i64: - shift = *constant->get_data_ptr(); + shift = static_cast(*constant->get_data_ptr()); break; default: throw std::runtime_error{"Input type can be only either i32 or i64"}; diff --git a/src/plugins/intel_gpu/src/plugin/ops/interpolate.cpp b/src/plugins/intel_gpu/src/plugin/ops/interpolate.cpp index 75715155653e07..b9f61bc10e8b9d 100644 --- a/src/plugins/intel_gpu/src/plugin/ops/interpolate.cpp +++ b/src/plugins/intel_gpu/src/plugin/ops/interpolate.cpp @@ -74,6 +74,7 @@ static void CreateInterpolateOp(ProgramBuilder& p, const std::shared_ptrget_attrs(); + const float cube_coeff = static_cast(attrs.cube_coeff); auto sizes_constant = ov::as_type_ptr(op->get_input_node_shared_ptr(SIZES_INDEX)); std::vector sizes = sizes_constant ? sizes_constant->cast_vector() : std::vector{}; @@ -101,7 +102,7 @@ static void CreateInterpolateOp(ProgramBuilder& p, const std::shared_ptrget_attrs(); + const float cube_coeff = static_cast(attrs.cube_coeff); auto scales_or_sizes_constant = ov::as_type_ptr(op->get_input_node_shared_ptr(eScalesOrSizesIndex)); std::vector scales = scales_or_sizes_constant && attrs.shape_calculation_mode == ov::op::v11::Interpolate::ShapeCalcMode::SCALES ? @@ -181,7 +183,7 @@ static void CreateInterpolateOp(ProgramBuilder& p, const std::shared_ptrm_output_index); + cldnn::input_info external_input_info(layerName, static_cast(loop_output_desc->m_output_index)); p.primitive_ids[layerName] = layerName; const auto& body_output = body_outputs.at(loop_output_desc->m_body_value_index); @@ -137,7 +137,7 @@ static void SetLoopInputOutputMap(ProgramBuilder& p, } } else { for (const auto& loop_output_desc : loop_output_descs) { - const uint64_t output_idx = loop_output_desc->m_output_index; + const int32_t output_idx = static_cast(loop_output_desc->m_output_index); // Add additional mutable_data for multiple outputs // primitive ID should be . if output_idx > 0 diff --git a/src/plugins/intel_gpu/src/plugin/ops/mvn.cpp b/src/plugins/intel_gpu/src/plugin/ops/mvn.cpp index a532b941f1a429..0ffd8687061c64 100644 --- a/src/plugins/intel_gpu/src/plugin/ops/mvn.cpp +++ b/src/plugins/intel_gpu/src/plugin/ops/mvn.cpp @@ -33,7 +33,7 @@ static void CreateMVNOp(ProgramBuilder& p, const std::shared_ptrget_across_channels(); bool normalize_variance = op->get_normalize_variance(); - float eps = op->get_eps(); + float eps = static_cast(op->get_eps()); int64_t axes_count = std::max(static_cast(op->get_input_partial_shape(0).size()) - 2, 0); std::vector axes(axes_count); @@ -56,7 +56,7 @@ static void CreateMVNOp(ProgramBuilder& p, const std::shared_ptrget_output_partial_shape(0).rank(), *op); bool normalize_variance = op->get_normalize_variance(); - float eps = op->get_eps(); + float eps = static_cast(op->get_eps()); bool eps_inside_sqrt = op->get_eps_mode() == ov::op::MVNEpsMode::INSIDE_SQRT; CreateCommonMVNOp(p, op, axes, normalize_variance, eps, eps_inside_sqrt); diff --git a/src/plugins/intel_gpu/src/plugin/ops/paged_attention.cpp b/src/plugins/intel_gpu/src/plugin/ops/paged_attention.cpp index 140fbc20030a01..ca4b6ace23b51b 100644 --- a/src/plugins/intel_gpu/src/plugin/ops/paged_attention.cpp +++ b/src/plugins/intel_gpu/src/plugin/ops/paged_attention.cpp @@ -51,7 +51,7 @@ static void CreatePagedAttentionExtensionOp(ProgramBuilder& p, const std::shared // WA: in some cases, the query input may have a bounded dimension // Use input shape of the input node in such cases - auto heads_num = 0; + int64_t heads_num = 0; auto query_merged_dim = query_ps[1]; if (query_merged_dim.is_static()) { heads_num = query_merged_dim.get_length() / k_head_size; diff --git a/src/plugins/intel_gpu/src/plugin/ops/rms.cpp b/src/plugins/intel_gpu/src/plugin/ops/rms.cpp index 49e9ba153b2973..bb2447bc54a155 100644 --- a/src/plugins/intel_gpu/src/plugin/ops/rms.cpp +++ b/src/plugins/intel_gpu/src/plugin/ops/rms.cpp @@ -15,10 +15,11 @@ static void CreateRMSOp(ProgramBuilder& p, const std::shared_ptr& op) { validate_inputs_count(op, {1, 2}); auto inputs = p.GetInputInfo(op); std::string primitive_name = layer_type_name_ID(op); + const float epsilon = static_cast(op->get_epsilon()); auto rms = op->get_elementwise_affine() - ? cldnn::rms(primitive_name, inputs[0], inputs[1], op->get_epsilon()) - : cldnn::rms(primitive_name, inputs[0], op->get_epsilon()); + ? cldnn::rms(primitive_name, inputs[0], inputs[1], epsilon) + : cldnn::rms(primitive_name, inputs[0], epsilon); rms.output_data_types = get_output_data_types(op); p.add_primitive(*op, rms); } diff --git a/src/plugins/intel_gpu/src/plugin/ops/roi_pooling.cpp b/src/plugins/intel_gpu/src/plugin/ops/roi_pooling.cpp index 8c326546e08b50..3224ec9880dc8a 100644 --- a/src/plugins/intel_gpu/src/plugin/ops/roi_pooling.cpp +++ b/src/plugins/intel_gpu/src/plugin/ops/roi_pooling.cpp @@ -31,17 +31,17 @@ static void CreateDeformablePSROIPoolingOp(ProgramBuilder& p, const std::shared_ cldnn::pooling_mode mode = GetPoolingMode(op->get_mode()); float trans_std = op->get_trans_std(); - int part_size = op->get_part_size(); + int part_size = static_cast(op->get_part_size()); bool no_trans = op->get_input_size() == 2 ? true : false; // temporary workaround due to incorrect usage of group_size in the nGraph operation for the DeformablePSROIPooling - int pooled_width = op->get_group_size(); - int pooled_height = op->get_group_size(); - int group_size = op->get_group_size(); - int output_dim = op->get_output_dim(); + int pooled_width = static_cast(op->get_group_size()); + int pooled_height = static_cast(op->get_group_size()); + int group_size = static_cast(op->get_group_size()); + int output_dim = static_cast(op->get_output_dim()); float spatial_scale = op->get_spatial_scale(); - int spatial_bins_x = op->get_spatial_bins_x(); - int spatial_bins_y = op->get_spatial_bins_y(); + int spatial_bins_x = static_cast(op->get_spatial_bins_x()); + int spatial_bins_y = static_cast(op->get_spatial_bins_y()); bool position_sensitive = true; auto psROIPoolingPrim = cldnn::roi_pooling(layerName, diff --git a/src/plugins/intel_gpu/src/plugin/ops/shuffle_channels.cpp b/src/plugins/intel_gpu/src/plugin/ops/shuffle_channels.cpp index ee35be40dbfebd..d4722301576d4b 100644 --- a/src/plugins/intel_gpu/src/plugin/ops/shuffle_channels.cpp +++ b/src/plugins/intel_gpu/src/plugin/ops/shuffle_channels.cpp @@ -15,8 +15,8 @@ static void CreateShuffleChannelsOp(ProgramBuilder& p, const std::shared_ptrget_group(); - int64_t axis = ov::util::try_normalize_axis(op->get_axis(), op->get_input_partial_shape(0).rank(), *op); + int32_t group = static_cast(op->get_group()); + int32_t axis = static_cast(ov::util::try_normalize_axis(op->get_axis(), op->get_input_partial_shape(0).rank(), *op)); auto shuffleChannelsPrim = cldnn::shuffle_channels(layerName, inputs[0], diff --git a/src/plugins/intel_gpu/src/plugin/plugin.cpp b/src/plugins/intel_gpu/src/plugin/plugin.cpp index 15d6d9202fc1b7..9b37b4dd11be5d 100644 --- a/src/plugins/intel_gpu/src/plugin/plugin.cpp +++ b/src/plugins/intel_gpu/src/plugin/plugin.cpp @@ -969,12 +969,12 @@ uint32_t Plugin::get_optimal_batch_size(const ov::AnyMap& options) const { ov::MemBandwidthPressure memPressure = ov::mem_bandwidth_pressure_tolerance(cloned_model, L3_cache_size); uint32_t batch = 1; if (memPressure.max_mem_tolerance != ov::MemBandwidthPressure::UNKNOWN) - batch = std::max(1.0, 16 * closest_pow_of_2(memPressure.max_mem_tolerance)); + batch = static_cast(std::max(1.0, 16 * closest_pow_of_2(memPressure.max_mem_tolerance))); ov::AnyMap options_for_max_batch; options_for_max_batch[ov::hint::model.name()] = model; options_for_max_batch[ov::num_streams.name()] = ov::streams::AUTO; auto max_batch_size = get_metric(ov::max_batch_size.name(), options_for_max_batch).as(); - uint32_t closest = closest_pow_of_2(max_batch_size); + uint32_t closest = static_cast(closest_pow_of_2(max_batch_size)); batch = std::min(closest, batch); batch = std::min(256u, batch); //batch 256 is a max GPU_DEBUG_INFO << memPressure.max_mem_tolerance << std::endl; diff --git a/src/plugins/intel_gpu/src/plugin/transformations/optimize_subsequent_reshapes.cpp b/src/plugins/intel_gpu/src/plugin/transformations/optimize_subsequent_reshapes.cpp index 91eea0e2794fc0..7cb49d9611e681 100644 --- a/src/plugins/intel_gpu/src/plugin/transformations/optimize_subsequent_reshapes.cpp +++ b/src/plugins/intel_gpu/src/plugin/transformations/optimize_subsequent_reshapes.cpp @@ -77,7 +77,7 @@ OptimizeSubsequentReshapes::OptimizeSubsequentReshapes() { if (dim.is_dynamic()) { new_pattern.push_back(-1); } else { - new_pattern.push_back(dim.get_length()); + new_pattern.push_back(static_cast(dim.get_length())); } } diff --git a/src/plugins/intel_gpu/src/plugin/transformations/swiglu_fusion_with_clamp.cpp b/src/plugins/intel_gpu/src/plugin/transformations/swiglu_fusion_with_clamp.cpp index 590680fcbecfe5..8130574b8e3000 100644 --- a/src/plugins/intel_gpu/src/plugin/transformations/swiglu_fusion_with_clamp.cpp +++ b/src/plugins/intel_gpu/src/plugin/transformations/swiglu_fusion_with_clamp.cpp @@ -61,8 +61,8 @@ namespace intel_gpu { return false; auto clamp_node = ov::as_type_ptr(pattern_map.at(clamp_m).get_node_shared_ptr()); - auto clamp_min_value = clamp_node->get_min(); - auto clamp_max_value = clamp_node->get_max(); + auto clamp_min_value = static_cast(clamp_node->get_min()); + auto clamp_max_value = static_cast(clamp_node->get_max()); size_t gate_idx = 0; if (ov::is_type(mul_node_ptr->get_input_node_shared_ptr(1))) diff --git a/src/plugins/intel_gpu/src/plugin/transformations/unsqueeze_broadcast_reshape_sdpa_fusion.cpp b/src/plugins/intel_gpu/src/plugin/transformations/unsqueeze_broadcast_reshape_sdpa_fusion.cpp index f790adcf9863ef..fc45b7a878bc83 100644 --- a/src/plugins/intel_gpu/src/plugin/transformations/unsqueeze_broadcast_reshape_sdpa_fusion.cpp +++ b/src/plugins/intel_gpu/src/plugin/transformations/unsqueeze_broadcast_reshape_sdpa_fusion.cpp @@ -81,7 +81,10 @@ UnsqueezeBroadcastReshapeSDPAFusion::UnsqueezeBroadcastReshapeSDPAFusion() { auto input_node = broadcast->get_input_node_shared_ptr(0); if (input_node && input_node->get_output_partial_shape(0).is_static()) { auto pshape = input_node->get_output_shape(0); - return std::vector(pshape.begin(), pshape.end()); + std::vector result(pshape.size()); + std::transform(pshape.begin(), pshape.end(), result.begin(), + [](size_t v) { return static_cast(v); }); + return result; } return {}; }; diff --git a/src/plugins/intel_gpu/src/runtime/memory_pool.cpp b/src/plugins/intel_gpu/src/runtime/memory_pool.cpp index f57c6f4a1c46f0..76ef9c9dd8571f 100644 --- a/src/plugins/intel_gpu/src/runtime/memory_pool.cpp +++ b/src/plugins/intel_gpu/src/runtime/memory_pool.cpp @@ -441,13 +441,13 @@ size_t memory_pool::get_total_mem_pool_size(allocation_type type) { #endif } -void memory_pool::dump(uint32_t net_id, uint32_t iter, std::string dump_dir_path) { +void memory_pool::dump(uint32_t net_id, int64_t iter, std::string dump_dir_path) { dump_to_screen(net_id, iter); if (!dump_dir_path.empty()) dump_to_file(net_id, iter, dump_dir_path); } -void memory_pool::dump_to_file(uint32_t net_id, uint32_t iter, std::string dump_dir_path) { +void memory_pool::dump_to_file(uint32_t net_id, int64_t iter, std::string dump_dir_path) { #ifdef GPU_DEBUG_CONFIG const std::string dump_file_name = "dump_runtime_memory_pool_net_" + std::to_string(net_id) + "_iter_" + std::to_string(iter) + ".csv"; const std::string desc = "pool_type,layout,mem_ptr,mem_type,mem_pool_size,prim_id,unique_id,mem_size"; @@ -482,7 +482,7 @@ void memory_pool::dump_to_file(uint32_t net_id, uint32_t iter, std::string dump_ #endif } -void memory_pool::dump_to_screen(uint32_t net_id, uint32_t iter) { +void memory_pool::dump_to_screen(uint32_t net_id, int64_t iter) { #ifdef GPU_DEBUG_CONFIG GPU_DEBUG_COUT << "Dump memory pool of network (net_id : " << net_id << ", iter : " << iter << ")" << std::endl; size_t total_requested_mem_non_padded_pool = 0; diff --git a/src/plugins/intel_gpu/src/runtime/ocl/ocl_stream.cpp b/src/plugins/intel_gpu/src/runtime/ocl/ocl_stream.cpp index 2d19fbc169d4c5..ff492979f00db4 100644 --- a/src/plugins/intel_gpu/src/runtime/ocl/ocl_stream.cpp +++ b/src/plugins/intel_gpu/src/runtime/ocl/ocl_stream.cpp @@ -187,7 +187,7 @@ void set_arguments_impl(ocl_kernel_type& kernel, case args_t::LOCAL_MEMORY_SIZE: OPENVINO_ASSERT(args[i].index < data.local_memory_args->size() && data.local_memory_args->at(args[i].index), "The allocated local memory is necessary to set kernel arguments."); - status = set_kernel_arg(kernel, i, data.local_memory_args->at(args[i].index)); + status = set_kernel_arg(kernel, i, static_cast(data.local_memory_args->at(args[i].index))); break; break; default: From 571863cb8783356cd37b4c9e4c72da232d46d94d Mon Sep 17 00:00:00 2001 From: Egor Tyuvaev Date: Thu, 23 Apr 2026 11:48:48 +0200 Subject: [PATCH 036/545] Update memory tests (#35214) ### Details: - add cpu family and model weights size to memory tests runner reported values - rename fields that started with `vm` to avoid confusion ### AI Assistance: - *AI assistance used: no* reference 183990 --- tests/memory_tests/src/memory_test.hpp | 8 +- tests/memory_tests/tools/run_tests.py | 171 +++++++++++++++++++++---- 2 files changed, 152 insertions(+), 27 deletions(-) diff --git a/tests/memory_tests/src/memory_test.hpp b/tests/memory_tests/src/memory_test.hpp index b3a273d9f3de26..810a3363e38174 100644 --- a/tests/memory_tests/src/memory_test.hpp +++ b/tests/memory_tests/src/memory_test.hpp @@ -153,10 +153,10 @@ struct Context { << "\"samples\": {"; for (auto &sample: samples) { std::cout << "\"" << sample.first << "\": {" - << "\"vmsize\": " << sample.second.virtual_size << ", " - << "\"vmpeak\": " << sample.second.virtual_peak << ", " - << "\"vmrss\": " << sample.second.resident_size << ", " - << "\"vmhwm\": " << sample.second.resident_peak << ", " + << "\"system_size\": " << sample.second.virtual_size << ", " + << "\"system_peak\": " << sample.second.virtual_peak << ", " + << "\"system_rss\": " << sample.second.resident_size << ", " + << "\"system_hwm\": " << sample.second.resident_peak << ", " << "\"threads\": " << sample.second.thread_count << ", " << "\"gpu_local_used\": " << sample.second.gpu_local_used << ", " << "\"gpu_local_total\": " << sample.second.gpu_local_total << ", " diff --git a/tests/memory_tests/tools/run_tests.py b/tests/memory_tests/tools/run_tests.py index 071dc373edcecd..7fd12aec3b0b33 100644 --- a/tests/memory_tests/tools/run_tests.py +++ b/tests/memory_tests/tools/run_tests.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # +import platform import argparse import glob import os @@ -10,11 +11,11 @@ import subprocess import json import time +import sys +from typing import Any from pathlib import Path -from datetime import datetime, timedelta from dataclasses import dataclass, asdict -from collections import defaultdict try: import requests @@ -22,6 +23,102 @@ requests = None +INTEL_FAMILIES = { + (0x06, 0x5E): "skylake", + (0x06, 0x55): "skylake", + (0x06, 0x8E): "kabylake", + (0x06, 0x9E): "kabylake", + (0x06, 0xA5): "cometlake", + (0x06, 0xA6): "cometlake", + (0x06, 0x66): "cannonlake", + (0x06, 0x6A): "icelake", + (0x06, 0x6C): "icelake", + (0x06, 0x7D): "icelake", + (0x06, 0x7E): "icelake", + (0x06, 0x9D): "icelake", + (0x06, 0xA7): "rocketlake", + (0x06, 0x8C): "tigerlake", + (0x06, 0x8D): "tigerlake", + (0x06, 0x8F): "sapphirerapids-server", + (0x06, 0xCF): "emeraldrapids-server", + (0x06, 0xAD): "graniterapids", + (0x06, 0xAE): "graniterapids", + (0x13, 0x01): "diamondrapids", + (0x06, 0xD7): "bartlettlake", + (0x06, 0x8A): "lakefield", + (0x06, 0x97): "alderlake", + (0x06, 0x9A): "alderlake", + (0x06, 0xB7): "raptorlake", + (0x06, 0xBA): "raptorlake", + (0x06, 0xBF): "raptorlake", + (0x06, 0xAC): "meteorlake", + (0x06, 0xAA): "meteorlake", + (0x06, 0xC5): "arrowlake", + (0x06, 0xC6): "arrowlake", + (0x06, 0xB5): "arrowlake", + (0x06, 0xBD): "lunarlake", + (0x06, 0xCC): "pantherlake", + (0x06, 0xD5): "wildcatlake", + (0x12, 0x01): "novalake", + (0x12, 0x03): "novalake" +} + + +def get_cpu_family(): + arch = platform.machine().lower() + system = platform.system() + if arch in ("arm64", "aarch64"): + if system == "Darwin": + return subprocess.check_output( + ["sysctl", "machdep.cpu.brand_string"]).decode().strip() + else: + # not implemented + return "Unknown arm64" + elif arch in ("x86_64", "amd64"): + if system == "Windows": + cpuinfo = subprocess.check_output( + ["powershell", "(Get-WmiObject -Class Win32_Processor).Caption"]).decode().strip() + infomatch = re.match(r".* Family (\d+) Model (\d+) .*", + cpuinfo.splitlines()[-1]) + if not infomatch: + return "Unknown x86_64" + try: + family, model = map(int, infomatch.groups()) + except ValueError: + return "Unknown x86_64" + elif system == "Linux": + with open("/proc/cpuinfo") as cpuinfofile: + cpuinfo = cpuinfofile.read().strip().split("\n\n")[0] + cpuinfo = ( + map(str.strip, line.split(":", 1)) + for line in cpuinfo.split("\n") + ) + cpuinfo = {k: v for k, v in cpuinfo} + try: + family = int(cpuinfo.get("cpu family", "")) + model = int(cpuinfo.get("model", "")) + except ValueError: + return "Unknown x86_64" + elif system == "Darwin": + family = subprocess.check_output( + ["sysctl", "machdep.cpu.family"]).decode().strip() + model = subprocess.check_output( + ["sysctl", "machdep.cpu.model"]).decode().strip() + try: + family = int(family) + model = int(model) + except ValueError: + return "Unknown x86_64" + else: + return "Unknown x86_64" + return INTEL_FAMILIES.get((family, model), "Unknown x86_64") + else: + return "Unknown" + + +CPU_FAMILY = get_cpu_family() + + def value_diff(value, reference): difference = value - reference diff_ratio = difference / reference @@ -42,10 +139,10 @@ def attempt(func, *args, **kwargs): @dataclass class MemSample: - vmsize: int - vmpeak: int - vmrss: int - vmhwm: int + system_size: int + system_peak: int + system_rss: int + system_hwm: int threads: int gpu_local_used: int = -1 gpu_local_total: int = -1 @@ -76,7 +173,7 @@ def __repr__(self): def run_test_executable_extract_result(command): - proc = subprocess.run(command, stdout=subprocess.PIPE) + proc = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) run_out = proc.stdout.decode() if run_out.startswith("TEST_RESULTS: "): results_json = run_out.splitlines()[0].removeprefix("TEST_RESULTS: ") @@ -137,7 +234,7 @@ def get_test_info(self): return result def api(self, method, data=None, **kwargs): - extra_args = {"timeout": 30} + extra_args: dict[str, Any] = {"timeout": 30} extra_args.update(kwargs) if self.report_api is None: raise Exception("Report API was not specified") @@ -149,7 +246,7 @@ def api(self, method, data=None, **kwargs): print(f"API Error: {response.text}") return response.json() - def api_push_test_result(self, source, modelid, device, result): + def api_push_test_result(self, model_path, modelid, weights_size, device, result): if not self.report_metadata: print("No job metadata found, no report will be made.") return @@ -163,17 +260,19 @@ def api_push_test_result(self, source, modelid, device, result): sample_report.update({ "test_name": f"{result.get('test', self.test_name)}:{sname}", "status": "failed" if "error" in result else "passed", - "source": source, + "source": model_path, "log": result.get("stderr", ""), "model_name": modelname, "model": modelid, "device": result.get("device") or device, "framework": framework, "precision": precision, - "metrics": sample.as_dict() + "metrics": sample.as_dict(), + "familyCpu": CPU_FAMILY, + "model_size": weights_size }) test_report.append(sample_report) - response = attempt(self.api, "v1/memory/push-2-db-facade", {"data": test_report}) + response = attempt(self.api, "v2/memory/push-2-db-facade", {"data": test_report}) if response: print(f"Push result to API: {response}") @@ -189,7 +288,7 @@ def detect_report_metadata(self): "branch": os.environ.get("sourceBranch", "unknown"), "target_branch": os.environ.get("targetBranch", "unknown"), "log_path": os.environ.get("SHARED_LOG_PATH", ""), - "dldt_version": build_number, + "version": build_number, "ext": {} } except KeyError as err: @@ -219,9 +318,21 @@ def scan_directory(self, directory: Path): yield from ((path.removeprefix(cache_dir).replace("\\", "/"), path) for path in new_files) def generate_test_cases(self): + def _with_filesize(paths): + for (modelid, path) in paths: + weights_path, _ = os.path.splitext(path) + weights_path = f"{weights_path}.bin" + if os.path.isfile(weights_path): + weights_size = os.path.getsize(weights_path) + else: + # weights file does not exist -> invalid test case + continue + yield modelid, path, weights_size for ir_cache_dir in self.ir_cache_dirs: yield from itertools.product( - self.scan_directory(ir_cache_dir), self.devices) + _with_filesize(self.scan_directory(ir_cache_dir)), + self.devices + ) def run_test_case(self, model_path, device): try: @@ -230,9 +341,21 @@ def run_test_case(self, model_path, device): print(f" When running test an unexpected error happened: {ex}") return {"error": "unexpected error", "exception": ex} - def handle_test_result(self, modelid, device, result): + def handle_test_result(self, modelid, weights_size, device, result): + base2_suffixes = ["bytes", "KiB", "MiB", "GiB", "TiB", "PiB"] + + def _base2_human_readable(number): + order_of_magnitude = 0 + one_order_higher = 1 << 10 + while number > one_order_higher and order_of_magnitude < len(base2_suffixes) - 1: + number >>= 10 + order_of_magnitude += 1 + suffix = base2_suffixes[order_of_magnitude] + return f"{number} {suffix}" + status = "error" if "error" in result else "ok" - print(f"TEST {modelid} x {device}: {status}") + weights_size_human_read = _base2_human_readable(weights_size) + print(f"TEST {modelid} ({weights_size_human_read}) x {device}: {status}") if status == "error": error = result.get("error") stdout = result.get("stdout") @@ -245,16 +368,18 @@ def handle_test_result(self, modelid, device, result): print(f" stderr: {stderr.strip()}\n === END OF STDERR ===") if exception: print(f" {repr(exception)}") - return - for sname, sample in result["samples"].items(): - print(f" {sname:>15}: {sample}") + else: + for sname, sample in result["samples"].items(): + print(f" {sname:>15}: {sample}") + sys.stdout.flush() + sys.stderr.flush() def run(self): - for (modelid, model_path), device in self.generate_test_cases(): + for (modelid, model_path, weights_size), device in self.generate_test_cases(): result = self.run_test_case(model_path, device) test_name = result.get("test", self.test_name) - self.api_push_test_result(model_path, modelid, device, result) - self.handle_test_result(modelid, device, result) + self.api_push_test_result(model_path, modelid, weights_size, device, result) + self.handle_test_result(modelid, weights_size, device, result) if __name__ == "__main__": @@ -282,7 +407,7 @@ def run(self): TestSession( args.test_executable, args.ir_cache, - args.devices.split(","), + [device.upper() for device in args.devices.split(",")], args.api, args.upload_reference ).run() From 349dcb5cb82047167e98ede744273095660d0776 Mon Sep 17 00:00:00 2001 From: SamareshSingh <97642706+ssam18@users.noreply.github.com> Date: Thu, 23 Apr 2026 05:05:20 -0500 Subject: [PATCH 037/545] Preserve current_iteration element type in UnrollTensorIterator (#35428) When UnrollTensorIterator unrolls a Loop, it was replacing the current_iteration parameter with an i64 Constant regardless of the parameter's actual type. If the body used an i32 current_iteration feeding into arithmetic ops (e.g. Convert + Unsqueeze + Add), the unrolled graph ended up with mismatched input types and failed on GPU. Picking up the parameter's own element type fixes it; added unit and GPU subgraph tests covering the i32 case reported in #35411. --- .../control_flow/unroll_tensor_iterator.cpp | 3 +- .../unroll_tensor_iterator_test.cpp | 61 +++++++++++++++++++ .../tests/functional/subgraph_tests/loop.cpp | 56 +++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) diff --git a/src/common/transformations/src/transformations/control_flow/unroll_tensor_iterator.cpp b/src/common/transformations/src/transformations/control_flow/unroll_tensor_iterator.cpp index afe51852ac970a..d9fc4f84f72a04 100644 --- a/src/common/transformations/src/transformations/control_flow/unroll_tensor_iterator.cpp +++ b/src/common/transformations/src/transformations/control_flow/unroll_tensor_iterator.cpp @@ -193,7 +193,8 @@ bool ov::pass::UnrollTensorIterator::run_on_model(const std::shared_ptrget_special_body_ports().current_iteration_input_idx; const auto& param_to_delete = body_functions[idx]->get_parameters()[iter_idx]; - auto cur_iter_const = std::make_shared(ov::element::i64, Shape{}, idx); + auto cur_iter_const = + std::make_shared(param_to_delete->get_element_type(), Shape{}, idx); replace_node(param_to_delete, cur_iter_const); body_functions[idx]->remove_parameter(param_to_delete); } diff --git a/src/common/transformations/tests/control_flow/unroll_tensor_iterator_test.cpp b/src/common/transformations/tests/control_flow/unroll_tensor_iterator_test.cpp index 1a89fac09967bb..e5ebcfad93cd86 100644 --- a/src/common/transformations/tests/control_flow/unroll_tensor_iterator_test.cpp +++ b/src/common/transformations/tests/control_flow/unroll_tensor_iterator_test.cpp @@ -13,9 +13,13 @@ #include "common_test_utils/ov_test_utils.hpp" #include "common_test_utils/test_common.hpp" #include "openvino/core/model.hpp" +#include "openvino/op/add.hpp" #include "openvino/op/concat.hpp" +#include "openvino/op/constant.hpp" +#include "openvino/op/convert.hpp" #include "openvino/op/gru_cell.hpp" #include "openvino/op/gru_sequence.hpp" +#include "openvino/op/loop.hpp" #include "openvino/op/lstm_cell.hpp" #include "openvino/op/rnn_cell.hpp" #include "openvino/op/split.hpp" @@ -627,3 +631,60 @@ TEST(TransformationTests, CheckTensorNamesAfterUnrolling) { ASSERT_EQ(names_after.size(), 0); EXPECT_EQ(names_before, names_after); } + +// Regression test: when the Loop's current_iteration parameter has type i32, the +// replacement Constant generated by UnrollTensorIterator must also be i32 (not i64). +// Hardcoding i64 causes type mismatches downstream (e.g. Add gets i64+i32 inputs), +// which is the root cause of the GPU bug reported in GitHub issue #35411. +TEST(TransformationTests, UnrollLoopCurrentIterationPreservesElementType) { + const size_t num_iter = 4; + const ov::Shape scalar{}; + + auto b_iter = std::make_shared(ov::element::i32, scalar); + auto b_data = std::make_shared(ov::element::f32, ov::Shape{1}); + b_iter->set_friendly_name("b_iter"); + b_data->set_friendly_name("b_data"); + + auto cast = std::make_shared(b_iter, ov::element::f32); + auto unsqueeze_axis = ov::op::v0::Constant::create(ov::element::i32, ov::Shape{1}, {0}); + auto iter_f32 = std::make_shared(cast, unsqueeze_axis); + auto b_add = std::make_shared(b_data, iter_f32); + auto b_cond = ov::op::v0::Constant::create(ov::element::boolean, scalar, {true}); + + auto body = std::make_shared(ov::OutputVector{b_cond, b_add}, ov::ParameterVector{b_iter, b_data}); + + auto trip_count = ov::op::v0::Constant::create(ov::element::i64, scalar, {num_iter}); + auto exec_cond = ov::op::v0::Constant::create(ov::element::boolean, scalar, {true}); + + auto loop = std::make_shared(trip_count, exec_cond); + loop->set_function(body); + loop->set_special_body_ports({0, 0}); + + auto outer_data = std::make_shared(ov::element::f32, ov::Shape{1}); + loop->set_merged_input(b_data, outer_data, b_add); + loop->get_iter_value(b_add, -1); + + auto result = std::make_shared(loop->output(0)); + auto model = std::make_shared(ov::ResultVector{result}, ov::ParameterVector{outer_data}); + + ov::pass::Manager manager; + manager.register_pass(); + manager.register_pass(); + manager.run_passes(model); + + for (const auto& node : model->get_ops()) { + if (const auto constant = std::dynamic_pointer_cast(node)) { + if (constant->get_element_type() == ov::element::i64 && constant->get_shape() == ov::Shape{}) { + const auto& consumers = constant->output(0).get_target_inputs(); + for (const auto& consumer : consumers) { + EXPECT_FALSE(std::dynamic_pointer_cast( + consumer.get_node()->shared_from_this()) != nullptr) + << "i64 constant feeds a Convert node after unrolling; " + "current_iteration replacement should be i32, not i64"; + } + } + } + } + + EXPECT_NO_THROW(model->validate_nodes_and_infer_types()); +} diff --git a/src/plugins/intel_gpu/tests/functional/subgraph_tests/loop.cpp b/src/plugins/intel_gpu/tests/functional/subgraph_tests/loop.cpp index 597f9eab00dd7a..eaca55cc5d6804 100644 --- a/src/plugins/intel_gpu/tests/functional/subgraph_tests/loop.cpp +++ b/src/plugins/intel_gpu/tests/functional/subgraph_tests/loop.cpp @@ -602,4 +602,60 @@ INSTANTIATE_TEST_SUITE_P(smoke_LoopWithScatterUpdate, LoopWithScatterUpdateTest, testing::Values(ov::element::f32, ov::element::f16), LoopWithScatterUpdateTest::getTestCaseName); +// Regression test for GitHub issue #35411. +// Loop body where the i32 current_iteration feeds via Cast+Unsqueeze into an Add +// together with an f32/f16 buffer. Before the fix, UnrollTensorIterator replaced +// the i32 Parameter with Constant(i64), causing a type mismatch at Add on GPU. +class LoopWithAddTest : public testing::WithParamInterface, virtual public ov::test::SubgraphBaseTest { +public: + static std::string getTestCaseName(const testing::TestParamInfo& obj) { + std::ostringstream result; + result << "data_type=" << obj.param; + return result.str(); + } + +protected: + void SetUp() override { + targetDevice = ov::test::utils::DEVICE_GPU; + const auto data_type = GetParam(); + + const size_t num_iter = 8; + const size_t feature_sz = 4; + const ov::Shape data_shape{feature_sz}; + + auto outer_data = std::make_shared(data_type, data_shape); + outer_data->set_friendly_name("outer_data"); + + auto trip_count = std::make_shared(ov::element::i64, ov::Shape{}, num_iter); + auto exec_cond = std::make_shared(ov::element::boolean, ov::Shape{}, true); + auto b_timestep = std::make_shared(ov::element::i32, ov::Shape{}); + auto b_data = std::make_shared(data_type, data_shape); + b_timestep->set_friendly_name("timestep"); + b_data->set_friendly_name("b_data"); + + auto cast = std::make_shared(b_timestep, data_type); + auto unsqueeze_axis = std::make_shared(ov::element::i32, ov::Shape{1}, std::vector{0}); + auto timestep_1d = std::make_shared(cast, unsqueeze_axis); + auto b_add = std::make_shared(b_data, timestep_1d); + auto b_cond = std::make_shared(ov::element::boolean, ov::Shape{}, true); + auto body = std::make_shared(ov::OutputVector{b_cond, b_add}, ov::ParameterVector{b_timestep, b_data}); + auto loop = std::make_shared(trip_count, exec_cond); + loop->set_function(body); + loop->set_special_body_ports({0, 0}); + loop->set_merged_input(b_data, outer_data, b_add); + loop->get_iter_value(b_add, -1); + + auto result = std::make_shared(loop->output(0)); + function = std::make_shared(ov::ResultVector{result}, ov::ParameterVector{outer_data}); + + targetStaticShapes = {{data_shape}}; + } +}; + +TEST_P(LoopWithAddTest, Inference) { + run(); +} + +INSTANTIATE_TEST_SUITE_P(smoke_LoopWithAdd, LoopWithAddTest, testing::Values(ov::element::f32, ov::element::f16), LoopWithAddTest::getTestCaseName); + } // namespace From 2699faec78c40438a8f57ffe2608482c91892786 Mon Sep 17 00:00:00 2001 From: Mikhail Ryzhov Date: Thu, 23 Apr 2026 12:28:47 +0200 Subject: [PATCH 038/545] [TRANSFORMATIONS] Move convert_pagedattn_inputs to paged_attention dir (#35471) ### Details: - moving the implementation and references of this transformation from the `common_optimizations` directory to a new `paged_attention` directory, - simplify the pattern and prepare it for adding the new LA operations (see #35128) ### Tickets: [CVS-183796](https://jira.devtools.intel.com/browse/CVS-183796) [CVS-183797](https://jira.devtools.intel.com/browse/CVS-183797) ### AI Assistance: - *AI assistance used: no* - *If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).* --------- Co-authored-by: Mateusz Mikolajczyk Co-authored-by: liubo-intel --- .../convert_pagedattn_inputs.hpp | 2 +- .../convert_pagedattn_inputs.cpp | 184 ------------------ .../convert_pagedattn_inputs.cpp | 136 +++++++++++++ .../convert_pagedattn_inputs.cpp | 2 +- .../transformation_pipeline.cpp | 2 +- .../src/plugin/transformations_pipeline.cpp | 2 +- 6 files changed, 140 insertions(+), 188 deletions(-) rename src/common/transformations/include/transformations/{common_optimizations => paged_attention}/convert_pagedattn_inputs.hpp (98%) delete mode 100644 src/common/transformations/src/transformations/common_optimizations/convert_pagedattn_inputs.cpp create mode 100644 src/common/transformations/src/transformations/paged_attention/convert_pagedattn_inputs.cpp diff --git a/src/common/transformations/include/transformations/common_optimizations/convert_pagedattn_inputs.hpp b/src/common/transformations/include/transformations/paged_attention/convert_pagedattn_inputs.hpp similarity index 98% rename from src/common/transformations/include/transformations/common_optimizations/convert_pagedattn_inputs.hpp rename to src/common/transformations/include/transformations/paged_attention/convert_pagedattn_inputs.hpp index 9e7d945cf87f1e..971ad7b430052d 100644 --- a/src/common/transformations/include/transformations/common_optimizations/convert_pagedattn_inputs.hpp +++ b/src/common/transformations/include/transformations/paged_attention/convert_pagedattn_inputs.hpp @@ -52,4 +52,4 @@ class ConvertPagedAttnInputs : public ov::pass::MatcherPass { }; } // namespace pass -} // namespace ov +} // namespace ov \ No newline at end of file diff --git a/src/common/transformations/src/transformations/common_optimizations/convert_pagedattn_inputs.cpp b/src/common/transformations/src/transformations/common_optimizations/convert_pagedattn_inputs.cpp deleted file mode 100644 index 403ae25362b7ec..00000000000000 --- a/src/common/transformations/src/transformations/common_optimizations/convert_pagedattn_inputs.cpp +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (C) 2018-2026 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 -// - -#include "transformations/common_optimizations/convert_pagedattn_inputs.hpp" - -#include -#include - -#include "itt.hpp" -#include "openvino/core/rt_info.hpp" -#include "openvino/op/add.hpp" -#include "openvino/op/constant.hpp" -#include "openvino/op/paged_attention.hpp" -#include "openvino/pass/pattern/op/wrap_type.hpp" -#include "openvino/util/log.hpp" -#include "transformations/utils/utils.hpp" - -using namespace ov::pass; - -namespace v0 = ov::op::v0; - -namespace ov::pass { - -ConvertPagedAttnInputs::ConvertPagedAttnInputs(const KVCacheConfig& config, - UpdateShapeFunc func, - UpdatePrecisionFunc update_precision_func) - : m_config(config), - m_update_shape_func(std::move(func)), - m_update_precision_func(std::move(update_precision_func)) { - MATCHER_SCOPE(ConvertPagedAttnInputs); - - auto Q = pattern::any_input(pattern::has_static_rank()); - auto K = pattern::any_input(pattern::has_static_rank()); - auto V = pattern::any_input(pattern::has_static_rank()); - auto key_cache_0 = pattern::wrap_type({}); - auto value_cache_0 = pattern::wrap_type({}); - auto past_lens = pattern::any_input(pattern::has_static_rank()); - auto subsequence_begins = pattern::any_input(pattern::has_static_rank()); - auto block_indices = pattern::any_input(pattern::has_static_rank()); - auto block_indices_begins = pattern::any_input(pattern::has_static_rank()); - auto scale = pattern::any_input(pattern::has_static_rank()); - auto sliding_window = pattern::any_input(pattern::has_static_rank()); - auto alibi_slopes = pattern::any_input(pattern::has_static_rank()); - auto max_context_len = pattern::any_input(pattern::has_static_rank()); - auto score_aggregation_window = pattern::any_input(pattern::has_static_rank()); - auto rotated_block_indices = pattern::any_input(pattern::has_static_rank()); - auto rotation_deltas = pattern::any_input(pattern::has_static_rank()); - auto rotation_trig_lut = pattern::any_input(pattern::has_static_rank()); - auto xattention_threshold = pattern::any_input(pattern::has_static_rank()); - auto xattention_block_size = pattern::any_input(pattern::has_static_rank()); - auto xattention_stride = pattern::any_input(pattern::has_static_rank()); - auto sinks = pattern::any_input(pattern::has_static_rank()); - auto adaptive_rkv_start_size = pattern::any_input(pattern::has_static_rank()); - auto adaptive_rkv_evictable_sizes = pattern::any_input(pattern::has_static_rank()); - auto adaptive_rkv_diversity_block_set_indices = pattern::any_input(pattern::has_static_rank()); - auto adaptive_rkv_diversity_block_set_indices_begins = pattern::any_input(pattern::has_static_rank()); - auto token_type_ids = pattern::any_input(pattern::has_static_rank()); - - auto qq_bias = pattern::any_input(pattern::has_static_rank()); - auto qq_bias_begins = pattern::any_input(pattern::has_static_rank()); - auto result = pattern::wrap_type({Q, - K, - V, - key_cache_0, - value_cache_0, - past_lens, - subsequence_begins, - block_indices, - block_indices_begins, - scale, - sliding_window, - alibi_slopes, - max_context_len, - score_aggregation_window, - rotated_block_indices, - rotation_deltas, - rotation_trig_lut, - xattention_threshold, - xattention_block_size, - xattention_stride, - sinks, - adaptive_rkv_start_size, - adaptive_rkv_evictable_sizes, - adaptive_rkv_diversity_block_set_indices, - adaptive_rkv_diversity_block_set_indices_begins, - token_type_ids, - qq_bias, - qq_bias_begins}); - ov::matcher_pass_callback callback = [OV_CAPTURE_CPY_AND_THIS](pattern::Matcher& m) { - const auto pa_op = m.get_match_root(); - auto key_cache = ov::as_type_ptr(pa_op->get_input_node_shared_ptr(3)); - auto value_cache = ov::as_type_ptr(pa_op->get_input_node_shared_ptr(4)); - auto format_cache_precision = [](ov::element::Type cache_precision, ov::element::Type infer_precision) { - return cache_precision == ov::element::f16 && infer_precision == ov::element::bf16 ? infer_precision - : cache_precision; - }; - - auto init_cache_shape = [&](const size_t head_nums, - const size_t head_size, - const size_t block_size, - const ov::element::Type precision, - const size_t group_size, - const bool bychannel, - const std::vector& orders) { - ov::Dimension::value_type _block_size = block_size; - ov::Dimension::value_type _head_nums = head_nums; - ov::Dimension::value_type _head_size = head_size; - ov::Dimension::value_type _group_size = group_size; - _group_size = _group_size ? _group_size : _head_size; - if (!bychannel) { - if (_head_size % _group_size != 0) { - OPENVINO_THROW("cache head_size ", head_size, "cannot be divided by group_size ", group_size); - } - } - size_t group_num = _head_size / _group_size; - // Update head_size and block_size by precision and quantizing channel mode - m_update_shape_func(precision, bychannel, group_num, _head_size, _block_size); - - auto block_shape = ov::PartialShape::dynamic(4); - block_shape[orders[0]] = -1; - block_shape[orders[1]] = _head_nums; - block_shape[orders[2]] = _block_size; - block_shape[orders[3]] = _head_size; - - return block_shape; - }; - auto key_cache_precision = format_cache_precision(m_config.keyCachePrecision, m_config.inferencePrecision); - auto value_cache_precision = format_cache_precision(m_config.valueCachePrecision, m_config.inferencePrecision); - key_cache->set_element_type(key_cache_precision); - value_cache->set_element_type(value_cache_precision); - bool status = false; - if (pa_op->get_rt_info().count("num_k_heads") && pa_op->get_rt_info().count("k_head_size") && - pa_op->get_rt_info().count("num_v_heads") && pa_op->get_rt_info().count("v_head_size")) { - const auto key_cache_shape = init_cache_shape(pa_op->get_rt_info()["num_k_heads"].as(), - pa_op->get_rt_info()["k_head_size"].as(), - m_config.keyCacheBlockSize, - key_cache_precision, - m_config.keyCacheGroupSize, - m_config.keyCacheQuantBychannel, - m_config.keyCacheDimOrder); - const auto value_cache_shape = init_cache_shape(pa_op->get_rt_info()["num_v_heads"].as(), - pa_op->get_rt_info()["v_head_size"].as(), - m_config.valueCacheBlockSize, - value_cache_precision, - m_config.valueCacheGroupSize, - m_config.valueCacheQuantBychannel, - m_config.valueCacheDimOrder); - - key_cache->set_partial_shape(key_cache_shape); - value_cache->set_partial_shape(value_cache_shape); - status = true; - } else { - OPENVINO_DEBUG("PagedAttn ", - pa_op->get_friendly_name(), - " doesn't have rtinfo for num_k_heads/k_head_size/num_v_heads/num_v_heads"); - status = false; - } - - if (m_update_precision_func) { - m_update_precision_func(key_cache_precision); - m_update_precision_func(value_cache_precision); - key_cache->set_element_type(key_cache_precision); - value_cache->set_element_type(value_cache_precision); - } - - key_cache->validate_and_infer_types(); - value_cache->validate_and_infer_types(); - return status; - }; - - auto m = std::make_shared(result, matcher_name); - this->register_matcher(m, callback); -} - -void ConvertPagedAttnInputs::setKVCacheConfig(const KVCacheConfig& config) { - m_config = config; -} - -const ConvertPagedAttnInputs::KVCacheConfig& ConvertPagedAttnInputs::getKVCacheConfig() const { - return m_config; -} - -} // namespace ov::pass diff --git a/src/common/transformations/src/transformations/paged_attention/convert_pagedattn_inputs.cpp b/src/common/transformations/src/transformations/paged_attention/convert_pagedattn_inputs.cpp new file mode 100644 index 00000000000000..84fa60b0268f66 --- /dev/null +++ b/src/common/transformations/src/transformations/paged_attention/convert_pagedattn_inputs.cpp @@ -0,0 +1,136 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "transformations/paged_attention/convert_pagedattn_inputs.hpp" + +#include +#include + +#include "itt.hpp" +#include "openvino/core/rt_info.hpp" +#include "openvino/op/add.hpp" +#include "openvino/op/constant.hpp" +#include "openvino/op/paged_attention.hpp" +#include "openvino/pass/pattern/op/wrap_type.hpp" +#include "openvino/util/log.hpp" +#include "transformations/utils/utils.hpp" + +using namespace ov::pass; + +namespace v0 = ov::op::v0; + +namespace ov::pass { + +ConvertPagedAttnInputs::ConvertPagedAttnInputs(const KVCacheConfig& config, + UpdateShapeFunc func, + UpdatePrecisionFunc update_precision_func) + : m_config(config), + m_update_shape_func(std::move(func)), + m_update_precision_func(std::move(update_precision_func)) { + MATCHER_SCOPE(ConvertPagedAttnInputs); + + auto result = pattern::wrap_type(); + ov::matcher_pass_callback callback = [OV_CAPTURE_CPY_AND_THIS](pattern::Matcher& m) { + const auto root = m.get_match_root(); + bool status = false; + + if (const auto pa_op = ov::as_type_ptr(root)) { + auto key_cache = ov::as_type_ptr(pa_op->get_input_node_shared_ptr(3)); + auto value_cache = ov::as_type_ptr(pa_op->get_input_node_shared_ptr(4)); + if (!key_cache || !value_cache) { + return false; + } + auto format_cache_precision = [](ov::element::Type cache_precision, ov::element::Type infer_precision) { + return cache_precision == ov::element::f16 && infer_precision == ov::element::bf16 ? infer_precision + : cache_precision; + }; + + auto init_cache_shape = [&](const size_t head_nums, + const size_t head_size, + const size_t block_size, + const ov::element::Type precision, + const size_t group_size, + const bool bychannel, + const std::vector& orders) { + ov::Dimension::value_type _block_size = block_size; + ov::Dimension::value_type _head_nums = head_nums; + ov::Dimension::value_type _head_size = head_size; + ov::Dimension::value_type _group_size = group_size; + _group_size = _group_size ? _group_size : _head_size; + if (!bychannel) { + if (_head_size % _group_size != 0) { + OPENVINO_THROW("cache head_size ", head_size, "cannot be divided by group_size ", group_size); + } + } + size_t group_num = _head_size / _group_size; + // Update head_size and block_size by precision and quantizing channel mode + m_update_shape_func(precision, bychannel, group_num, _head_size, _block_size); + + auto block_shape = ov::PartialShape::dynamic(4); + block_shape[orders[0]] = -1; + block_shape[orders[1]] = _head_nums; + block_shape[orders[2]] = _block_size; + block_shape[orders[3]] = _head_size; + + return block_shape; + }; + auto key_cache_precision = format_cache_precision(m_config.keyCachePrecision, m_config.inferencePrecision); + auto value_cache_precision = + format_cache_precision(m_config.valueCachePrecision, m_config.inferencePrecision); + key_cache->set_element_type(key_cache_precision); + value_cache->set_element_type(value_cache_precision); + if (pa_op->get_rt_info().count("num_k_heads") && pa_op->get_rt_info().count("k_head_size") && + pa_op->get_rt_info().count("num_v_heads") && pa_op->get_rt_info().count("v_head_size")) { + const auto key_cache_shape = init_cache_shape(pa_op->get_rt_info()["num_k_heads"].as(), + pa_op->get_rt_info()["k_head_size"].as(), + m_config.keyCacheBlockSize, + key_cache_precision, + m_config.keyCacheGroupSize, + m_config.keyCacheQuantBychannel, + m_config.keyCacheDimOrder); + const auto value_cache_shape = init_cache_shape(pa_op->get_rt_info()["num_v_heads"].as(), + pa_op->get_rt_info()["v_head_size"].as(), + m_config.valueCacheBlockSize, + value_cache_precision, + m_config.valueCacheGroupSize, + m_config.valueCacheQuantBychannel, + m_config.valueCacheDimOrder); + + key_cache->set_partial_shape(key_cache_shape); + value_cache->set_partial_shape(value_cache_shape); + status = true; + } else { + OPENVINO_DEBUG("PagedAttn ", + pa_op->get_friendly_name(), + " doesn't have rtinfo for num_k_heads/k_head_size/num_v_heads/v_head_size"); + status = false; + } + + if (m_update_precision_func) { + m_update_precision_func(key_cache_precision); + m_update_precision_func(value_cache_precision); + key_cache->set_element_type(key_cache_precision); + value_cache->set_element_type(value_cache_precision); + } + + key_cache->validate_and_infer_types(); + value_cache->validate_and_infer_types(); + } + + return status; + }; + + auto m = std::make_shared(result, matcher_name); + this->register_matcher(m, callback); +} + +void ConvertPagedAttnInputs::setKVCacheConfig(const KVCacheConfig& config) { + m_config = config; +} + +const ConvertPagedAttnInputs::KVCacheConfig& ConvertPagedAttnInputs::getKVCacheConfig() const { + return m_config; +} + +} // namespace ov::pass diff --git a/src/common/transformations/tests/common_optimizations/convert_pagedattn_inputs.cpp b/src/common/transformations/tests/common_optimizations/convert_pagedattn_inputs.cpp index 0aa648aab3f48b..0a056e2cac211a 100644 --- a/src/common/transformations/tests/common_optimizations/convert_pagedattn_inputs.cpp +++ b/src/common/transformations/tests/common_optimizations/convert_pagedattn_inputs.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // -#include "transformations/common_optimizations/convert_pagedattn_inputs.hpp" +#include "transformations/paged_attention/convert_pagedattn_inputs.hpp" #include diff --git a/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp b/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp index 7b2ee3251ea646..9ec13b6a962288 100644 --- a/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp +++ b/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp @@ -59,7 +59,6 @@ #include "transformations/common_optimizations/add_fake_quantize_fusion.hpp" #include "transformations/common_optimizations/augru_cell_fusion.hpp" #include "transformations/common_optimizations/common_optimizations.hpp" -#include "transformations/common_optimizations/convert_pagedattn_inputs.hpp" #include "transformations/common_optimizations/convert_quantize_dequantize.hpp" #include "transformations/common_optimizations/fq_mul_fusion.hpp" #include "transformations/common_optimizations/fuse_gated_delta_net.hpp" @@ -126,6 +125,7 @@ #include "transformations/op_conversions/softsign_decomposition.hpp" #include "transformations/op_conversions/unique_decomposition.hpp" #include "transformations/opset_conversions/convert_opset2_to_opset1.hpp" +#include "transformations/paged_attention/convert_pagedattn_inputs.hpp" #include "transformations/rt_info/keep_const_precision.hpp" #include "transformations/smart_reshape/matmul_sr.hpp" #include "transformations/symbolic_transformations/symbolic_optimizations.hpp" diff --git a/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp b/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp index dd72028ce26a4e..a661bc13a17319 100644 --- a/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp +++ b/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp @@ -111,7 +111,6 @@ #include "transformations/common_optimizations/broadcast_elementwise_fusion.hpp" #include "transformations/common_optimizations/broadcast_transition.hpp" #include "transformations/common_optimizations/common_optimizations.hpp" -#include "transformations/common_optimizations/convert_pagedattn_inputs.hpp" #include "transformations/common_optimizations/convert_quantize_dequantize.hpp" #include "transformations/common_optimizations/fuse_rotary_positional_embeddings.hpp" #include "transformations/common_optimizations/fuse_gated_delta_net.hpp" @@ -195,6 +194,7 @@ #include "transformations/op_conversions/softplus_decomposition.hpp" #include "transformations/opset_conversions/convert_opset2_to_opset1.hpp" #include "transformations/opset_conversions/convert_opset3_to_opset2.hpp" +#include "transformations/paged_attention/convert_pagedattn_inputs.hpp" #include "transformations/resolve_names_collisions.hpp" #include "transformations/rt_info/dequantization_node.hpp" #include "transformations/rt_info/fused_names_attribute.hpp" From c1bf755000e85377d2259af1499882b823568deb Mon Sep 17 00:00:00 2001 From: Susanta Bhattacharjee Date: Thu, 23 Apr 2026 16:07:57 +0530 Subject: [PATCH 039/545] Memory optimization during conversion of onnx model OV IR format (#35207) https://jira.devtools.intel.com/browse/CVS-184755 Onnx front end receives onnx model as InputModel object and converts it to ov::Model object. The constants are copied during this process, doubling the RAM usage. This PR removes this extra copy and reuses original buffer for GPU and NPU only. CPU does not have separate device memory. The buffers inside InputModel go out of scope during inference. GPU and NPU creates constants nodes in their device memory separately. Hence, the copy during InputModel to ov::Model conversion is redundant for GPU/NPU. This approach helps saving GBs or RAM while full compilation of onxx model using ORT infra. It also improves model compilation time by 20-30% as a byproduct. Notes: 1. This OV side optimization is disabled by default. OVEP has to trigger the optimization during model loading through onnx FE. Memory and session creation time benefit will be available after OVEP changes. 2. This optimization works for models with embedded weights. External weight file case is already optimized. 3. This optimization works for GPU and NPU. --------- Co-authored-by: Michal Lukaszewski --- .../onnx/frontend/src/core/tensor.cpp | 108 ++++++++++-------- .../onnx/frontend/src/core/tensor.hpp | 11 +- src/frontends/onnx/frontend/src/frontend.cpp | 17 ++- .../onnx/frontend/src/input_model.cpp | 28 +++-- .../onnx/frontend/src/input_model.hpp | 4 +- 5 files changed, 104 insertions(+), 64 deletions(-) diff --git a/src/frontends/onnx/frontend/src/core/tensor.cpp b/src/frontends/onnx/frontend/src/core/tensor.cpp index 9e98277a5a2206..8d02203a2cf2a8 100644 --- a/src/frontends/onnx/frontend/src/core/tensor.cpp +++ b/src/frontends/onnx/frontend/src/core/tensor.cpp @@ -536,56 +536,64 @@ std::shared_ptr Tensor::get_ov_constant() const { "UINT4, UINT8, UINT16, UINT32, UINT64, STRING"); } } else if (m_tensor_place != nullptr) { - switch (m_tensor_place->get_element_type()) { - case ov::element::f32: - case ov::element::f64: - case ov::element::i32: - case ov::element::i64: - case ov::element::u32: - case ov::element::u64: - constant = std::make_shared(ov_type, m_shape, get_data_ptr()); - break; - case ov::element::i4: - constant = std::make_shared(ov_type, m_shape, get_data().data()); - break; - case ov::element::i8: - constant = std::make_shared(ov_type, m_shape, get_data().data()); - break; - case ov::element::i16: - constant = std::make_shared(ov_type, m_shape, get_data().data()); - break; - case ov::element::u4: - constant = std::make_shared(ov_type, m_shape, get_data().data()); - break; - case ov::element::u8: - constant = std::make_shared(ov_type, m_shape, get_data().data()); - break; - case ov::element::u16: - constant = std::make_shared(ov_type, m_shape, get_data().data()); - break; - case ov::element::boolean: - constant = std::make_shared(ov_type, m_shape, get_data().data()); - break; - case ov::element::bf16: - constant = std::make_shared(ov_type, m_shape, get_data().data()); - break; - case ov::element::f16: - constant = std::make_shared(ov_type, m_shape, get_data().data()); - break; - case ov::element::f8e4m3: - constant = std::make_shared(ov_type, m_shape, get_data().data()); - break; - case ov::element::f8e5m2: - constant = std::make_shared(ov_type, m_shape, get_data().data()); - break; - case ov::element::string: - constant = std::make_shared(ov_type, m_shape, get_data().data()); - break; - default: - ONNX_UNSUPPORTED_DATA_TYPE(m_tensor_proto->data_type(), - "BOOL, BFLOAT16, FLOAT8E4M3FN, FLOAT8E5M2, FLOAT, FLOAT16, DOUBLE, INT4, " - "INT8, INT16, INT32, INT64, " - "UINT4, UINT8, UINT16, UINT32, UINT64, STRING"); + auto elemnt_type = m_tensor_place->get_element_type(); + + if (!m_tensor_place->is_const_data_reusable() || elemnt_type == ov::element::string) { + switch (elemnt_type) { + case ov::element::f32: + case ov::element::f64: + case ov::element::i32: + case ov::element::i64: + case ov::element::u32: + case ov::element::u64: + constant = std::make_shared(ov_type, m_shape, get_data_ptr()); + break; + case ov::element::i4: + constant = std::make_shared(ov_type, m_shape, get_data().data()); + break; + case ov::element::i8: + constant = std::make_shared(ov_type, m_shape, get_data().data()); + break; + case ov::element::i16: + constant = std::make_shared(ov_type, m_shape, get_data().data()); + break; + case ov::element::u4: + constant = std::make_shared(ov_type, m_shape, get_data().data()); + break; + case ov::element::u8: + constant = std::make_shared(ov_type, m_shape, get_data().data()); + break; + case ov::element::u16: + constant = std::make_shared(ov_type, m_shape, get_data().data()); + break; + case ov::element::boolean: + constant = std::make_shared(ov_type, m_shape, get_data().data()); + break; + case ov::element::bf16: + constant = std::make_shared(ov_type, m_shape, get_data().data()); + break; + case ov::element::f16: + constant = std::make_shared(ov_type, m_shape, get_data().data()); + break; + case ov::element::f8e4m3: + constant = std::make_shared(ov_type, m_shape, get_data().data()); + break; + case ov::element::f8e5m2: + constant = std::make_shared(ov_type, m_shape, get_data().data()); + break; + case ov::element::string: + constant = std::make_shared(ov_type, m_shape, get_data().data()); + break; + default: + ONNX_UNSUPPORTED_DATA_TYPE(m_tensor_proto->data_type(), + "BOOL, BFLOAT16, FLOAT8E4M3FN, FLOAT8E5M2, FLOAT, FLOAT16, DOUBLE, INT4, " + "INT8, INT16, INT32, INT64, " + "UINT4, UINT8, UINT16, UINT32, UINT64, STRING"); + } + } else { + const void* data_ptr = m_tensor_place->get_data(); + auto constant_buffer = std::make_shared(data_ptr); + constant = std::make_shared(ov_type, m_shape, data_ptr, constant_buffer); } } else { FRONT_END_THROW("Tensor shape doesn't match data size"); diff --git a/src/frontends/onnx/frontend/src/core/tensor.hpp b/src/frontends/onnx/frontend/src/core/tensor.hpp index 6e811373d29f52..adbbf7c8845e39 100644 --- a/src/frontends/onnx/frontend/src/core/tensor.hpp +++ b/src/frontends/onnx/frontend/src/core/tensor.hpp @@ -88,14 +88,16 @@ class TensorONNXPlace : public ov::frontend::onnx::TensorPlace { const size_t data_size, const ov::Any& data_any, std::shared_ptr data_location, - const bool is_raw) + const bool is_raw, + const bool reuse_const_data = false) : ov::frontend::onnx::TensorPlace(input_model, pshape, type, names), m_input_model(input_model), m_data(data), m_data_any(data_any), m_data_size(data_size), m_data_location(data_location), - m_is_raw(is_raw) {}; + m_is_raw(is_raw), + m_reuse_const_data(reuse_const_data) {}; void translate(ov::Output& output); @@ -140,6 +142,10 @@ class TensorONNXPlace : public ov::frontend::onnx::TensorPlace { return m_is_raw; } + bool is_const_data_reusable() const { + return m_reuse_const_data; + } + detail::MappedMemoryHandles get_mmap_cache(); detail::LocalStreamHandles get_stream_cache(); std::filesystem::path get_model_dir() const; @@ -152,6 +158,7 @@ class TensorONNXPlace : public ov::frontend::onnx::TensorPlace { size_t m_data_size; std::shared_ptr m_data_location; bool m_is_raw; + bool m_reuse_const_data; }; class Tensor { diff --git a/src/frontends/onnx/frontend/src/frontend.cpp b/src/frontends/onnx/frontend/src/frontend.cpp index 0b3602661cd080..04a2a28b0fe931 100644 --- a/src/frontends/onnx/frontend/src/frontend.cpp +++ b/src/frontends/onnx/frontend/src/frontend.cpp @@ -172,8 +172,12 @@ ov::frontend::InputModel::Ptr FrontEnd::load_impl(const std::vector& va if (variants.empty()) { return nullptr; } + // enable mmap by default - const bool enable_mmap = variants[variants.size() - 1].is() ? variants[variants.size() - 1].as() : true; + bool enable_mmap = true; + + if (variants.size() > 1 && variants[1].is()) + enable_mmap = variants[1].as(); const auto create_iterator_model = [&](const std::filesystem::path& model_path) { OPENVINO_DEBUG("[ONNX Frontend] Enabled an experimental GraphIteratorProto interface!!!"); @@ -209,10 +213,19 @@ ov::frontend::InputModel::Ptr FrontEnd::load_impl(const std::vector& va return std::make_shared(std::make_shared(*model_proto_ptr), m_extensions); } // !!! End of Experimental feature + if (variants[0].is()) { auto graph_iterator = variants[0].as(); + bool reuse_const_data = false; + + if (variants.size() > 2 && variants[2].is()) + reuse_const_data = variants[2].as(); + // enable_mmap is a hint for a fallback in case external GraphIterator cannot work with external data - return std::make_shared(graph_iterator, enable_mmap, m_extensions.telemetry); + auto inputModel = + std::make_shared(graph_iterator, enable_mmap, m_extensions.telemetry, reuse_const_data); + + return inputModel; } return nullptr; } diff --git a/src/frontends/onnx/frontend/src/input_model.cpp b/src/frontends/onnx/frontend/src/input_model.cpp index 1e29ab34dc7800..a8ddb06a5f7bcb 100644 --- a/src/frontends/onnx/frontend/src/input_model.cpp +++ b/src/frontends/onnx/frontend/src/input_model.cpp @@ -558,7 +558,9 @@ class InputModel::InputModelONNXImpl { InputModelONNXImpl(const GraphIterator::Ptr& graph_iterator, const ov::frontend::InputModel& input_model, const std::shared_ptr& telemetry, - const bool enable_mmap); + const bool enable_mmap, + const bool reuse_const_data); + InputModelONNXImpl(const GraphIterator::Ptr& graph_iterator, const ov::frontend::InputModel& input_model, unify::InputModel::Ptr parent_model); @@ -633,6 +635,7 @@ class InputModel::InputModelONNXImpl { std::map m_metadata; std::shared_ptr m_telemetry; bool m_enable_mmap; + bool m_reuse_const_data; // This is used for keeping MMAP cache handles detail::MappedMemoryHandles m_mmap_cache; @@ -651,7 +654,8 @@ class InputModel::InputModelONNXImpl { namespace { std::shared_ptr decode_tensor_place( const ov::frontend::onnx::TensorMetaInfo& tensor_meta_info, - const ov::frontend::InputModel& model) { + const ov::frontend::InputModel& model, + const bool reuse_const_data) { auto tensor_place = std::make_shared(model, tensor_meta_info.m_partial_shape, @@ -661,7 +665,8 @@ std::shared_ptr decode_tensor_place( tensor_meta_info.m_tensor_data_size, tensor_meta_info.m_tensor_data_any, tensor_meta_info.m_external_location, - tensor_meta_info.m_is_raw); + tensor_meta_info.m_is_raw, + reuse_const_data); return tensor_place; } @@ -678,7 +683,8 @@ void InputModel::InputModelONNXImpl::load_model() { const auto& decoder = m_graph_iterator->get_decoder(); if (auto tensor_decoder = std::dynamic_pointer_cast(decoder)) { - auto tensor_place = decode_tensor_place(tensor_decoder->get_tensor_info(), m_input_model); + auto tensor_place = + decode_tensor_place(tensor_decoder->get_tensor_info(), m_input_model, m_reuse_const_data); tensor_place->set_input_index(tensor_decoder->get_input_idx()); tensor_place->set_output_index(tensor_decoder->get_output_idx()); @@ -797,7 +803,7 @@ std::shared_ptr InputModel::InputModelONNXImpl::ensure_tensor_p if (auto existing = find_tensor_place(tensor_meta_info)) { return existing; } - return register_tensor_place(decode_tensor_place(tensor_meta_info, m_input_model)); + return register_tensor_place(decode_tensor_place(tensor_meta_info, m_input_model, m_reuse_const_data)); } void InputModel::InputModelONNXImpl::connect_inputs(const std::shared_ptr& op_place, @@ -842,11 +848,13 @@ void InputModel::InputModelONNXImpl::connect_outputs(const std::shared_ptr& telemetry, - const bool enable_mmap) + const bool enable_mmap, + const bool reuse_const_data) : m_graph_iterator(graph_iterator), m_input_model(input_model), m_telemetry(telemetry), - m_enable_mmap(enable_mmap) { + m_enable_mmap(enable_mmap), + m_reuse_const_data(reuse_const_data) { FRONT_END_GENERAL_CHECK(m_graph_iterator, "Null pointer specified for GraphIterator"); if (const auto graph_iterator = std::dynamic_pointer_cast(m_graph_iterator)) { m_model_dir = graph_iterator->get_model_dir(); @@ -868,6 +876,7 @@ InputModel::InputModelONNXImpl::InputModelONNXImpl(const GraphIterator::Ptr& gra m_input_model(input_model), m_telemetry(parent_model->get_telemetry_extension()), m_enable_mmap(parent_model->is_enabled_mmap()), + m_reuse_const_data(false), m_mmap_cache(parent_model->_impl->m_mmap_cache), m_stream_cache(parent_model->_impl->m_stream_cache) { FRONT_END_GENERAL_CHECK(m_graph_iterator, "Null pointer specified for GraphIterator"); @@ -996,8 +1005,9 @@ void InputModel::InputModelONNXImpl::clean_up() {} InputModel::InputModel(const GraphIterator::Ptr& graph_iterator, const bool enable_mmap, - const std::shared_ptr& telemetry) - : _impl{std::make_shared(graph_iterator, *this, telemetry, enable_mmap)} {} + const std::shared_ptr& telemetry, + const bool reuse_const_data) + : _impl{std::make_shared(graph_iterator, *this, telemetry, enable_mmap, reuse_const_data)} {} InputModel::InputModel(const GraphIterator::Ptr& graph_iterator, ov::frontend::onnx::unify::InputModel::Ptr parent_model) diff --git a/src/frontends/onnx/frontend/src/input_model.hpp b/src/frontends/onnx/frontend/src/input_model.hpp index 6d40aabb304c0c..20ebcc7cee06bf 100644 --- a/src/frontends/onnx/frontend/src/input_model.hpp +++ b/src/frontends/onnx/frontend/src/input_model.hpp @@ -111,7 +111,9 @@ class InputModel : public ov::frontend::InputModel { explicit InputModel(const ov::frontend::onnx::GraphIterator::Ptr& graph_iterator, const bool enable_mmap, - const std::shared_ptr& telemetry = {}); + const std::shared_ptr& telemetry = {}, + const bool reuse_const_data = false); + explicit InputModel(const ov::frontend::onnx::GraphIterator::Ptr& graph_iterator, unify::InputModel::Ptr parent_model); From f2959dc66e1ad19139acb52655e98042da65e377 Mon Sep 17 00:00:00 2001 From: Mateusz Mikolajczyk Date: Thu, 23 Apr 2026 13:03:54 +0200 Subject: [PATCH 040/545] [CORE] Update GatedDeltaNet to support GQA (#35472) ### Details: - *item1* - *...* ### Tickets: - *ticket-id* ### AI Assistance: - *AI assistance used: no / yes* - *If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).* --- .../gated_delta_net_shape_inference.hpp | 8 +++---- src/core/src/op/gated_delta_net.cpp | 22 +++++++++---------- src/core/tests/type_prop/gated_delta_net.cpp | 22 ++++++++++++++++--- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/core/shape_inference/include/gated_delta_net_shape_inference.hpp b/src/core/shape_inference/include/gated_delta_net_shape_inference.hpp index a54773d1852d23..238b5c7c2ec117 100644 --- a/src/core/shape_inference/include/gated_delta_net_shape_inference.hpp +++ b/src/core/shape_inference/include/gated_delta_net_shape_inference.hpp @@ -31,8 +31,8 @@ std::vector shape_infer(const GatedDeltaNet* op, const std::vector& NODE_SHAPE_INFER_CHECK(op, input_shapes, - q_head_num.compatible(k_head_num) && q_head_num.compatible(v_head_num), - "The number of heads in query key and value should be the same, but got ", + q_head_num.compatible(k_head_num), + "The number of heads in query and key should be the same, but got ", q_head_num, " and ", k_head_num); @@ -50,8 +50,8 @@ std::vector shape_infer(const GatedDeltaNet* op, const std::vector& NODE_SHAPE_INFER_CHECK(op, input_shapes, - gate_head_num.compatible(beta_head_num) && gate_head_num.compatible(q_head_num), - "The number of heads in gate, beta, and query should be the same, but got ", + gate_head_num.compatible(beta_head_num) && gate_head_num.compatible(v_head_num), + "The number of heads in gate, beta, and value should be the same, but got ", gate_head_num, " and ", beta_head_num); diff --git a/src/core/src/op/gated_delta_net.cpp b/src/core/src/op/gated_delta_net.cpp index 569a7f344b5a6c..32d19b176df3ac 100644 --- a/src/core/src/op/gated_delta_net.cpp +++ b/src/core/src/op/gated_delta_net.cpp @@ -13,11 +13,11 @@ namespace { // Validates input rank and type for a node input. -inline void input_check(const ov::Node* node, - size_t idx, - const std::string_view input_name, - std::initializer_list&& allowed_ranks, - const std::vector& allowed_types) { +inline void gdn_input_check(const ov::Node* node, + size_t idx, + const std::string_view input_name, + std::initializer_list&& allowed_ranks, + const std::vector& allowed_types) { using namespace ov; using namespace ov::util; using namespace ov::element; @@ -91,12 +91,12 @@ void GatedDeltaNet::validate_and_infer_types() { NODE_VALIDATION_CHECK(this, get_input_size() == 6, "GatedDeltaNet expects 6 inputs, but it has ", get_input_size()); // format: Node*, input_idx, name, {rank_list}, {type_list} - input_check(this, 0, "query", {4}, {ov::element::f32, ov::element::f16, ov::element::bf16}); - input_check(this, 1, "key", {4}, {ov::element::f32, ov::element::f16, ov::element::bf16}); - input_check(this, 2, "value", {4}, {ov::element::f32, ov::element::f16, ov::element::bf16}); - input_check(this, 3, "recurrent_state", {4}, {ov::element::f32, ov::element::f16, ov::element::bf16}); - input_check(this, 4, "gate", {3}, {ov::element::f32, ov::element::f16, ov::element::bf16}); - input_check(this, 5, "beta", {3}, {ov::element::f32, ov::element::f16, ov::element::bf16}); + gdn_input_check(this, 0, "query", {4}, {ov::element::f32, ov::element::f16, ov::element::bf16}); + gdn_input_check(this, 1, "key", {4}, {ov::element::f32, ov::element::f16, ov::element::bf16}); + gdn_input_check(this, 2, "value", {4}, {ov::element::f32, ov::element::f16, ov::element::bf16}); + gdn_input_check(this, 3, "recurrent_state", {4}, {ov::element::f32, ov::element::f16, ov::element::bf16}); + gdn_input_check(this, 4, "gate", {3}, {ov::element::f32, ov::element::f16, ov::element::bf16}); + gdn_input_check(this, 5, "beta", {3}, {ov::element::f32, ov::element::f16, ov::element::bf16}); const auto output_shapes = shape_infer(this, ov::util::get_node_input_partial_shapes(*this)); set_output_type(0, get_input_element_type(0), output_shapes[0]); set_output_type(1, get_input_element_type(3), output_shapes[1]); diff --git a/src/core/tests/type_prop/gated_delta_net.cpp b/src/core/tests/type_prop/gated_delta_net.cpp index 6ad8cfaafdacc1..042877696b4250 100644 --- a/src/core/tests/type_prop/gated_delta_net.cpp +++ b/src/core/tests/type_prop/gated_delta_net.cpp @@ -114,7 +114,7 @@ TEST(type_prop, gated_delta_net_invalid_type) { testing::HasSubstr("Element type of `query` input should be in")); } -TEST(type_prop, gated_delta_net_head_num_mismatch_qkv) { +TEST(type_prop, gated_delta_net_head_num_mismatch_qk) { OV_EXPECT_THROW(std::ignore = make_gdn(element::f32, Shape{2, 5, 4, 8}, Shape{2, 5, 6, 8}, @@ -123,7 +123,7 @@ TEST(type_prop, gated_delta_net_head_num_mismatch_qkv) { Shape{2, 5, 4}, Shape{2, 5, 4}), NodeValidationFailure, - testing::HasSubstr("The number of heads in query key and value should be the same")); + testing::HasSubstr("The number of heads in query and key should be the same")); } TEST(type_prop, gated_delta_net_head_size_mismatch_qk) { @@ -147,7 +147,23 @@ TEST(type_prop, gated_delta_net_gate_beta_head_num_mismatch) { Shape{2, 5, 6}, Shape{2, 5, 4}), NodeValidationFailure, - testing::HasSubstr("The number of heads in gate, beta, and query should be the same")); + testing::HasSubstr("The number of heads in gate, beta, and value should be the same")); +} + +TEST(type_prop, gated_delta_net_gqa_v_num_heads_greater_than_num_heads) { + // GQA: query/key have 4 heads, value/gate/beta/state have 8 heads + const auto op = make_gdn(element::f32, + Shape{2, 5, 4, 8}, + Shape{2, 5, 4, 8}, + Shape{2, 5, 8, 16}, + Shape{2, 8, 8, 16}, + Shape{2, 5, 8}, + Shape{2, 5, 8}); + + EXPECT_EQ(op->get_output_size(), 2); + EXPECT_EQ(op->get_output_element_type(0), element::f32); + EXPECT_EQ(op->get_output_partial_shape(0), PartialShape(Shape{2, 5, 8, 16})); + EXPECT_EQ(op->get_output_partial_shape(1), PartialShape(Shape{2, 8, 8, 16})); } TEST(type_prop, gated_delta_net_state_shape_mismatch) { From b3de8aa1c4e1917f2ae7b5cdd581cb728d82a457 Mon Sep 17 00:00:00 2001 From: Eugene Smirnov Date: Thu, 23 Apr 2026 13:17:53 +0200 Subject: [PATCH 041/545] [NPUW] support for Whisper KV-cache names in precision conversion (#35431) ### Details: - Whisper pipeline adds certain suffix .decoder. and .encoder. so previous regexpr missed past and previous kv-cache nodes. ### NPUW-CI: - whisper jobs passed: [/Staging/job/evgenii/job/Validate/12/Validation_20report/](url) - LLM jobs passed: [/job/Staging/job/evgenii/job/Validate/13/Validation_20report/](url) ### Regressed PR: - https://github.com/openvinotoolkit/openvino/pull/34516 ### AI Assistance: - *AI assistance used: yes* - *Copilot used for tests generation* --- .../convert_kvcache_to_precision.cpp | 2 - .../intel_npu/src/plugin/npuw/util.cpp | 16 ++--- .../convert_kvcache_to_precision_test.cpp | 60 ++++++++++++++----- 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/src/plugins/intel_npu/src/plugin/npuw/npuw_transformations/convert_kvcache_to_precision.cpp b/src/plugins/intel_npu/src/plugin/npuw/npuw_transformations/convert_kvcache_to_precision.cpp index a408c5c0a63d63..720336a65a85d4 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/npuw_transformations/convert_kvcache_to_precision.cpp +++ b/src/plugins/intel_npu/src/plugin/npuw/npuw_transformations/convert_kvcache_to_precision.cpp @@ -79,7 +79,6 @@ std::shared_ptr cvt_kvcache_to_low_precision(const std::shared_ptrinputs()) { @@ -112,7 +111,6 @@ std::shared_ptr cvt_kvcache_to_low_precision(const std::shared_ptrget_friendly_name() << "]"); ov::npuw::run_kv_cache_dynamic_quantization_passes(new_model, dq_params); } - return new_model; } diff --git a/src/plugins/intel_npu/src/plugin/npuw/util.cpp b/src/plugins/intel_npu/src/plugin/npuw/util.cpp index 188c969f6afd9e..13fa215954f772 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/util.cpp +++ b/src/plugins/intel_npu/src/plugin/npuw/util.cpp @@ -920,11 +920,9 @@ void ov::npuw::util::fill_tensor_bytes(ov::SoPtr tensor, uint8_t fi } std::optional ov::npuw::util::isPastKeyValuesKey(const std::string& str) { - // Capture the number in parentheses - std::regex pattern(R"(past_key_values\.(\d+)\.key)"); + std::regex pattern(R"(past_key_values\.(\d+)(?:\.[^.]+)*\.key)"); std::smatch match; if (std::regex_match(str, match, pattern)) { - // match[1] contains the number int index = std::stoi(match[1].str()); return index; } @@ -932,11 +930,9 @@ std::optional ov::npuw::util::isPastKeyValuesKey(const std::string& str) { } std::optional ov::npuw::util::isPastKeyValuesValue(const std::string& str) { - // Capture the number in parentheses - std::regex pattern(R"(past_key_values\.(\d+)\.value)"); + std::regex pattern(R"(past_key_values\.(\d+)(?:\.[^.]+)*\.value)"); std::smatch match; if (std::regex_match(str, match, pattern)) { - // match[1] contains the number int index = std::stoi(match[1].str()); return index; } @@ -944,11 +940,9 @@ std::optional ov::npuw::util::isPastKeyValuesValue(const std::string& str) } std::optional ov::npuw::util::isPresentKeyValuesKey(const std::string& str) { - // Capture the number in parentheses - std::regex pattern(R"(present\.(\d+)\.key)"); + std::regex pattern(R"(present\.(\d+)(?:\.[^.]+)*\.key)"); std::smatch match; if (std::regex_match(str, match, pattern)) { - // match[1] contains the number int index = std::stoi(match[1].str()); return index; } @@ -956,11 +950,9 @@ std::optional ov::npuw::util::isPresentKeyValuesKey(const std::string& str) } std::optional ov::npuw::util::isPresentKeyValuesValue(const std::string& str) { - // Capture the number in parentheses - std::regex pattern(R"(present\.(\d+)\.value)"); + std::regex pattern(R"(present\.(\d+)(?:\.[^.]+)*\.value)"); std::smatch match; if (std::regex_match(str, match, pattern)) { - // match[1] contains the number int index = std::stoi(match[1].str()); return index; } diff --git a/src/plugins/intel_npu/tests/unit/npuw/pipeline_passes/convert_kvcache_to_precision_test.cpp b/src/plugins/intel_npu/tests/unit/npuw/pipeline_passes/convert_kvcache_to_precision_test.cpp index b894dac24b9497..1b0019f7136fca 100644 --- a/src/plugins/intel_npu/tests/unit/npuw/pipeline_passes/convert_kvcache_to_precision_test.cpp +++ b/src/plugins/intel_npu/tests/unit/npuw/pipeline_passes/convert_kvcache_to_precision_test.cpp @@ -10,7 +10,10 @@ #include "llm_pass_test_fixture.hpp" #include "../util.hpp" +#include "npuw_transformations/convert_kvcache_to_precision.hpp" +#include "openvino/pass/stateful_to_stateless.hpp" #include "openvino/runtime/properties.hpp" +#include "whisper/prepare_whisper_model.hpp" // --- Design note ------------------------------------------------------------------------- // The model builder creates KV cache state with ov::element::f32 (the default @@ -65,7 +68,9 @@ ov::AnyMap make_kv_precision_props(const ov::element::Type kv_type) { return props; } -void expect_kv_cache_input_types(const std::shared_ptr& model, const ov::element::Type kv_type) { +void expect_kv_cache_input_types(const std::shared_ptr& model, + const ov::element::Type kv_type, + const bool check_quant_aux_ports = true) { // Key cache: asymmetric quantization -> value tensor + scale (f32) + zero_point (same as quant type). // Value cache: symmetric quantization -> value tensor (i4) + scale (f32), no zero_point. const bool is_quantized = is_quantized_kv_type(kv_type); @@ -109,28 +114,28 @@ void expect_kv_cache_input_types(const std::shared_ptr& model, const << "past_key_values..value input must have type " << expected; } - if (any_name_contains(input, past_key_scale_name)) { + if (check_quant_aux_ports && any_name_contains(input, past_key_scale_name)) { found_key_scale_input = true; const auto expected = precision_key_matrix().at(kv_type).at("scale"); EXPECT_EQ(input.get_element_type(), expected) << "past_key scale input must have type " << expected; } - if (any_name_contains(input, past_key_zp_name)) { + if (check_quant_aux_ports && any_name_contains(input, past_key_zp_name)) { found_key_zp_input = true; const auto expected = precision_key_matrix().at(kv_type).at("zero_point"); EXPECT_EQ(input.get_element_type(), expected) << "past_key zero-point input must have type " << expected; } - if (any_name_contains(input, past_value_scale_name)) { + if (check_quant_aux_ports && any_name_contains(input, past_value_scale_name)) { found_value_scale_input = true; const auto expected = precision_value_matrix().at(kv_type).at("scale"); EXPECT_EQ(input.get_element_type(), expected) << "past_value scale input must have type " << expected; } - if (any_name_contains(input, past_value_zp_name)) { + if (check_quant_aux_ports && any_name_contains(input, past_value_zp_name)) { found_value_zp_input = true; } } @@ -138,7 +143,7 @@ void expect_kv_cache_input_types(const std::shared_ptr& model, const EXPECT_TRUE(found_key_cache_input) << "No past_key_values..key input found in model"; EXPECT_TRUE(found_value_cache_input) << "No past_key_values..value input found in model"; - if (is_quantized) { + if (is_quantized && check_quant_aux_ports) { EXPECT_TRUE(found_key_scale_input) << "Asymmetric quantized KV key-cache must expose scale input"; EXPECT_TRUE(found_key_zp_input) @@ -147,7 +152,7 @@ void expect_kv_cache_input_types(const std::shared_ptr& model, const << "Symmetric quantized KV value-cache must expose scale input"; EXPECT_FALSE(found_value_zp_input) << "Symmetric quantized KV value-cache must not expose zero-point input"; - } else { + } else if (!is_quantized && check_quant_aux_ports) { EXPECT_FALSE(found_key_scale_input) << "Non-quantized KV-cache must not expose key scale input"; EXPECT_FALSE(found_key_zp_input) << "Non-quantized KV-cache must not expose key zero-point input"; EXPECT_FALSE(found_value_scale_input) << "Non-quantized KV-cache must not expose value scale input"; @@ -155,7 +160,9 @@ void expect_kv_cache_input_types(const std::shared_ptr& model, const } } -void expect_kv_cache_present_output_types(const std::shared_ptr& model, const ov::element::Type kv_type) { +void expect_kv_cache_present_output_types(const std::shared_ptr& model, + const ov::element::Type kv_type, + const bool check_quant_aux_ports = true) { const bool is_quantized = is_quantized_kv_type(kv_type); constexpr std::string_view present_key_scale_name = "/present/key/scale"; @@ -197,28 +204,28 @@ void expect_kv_cache_present_output_types(const std::shared_ptr& mode << "present..value output must have type " << expected; } - if (any_name_contains(output, present_key_scale_name)) { + if (check_quant_aux_ports && any_name_contains(output, present_key_scale_name)) { found_present_key_scale = true; const auto expected = precision_key_matrix().at(kv_type).at("scale"); EXPECT_EQ(output.get_element_type(), expected) << "present key scale output must have type " << expected; } - if (any_name_contains(output, present_key_zp_name)) { + if (check_quant_aux_ports && any_name_contains(output, present_key_zp_name)) { found_present_key_zp = true; const auto expected = precision_key_matrix().at(kv_type).at("zero_point"); EXPECT_EQ(output.get_element_type(), expected) << "present key zero-point output must have type " << expected; } - if (any_name_contains(output, present_value_scale_name)) { + if (check_quant_aux_ports && any_name_contains(output, present_value_scale_name)) { found_present_value_scale = true; const auto expected = precision_value_matrix().at(kv_type).at("scale"); EXPECT_EQ(output.get_element_type(), expected) << "present value scale output must have type " << expected; } - if (any_name_contains(output, present_value_zp_name)) { + if (check_quant_aux_ports && any_name_contains(output, present_value_zp_name)) { found_present_value_zp = true; } } @@ -226,7 +233,7 @@ void expect_kv_cache_present_output_types(const std::shared_ptr& mode EXPECT_TRUE(found_present_key) << "No present..key output found in model"; EXPECT_TRUE(found_present_value) << "No present..value output found in model"; - if (is_quantized) { + if (is_quantized && check_quant_aux_ports) { EXPECT_TRUE(found_present_key_scale) << "Asymmetric quantized KV key-cache must expose present scale output"; EXPECT_TRUE(found_present_key_zp) @@ -235,7 +242,7 @@ void expect_kv_cache_present_output_types(const std::shared_ptr& mode << "Symmetric quantized KV value-cache must expose present scale output"; EXPECT_FALSE(found_present_value_zp) << "Symmetric quantized KV value-cache must not expose present zero-point output"; - } else { + } else if (!is_quantized && check_quant_aux_ports) { EXPECT_FALSE(found_present_key_scale) << "Non-quantized KV-cache must not expose present key scale output"; EXPECT_FALSE(found_present_key_zp) @@ -309,6 +316,31 @@ TEST_P(ConvertKVCacheHintPrecisionTest, PrefillModelPresentOutputsHaveExpectedPr expect_kv_cache_present_output_types(prefill.model, kv_type); } +// Whisper decoder_with_past model uses names like: +// past_key_values..decoder.key / present..decoder.key +// past_key_values..encoder.key / present..encoder.key +// Ensure KV-cache precision conversion handles those variants too. +TEST_P(ConvertKVCacheHintPrecisionTest, WhisperKVCacheModelPastKeyInputsHaveExpectedPrecision) { + const auto kv_type = GetParam(); + auto model = ov::test::npuw::build_whisper_decoder_test_model(); + ov::pass::StatefulToStateless().run_on_model(model); + model = model->clone(); + ASSERT_TRUE(ov::npuw::util::PrepareWhisperKVCacheModel().run_on_model(model)); + ASSERT_TRUE(ov::npuw::ConvertKVCacheToPrecision(kv_type).run_on_model(model)); + + expect_kv_cache_input_types(model, kv_type, false); +} + +TEST_P(ConvertKVCacheHintPrecisionTest, WhisperKVCacheModelPresentOutputsHaveExpectedPrecision) { + const auto kv_type = GetParam(); + auto model = ov::test::npuw::build_whisper_decoder_test_model(); + ov::pass::StatefulToStateless().run_on_model(model); + model = model->clone(); + ASSERT_TRUE(ov::npuw::util::PrepareWhisperKVCacheModel().run_on_model(model)); + ASSERT_TRUE(ov::npuw::ConvertKVCacheToPrecision(kv_type).run_on_model(model)); + + expect_kv_cache_present_output_types(model, kv_type, false); +} // --- Non-parametric tests ------------------------------------------------------------------------- From 728dd933de6daf981f1cafc53d12deb6319d91c8 Mon Sep 17 00:00:00 2001 From: Szymon Gutaj Date: Thu, 23 Apr 2026 14:59:44 +0200 Subject: [PATCH 042/545] Out-of-bounds Write in ONNX Loop Converter body_inputs Access leads to Crash / DoS (#34762) - Fix and tests ### Details: - Fixed by adding assertions ### Tickets: - CVS-182185 - [PTK0006429](https://intelait.intel.com/x_volt2_csi_product_vulnerability.do?sysparm_tiny=2XQRMPMypIHMISVXAZBirXEgVUr17ZtI&sys_id=0abbeccbdf577a105348b094c23889b9&sysparm_record_row=30) --- src/frontends/onnx/frontend/src/op/loop.cpp | 91 +++++++----- .../loop_too_few_body_inputs.prototxt | 138 ++++++++++++++++++ .../loop_too_few_body_outputs.prototxt | 112 ++++++++++++++ .../onnx/tests/onnx_import_controlflow.in.cpp | 18 +++ 4 files changed, 321 insertions(+), 38 deletions(-) create mode 100644 src/frontends/onnx/tests/models/controlflow/loop_too_few_body_inputs.prototxt create mode 100644 src/frontends/onnx/tests/models/controlflow/loop_too_few_body_outputs.prototxt diff --git a/src/frontends/onnx/frontend/src/op/loop.cpp b/src/frontends/onnx/frontend/src/op/loop.cpp index 31570e6c8c163c..8c497c1d528c8e 100644 --- a/src/frontends/onnx/frontend/src/op/loop.cpp +++ b/src/frontends/onnx/frontend/src/op/loop.cpp @@ -100,7 +100,14 @@ namespace detail { ov::OutputVector loop_legacy(const ov::frontend::onnx::Node& node) { const auto& ng_inputs = node.get_ov_inputs(); - const ov::OutputVector loop_carried_dependencies{std::next(ng_inputs.begin(), 2), ng_inputs.end()}; + constexpr size_t control_inputs_count = 2; + + FRONT_END_GENERAL_CHECK( + ng_inputs.size() >= control_inputs_count, + "Expecting at least two canonical inputs for Loop op: trip count and termination condition"); + + const ov::OutputVector loop_carried_dependencies{std::next(ng_inputs.begin(), control_inputs_count), + ng_inputs.end()}; const auto& subgraphs = node.get_subgraphs(); auto body_graph_it = subgraphs.find("body"); @@ -109,10 +116,32 @@ ov::OutputVector loop_legacy(const ov::frontend::onnx::Node& node) { auto body_outputs = body_graph->get_ov_outputs(); const auto& body_inputs = body_graph->get_ng_parameters(); + // NOTE: We're relaxing the check on body_inputs size here to allow extra parameters that are not loop-carried + // dependencies and are not control inputs (iteration number and termination condition). These extra parameters are + // expected to be invariant inputs that are wired from the parent graph via Loop::set_invariant_input, and their + // presence does not violate ONNX spec as long as the loop body graph is well-formed and can be executed by ONNX + // Runtime. + CHECK_VALID_NODE(node, + body_inputs.size() >= control_inputs_count && + body_inputs.size() - control_inputs_count >= loop_carried_dependencies.size(), + "The provided loop body graph canonical inputs size (", + body_inputs.size(), + "), does not match the sum of loop carried dependencies " + "and two mandatory inputs (", + loop_carried_dependencies.size() + control_inputs_count, + ")"); + + CHECK_VALID_NODE(node, + body_outputs.size() >= 1 && body_outputs.size() - 1 >= loop_carried_dependencies.size(), + "The provided loop body graph outputs size (", + body_outputs.size(), + ") is smaller than number of outputs. Required at least: ", + loop_carried_dependencies.size() + 1); + // Infer loop body inputs' element type based on carried dependencies for (size_t i = 0; i < loop_carried_dependencies.size(); i++) { - body_inputs[i + 2]->set_element_type(loop_carried_dependencies[i].get_element_type()); - body_inputs[i + 2]->set_partial_shape(loop_carried_dependencies[i].get_partial_shape()); + body_inputs[i + control_inputs_count]->set_element_type(loop_carried_dependencies[i].get_element_type()); + body_inputs[i + control_inputs_count]->set_partial_shape(loop_carried_dependencies[i].get_partial_shape()); } // optional inputs @@ -165,30 +194,13 @@ ov::OutputVector loop_legacy(const ov::frontend::onnx::Node& node) { body_outputs[0] = v0::Constant::create(ov::element::boolean, {1}, {true}); // Construct body_params without the condition parameter (body_inputs[1]) body_params = ov::ParameterVector{body_inputs[0]}; - body_params.insert(body_params.end(), body_inputs.begin() + 2, body_inputs.end()); + body_params.insert(body_params.end(), body_inputs.begin() + control_inputs_count, body_inputs.end()); needs_condition_param = false; } else { // Construct body_params with all body_inputs body_params = ov::ParameterVector(body_inputs.begin(), body_inputs.end()); } - CHECK_VALID_NODE(node, - body_inputs.size() >= loop_carried_dependencies.size() + 2, - "The provided loop body graph inputs size (", - body_inputs.size(), - "), is not greater than the sum of loop carried dependencies " - "and two mandatory" - " inputs (", - loop_carried_dependencies.size() + 2, - ")"); - - CHECK_VALID_NODE(node, - body_outputs.size() >= loop_carried_dependencies.size() + 1, - "The provided loop body graph outputs size (", - body_outputs.size(), - ") is not greater than number of outputs. Required at least: ", - loop_carried_dependencies.size() + 1); - const auto body = std::make_shared(body_outputs, body_params); auto loop = std::make_shared(trip_count, termination_cond); v5::Loop::SpecialBodyPorts spec_ports{0, 0}; @@ -200,7 +212,7 @@ ov::OutputVector loop_legacy(const ov::frontend::onnx::Node& node) { } // Setting up other Loop body inputs. // body_inputs[0] is iteration number, body_inputs[1] is termination condition - auto body_inputs_it = std::next(body_inputs.begin(), 2); + auto body_inputs_it = std::next(body_inputs.begin(), control_inputs_count); // body_outputs[0] is termination condition output auto body_outputs_it = std::next(body_outputs.begin(), 1); @@ -246,7 +258,14 @@ ov::OutputVector loop_legacy(const ov::frontend::onnx::Node& node) { ov::OutputVector loop(const ov::frontend::onnx::Node& node) { const auto& ng_inputs = node.get_ov_inputs(); - const ov::OutputVector loop_carried_dependencies{std::next(ng_inputs.begin(), 2), ng_inputs.end()}; + constexpr size_t control_inputs_count = 2; + + FRONT_END_GENERAL_CHECK( + ng_inputs.size() >= control_inputs_count, + "Expecting at least two canonical inputs for Loop op: trip count and termination condition"); + + const ov::OutputVector loop_carried_dependencies{std::next(ng_inputs.begin(), control_inputs_count), + ng_inputs.end()}; auto body_graph = node.get_attribute_value>("body"); const auto& body_results = body_graph->get_results(); @@ -277,14 +296,18 @@ ov::OutputVector loop(const ov::frontend::onnx::Node& node) { body_outputs = std::move(filtered_body_outputs); CHECK_VALID_NODE(node, - canonical_inputs.size() >= 2, - "The provided loop body graph inputs size (", + canonical_inputs.size() >= control_inputs_count && + canonical_inputs.size() - control_inputs_count == loop_carried_dependencies.size(), + "The provided loop body graph canonical inputs size (", canonical_inputs.size(), - ") is not greater than the mandatory iteration and condition inputs (2)"); + "), does not match the sum of loop carried dependencies " + "and two mandatory inputs (", + loop_carried_dependencies.size() + control_inputs_count, + ")"); auto iteration_param = canonical_inputs[0]; auto condition_param = canonical_inputs[1]; - ov::ParameterVector state_parameters(canonical_inputs.begin() + 2, canonical_inputs.end()); + ov::ParameterVector state_parameters(canonical_inputs.begin() + control_inputs_count, canonical_inputs.end()); const auto default_trip_count = v0::Constant::create(ov::element::i64, {1}, {-1}); const auto true_condition = v0::Constant::create(ov::element::boolean, {1}, {true}); @@ -338,14 +361,6 @@ ov::OutputVector loop(const ov::frontend::onnx::Node& node) { } } - CHECK_VALID_NODE(node, - state_parameters.size() == loop_carried_dependencies.size(), - "The provided loop body state parameters size (", - state_parameters.size(), - ") must match the number of loop carried dependencies (", - loop_carried_dependencies.size(), - ")"); - const auto mapped = loop_carried_dependencies.size(); // Infer loop body inputs' element type based on carried dependencies @@ -355,11 +370,11 @@ ov::OutputVector loop(const ov::frontend::onnx::Node& node) { } CHECK_VALID_NODE(node, - body_outputs.size() >= state_parameters.size() + 1, + body_outputs.size() >= 1 && body_outputs.size() - 1 >= loop_carried_dependencies.size(), "The provided loop body graph outputs size (", body_outputs.size(), - ") is not greater than number of outputs. Required at least: ", - state_parameters.size() + 1); + ") is smaller than number of outputs. Required at least: ", + loop_carried_dependencies.size() + 1); ov::ParameterVector body_params; body_params.push_back(iteration_param); diff --git a/src/frontends/onnx/tests/models/controlflow/loop_too_few_body_inputs.prototxt b/src/frontends/onnx/tests/models/controlflow/loop_too_few_body_inputs.prototxt new file mode 100644 index 00000000000000..7be0c77bf9546f --- /dev/null +++ b/src/frontends/onnx/tests/models/controlflow/loop_too_few_body_inputs.prototxt @@ -0,0 +1,138 @@ +ir_version: 6 +producer_name: "OpenVINO ONNX Frontend" +doc_string: "Malformed Loop: 3 loop-carried deps but body only has 2 params (iter + cond, missing state params)" +graph { + name: "loop_body_inputs_oob" + node { + input: "trip_count" + input: "" + input: "dep1" + input: "dep2" + input: "dep3" + output: "res1" + output: "res2" + output: "res3" + op_type: "Loop" + attribute { + name: "body" + g { + node { + input: "cond_in" + output: "cond_out" + name: "cond_identity" + op_type: "Identity" + } + name: "loop body with too few inputs" + input { + name: "i" + type { + tensor_type { + elem_type: 7 + shape { + } + } + } + } + input { + name: "cond_in" + type { + tensor_type { + elem_type: 9 + shape { + } + } + } + } + output { + name: "cond_out" + type { + tensor_type { + elem_type: 9 + shape { + } + } + } + } + } + type: GRAPH + } + } + initializer { + dims: 1 + data_type: 7 + int64_data: 3 + name: "trip_count" + } + input { + name: "dep1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + input { + name: "dep2" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + input { + name: "dep3" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + output { + name: "res1" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "res2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "res3" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } +} +opset_import { + version: 11 +} diff --git a/src/frontends/onnx/tests/models/controlflow/loop_too_few_body_outputs.prototxt b/src/frontends/onnx/tests/models/controlflow/loop_too_few_body_outputs.prototxt new file mode 100644 index 00000000000000..01fd168345c265 --- /dev/null +++ b/src/frontends/onnx/tests/models/controlflow/loop_too_few_body_outputs.prototxt @@ -0,0 +1,112 @@ +ir_version: 6 +producer_name: "OpenVINO ONNX Frontend" +doc_string: "Malformed Loop: has loop-carried deps but body has too few outputs (only cond_out, missing state outputs)" +graph { + name: "loop_body_outputs_oob" + node { + input: "trip_count" + input: "" + input: "a_init" + output: "a_final" + output: "a_values" + op_type: "Loop" + attribute { + name: "body" + g { + node { + input: "cond_in" + output: "cond_out" + name: "cond_identity" + op_type: "Identity" + } + name: "loop body with too few outputs" + input { + name: "i" + type { + tensor_type { + elem_type: 7 + shape { + } + } + } + } + input { + name: "cond_in" + type { + tensor_type { + elem_type: 9 + shape { + } + } + } + } + input { + name: "a_in" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + output { + name: "cond_out" + type { + tensor_type { + elem_type: 9 + shape { + } + } + } + } + } + type: GRAPH + } + } + initializer { + dims: 1 + data_type: 7 + int64_data: 3 + name: "trip_count" + } + input { + name: "a_init" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + output { + name: "a_final" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "a_values" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } +} +opset_import { + version: 11 +} diff --git a/src/frontends/onnx/tests/onnx_import_controlflow.in.cpp b/src/frontends/onnx/tests/onnx_import_controlflow.in.cpp index e8c2c75211152b..14e3d3f931da77 100644 --- a/src/frontends/onnx/tests/onnx_import_controlflow.in.cpp +++ b/src/frontends/onnx/tests/onnx_import_controlflow.in.cpp @@ -9,6 +9,7 @@ #include "common_test_utils/type_prop.hpp" #include "gtest/gtest.h" #include "onnx_utils.hpp" +#include "openvino/frontend/exception.hpp" #include "openvino/op/loop.hpp" #include "openvino/op/multiply.hpp" @@ -237,6 +238,23 @@ OPENVINO_TEST(${BACKEND_NAME}, onnx_controlflow_loop_add_value_access_to_body_sc } } +// Regression test: Loop body with too few inputs relative to loop-carried +// dependencies must throw instead of accessing body_inputs out of bounds. +OPENVINO_TEST(${BACKEND_NAME}, onnx_controlflow_loop_too_few_body_inputs_exception) { + OV_EXPECT_THROW(convert_model("controlflow/loop_too_few_body_inputs.onnx"), + ov::AssertFailure, + testing::AllOf(testing::HasSubstr("loop body graph canonical inputs size"), + testing::HasSubstr("does not match the sum of loop carried dependencies"))); +} + +// Regression test: Loop body with too few outputs relative to loop-carried +// dependencies must throw instead of accessing body_outputs out of bounds. +OPENVINO_TEST(${BACKEND_NAME}, onnx_controlflow_loop_too_few_body_outputs_exception) { + OV_EXPECT_THROW(convert_model("controlflow/loop_too_few_body_outputs.onnx"), + ov::AssertFailure, + testing::HasSubstr("loop body graph outputs size")); +} + OPENVINO_TEST(${BACKEND_NAME}, onnx_controlflow_loop_add_value_the_same_node_from_parent_and_subgraph) { const auto model = convert_model("controlflow/loop_2d_add_the_same_name.onnx"); From 70bcd498267b6d95a41f0a1a1815a533b156ff0e Mon Sep 17 00:00:00 2001 From: Mikhail Dvoretckii Date: Thu, 23 Apr 2026 14:30:18 +0100 Subject: [PATCH 043/545] [GPU] Add squeeze/unsqueeze to the compressed conv1x1 transformation (#34957) ### Details: - Adds an activation squeeze/unsqueeze node pair to the ConvertWeightCompressedConv1x1ToMatmul transformation when outermost dimension is a static 1 - This is intended to reduce the activation to 3D, allowing compile-time weight reordering on non-systolic devices ### Tickets: - CVS-182319 ### AI Assistance: - AI assistance used: no --- ...rt_weight_compressed_conv1x1_to_matmul.cpp | 29 ++++++++++++ ...ight_compressed_conv1x1_to_matmul_test.cpp | 44 +++++++++++++------ 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/common/transformations/src/transformations/op_conversions/convert_weight_compressed_conv1x1_to_matmul.cpp b/src/common/transformations/src/transformations/op_conversions/convert_weight_compressed_conv1x1_to_matmul.cpp index 582a4f3f6b5442..6d68c072d7d0f6 100644 --- a/src/common/transformations/src/transformations/op_conversions/convert_weight_compressed_conv1x1_to_matmul.cpp +++ b/src/common/transformations/src/transformations/op_conversions/convert_weight_compressed_conv1x1_to_matmul.cpp @@ -233,6 +233,23 @@ ov::pass::ConvertWeightCompressedConv1x1ToMatmul::ConvertWeightCompressedConv1x1 } } + // If the activation has a static leading dimension of 1, squeeze it. + // This is done to allow pre-selection of OCL implementations for non-IMMAD devices, reducing memory pressure. + bool squeeze_activation = false; + auto act_pshape = activation->get_output_partial_shape(0); + if (act_pshape.rank().is_static() && act_pshape.rank().get_length() >= 4 && act_pshape[0].is_static() && + act_pshape[0] == 1) { + squeeze_activation = true; + auto squeeze_const = + std::make_shared(ov::element::i64, + ov::Shape{3}, + std::vector{1, -1, act_pshape[-1].get_length()}); + auto squeeze = std::make_shared(activation, squeeze_const, false); + ov::copy_runtime_info(activation, squeeze); + squeeze->set_friendly_name(activation->get_friendly_name() + "_squeeze"); + activation = squeeze; + } + auto matmul = std::make_shared(activation, scaled_weight, false, true); ov::copy_runtime_info(conv1x1, matmul); std::shared_ptr matmul_out; @@ -258,6 +275,18 @@ ov::pass::ConvertWeightCompressedConv1x1ToMatmul::ConvertWeightCompressedConv1x1 matmul_out = matmul; } + if (squeeze_activation) { + auto shape_out = matmul_out->get_output_partial_shape(0); + auto unsqueeze_const = + std::make_shared(ov::element::i64, + ov::Shape{4}, + std::vector{1, 1, -1, shape_out[-1].get_length()}); + auto unsqueeze = std::make_shared(matmul_out, unsqueeze_const, false); + ov::copy_runtime_info(matmul_out, unsqueeze); + unsqueeze->set_friendly_name(matmul_out->get_friendly_name() + "_unsqueeze"); + matmul_out = unsqueeze; + } + if (reshape_out) { if (convert_out) { auto convert_final = convert_out->clone_with_new_inputs({matmul_out}); diff --git a/src/common/transformations/tests/op_conversions/convert_weight_compressed_conv1x1_to_matmul_test.cpp b/src/common/transformations/tests/op_conversions/convert_weight_compressed_conv1x1_to_matmul_test.cpp index 20344930bafec3..96b15debc1a2ff 100644 --- a/src/common/transformations/tests/op_conversions/convert_weight_compressed_conv1x1_to_matmul_test.cpp +++ b/src/common/transformations/tests/op_conversions/convert_weight_compressed_conv1x1_to_matmul_test.cpp @@ -41,20 +41,23 @@ struct Conv1x1ToMatmulTestParams { bool with_convert; bool with_param_weight; bool with_act_new_reshape; + bool with_batched_input; std::string activation_op_type; }; std::shared_ptr gen_model(const Conv1x1ToMatmulTestParams& p) { - auto input = std::make_shared( - ov::element::f16, - (p.activation_op_type == "Reshape" && p.with_act_new_reshape) ? ov::Shape{1, 1, 2, 5} : ov::Shape{1, 1, 1, 10}); + int input_batch = p.with_batched_input ? 4 : 1; + auto input = std::make_shared(ov::element::f16, + (p.activation_op_type == "Reshape" && p.with_act_new_reshape) + ? ov::Shape{(size_t)input_batch, 1, 2, 5} + : ov::Shape{(size_t)input_batch, 1, 1, 10}); std::shared_ptr act_node; if (p.activation_op_type == "Transpose") { auto transpose_const = ov::opset1::Constant::create(ov::element::i32, ov::Shape{4}, {0, 3, 1, 2}); act_node = std::make_shared(input, transpose_const); } else { - auto reshape_const = ov::opset1::Constant::create(ov::element::i32, ov::Shape{4}, {1, 10, 1, 1}); + auto reshape_const = ov::opset1::Constant::create(ov::element::i32, ov::Shape{4}, {input_batch, 10, 1, 1}); act_node = std::make_shared(input, reshape_const, false); } @@ -118,7 +121,7 @@ std::shared_ptr gen_model(const Conv1x1ToMatmulTestParams& p) { auto transpose_const = ov::opset1::Constant::create(ov::element::i32, ov::Shape{4}, {0, 2, 3, 1}); out_node = std::make_shared(current_node, transpose_const); } else { - auto reshape_const = ov::opset1::Constant::create(ov::element::i32, ov::Shape{4}, {1, 1, 1, 15}); + auto reshape_const = ov::opset1::Constant::create(ov::element::i32, ov::Shape{4}, {input_batch, 1, 1, 15}); out_node = std::make_shared(current_node, reshape_const, false); } @@ -126,9 +129,11 @@ std::shared_ptr gen_model(const Conv1x1ToMatmulTestParams& p) { } std::shared_ptr gen_model_ref(const Conv1x1ToMatmulTestParams& p) { - auto input = std::make_shared( - ov::element::f16, - (p.activation_op_type == "Reshape" && p.with_act_new_reshape) ? ov::Shape{1, 1, 2, 5} : ov::Shape{1, 1, 1, 10}); + int input_batch = p.with_batched_input ? 4 : 1; + auto input = std::make_shared(ov::element::f16, + (p.activation_op_type == "Reshape" && p.with_act_new_reshape) + ? ov::Shape{(size_t)input_batch, 1, 2, 5} + : ov::Shape{(size_t)input_batch, 1, 1, 10}); std::shared_ptr weights_node; ov::ParameterVector params = {input}; @@ -170,15 +175,23 @@ std::shared_ptr gen_model_ref(const Conv1x1ToMatmulTestParams& p) { std::shared_ptr act_node = input; if (p.activation_op_type == "Reshape" && p.with_act_new_reshape) { - auto reshape_const = ov::opset1::Constant::create(ov::element::i64, ov::Shape{4}, {1, 1, 1, 10}); + auto reshape_const = ov::opset1::Constant::create(ov::element::i64, ov::Shape{4}, {1, 1, input_batch, 10}); act_node = std::make_shared(input, reshape_const, false); } + if (input_batch == 1 || (p.activation_op_type == "Reshape" && p.with_act_new_reshape)) { + auto squeeze_const = ov::opset1::Constant::create(ov::element::i64, ov::Shape{3}, {1, input_batch, 10}); + act_node = std::make_shared(act_node, squeeze_const, false); + } auto matmul = std::make_shared(act_node, mul, false, true); current_node = matmul; if (p.with_bias) { auto bias_const = ov::opset1::Constant::create(ov::element::f16, ov::Shape{1, 1, 1, 15}, {1}); - current_node = std::make_shared(matmul, bias_const); + current_node = std::make_shared(current_node, bias_const); + } + if (input_batch == 1 || (p.activation_op_type == "Reshape" && p.with_act_new_reshape)) { + auto unsqueeze_const = ov::opset1::Constant::create(ov::element::i64, ov::Shape{4}, {1, 1, input_batch, 15}); + current_node = std::make_shared(current_node, unsqueeze_const, false); } if (p.with_convert) { current_node = std::make_shared(current_node, ov::element::f32); @@ -186,7 +199,7 @@ std::shared_ptr gen_model_ref(const Conv1x1ToMatmulTestParams& p) { std::shared_ptr out_node; if (p.activation_op_type == "Reshape") { - auto reshape_const = ov::opset1::Constant::create(ov::element::i32, ov::Shape{4}, {1, 1, 1, 15}); + auto reshape_const = ov::opset1::Constant::create(ov::element::i32, ov::Shape{4}, {input_batch, 1, 1, 15}); out_node = std::make_shared(current_node, reshape_const, false); } else { out_node = current_node; @@ -198,16 +211,17 @@ std::shared_ptr gen_model_ref(const Conv1x1ToMatmulTestParams& p) { class ConvertWeightCompressedConv1x1ToMatmulTest : public TransformationTestsF, - public WithParamInterface> { + public WithParamInterface> { public: static std::string get_test_case_name( - const testing::TestParamInfo>& obj) { + const testing::TestParamInfo>& obj) { const auto& [with_group_quant, with_zp, with_bias, with_convert, with_param_weight, with_act_new_reshape, + with_batched_input, activation_op_type] = obj.param; std::ostringstream result; @@ -217,6 +231,7 @@ class ConvertWeightCompressedConv1x1ToMatmulTest result << "with_convert=" << with_convert << "_"; result << "with_param_weight=" << with_param_weight << "_"; result << "with_act_new_reshape=" << with_act_new_reshape << "_"; + result << "with_batched_input=" << with_batched_input << "_"; result << "activation_op_type=" << activation_op_type; return result.str(); } @@ -230,6 +245,7 @@ class ConvertWeightCompressedConv1x1ToMatmulTest with_convert, with_param_weight, with_act_new_reshape, + with_batched_input, activation_op_type] = GetParam(); Conv1x1ToMatmulTestParams params{with_group_quant, with_zp, @@ -237,6 +253,7 @@ class ConvertWeightCompressedConv1x1ToMatmulTest with_convert, with_param_weight, with_act_new_reshape, + with_batched_input, activation_op_type}; model = gen_model(params); model_ref = gen_model_ref(params); @@ -254,6 +271,7 @@ INSTANTIATE_TEST_SUITE_P(TransformationTests, ::testing::Bool(), ::testing::Bool(), ::testing::Bool(), + ::testing::Bool(), ::testing::Values("Transpose", "Reshape")), ConvertWeightCompressedConv1x1ToMatmulTest::get_test_case_name); From 83bc875175a744f2c3d8c35555688085df08785d Mon Sep 17 00:00:00 2001 From: Dylan Neve Date: Thu, 23 Apr 2026 14:47:18 +0100 Subject: [PATCH 044/545] [NPUW] Model builder: refactor Qwen3 embedding support (#35045) ### Details: - *Add test to verify generating and compiling decoder-style embedding models..* ### Tickets: - *EISW-209517* ### AI Assistance: - *AI assistance used: yes* - *Used Claude Code to generate code.* --- .../llm_compiled_model_factory_options_test.cpp | 17 +++++++++++++++++ .../tests/unit/npuw/llm_test_helpers.hpp | 9 +++++++++ 2 files changed, 26 insertions(+) diff --git a/src/plugins/intel_npu/tests/unit/npuw/llm_compiled_model_factory_options_test.cpp b/src/plugins/intel_npu/tests/unit/npuw/llm_compiled_model_factory_options_test.cpp index db183991ad0c4c..bf594b584673d2 100644 --- a/src/plugins/intel_npu/tests/unit/npuw/llm_compiled_model_factory_options_test.cpp +++ b/src/plugins/intel_npu/tests/unit/npuw/llm_compiled_model_factory_options_test.cpp @@ -40,6 +40,10 @@ class LLMCompiledModelFactoryOptionsTest : public ::testing::Test { return ov::test::npuw::build_embedding_test_model(); } + std::shared_ptr build_embedding_decoder_model() const { + return ov::test::npuw::build_embedding_decoder_test_model(); + } + static ov::AnyMap base_props() { return {{"NPUW_LLM", "YES"}, {"NPUW_LLM_MAX_PROMPT_LEN", "128"}, {"NPUW_LLM_MIN_RESPONSE_LEN", "64"}}; } @@ -500,4 +504,17 @@ TEST_F(LLMCompiledModelFactoryOptionsTest, WhisperPrefillPreparationAddsCrossAtt EXPECT_TRUE(has_output_name(prepared, "present")); } +TEST_F(LLMCompiledModelFactoryOptionsTest, TextEmbedOptionCompilesEmbeddingDecoderModel) { + RecordingFactory recorder; + std::unique_ptr compiled; + + ASSERT_NO_THROW(compiled = create_compiled_model(build_embedding_decoder_model(), + {{"NPUW_TEXT_EMBED", "YES"}, + {"NPUW_LLM_SHARED_HEAD", "NO"}}, + recorder)); + ASSERT_NE(compiled, nullptr); + EXPECT_GE(recorder.calls().size(), 1u); + EXPECT_NE(recorder.find_suffix("_prefill"), nullptr); +} + } // namespace diff --git a/src/plugins/intel_npu/tests/unit/npuw/llm_test_helpers.hpp b/src/plugins/intel_npu/tests/unit/npuw/llm_test_helpers.hpp index f949403b981393..41479918675fc2 100644 --- a/src/plugins/intel_npu/tests/unit/npuw/llm_test_helpers.hpp +++ b/src/plugins/intel_npu/tests/unit/npuw/llm_test_helpers.hpp @@ -76,6 +76,15 @@ inline std::shared_ptr build_embedding_test_model() { return mb.build_embedding_encoder(make_test_model_config()); } +inline std::shared_ptr build_embedding_decoder_test_model() { + ModelBuilder mb; + auto cfg = make_test_model_config(); + cfg.use_kv_cache = false; + cfg.internal_position_ids = true; + cfg.lm_head_weight = {}; + return mb.build_llm(cfg); +} + class NullPlugin : public ov::IPlugin { public: std::shared_ptr compile_model(const std::shared_ptr&, From 052f8039716231c027d475e6e20c6c7b81a7eb11 Mon Sep 17 00:00:00 2001 From: Arseniy Obolenskiy Date: Thu, 23 Apr 2026 16:39:36 +0200 Subject: [PATCH 045/545] [PyOV][ARM] Skip i16 async mixed-values case with share_inputs (#34384) ### Details: Skip `test_async_mixed_values[True-ov_type8-int16]` on ARM64 (aarch64/arm64) due to Illegal instruction in CI ### Tickets: - 179098 Co-authored-by: Vladislav Golubev --- .../python/tests/test_runtime/test_async_infer_request.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/bindings/python/tests/test_runtime/test_async_infer_request.py b/src/bindings/python/tests/test_runtime/test_async_infer_request.py index 2064b476911159..b5a7b963e16a3a 100644 --- a/src/bindings/python/tests/test_runtime/test_async_infer_request.py +++ b/src/bindings/python/tests/test_runtime/test_async_infer_request.py @@ -7,7 +7,7 @@ import numpy as np import pytest import time -import sysconfig +import platform import openvino.opset13 as ops from openvino import ( @@ -327,6 +327,11 @@ def callback(callbacks_info): ]) @pytest.mark.parametrize("share_inputs", [True, False]) def test_async_mixed_values(device, ov_type, numpy_dtype, share_inputs): + if share_inputs and ov_type == Type.i16 and numpy_dtype == np.int16 and ( + platform.machine().lower() in {"aarch64", "arm64"} + ): + pytest.skip("Illegal instruction on ARM64 for Type.i16/np.int16 with share_inputs=True, ticket: 179098") + request, tensor1, array1 = generate_concat_compiled_model_with_data( device=device, ov_type=ov_type, numpy_dtype=numpy_dtype ) From 943290eb411d9b08913d18ee95c871fb683564ea Mon Sep 17 00:00:00 2001 From: Dmitry Matveev Date: Thu, 23 Apr 2026 16:15:55 +0100 Subject: [PATCH 046/545] NPUW: Move subgraph accuracy checking logic into an individual wrapper class (#35461) ### Details: - Subgraph-level accuracy checking is now moved into a separate component ### Tickets: - EISW-213302 ### AI Assistance: - *AI assistance used: yes* - *Implemented according to spec* --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../plugin/npuw/base_sync_infer_request.cpp | 78 --- .../plugin/npuw/base_sync_infer_request.hpp | 3 - .../src/plugin/npuw/compiled_model.cpp | 45 +- .../src/plugin/npuw/compiled_model.hpp | 4 - .../npuw/v1/elements/accuracy_checked.cpp | 248 +++++++++ .../npuw/v1/elements/accuracy_checked.hpp | 108 ++++ .../intel_npu/tests/unit/CMakeLists.txt | 1 + .../tests/unit/npuw/accuracy_checked.cpp | 505 ++++++++++++++++++ 8 files changed, 887 insertions(+), 105 deletions(-) create mode 100644 src/plugins/intel_npu/src/plugin/npuw/v1/elements/accuracy_checked.cpp create mode 100644 src/plugins/intel_npu/src/plugin/npuw/v1/elements/accuracy_checked.hpp create mode 100644 src/plugins/intel_npu/tests/unit/npuw/accuracy_checked.cpp diff --git a/src/plugins/intel_npu/src/plugin/npuw/base_sync_infer_request.cpp b/src/plugins/intel_npu/src/plugin/npuw/base_sync_infer_request.cpp index b9780d31acc022..9e5df682d7b07f 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/base_sync_infer_request.cpp +++ b/src/plugins/intel_npu/src/plugin/npuw/base_sync_infer_request.cpp @@ -23,9 +23,6 @@ ov::npuw::IBaseInferRequest::IBaseInferRequest(const std::shared_ptrm_compiled_submodels.size()) { m_subrequests.resize(m_num_submodels, {}); m_completion_cbs.resize(m_num_submodels, {}); - if (m_npuw_model->m_acc_check) { - m_ref_subrequests.resize(m_num_submodels); - } // Initialize profiling m_profile.report_on_die = ov::npuw::profiling_enabled(); @@ -65,81 +62,9 @@ ov::npuw::IBaseInferRequest::RqPtrs ov::npuw::IBaseInferRequest::create_infer_re } NPUW_ASSERT(rqs.size() == nireq); - // TODO: Support creation and return of multiple infer requests - if (m_npuw_model->m_acc_check && m_ref_subrequests.at(id) == nullptr) { - if (nireq > 1) { - OPENVINO_THROW("NPUW: TEMPORARY LIMITATION: Couldn't create reference infer " - "requests if 'nireq' is set to > 1!"); - } - LOG_INFO("Create reference subrequest for submodel [" << id << "] on " << m_npuw_model->m_ref_device << "..."); - LOG_BLOCK(); - if (m_npuw_model->submodel_device(id) != m_npuw_model->m_ref_device) { - auto& ref_submodel = m_npuw_model->m_compiled_submodels.at(id).ref_compiled_model; - ov::SoPtr ref_infer_request = {ref_submodel->create_infer_request(), - ref_submodel._so}; - NPUW_ASSERT(ref_infer_request); - m_ref_subrequests.at(id) = std::move(ref_infer_request); - LOG_INFO("Done"); - } else { - LOG_INFO("Skip creation of reference subrequest for submodule[" - << id << "] on reference device: " << m_npuw_model->m_ref_device << ", as actual subrequest [" - << id << "] has been already created on " << "it ."); - } - } - return rqs; } -void ov::npuw::IBaseInferRequest::ensure_subrequest_is_accurate(std::size_t idx) { - LOG_INFO("Check if subrequest[" << idx << "] is accurate..."); - LOG_BLOCK(); - if (m_ref_subrequests.at(idx) != nullptr && m_subrequests.at(idx)._ptr != m_ref_subrequests.at(idx)._ptr) { - NPUW_ASSERT(m_npuw_model->m_compiled_submodels.at(idx).switched_to_ref == false); - NPUW_ASSERT(m_npuw_model->m_compiled_submodels.at(idx).replaced_by.value_or(idx) == idx); - - const auto& ref_comp_model = m_ref_subrequests.at(idx)->get_compiled_model(); - const auto& actual_comp_model = m_subrequests.at(idx)->get_compiled_model(); - NPUW_ASSERT(actual_comp_model->inputs().size() == ref_comp_model->inputs().size()); - // Setting inputs: - for (size_t i = 0; i < actual_comp_model->inputs().size(); i++) { - const auto& itensor = m_subrequests.at(idx)->get_tensor(actual_comp_model->inputs()[i]); - m_ref_subrequests.at(idx)->set_tensor(ref_comp_model->inputs()[i], itensor); - } - m_ref_subrequests.at(idx)->infer(); - - LOG_INFO("Compare actual outputs against references:"); - bool tensors_converge = true; - for (size_t i = 0; i < actual_comp_model->outputs().size(); i++) { - LOG_INFO(" - " << actual_comp_model->outputs()[i]); - const auto& actual_tensor = m_subrequests.at(idx)->get_tensor(actual_comp_model->outputs()[i]); - const auto& ref_tensor = m_ref_subrequests.at(idx)->get_tensor(ref_comp_model->outputs()[i]); - LOG_BLOCK(); - tensors_converge &= m_npuw_model->m_acc_check(actual_tensor, ref_tensor); - } - LOG_INFO((tensors_converge ? "PASS" : "FAIL")); - - if (!tensors_converge) { - LOG_INFO("Subrequest is inaccurate, failover to reference."); - // FIXME: We need to copy reference tensors to actual only in single-model-inference mode - // or if our subgraph is last in the chain. - for (size_t i = 0; i < actual_comp_model->outputs().size(); i++) { - const auto& actual_tensor = m_subrequests.at(idx)->get_tensor(actual_comp_model->outputs()[i]); - const auto& ref_tensor = m_ref_subrequests.at(idx)->get_tensor(ref_comp_model->outputs()[i]); - ref_tensor->copy_to(actual_tensor._ptr); - } - m_npuw_model->m_compiled_submodels.at(idx).compiled_model = - m_npuw_model->m_compiled_submodels.at(idx).ref_compiled_model; - m_npuw_model->m_compiled_submodels.at(idx).switched_to_ref = true; - m_subrequests.at(idx) = m_ref_subrequests.at(idx); - update_subrequest_links(idx); - } - - LOG_INFO("Done"); - } else { - LOG_INFO("Skipped, subrequest is launched on reference device."); - } -} - ov::SoPtr ov::npuw::IBaseInferRequest::get_tensor(const ov::Output& port) const { std::unique_lock lock(m_io_storages_mutex); @@ -297,9 +222,6 @@ void ov::npuw::IBaseInferRequest::infer() { run_subrequest_for_success(idx); }); complete_subrequest(idx); - if (m_npuw_model->m_acc_check) { - ensure_subrequest_is_accurate(idx); - } } // Increment counter regardless if dumps etc are enabled or not. diff --git a/src/plugins/intel_npu/src/plugin/npuw/base_sync_infer_request.hpp b/src/plugins/intel_npu/src/plugin/npuw/base_sync_infer_request.hpp index 9e7111cbe50651..a149721ca3f629 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/base_sync_infer_request.hpp +++ b/src/plugins/intel_npu/src/plugin/npuw/base_sync_infer_request.hpp @@ -92,7 +92,6 @@ class IBaseInferRequest : public ov::ISyncInferRequest { // their inference requests anymore - they must be stored // only once in the subrequests list RqPtrs create_infer_requests(std::size_t id, size_t nireq = 1); - void ensure_subrequest_is_accurate(std::size_t idx); virtual void update_subrequest_links(std::size_t idx) = 0; std::shared_ptr m_npuw_model; @@ -232,8 +231,6 @@ class IBaseInferRequest : public ov::ISyncInferRequest { std::size_t next(std::size_t idx_base) const; std::size_t real(std::size_t idx) const; - RqPtrs m_ref_subrequests; - using now_t = std::optional; now_t now_idx() const; diff --git a/src/plugins/intel_npu/src/plugin/npuw/compiled_model.cpp b/src/plugins/intel_npu/src/plugin/npuw/compiled_model.cpp index f4c2f9c21405cb..97fa07bc28a45c 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/compiled_model.cpp +++ b/src/plugins/intel_npu/src/plugin/npuw/compiled_model.cpp @@ -26,6 +26,7 @@ #include "plugin.hpp" #include "unfold_sync_infer_request.hpp" #include "util.hpp" +#include "v1/elements/accuracy_checked.hpp" #include "v1/elements/failsafe.hpp" // required for get_properties_per_device() @@ -608,19 +609,7 @@ ov::npuw::CompiledModel::CompiledModel(const std::shared_ptr& model, "]"); } - if (m_acc_check) { - if (submodel_device(real_id) != m_ref_device) { - LOG_INFO("Compile Subgraph[" << real_id << "] for reference device: " << m_ref_device << "."); - LOG_BLOCK(); - m_compiled_submodels.at(real_id).ref_compiled_model = - compile_submodel(m_compiled_submodels.at(real_id).model, m_ref_device); - LOG_INFO("Done (reference)"); - } else { - LOG_INFO("Skip compilation of submodel[" << real_id << "] for reference device: " << m_ref_device - << ", as original submodel[" << real_id - << "] has been already compiled for it."); - } - } + LOG_INFO("Done (Subgraph[" << id << "])."); }; // compile // Parallel compilation is unstable so is disabled by default. @@ -1768,9 +1757,9 @@ bool ov::npuw::CompiledModel::compile_for_success(std::size_t id, const std::vec }; }; - auto make_wrapped = [&](const std::shared_ptr& model, - const std::string& profile_suffix, - const std::vector& devs) -> ov::SoPtr { + auto make_failsafe = [&](const std::shared_ptr& model, + const std::string& profile_suffix, + const std::vector& devs) -> ov::SoPtr { if (!m_cfg.get<::intel_npu::NPUW_FALLBACK_EXEC>()) { std::exception_ptr last_failure; auto factory = make_factory(model, profile_suffix); @@ -1797,6 +1786,26 @@ bool ov::npuw::CompiledModel::compile_for_success(std::size_t id, const std::vec make_factory(model, profile_suffix)); }; + auto make_wrapped = [&](const std::shared_ptr& model, + const std::string& profile_suffix, + const std::vector& devs) -> ov::SoPtr { + auto main_cm = make_failsafe(model, profile_suffix, devs); + if (m_acc_check) { + const auto exec_devs = main_cm->get_property(ov::execution_devices.name()).as>(); + const std::string actual_device = exec_devs.empty() ? "" : exec_devs.front(); + if (actual_device != m_ref_device) { + LOG_INFO("Wrapping with AccuracyChecked (main: " << actual_device << ", ref: " << m_ref_device << ")."); + auto ref_cm = compile_submodel(model, m_ref_device); + return ov::npuw::accuracy_checked::CompiledModel::create(model, + get_plugin(), + std::move(main_cm), + std::move(ref_cm), + m_acc_check); + } + } + return main_cm; + }; + if (auto& moe_experts_opt = desc.moe_experts; moe_experts_opt.has_value()) { LOG_INFO("Compiling MoE expert models for Subgraph[" << id << "]..."); LOG_BLOCK(); @@ -2247,10 +2256,6 @@ std::string ov::npuw::CompiledModel::submodel_device(const std::size_t idx) cons return ""; } - if (comp_subm_desc.switched_to_ref) { - return m_ref_device; - } - const auto exec_devs = comp_subm_desc.compiled_model->get_property(ov::execution_devices.name()).as>(); return exec_devs.empty() ? "" : exec_devs.front(); diff --git a/src/plugins/intel_npu/src/plugin/npuw/compiled_model.hpp b/src/plugins/intel_npu/src/plugin/npuw/compiled_model.hpp index 3a2f042f5ccfa5..807f390576ff92 100644 --- a/src/plugins/intel_npu/src/plugin/npuw/compiled_model.hpp +++ b/src/plugins/intel_npu/src/plugin/npuw/compiled_model.hpp @@ -279,10 +279,6 @@ class CompiledModel : public ov::npuw::ICompiledModel_v0 { bool forced_to_fcall = false; - // FIXME: Take it out of structure - ov::SoPtr ref_compiled_model; - bool switched_to_ref = false; - // Metrics execution_stats stat; diff --git a/src/plugins/intel_npu/src/plugin/npuw/v1/elements/accuracy_checked.cpp b/src/plugins/intel_npu/src/plugin/npuw/v1/elements/accuracy_checked.cpp new file mode 100644 index 00000000000000..be2da0e99db06f --- /dev/null +++ b/src/plugins/intel_npu/src/plugin/npuw/v1/elements/accuracy_checked.cpp @@ -0,0 +1,248 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "accuracy_checked.hpp" + +#include + +#include "openvino/core/except.hpp" +#include "openvino/runtime/make_tensor.hpp" + +namespace ov::npuw::accuracy_checked { + +// ============================================================================ +// CompiledModel +// ============================================================================ + +ov::SoPtr CompiledModel::create(const std::shared_ptr& model, + const std::shared_ptr& plugin, + ov::SoPtr main_compiled, + ov::SoPtr ref_compiled, + Checker checker) { + OPENVINO_ASSERT(main_compiled._ptr != nullptr, "AccuracyChecked: main compiled model must not be null"); + OPENVINO_ASSERT(static_cast(checker), "AccuracyChecked: checker function must not be null"); + + if (ref_compiled._ptr == nullptr) { + return main_compiled; + } + + auto cm = std::make_shared(model, + plugin, + std::move(main_compiled), + std::move(ref_compiled), + std::move(checker)); + return {cm, {}}; +} + +CompiledModel::CompiledModel(const std::shared_ptr& model, + const std::shared_ptr& plugin, + ov::SoPtr main_compiled, + ov::SoPtr ref_compiled, + Checker checker) + : ov::ICompiledModel(model, plugin), + m_main_compiled(std::move(main_compiled)), + m_ref_compiled(std::move(ref_compiled)), + m_checker(std::move(checker)) {} + +ov::SoPtr CompiledModel::active_compiled_model_locked() const { + return m_switched_to_reference ? m_ref_compiled : m_main_compiled; +} + +bool CompiledModel::has_switched_to_reference() const { + std::lock_guard lock(m_mutex); + return m_switched_to_reference; +} + +void CompiledModel::export_model(std::ostream& stream) const { + std::lock_guard lock(m_mutex); + active_compiled_model_locked()->export_model(stream); +} + +std::shared_ptr CompiledModel::get_runtime_model() const { + std::lock_guard lock(m_mutex); + return active_compiled_model_locked()->get_runtime_model(); +} + +void CompiledModel::set_property(const ov::AnyMap& properties) { + std::lock_guard lock(m_mutex); + active_compiled_model_locked()->set_property(properties); +} + +ov::Any CompiledModel::get_property(const std::string& name) const { + std::lock_guard lock(m_mutex); + return active_compiled_model_locked()->get_property(name); +} + +std::shared_ptr CompiledModel::create_sync_infer_request() const { + auto self = std::static_pointer_cast(shared_from_this()); + return std::make_shared(std::move(self)); +} + +std::shared_ptr CompiledModel::create_infer_request() const { + return std::make_shared(create_sync_infer_request(), + get_task_executor(), + get_callback_executor()); +} + +// ============================================================================ +// InferRequest +// ============================================================================ + +InferRequest::InferRequest(std::shared_ptr compiled_model) + : ov::ISyncInferRequest(compiled_model), + m_acc_compiled_model(std::move(compiled_model)), + m_using_reference(m_acc_compiled_model->has_switched_to_reference()) {} + +void InferRequest::ensure_main_request_locked() const { + if (m_main_request) { + return; + } + m_main_request = m_acc_compiled_model->m_main_compiled->create_infer_request(); + OPENVINO_ASSERT(m_main_request, "AccuracyChecked: failed to create main infer request"); +} + +void InferRequest::ensure_ref_request_locked() const { + if (m_ref_request) { + return; + } + m_ref_request = m_acc_compiled_model->m_ref_compiled->create_infer_request(); + OPENVINO_ASSERT(m_ref_request, "AccuracyChecked: failed to create reference infer request"); +} + +void InferRequest::infer() { + std::lock_guard lock(m_mutex); + + if (m_using_reference) { + ensure_ref_request_locked(); + m_ref_request->infer(); + return; + } + + ensure_main_request_locked(); + + const auto& main_cm = m_acc_compiled_model->m_main_compiled; + const auto& ref_cm = m_acc_compiled_model->m_ref_compiled; + + // Snapshot current input/output tensors from the main request. + // These are the tensors the user has bound (or the request's defaults). + // We use them to (a) feed the reference request and (b) rebind after a + // permanent switch so that downstream requests keep reading from the + // same memory. + std::vector> main_input_tensors; + std::vector> main_output_tensors; + main_input_tensors.reserve(main_cm->inputs().size()); + main_output_tensors.reserve(main_cm->outputs().size()); + + for (const auto& port : main_cm->inputs()) { + main_input_tensors.push_back(m_main_request->get_tensor(port)); + } + for (const auto& port : main_cm->outputs()) { + main_output_tensors.push_back(m_main_request->get_tensor(port)); + } + + m_main_request->infer(); + + // Accuracy check: feed reference request with the same inputs and run it. + ensure_ref_request_locked(); + for (size_t i = 0; i < ref_cm->inputs().size(); i++) { + m_ref_request->set_tensor(ref_cm->inputs()[i], main_input_tensors[i]); + } + m_ref_request->infer(); + + // Compare outputs using the provided checker. + bool accurate = true; + for (size_t i = 0; i < main_cm->outputs().size(); i++) { + const auto& ref_tensor = m_ref_request->get_tensor(ref_cm->outputs()[i]); + if (!m_acc_compiled_model->m_checker(main_output_tensors[i], ref_tensor)) { + accurate = false; + } + } + + if (!accurate) { + // Copy reference outputs into the main output buffers so that any + // downstream request already bound to those buffers sees the corrected + // values immediately. + for (size_t i = 0; i < main_cm->outputs().size(); i++) { + const auto& ref_tensor = m_ref_request->get_tensor(ref_cm->outputs()[i]); + ref_tensor->copy_to(main_output_tensors[i]._ptr); + } + + // Rebind the reference request to use the same tensor objects that the + // main request was using. From this point on, the reference request + // writes results directly into those buffers, keeping downstream + // tensor bindings valid without requiring update_subrequest_links(). + for (size_t i = 0; i < ref_cm->inputs().size(); i++) { + m_ref_request->set_tensor(ref_cm->inputs()[i], main_input_tensors[i]); + } + for (size_t i = 0; i < ref_cm->outputs().size(); i++) { + m_ref_request->set_tensor(ref_cm->outputs()[i], main_output_tensors[i]); + } + + m_using_reference = true; + { + std::lock_guard model_lock(m_acc_compiled_model->m_mutex); + m_acc_compiled_model->m_switched_to_reference = true; + } + } +} + +ov::SoPtr InferRequest::get_tensor(const ov::Output& port) const { + std::lock_guard lock(m_mutex); + + if (m_using_reference) { + ensure_ref_request_locked(); + auto found = find_port(port); + OPENVINO_ASSERT(found.found(), "AccuracyChecked: unknown port"); + const auto& ref_cm = m_acc_compiled_model->m_ref_compiled; + return found.is_output() ? m_ref_request->get_tensor(ref_cm->outputs()[found.idx]) + : m_ref_request->get_tensor(ref_cm->inputs()[found.idx]); + } + + ensure_main_request_locked(); + return m_main_request->get_tensor(port); +} + +void InferRequest::set_tensor(const ov::Output& port, const ov::SoPtr& tensor) { + std::lock_guard lock(m_mutex); + + if (m_using_reference) { + ensure_ref_request_locked(); + auto found = find_port(port); + OPENVINO_ASSERT(found.found(), "AccuracyChecked: unknown port"); + const auto& ref_cm = m_acc_compiled_model->m_ref_compiled; + if (found.is_output()) { + m_ref_request->set_tensor(ref_cm->outputs()[found.idx], tensor); + } else { + m_ref_request->set_tensor(ref_cm->inputs()[found.idx], tensor); + } + return; + } + + ensure_main_request_locked(); + m_main_request->set_tensor(port, tensor); +} + +void InferRequest::check_tensors() const {} + +std::vector> InferRequest::query_state() const { + std::lock_guard lock(m_mutex); + if (m_using_reference) { + ensure_ref_request_locked(); + return m_ref_request->query_state(); + } + ensure_main_request_locked(); + return m_main_request->query_state(); +} + +std::vector InferRequest::get_profiling_info() const { + std::lock_guard lock(m_mutex); + if (m_using_reference) { + ensure_ref_request_locked(); + return m_ref_request->get_profiling_info(); + } + ensure_main_request_locked(); + return m_main_request->get_profiling_info(); +} + +} // namespace ov::npuw::accuracy_checked diff --git a/src/plugins/intel_npu/src/plugin/npuw/v1/elements/accuracy_checked.hpp b/src/plugins/intel_npu/src/plugin/npuw/v1/elements/accuracy_checked.hpp new file mode 100644 index 00000000000000..6f6c8cca41d716 --- /dev/null +++ b/src/plugins/intel_npu/src/plugin/npuw/v1/elements/accuracy_checked.hpp @@ -0,0 +1,108 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include +#include + +#include "openvino/runtime/iasync_infer_request.hpp" +#include "openvino/runtime/icompiled_model.hpp" +#include "openvino/runtime/isync_infer_request.hpp" +#include "openvino/runtime/so_ptr.hpp" + +namespace ov::npuw::accuracy_checked { + +class InferRequest; + +// A compiled-model wrapper that validates inference accuracy against a +// reference device and permanently switches to that reference if the +// normalised RMSE threshold is exceeded. +// +// The wrapper is transparent: it exposes the same I/O as the wrapped +// model and forwards all property queries to the currently active model +// (main until a switch happens, reference afterwards). +// +// Intended to be composed on top of a failsafe::CompiledModel so that +// the full chain becomes: +// +// AccuracyChecked( main = Failsafe(NPU -> CPU), ref = CPU ) +class CompiledModel final : public ov::ICompiledModel { +public: + // Checker: returns true when the output pair is considered accurate. + using Checker = std::function&, const ov::SoPtr&)>; + + // Factory method. Returns main_compiled unwrapped when ref_compiled is + // null (no-op wrapper) to keep the zero-overhead path trivial. + static ov::SoPtr create(const std::shared_ptr& model, + const std::shared_ptr& plugin, + ov::SoPtr main_compiled, + ov::SoPtr ref_compiled, + Checker checker); + + CompiledModel(const std::shared_ptr& model, + const std::shared_ptr& plugin, + ov::SoPtr main_compiled, + ov::SoPtr ref_compiled, + Checker checker); + + void export_model(std::ostream& model) const override; + std::shared_ptr get_runtime_model() const override; + + void set_property(const ov::AnyMap& properties) override; + ov::Any get_property(const std::string& name) const override; + + std::shared_ptr create_sync_infer_request() const override; + std::shared_ptr create_infer_request() const override; + + // Returns true if any InferRequest has triggered a permanent switch + // to the reference compiled model. + bool has_switched_to_reference() const; + +private: + friend class InferRequest; + + ov::SoPtr active_compiled_model_locked() const; + + ov::SoPtr m_main_compiled; + ov::SoPtr m_ref_compiled; + Checker m_checker; + mutable std::mutex m_mutex; + mutable bool m_switched_to_reference = false; +}; + +// Sync infer request wrapper produced by AccuracyChecked::CompiledModel. +// +// On each infer() call it runs the main request, then copies its inputs +// to the reference request and runs that too. Outputs from both are +// compared using the Checker provided at construction. If any output +// fails the check the reference results are copied into the main output +// buffers and this request (and all subsequent requests from the same +// CompiledModel) permanently switch to reference-only inference. +class InferRequest final : public ov::ISyncInferRequest { +public: + explicit InferRequest(std::shared_ptr compiled_model); + + void infer() override; + + ov::SoPtr get_tensor(const ov::Output& port) const override; + void set_tensor(const ov::Output& port, const ov::SoPtr& tensor) override; + void check_tensors() const override; + + std::vector> query_state() const override; + std::vector get_profiling_info() const override; + +private: + void ensure_main_request_locked() const; + void ensure_ref_request_locked() const; + + std::shared_ptr m_acc_compiled_model; + mutable std::mutex m_mutex; + mutable std::shared_ptr m_main_request; + mutable std::shared_ptr m_ref_request; + mutable bool m_using_reference = false; +}; + +} // namespace ov::npuw::accuracy_checked diff --git a/src/plugins/intel_npu/tests/unit/CMakeLists.txt b/src/plugins/intel_npu/tests/unit/CMakeLists.txt index 9989274e349505..0efbac1b97dbd2 100644 --- a/src/plugins/intel_npu/tests/unit/CMakeLists.txt +++ b/src/plugins/intel_npu/tests/unit/CMakeLists.txt @@ -63,6 +63,7 @@ ov_add_test_target( ${OpenVINO_SOURCE_DIR}/src/plugins/intel_npu/src/plugin/npuw/serialization.cpp ${OpenVINO_SOURCE_DIR}/src/plugins/intel_npu/src/plugin/npuw/util.cpp ${OpenVINO_SOURCE_DIR}/src/plugins/intel_npu/src/plugin/npuw/util_xarch.cpp + ${OpenVINO_SOURCE_DIR}/src/plugins/intel_npu/src/plugin/npuw/v1/elements/accuracy_checked.cpp ${OpenVINO_SOURCE_DIR}/src/plugins/intel_npu/src/plugin/npuw/v1/elements/failsafe.cpp ${OpenVINO_SOURCE_DIR}/src/plugins/intel_npu/src/plugin/npuw/lazy_tensor.cpp ${OpenVINO_SOURCE_DIR}/src/plugins/intel_npu/src/plugin/npuw/attention.cpp diff --git a/src/plugins/intel_npu/tests/unit/npuw/accuracy_checked.cpp b/src/plugins/intel_npu/tests/unit/npuw/accuracy_checked.cpp new file mode 100644 index 00000000000000..708c7bdc166d71 --- /dev/null +++ b/src/plugins/intel_npu/tests/unit/npuw/accuracy_checked.cpp @@ -0,0 +1,505 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include +#include +#include + +#include "v1/elements/accuracy_checked.hpp" +#include "v1/elements/failsafe.hpp" +#include "openvino/opsets/opset10.hpp" +#include "openvino/runtime/make_tensor.hpp" +#include "openvino/runtime/properties.hpp" + +namespace { + +// --------------------------------------------------------------------------- +// Shared test infrastructure (mirrored from failsafe.cpp) +// --------------------------------------------------------------------------- + +std::shared_ptr make_test_model() { + auto input = std::make_shared(ov::element::f32, ov::Shape{1}); + input->set_friendly_name("input"); + auto zero = ov::opset10::Constant::create(ov::element::f32, ov::Shape{1}, {0.f}); + auto add = std::make_shared(input, zero); + add->set_friendly_name("output_add"); + auto result = std::make_shared(add); + result->set_friendly_name("output"); + return std::make_shared(ov::ResultVector{result}, ov::ParameterVector{input}, "AccTestModel"); +} + +class NullPlugin final : public ov::IPlugin { +public: + std::shared_ptr compile_model(const std::shared_ptr&, + const ov::AnyMap&) const override { return {}; } + std::shared_ptr compile_model(const std::shared_ptr&, + const ov::AnyMap&, + const ov::SoPtr&) const override { return {}; } + std::shared_ptr import_model(std::istream&, const ov::AnyMap&) const override { return {}; } + std::shared_ptr import_model(std::istream&, + const ov::SoPtr&, + const ov::AnyMap&) const override { return {}; } + std::shared_ptr import_model(const ov::Tensor&, const ov::AnyMap&) const override { return {}; } + std::shared_ptr import_model(const ov::Tensor&, + const ov::SoPtr&, + const ov::AnyMap&) const override { return {}; } + ov::SupportedOpsMap query_model(const std::shared_ptr&, const ov::AnyMap&) const override { return {}; } + void set_property(const ov::AnyMap&) override {} + ov::Any get_property(const std::string&, const ov::AnyMap&) const override { return {}; } + ov::SoPtr create_context(const ov::AnyMap&) const override { return {}; } + ov::SoPtr get_default_context(const ov::AnyMap&) const override { return {}; } +}; + +// Compiled model that adds a fixed bias to its single input and writes the +// result to its single output. Optionally tracks which events occurred. +struct ModelState { + std::string name; + std::vector* events = nullptr; + float output_bias = 0.f; + float last_input_value = 0.f; + float last_output_value = 0.f; + // Optional glitch: from call number glitch_at_call onwards, output_bias is + // replaced with glitch_bias. Set glitch_at_call <= 0 to disable. + int infer_count = 0; + int glitch_at_call = 0; + float glitch_bias = 0.f; +}; + +class TestCompiledModel; + +class TestInferRequest final : public ov::ISyncInferRequest { +public: + TestInferRequest(std::shared_ptr cm, std::shared_ptr state); + + void infer() override; + ov::SoPtr get_tensor(const ov::Output& port) const override { + return ov::ISyncInferRequest::get_tensor(port); + } + void set_tensor(const ov::Output& port, const ov::SoPtr& tensor) override { + ov::ISyncInferRequest::set_tensor(port, tensor); + } + void check_tensors() const override {} + std::vector> query_state() const override { return {}; } + std::vector get_profiling_info() const override { return {}; } + +private: + std::shared_ptr m_state; +}; + +class TestCompiledModel final : public ov::ICompiledModel { +public: + TestCompiledModel(const std::shared_ptr& model, + const std::shared_ptr& plugin, + std::shared_ptr state) + : ov::ICompiledModel(model, plugin), m_model(model), m_state(std::move(state)) {} + + void export_model(std::ostream&) const override {} + std::shared_ptr get_runtime_model() const override { return m_model; } + void set_property(const ov::AnyMap&) override {} + ov::Any get_property(const std::string& name) const override { + if (name == ov::execution_devices.name()) { + return std::vector{m_state->name}; + } + OPENVINO_THROW("Unsupported property: ", name); + } + std::shared_ptr create_sync_infer_request() const override { + if (m_state->events) { + m_state->events->push_back("create-request:" + m_state->name); + } + auto self = std::static_pointer_cast(shared_from_this()); + return std::make_shared(std::move(self), m_state); + } + std::shared_ptr create_infer_request() const override { + return std::make_shared(create_sync_infer_request(), + get_task_executor(), + get_callback_executor()); + } + +private: + std::shared_ptr m_model; + std::shared_ptr m_state; +}; + +TestInferRequest::TestInferRequest(std::shared_ptr cm, std::shared_ptr state) + : ov::ISyncInferRequest(cm), m_state(std::move(state)) { + for (const auto& input : get_compiled_model()->inputs()) { + ov::ISyncInferRequest::set_tensor( + input, ov::get_tensor_impl(ov::Tensor(input.get_element_type(), input.get_shape()))); + } + for (const auto& output : get_compiled_model()->outputs()) { + ov::ISyncInferRequest::set_tensor( + output, ov::get_tensor_impl(ov::Tensor(output.get_element_type(), output.get_shape()))); + } +} + +void TestInferRequest::infer() { + if (m_state->events) { + m_state->events->push_back("infer:" + m_state->name); + } + ++m_state->infer_count; + const float bias = (m_state->glitch_at_call > 0 && m_state->infer_count >= m_state->glitch_at_call) + ? m_state->glitch_bias + : m_state->output_bias; + const auto& input_port = get_compiled_model()->inputs().front(); + const auto& output_port = get_compiled_model()->outputs().front(); + const auto& in_tensor = ov::ISyncInferRequest::get_tensor(input_port); + const auto& out_tensor = ov::ISyncInferRequest::get_tensor(output_port); + m_state->last_input_value = in_tensor->data()[0]; + m_state->last_output_value = m_state->last_input_value + bias; + out_tensor->data()[0] = m_state->last_output_value; +} + +// Helper to build an ov::SoPtr backed by a TestCompiledModel. +ov::SoPtr make_test_compiled_model(const std::shared_ptr& model, + const std::shared_ptr& plugin, + std::shared_ptr state) { + return {std::make_shared(model, plugin, std::move(state)), {}}; +} + +// A checker that passes when |actual - reference| <= threshold for a scalar f32 tensor. +ov::npuw::accuracy_checked::CompiledModel::Checker make_threshold_checker(float threshold) { + return [threshold](const ov::SoPtr& actual, + const ov::SoPtr& reference) -> bool { + const float a = actual->data()[0]; + const float r = reference->data()[0]; + return std::abs(a - r) <= threshold; + }; +} + +} // namespace + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +TEST(AccuracyCheckedCompiledModelTest, NullRefReturnsMainUnwrapped) { + auto model = make_test_model(); + auto plugin = std::make_shared(); + auto main_state = std::make_shared(ModelState{"main", nullptr, 0.f}); + auto main_cm = make_test_compiled_model(model, plugin, main_state); + + auto so = ov::npuw::accuracy_checked::CompiledModel::create(model, plugin, main_cm, {}, make_threshold_checker(0.f)); + + // When ref is null create() must return the unwrapped main model. + EXPECT_EQ(std::dynamic_pointer_cast(so._ptr), nullptr); + ASSERT_NE(so._ptr, nullptr); +} + +TEST(AccuracyCheckedCompiledModelTest, AccurateInferencePassesThrough) { + auto model = make_test_model(); + auto plugin = std::make_shared(); + std::vector events; + + auto main_state = std::make_shared(ModelState{"main", &events, 10.f}); + auto ref_state = std::make_shared(ModelState{"ref", &events, 10.f}); // same bias → same output + + auto main_cm = make_test_compiled_model(model, plugin, main_state); + auto ref_cm = make_test_compiled_model(model, plugin, ref_state); + + auto so = ov::npuw::accuracy_checked::CompiledModel::create( + model, plugin, main_cm, ref_cm, make_threshold_checker(0.1f)); + auto compiled = std::dynamic_pointer_cast(so._ptr); + ASSERT_NE(compiled, nullptr); + + auto request = compiled->create_sync_infer_request(); + auto input = ov::get_tensor_impl(ov::Tensor(ov::element::f32, ov::Shape{1})); + input->data()[0] = 5.f; + request->set_tensor(model->inputs().front(), input); + + ASSERT_NO_THROW(request->infer()); + + // Both main and ref should have been inferred. + EXPECT_EQ(main_state->last_input_value, 5.f); + EXPECT_EQ(ref_state->last_input_value, 5.f); + // Output = input + bias = 5 + 10 = 15. + EXPECT_FLOAT_EQ(request->get_tensor(model->outputs().front())->data()[0], 15.f); + + // Model should NOT have switched. + EXPECT_FALSE(compiled->has_switched_to_reference()); +} + +TEST(AccuracyCheckedCompiledModelTest, InaccurateInferenceSwitchesToReference) { + auto model = make_test_model(); + auto plugin = std::make_shared(); + std::vector events; + + // main bias=10, ref bias=11 → difference=1 > threshold=0.5 → fail + auto main_state = std::make_shared(ModelState{"main", &events, 10.f}); + auto ref_state = std::make_shared(ModelState{"ref", &events, 11.f}); + + auto main_cm = make_test_compiled_model(model, plugin, main_state); + auto ref_cm = make_test_compiled_model(model, plugin, ref_state); + + auto so = ov::npuw::accuracy_checked::CompiledModel::create( + model, plugin, main_cm, ref_cm, make_threshold_checker(0.5f)); + auto compiled = std::dynamic_pointer_cast(so._ptr); + ASSERT_NE(compiled, nullptr); + + auto request = compiled->create_sync_infer_request(); + auto input = ov::get_tensor_impl(ov::Tensor(ov::element::f32, ov::Shape{1})); + input->data()[0] = 3.f; + request->set_tensor(model->inputs().front(), input); + + ASSERT_NO_THROW(request->infer()); + + // Output should be the reference value (3 + 11 = 14), copied into the main output buffer. + EXPECT_FLOAT_EQ(request->get_tensor(model->outputs().front())->data()[0], 14.f); + EXPECT_TRUE(compiled->has_switched_to_reference()); +} + +TEST(AccuracyCheckedCompiledModelTest, PermanentSwitchSkipsMainOnSubsequentInfers) { + auto model = make_test_model(); + auto plugin = std::make_shared(); + std::vector events; + + auto main_state = std::make_shared(ModelState{"main", &events, 10.f}); + auto ref_state = std::make_shared(ModelState{"ref", &events, 11.f}); + + auto main_cm = make_test_compiled_model(model, plugin, main_state); + auto ref_cm = make_test_compiled_model(model, plugin, ref_state); + + auto so = ov::npuw::accuracy_checked::CompiledModel::create( + model, plugin, main_cm, ref_cm, make_threshold_checker(0.5f)); + auto compiled = std::dynamic_pointer_cast(so._ptr); + auto request = compiled->create_sync_infer_request(); + + auto input = ov::get_tensor_impl(ov::Tensor(ov::element::f32, ov::Shape{1})); + input->data()[0] = 1.f; + request->set_tensor(model->inputs().front(), input); + + // First infer triggers the switch. + ASSERT_NO_THROW(request->infer()); + ASSERT_TRUE(compiled->has_switched_to_reference()); + + events.clear(); // Reset event log. + + input->data()[0] = 2.f; + ASSERT_NO_THROW(request->infer()); + + // After permanent switch the main model must NOT be invoked. + for (const auto& ev : events) { + EXPECT_NE(ev, "infer:main") << "main should not be inferred after switch"; + } + // Reference should have been inferred with the new input. + EXPECT_FLOAT_EQ(ref_state->last_input_value, 2.f); + EXPECT_FLOAT_EQ(request->get_tensor(model->outputs().front())->data()[0], 2.f + 11.f); +} + +TEST(AccuracyCheckedCompiledModelTest, UserOutputBufferReceivesReferenceValueOnSwitch) { + auto model = make_test_model(); + auto plugin = std::make_shared(); + + auto main_state = std::make_shared(ModelState{"main", nullptr, 10.f}); + auto ref_state = std::make_shared(ModelState{"ref", nullptr, 20.f}); + + auto main_cm = make_test_compiled_model(model, plugin, main_state); + auto ref_cm = make_test_compiled_model(model, plugin, ref_state); + + auto so = ov::npuw::accuracy_checked::CompiledModel::create( + model, plugin, main_cm, ref_cm, make_threshold_checker(0.5f)); + auto compiled = std::dynamic_pointer_cast(so._ptr); + auto request = compiled->create_sync_infer_request(); + + auto input = ov::get_tensor_impl(ov::Tensor(ov::element::f32, ov::Shape{1})); + auto output = ov::get_tensor_impl(ov::Tensor(ov::element::f32, ov::Shape{1})); + input->data()[0] = 4.f; + output->data()[0] = -1.f; // sentinel + request->set_tensor(model->inputs().front(), input); + request->set_tensor(model->outputs().front(), output); + + ASSERT_NO_THROW(request->infer()); + + // The user-provided output tensor must hold the reference result (4 + 20 = 24). + EXPECT_FLOAT_EQ(output->data()[0], 24.f); + // And get_tensor() must return the same object. + EXPECT_EQ(request->get_tensor(model->outputs().front())._ptr, output._ptr); +} + +TEST(AccuracyCheckedCompiledModelTest, NewRequestFromSwitchedModelStartsOnReference) { + auto model = make_test_model(); + auto plugin = std::make_shared(); + std::vector events; + + auto main_state = std::make_shared(ModelState{"main", &events, 10.f}); + auto ref_state = std::make_shared(ModelState{"ref", &events, 20.f}); + + auto main_cm = make_test_compiled_model(model, plugin, main_state); + auto ref_cm = make_test_compiled_model(model, plugin, ref_state); + + auto so = ov::npuw::accuracy_checked::CompiledModel::create( + model, plugin, main_cm, ref_cm, make_threshold_checker(0.5f)); + auto compiled = std::dynamic_pointer_cast(so._ptr); + + // Trigger switch via first request. + { + auto req1 = compiled->create_sync_infer_request(); + auto in1 = ov::get_tensor_impl(ov::Tensor(ov::element::f32, ov::Shape{1})); + in1->data()[0] = 1.f; + req1->set_tensor(model->inputs().front(), in1); + req1->infer(); + ASSERT_TRUE(compiled->has_switched_to_reference()); + } + + events.clear(); + + // A second request created after the switch should use reference directly. + auto req2 = compiled->create_sync_infer_request(); + auto in2 = ov::get_tensor_impl(ov::Tensor(ov::element::f32, ov::Shape{1})); + in2->data()[0] = 5.f; + req2->set_tensor(model->inputs().front(), in2); + ASSERT_NO_THROW(req2->infer()); + + for (const auto& ev : events) { + EXPECT_NE(ev, "infer:main") << "new request after switch must not use main"; + } + EXPECT_FLOAT_EQ(ref_state->last_input_value, 5.f); + EXPECT_FLOAT_EQ(req2->get_tensor(model->outputs().front())->data()[0], 5.f + 20.f); +} + +TEST(AccuracyCheckedCompiledModelTest, ChainedWithFailsafeModel) { + auto model = make_test_model(); + auto plugin = std::make_shared(); + std::vector events; + + // Failsafe inner layer: NPU fails, falls back to CPU. + auto npu_state = std::make_shared(ModelState{"NPU", &events, 10.f}); + auto cpu_state = std::make_shared(ModelState{"CPU", &events, 10.f}); + + auto failsafe_factory = [&](const std::string& device) -> ov::SoPtr { + if (device == "NPU") { + OPENVINO_THROW("NPU not available"); + } + if (device == "CPU") { + return make_test_compiled_model(model, plugin, cpu_state); + } + OPENVINO_THROW("Unknown device: ", device); + }; + + auto failsafe_cm = ov::npuw::failsafe::CompiledModel::create(model, plugin, {"NPU", "CPU"}, failsafe_factory); + + // Reference is a separate CPU model with a different bias (simulate inaccuracy). + auto ref_state = std::make_shared(ModelState{"ref_cpu", &events, 20.f}); + auto ref_cm = make_test_compiled_model(model, plugin, ref_state); + + auto so = ov::npuw::accuracy_checked::CompiledModel::create( + model, plugin, failsafe_cm, ref_cm, make_threshold_checker(0.5f)); + auto acc_compiled = std::dynamic_pointer_cast(so._ptr); + ASSERT_NE(acc_compiled, nullptr); + + auto request = acc_compiled->create_sync_infer_request(); + auto input = ov::get_tensor_impl(ov::Tensor(ov::element::f32, ov::Shape{1})); + input->data()[0] = 2.f; + request->set_tensor(model->inputs().front(), input); + + // Failsafe uses CPU (bias=10), ref uses ref_cpu (bias=20). diff=10 > 0.5 → switch. + ASSERT_NO_THROW(request->infer()); + + EXPECT_TRUE(acc_compiled->has_switched_to_reference()); + // Output should be ref result: 2 + 20 = 22. + EXPECT_FLOAT_EQ(request->get_tensor(model->outputs().front())->data()[0], 22.f); +} + +TEST(AccuracyCheckedCompiledModelTest, ExecutionDevicesReflectsActiveModel) { + auto model = make_test_model(); + auto plugin = std::make_shared(); + + auto main_state = std::make_shared(ModelState{"NPU", nullptr, 10.f}); + auto ref_state = std::make_shared(ModelState{"CPU", nullptr, 11.f}); + + auto main_cm = make_test_compiled_model(model, plugin, main_state); + auto ref_cm = make_test_compiled_model(model, plugin, ref_state); + + auto so = ov::npuw::accuracy_checked::CompiledModel::create( + model, plugin, main_cm, ref_cm, make_threshold_checker(0.5f)); + auto compiled = std::dynamic_pointer_cast(so._ptr); + ASSERT_NE(compiled, nullptr); + + // Before switch: reports main device. + auto devs_before = compiled->get_property(ov::execution_devices.name()).as>(); + EXPECT_EQ(devs_before, (std::vector{"NPU"})); + + // Trigger switch. + auto request = compiled->create_sync_infer_request(); + auto input = ov::get_tensor_impl(ov::Tensor(ov::element::f32, ov::Shape{1})); + input->data()[0] = 1.f; + request->set_tensor(model->inputs().front(), input); + request->infer(); + ASSERT_TRUE(compiled->has_switched_to_reference()); + + // After switch: reports reference device. + auto devs_after = compiled->get_property(ov::execution_devices.name()).as>(); + EXPECT_EQ(devs_after, (std::vector{"CPU"})); +} + +TEST(AccuracyCheckedCompiledModelTest, RepeatingBlockAccuracyFailsAtThirdCall) { + // In NPUW a function body (repeating block) is compiled once and its infer + // request pair (main + ref) is *reused* for every call-site invocation + // within a single forward pass. The AccuracyChecked::InferRequest wraps + // that pair and is called once per instance. The shared + // m_switched_to_reference flag ensures that once any instance detects an + // accuracy failure, all subsequent invocations automatically use reference. + // + // Scenario: main is accurate on calls 1 and 2, but "glitches" from call 3 + // onwards (output bias shifts by 1). Reference is always stable. + // Threshold = 0.5, so |glitch| = 1 triggers the permanent switch on call 3. + auto model = make_test_model(); + auto plugin = std::make_shared(); + std::vector events; + + auto main_state = std::make_shared(ModelState{"main", &events, 10.f}); + main_state->glitch_at_call = 3; + main_state->glitch_bias = 11.f; // diverges from ref by 1 on call 3+ + + auto ref_state = std::make_shared(ModelState{"ref", &events, 10.f}); + + auto main_cm = make_test_compiled_model(model, plugin, main_state); + auto ref_cm = make_test_compiled_model(model, plugin, ref_state); + + auto so = ov::npuw::accuracy_checked::CompiledModel::create( + model, plugin, main_cm, ref_cm, make_threshold_checker(0.5f)); + auto compiled = std::dynamic_pointer_cast(so._ptr); + ASSERT_NE(compiled, nullptr); + + // One AccuracyChecked::InferRequest reused for all N instances of the block. + auto request = compiled->create_sync_infer_request(); + auto input = ov::get_tensor_impl(ov::Tensor(ov::element::f32, ov::Shape{1})); + request->set_tensor(model->inputs().front(), input); + + // -- Forward pass 1, instance 1: main call #1, bias=10. Accurate. --------- + input->data()[0] = 1.f; + ASSERT_NO_THROW(request->infer()); + EXPECT_FALSE(compiled->has_switched_to_reference()); + EXPECT_FLOAT_EQ(request->get_tensor(model->outputs().front())->data()[0], 11.f); // 1+10 + + // -- Forward pass 1, instance 2: main call #2, bias=10. Accurate. --------- + input->data()[0] = 2.f; + ASSERT_NO_THROW(request->infer()); + EXPECT_FALSE(compiled->has_switched_to_reference()); + EXPECT_FLOAT_EQ(request->get_tensor(model->outputs().front())->data()[0], 12.f); // 2+10 + + // -- Forward pass 1, instance 3: main call #3, bias glitches to 11. + // main=3+11=14, ref=3+10=13, |14-13|=1 > 0.5 → permanent switch. ------ + input->data()[0] = 3.f; + ASSERT_NO_THROW(request->infer()); + EXPECT_TRUE(compiled->has_switched_to_reference()); + // Output must be the reference result (3+10=13), not the glitched main (14). + EXPECT_FLOAT_EQ(request->get_tensor(model->outputs().front())->data()[0], 13.f); + + // -- Forward pass 2: all instances use reference directly. ----------------- + events.clear(); + input->data()[0] = 10.f; + ASSERT_NO_THROW(request->infer()); // instance 1 + input->data()[0] = 20.f; + ASSERT_NO_THROW(request->infer()); // instance 2 + input->data()[0] = 30.f; + ASSERT_NO_THROW(request->infer()); // instance 3 + + for (const auto& ev : events) { + EXPECT_NE(ev, "infer:main") << "main must not be invoked after permanent reference switch"; + } + // Last output via reference: 30+10=40. + EXPECT_FLOAT_EQ(request->get_tensor(model->outputs().front())->data()[0], 40.f); +} From 07d62fd00466532f512dfc08606e17f1f15c1a15 Mon Sep 17 00:00:00 2001 From: Daria Mityagina Date: Thu, 23 Apr 2026 20:31:39 +0400 Subject: [PATCH 047/545] Bump NPU Compiler 7_7_0-98c0808 (#35488) ### Details: Copy of https://github.com/openvinotoolkit/openvino/pull/35466 ### Tickets: - *ticket-id* ### AI Assistance: - *AI assistance used: no / yes* - *If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).* --------- Co-authored-by: Doronin, Maksim --- .../intel_npu/cmake/download_compiler_libs.cmake | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/intel_npu/cmake/download_compiler_libs.cmake b/src/plugins/intel_npu/cmake/download_compiler_libs.cmake index f07b9075fc89e0..7a47d4643779fd 100644 --- a/src/plugins/intel_npu/cmake/download_compiler_libs.cmake +++ b/src/plugins/intel_npu/cmake/download_compiler_libs.cmake @@ -48,13 +48,13 @@ endfunction() if(ENABLE_INTEL_NPU_COMPILER) message(STATUS "Resolving prebuilt NPU Plugin Compiler dependencies...") - set(PLUGIN_COMPILER_VERSION_MAJOR 8) - set(PLUGIN_COMPILER_VERSION_MINOR 1) + set(PLUGIN_COMPILER_VERSION_MAJOR 7) + set(PLUGIN_COMPILER_VERSION_MINOR 7) set(PLUGIN_COMPILER_VERSION_PATCH 0) - set(PLUGIN_COMPILER_COMMIT_SHA 727e603) - set(PLUGIN_COMPILER_WINDOWS_2022_CHECKSUM f7cb6501df6e38fc90e6591470069d3a705749113b39438e92391271bc6835c1) - set(PLUGIN_COMPILER_UBUNTU_22_04_CHECKSUM d50bf866a37a2d599709fb6a805de86fd9b052a67b588d2a83558a224aaf7e95) - set(PLUGIN_COMPILER_UBUNTU_24_04_CHECKSUM 3c9c5960c4a86577652cd9a96cb4630576ec398f126fd8b37979063b63929448) + set(PLUGIN_COMPILER_COMMIT_SHA 98c0808) + set(PLUGIN_COMPILER_WINDOWS_2022_CHECKSUM d433c835d87ecf7bd16ae883adba788b51da4dfa025d08a49f0672c0809f3d9f) + set(PLUGIN_COMPILER_UBUNTU_22_04_CHECKSUM c6ecb0a212aa796e21409c9da4a33677d5bf9d6aa8ed4f67a9162317c3673c1e) + set(PLUGIN_COMPILER_UBUNTU_24_04_CHECKSUM 4d2ec0ab1bb34f90f132803b2b45ba8773f3c579c2e290ea259de27ed85247c3) set(PLUGIN_COMPILER_VERSION_UNDERSCORE "${PLUGIN_COMPILER_VERSION_MAJOR}_${PLUGIN_COMPILER_VERSION_MINOR}_${PLUGIN_COMPILER_VERSION_PATCH}") message(STATUS "The prebuilt compiler version is ${PLUGIN_COMPILER_VERSION_MAJOR}.${PLUGIN_COMPILER_VERSION_MINOR}.${PLUGIN_COMPILER_VERSION_PATCH}.${PLUGIN_COMPILER_COMMIT_SHA}") From 92ceda65d0a95497d174c9280c1ae821f6b78329 Mon Sep 17 00:00:00 2001 From: Wang Wangwang Date: Fri, 24 Apr 2026 08:48:40 +0800 Subject: [PATCH 048/545] Keep xattention_threshold in FP32 to avoid boundary instability cause (#34732) ### Details: This PR fixes an xattention accuracy regression caused by unintended FP32-to-FP16 conversion of the xattention_threshold input in the GPU transformation pipeline. xattention_threshold is not a regular activation tensor. It is a control parameter used by xattention block selection logic. For example. when this threshold is quantized from 0.8f to FP16, the runtime value becomes approximately 0.799805, which is enough to change sparse block mask decisions near selection boundaries. As a result, xattention may select a different set of KV blocks and produce incorrect long-context reasoning results. To fix this, this PR: Add `KeepXAttentionThresholdPrecision` in transformations_pipeline.cpp keeps `xattention_threshold `precision-sensitive so it remains in FP32 before `ConvertPrecision ` preserves the dtype-aware implementation of `get_xattn_thresh()`, so the kernel reads the actual runtime dtype correctly ### Tickets: - *CVS-179963* --------- Co-authored-by: Chen Peter Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Eddy Kim --- .../src/graph/impls/cm/include/cm_pa_xe1.hpp | 24 ++-- .../src/graph/impls/cm/include/cm_pa_xe2.hpp | 36 +++--- .../graph/impls/cm/paged_attention_gen.cpp | 23 +++- .../keep_xattention_threshold_precision.cpp | 35 +++++ .../keep_xattention_threshold_precision.hpp | 20 +++ .../src/plugin/transformations_pipeline.cpp | 3 + ...ep_xattention_threshold_precision_test.cpp | 121 ++++++++++++++++++ 7 files changed, 229 insertions(+), 33 deletions(-) create mode 100644 src/plugins/intel_gpu/src/plugin/transformations/keep_xattention_threshold_precision.cpp create mode 100644 src/plugins/intel_gpu/src/plugin/transformations/keep_xattention_threshold_precision.hpp create mode 100644 src/plugins/intel_gpu/tests/unit/transformations/keep_xattention_threshold_precision_test.cpp diff --git a/src/plugins/intel_gpu/src/graph/impls/cm/include/cm_pa_xe1.hpp b/src/plugins/intel_gpu/src/graph/impls/cm/include/cm_pa_xe1.hpp index 715da56c06abc2..84a9c3c39fc35a 100644 --- a/src/plugins/intel_gpu/src/graph/impls/cm/include/cm_pa_xe1.hpp +++ b/src/plugins/intel_gpu/src/graph/impls/cm/include/cm_pa_xe1.hpp @@ -1,7 +1,7 @@ // Copyright (C) 2018-2026 Intel Corporation // SPDX-License-Identifier: Apache-2.0 // - +// #define CMPA_DEBUG_ALL_MASKED // Enable verbose cm_printf logging (opt-in; do not enable by default) #ifndef CM_HAS_LSC_UNTYPED_2D #if KV_CACHE_COMPRESSION @@ -286,6 +286,9 @@ void pa_lsc_u8( apply_causal_mask<1>(St); } else if (causal_left < 0) { St = -3.4e38f; + } else if (causal_left < kv_step) { + for (int p = causal_left; p < kv_step; p++) + St[p] = -3.4e38f; } } else { if (causal_left == 0) { @@ -304,11 +307,10 @@ void pa_lsc_u8( } } causal_left -= kv_step; - } else { - int kv_tokens = kv_stop - kv_pos; - // LSC ensures no overflow-access, but mask off k-tails attn-score is still required - for(int p = kv_tokens; p < kv_step; p++) St[p] = -3.4e38f; } + int kv_tokens = kv_stop - kv_pos; + // LSC ensures no overflow-access, but mask off k-tails attn-score is still required + for(int p = kv_tokens; p < kv_step; p++) St[p] = -3.4e38f; auto max_comp = online_softmax_update(St, cur_max, cur_sum); matrix P; @@ -528,6 +530,10 @@ void pa_kernel_lsc_prefetch_f16( apply_causal_mask<1>(St); } else if (causal_left < 0) { St = -3.4e38f; + } else if (causal_left < kv_step) { + for (int p = causal_left; p < kv_step; p++) + St[p] = -3.4e38f; + } } } else { if (causal_left == 0) { @@ -546,12 +552,10 @@ void pa_kernel_lsc_prefetch_f16( } } causal_left -= kv_step; - } else { - int kv_tokens = kv_stop - kv_pos; - // LSC ensures no overflow-access, but mask off k-tails attn-score is still required - for(int p = kv_tokens; p < kv_step; p++) St[p] = -3.4e38f; } - + int kv_tokens = kv_stop - kv_pos; + // LSC ensures no overflow-access, but mask off k-tails attn-score is still required + for(int p = kv_tokens; p < kv_step; p++) St[p] = -3.4e38f; // show(St); auto max_comp = online_softmax_update(St, cur_max, cur_sum); diff --git a/src/plugins/intel_gpu/src/graph/impls/cm/include/cm_pa_xe2.hpp b/src/plugins/intel_gpu/src/graph/impls/cm/include/cm_pa_xe2.hpp index 7d829e4f0be2b4..33a129d9c2f73f 100644 --- a/src/plugins/intel_gpu/src/graph/impls/cm/include/cm_pa_xe2.hpp +++ b/src/plugins/intel_gpu/src/graph/impls/cm/include/cm_pa_xe2.hpp @@ -338,13 +338,13 @@ void pa_lsc_u8( apply_causal_mask<1>(St); } else if (causal_left < 0) { St = -3.4e38f; + } else if (causal_left < kv_step) { + for (int p = causal_left; p < kv_step; p++) St[p] = -3.4e38f; } causal_left -= kv_step; - } else { - int kv_tokens = kv_stop - kv_pos; - for (int p = kv_tokens; p < kv_step; p++) St[p] = -3.4e38f; } - + int kv_tokens = kv_stop - kv_pos; + for (int p = kv_tokens; p < kv_step; p++) St[p] = -3.4e38f; auto max_comp = online_softmax_update(St, cur_max, cur_sum); matrix P; @@ -529,13 +529,13 @@ void pa_lsc_u8( apply_causal_mask<1>(St); } else if (causal_left < 0) { St = -3.4e38f; + } else if (causal_left < kv_step) { + for (int p = causal_left; p < kv_step; p++) St[p] = -3.4e38f; } causal_left -= kv_step; - } else { - int kv_tokens = kv_stop - kv_pos; - for (int p = kv_tokens; p < kv_step; p++) St[p] = -3.4e38f; } - + int kv_tokens = kv_stop - kv_pos; + for (int p = kv_tokens; p < kv_step; p++) St[p] = -3.4e38f; auto max_comp = online_softmax_update(St, cur_max, cur_sum); matrix P; @@ -746,14 +746,14 @@ void pa_kernel_lsc_prefetch_f16( apply_causal_mask<1>(St); } else if (causal_left < 0) { St = -3.4e38f; + } else if (causal_left < kv_step) { + for (int p = causal_left; p < kv_step; p++) St[p] = -3.4e38f; } causal_left -= kv_step; - } else { - int kv_tokens = kv_stop - kv_pos; - // LSC ensures no overflow-access, but mask off k-tails attn-score is still required - for(int p = kv_tokens; p < kv_step; p++) St[p] = -3.4e38f; } - + int kv_tokens = kv_stop - kv_pos; + // LSC ensures no overflow-access, but mask off k-tails attn-score is still required + for(int p = kv_tokens; p < kv_step; p++) St[p] = -3.4e38f; //show(St); auto max_comp = online_softmax_update(St, cur_max, cur_sum); @@ -894,14 +894,14 @@ void pa_kernel_lsc_prefetch_f16( apply_causal_mask<1>(St); } else if (causal_left < 0) { St = -3.4e38f; + } else if (causal_left < kv_step) { + for (int p = causal_left; p < kv_step; p++) St[p] = -3.4e38f; } causal_left -= kv_step; - } else { - int kv_tokens = kv_stop - kv_pos; - // LSC ensures no overflow-access, but mask off k-tails attn-score is still required - for(int p = kv_tokens; p < kv_step; p++) St[p] = -3.4e38f; } - + int kv_tokens = kv_stop - kv_pos; + // LSC ensures no overflow-access, but mask off k-tails attn-score is still required + for(int p = kv_tokens; p < kv_step; p++) St[p] = -3.4e38f; //show(St); auto max_comp = online_softmax_update(St, cur_max, cur_sum); diff --git a/src/plugins/intel_gpu/src/graph/impls/cm/paged_attention_gen.cpp b/src/plugins/intel_gpu/src/graph/impls/cm/paged_attention_gen.cpp index 14ba0c04bcd980..8d8a3d0ae5bd0e 100644 --- a/src/plugins/intel_gpu/src/graph/impls/cm/paged_attention_gen.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/cm/paged_attention_gen.cpp @@ -84,13 +84,26 @@ float get_xattn_thresh(const kernel_impl_params& params, const size_t seq_idx) { OPENVINO_THROW("XAttention threshold input is required at index ", static_cast(PagedAttentionInputIdx::XATTENTION_THRESHOLD)); } - mem_lock lock(it->second, *params.strm); // converted - if (seq_idx >= lock.size()) { - OPENVINO_THROW("XAttention threshold input index out of range: seq_idx=", seq_idx, ", input_size=", lock.size()); + const auto threshold_mem = it->second; + const auto dt = threshold_mem->get_layout().data_type; + + if (dt == data_types::f16) { + mem_lock lock(threshold_mem, *params.strm); + if (seq_idx >= lock.size()) { + OPENVINO_THROW("XAttention threshold input index out of range: seq_idx=", seq_idx, ", input_size=", lock.size()); + } + return static_cast(lock[seq_idx]); + } + + if (dt == data_types::f32) { + mem_lock lock(threshold_mem, *params.strm); + if (seq_idx >= lock.size()) { + OPENVINO_THROW("XAttention threshold input index out of range: seq_idx=", seq_idx, ", input_size=", lock.size()); + } + return lock[seq_idx]; } - const auto thresh = static_cast(lock[seq_idx]); - return thresh; + OPENVINO_THROW("Unsupported xattention_threshold data type"); } // Bypass xattn stages in the following conditions - diff --git a/src/plugins/intel_gpu/src/plugin/transformations/keep_xattention_threshold_precision.cpp b/src/plugins/intel_gpu/src/plugin/transformations/keep_xattention_threshold_precision.cpp new file mode 100644 index 00000000000000..4516f04a8b75c8 --- /dev/null +++ b/src/plugins/intel_gpu/src/plugin/transformations/keep_xattention_threshold_precision.cpp @@ -0,0 +1,35 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "keep_xattention_threshold_precision.hpp" + +#include "intel_gpu/primitives/paged_attention.hpp" +#include "openvino/op/paged_attention.hpp" +#include "openvino/op/util/precision_sensitive_attribute.hpp" +#include "openvino/pass/pattern/op/wrap_type.hpp" + +namespace ov::intel_gpu { + +KeepXAttentionThresholdPrecision::KeepXAttentionThresholdPrecision() { + auto pa_m = ov::pass::pattern::wrap_type(); + + ov::matcher_pass_callback callback = [=](ov::pass::pattern::Matcher& m) { + auto pa = ov::as_type_ptr(m.get_match_root()); + if (!pa) + return false; + + if (transformation_callback(pa)) + return false; + + constexpr size_t thr_idx = cldnn::paged_attention::PagedAttentionInputIdx::XATTENTION_THRESHOLD; + ov::mark_as_precision_sensitive(pa->input(thr_idx)); + return true; + }; + + auto matcher = std::make_shared(pa_m, "KeepXAttentionThresholdPrecision"); + register_matcher(matcher, callback); +} + +} // namespace ov::intel_gpu + diff --git a/src/plugins/intel_gpu/src/plugin/transformations/keep_xattention_threshold_precision.hpp b/src/plugins/intel_gpu/src/plugin/transformations/keep_xattention_threshold_precision.hpp new file mode 100644 index 00000000000000..053c58bb53a53c --- /dev/null +++ b/src/plugins/intel_gpu/src/plugin/transformations/keep_xattention_threshold_precision.hpp @@ -0,0 +1,20 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include "openvino/pass/matcher_pass.hpp" + +namespace ov::intel_gpu { + +// Keeps xattention threshold input in fp32 by marking it as precision-sensitive +// before ConvertPrecision pass. This avoids down-conversion to fp16 which can +// make thresholding unstable at certain boundary values. +class KeepXAttentionThresholdPrecision : public ov::pass::MatcherPass { +public: + OPENVINO_MATCHER_PASS_RTTI("ov::intel_gpu::KeepXAttentionThresholdPrecision"); + KeepXAttentionThresholdPrecision(); +}; + +} // namespace ov::intel_gpu diff --git a/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp b/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp index a661bc13a17319..9d0e6c293b93b9 100644 --- a/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp +++ b/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp @@ -91,6 +91,7 @@ #include "plugin/transformations/increase_position_ids_precision.hpp" #include "plugin/transformations/indirect_kv_cache.hpp" #include "plugin/transformations/keep_moe_3gemm_const_precision.hpp" +#include "plugin/transformations/keep_xattention_threshold_precision.hpp" #include "plugin/transformations/kv_cache_compression.hpp" #include "plugin/transformations/kv_cache_fusion.hpp" #include "plugin/transformations/lora_horizontal_fusion.hpp" @@ -590,6 +591,8 @@ void TransformationsPipeline::apply(std::shared_ptr func) { manager.register_pass( ov::element::TypeVector{ov::element::i32, ov::element::u32, ov::element::u16}, add_precision_sensitive_convert); + // Keep xattention threshold in fp32 to avoid boundary issues caused by fp16 quantization. + manager.register_pass(); manager.register_pass(fp_convert_precision_map, empty_fuse_map, diff --git a/src/plugins/intel_gpu/tests/unit/transformations/keep_xattention_threshold_precision_test.cpp b/src/plugins/intel_gpu/tests/unit/transformations/keep_xattention_threshold_precision_test.cpp new file mode 100644 index 00000000000000..c49d53c9aa137f --- /dev/null +++ b/src/plugins/intel_gpu/tests/unit/transformations/keep_xattention_threshold_precision_test.cpp @@ -0,0 +1,121 @@ +// Copyright (C) 2018-2026 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "plugin/transformations/keep_xattention_threshold_precision.hpp" + +#include + +#include "common_test_utils/ov_test_utils.hpp" +#include "intel_gpu/primitives/paged_attention.hpp" +#include "openvino/core/model.hpp" +#include "openvino/op/paged_attention.hpp" +#include "openvino/op/parameter.hpp" +#include "openvino/op/result.hpp" +#include "openvino/op/util/precision_sensitive_attribute.hpp" + +using namespace testing; +using namespace ov::intel_gpu; + +namespace { + +using PAExt = ov::op::PagedAttentionExtension; + +std::shared_ptr make_pa_model_with_threshold_et(const ov::element::Type& thr_et) { + // Build a minimal model containing PagedAttentionExtension with valid 28 inputs. + // We keep shapes mostly dynamic but MUST satisfy rank/type checks in + // ov::op::PagedAttentionExtension::validate_and_infer_types(). + + const size_t thr_idx = cldnn::paged_attention::PagedAttentionInputIdx::XATTENTION_THRESHOLD; + + ov::ParameterVector params(28); + // 0..4: query/key/value/key_cache/value_cache + params[0] = std::make_shared(ov::element::f16, ov::PartialShape::dynamic(2)); + params[1] = std::make_shared(ov::element::f16, ov::PartialShape::dynamic(2)); + params[2] = std::make_shared(ov::element::f16, ov::PartialShape::dynamic(2)); + params[3] = std::make_shared(ov::element::f16, ov::PartialShape::dynamic()); + params[4] = std::make_shared(ov::element::f16, ov::PartialShape::dynamic()); + + // 5..8: i32 vectors + for (size_t i = 5; i <= 8; i++) + params[i] = std::make_shared(ov::element::i32, ov::PartialShape::dynamic(1)); + + // 9: scale scalar real + params[9] = std::make_shared(ov::element::f32, ov::PartialShape::dynamic(0)); + // 10: sliding_window scalar i32 + params[10] = std::make_shared(ov::element::i32, ov::PartialShape::dynamic(0)); + // 11: alibi_slopes rank1 real + params[11] = std::make_shared(ov::element::f32, ov::PartialShape::dynamic(1)); + // 12: max_context_len scalar i32 + params[12] = std::make_shared(ov::element::i32, ov::PartialShape::dynamic(0)); + // 13: score_aggregation_window rank0/1 i32 + params[13] = std::make_shared(ov::element::i32, ov::PartialShape::dynamic(0)); + + // 14: rotated_block_indices rank1 i32 + params[14] = std::make_shared(ov::element::i32, ov::PartialShape::dynamic(1)); + // 15: rotation_deltas rank1/2 i32 + params[15] = std::make_shared(ov::element::i32, ov::PartialShape::dynamic(1)); + // 16: rotation_trig_lut rank1/2 f16/f32 + params[16] = std::make_shared(ov::element::f16, ov::PartialShape::dynamic(1)); + + // 17: xattention_threshold rank1 f16/f32 (or other type in negative test) + params[thr_idx] = std::make_shared(thr_et, ov::PartialShape::dynamic(1)); + + // 18..19: scalars i32 + params[18] = std::make_shared(ov::element::i32, ov::PartialShape::dynamic(0)); + params[19] = std::make_shared(ov::element::i32, ov::PartialShape::dynamic(0)); + + // 20: sinks rank1/4 any type (use f16) + params[20] = std::make_shared(ov::element::f16, ov::PartialShape::dynamic(1)); + + // 21: scalar i32 + params[21] = std::make_shared(ov::element::i32, ov::PartialShape::dynamic(0)); + // 22..25: vectors/matrices i32 + params[22] = std::make_shared(ov::element::i32, ov::PartialShape::dynamic(1)); + params[23] = std::make_shared(ov::element::i32, ov::PartialShape::dynamic(1)); + params[24] = std::make_shared(ov::element::i32, ov::PartialShape::dynamic(1)); + params[25] = std::make_shared(ov::element::i32, ov::PartialShape::dynamic(1)); + // 26..27: qq_bias inputs + params[26] = std::make_shared(ov::element::u8, ov::PartialShape::dynamic(1)); + params[27] = std::make_shared(ov::element::i32, ov::PartialShape::dynamic(1)); + + ov::OutputVector inputs; + inputs.reserve(params.size()); + for (const auto& p : params) + inputs.emplace_back(p); + + auto pa = std::make_shared(inputs); + auto res = std::make_shared(pa); + return std::make_shared(ov::ResultVector{res}, params); +} + +std::shared_ptr get_pa_ext(const std::shared_ptr& model) { + return ov::as_type_ptr(model->get_results().at(0)->input_value(0).get_node_shared_ptr()); +} + +std::shared_ptr make_ref_pa_model_with_precision_sensitive_threshold(const ov::element::Type& thr_et) { + auto model = make_pa_model_with_threshold_et(thr_et); + const size_t thr_idx = cldnn::paged_attention::PagedAttentionInputIdx::XATTENTION_THRESHOLD; + auto pa = get_pa_ext(model); + OPENVINO_ASSERT(pa != nullptr); + ov::mark_as_precision_sensitive(pa->input(thr_idx)); + return model; +} + +} // namespace + +TEST_F(TransformationTestsF, KeepXAttentionThresholdPrecisionMarksF32Threshold) { + model = make_pa_model_with_threshold_et(ov::element::f32); + model_ref = make_ref_pa_model_with_precision_sensitive_threshold(ov::element::f32); + + manager.register_pass(); +} + +TEST_F(TransformationTestsF, KeepXAttentionThresholdPrecisionMarksF16Threshold) { + // PagedAttentionExtension validation allows only f16/f32 on xattention_threshold port, + // so only the valid real-type cases can be covered here. + model = make_pa_model_with_threshold_et(ov::element::f16); + model_ref = make_ref_pa_model_with_precision_sensitive_threshold(ov::element::f16); + + manager.register_pass(); +} From fc0cfe66e514a40002a4413d70835eeb2aa1f483 Mon Sep 17 00:00:00 2001 From: Jade Cho Date: Fri, 24 Apr 2026 14:31:40 +0900 Subject: [PATCH 049/545] [GPU] Fix PagedAttention intermediate buffer memory explosion (#35479) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description of the issue (symptom, root-cause, how it was resolved) - **Symptom**: On ARL-HX (iGPU) Windows, PagedAttention models consume 3–8 GB extra device memory for intermediate buffers (`exp_sums`, `max_logits`, `tmp_output`), each ~90–235 MB × 36 layers × 3 buffers, all individually allocated without pool reuse. - **Root cause**: The `paged_attention_node` constructor sets `can_share_internal_buffer(false)` as a blanket disable to prevent sharing of lockable buffers that use CPU `mem_lock` in `prepare_internal_buffers()`. However, this also blocks pool sharing for GPU-only (non-lockable) intermediate buffers that are safe to share. (Introduced in PR #33204) - **Resolution**: Removed the blanket `can_share_internal_buffer(false)` and instead made the pool-sharing decision per-buffer in `allocate_internal_buffer()`: lockable buffers (`m_lockable=true`) bypass the pool (preserving the original safety intent), while non-lockable buffers participate in `_non_padded_pool` sharing as designed. - DG2 is unaffected because it uses `sdpa_micro_generate` kernel which does not allocate buf5/6/7. #### The code and line that caused this issue (if it is not changed directly) - `intel_gpu/src/graph/paged_attention.cpp` L16: `can_share_internal_buffer(false);` - `intel_gpu/src/graph/impls/ocl_v2/sdpa/paged_attention_opt.cpp` `get_internal_buffer_descs()` — defines `m_lockable` per buffer descriptor #### Problematic graph - Any transformer model with 36+ PagedAttention layers where `pa_multi_tokens` kernel is selected (iGPU path, not `sdpa_micro_generate`) - Each PA layer allocates ~10 internal buffers; buf5 (`exp_sums`), buf6 (`max_logits`), buf7 (`tmp_output`) are non-lockable GPU-only buffers that should be shared across layers #### Checklist - [x] Is it a proper fix? (not a workaround) - [ ] Did you include test case for this fix, if necessary? - Unit test is not feasible: requires real GPU hardware, full PA topology compilation, and access to private `_intermediates_memory` / pool internals. No mock engine infrastructure exists. Verified correctness through static code analysis of the `_non_padded_pool` sharing path, `has_conflict()` / `basic_memory_dependencies` restriction sets, and `realloc_intermediates()` lifecycle. - [x] Did you review existing test that can be extended to cover this scenario? Which test did you review? - Reviewed `memory_pool` tests in `memory_test.cpp` — tests output buffer pool sharing but not internal buffer lockable/non-lockable distinction. - Reviewed `paged_attention_gpu_test.cpp` (~5300 lines) — tests PA correctness/accuracy but not memory pool behavior. Existing PA tests serve as regression coverage for this change. ### Tickets: - *185192* --- .../impls/ocl_v2/sdpa/paged_attention_opt.cpp | 15 ++++++++------- .../intel_gpu/src/graph/include/primitive_inst.h | 16 ++++++++-------- .../intel_gpu/src/graph/include/program_node.h | 4 ---- .../intel_gpu/src/graph/paged_attention.cpp | 1 - .../intel_gpu/src/graph/primitive_inst.cpp | 11 +++++------ 5 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl_v2/sdpa/paged_attention_opt.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl_v2/sdpa/paged_attention_opt.cpp index b1eeaa47effc4d..b014c337a4db82 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl_v2/sdpa/paged_attention_opt.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl_v2/sdpa/paged_attention_opt.cpp @@ -1627,9 +1627,10 @@ class PagedAttentionOptImpl : public SDPAImplBase { const auto indexes_buf_size = static_cast(ceil_div(target_seq_len, target_seq_len_block_size)) * element_size; const bool lockable = true; - internal_buffers.emplace_back(indexes_buf_size, indexes_dt, lockable); // 0 - internal_buffers.emplace_back(indexes_buf_size, indexes_dt, lockable); // 1 - internal_buffers.emplace_back(indexes_buf_size, indexes_dt, lockable); // 2 + const bool not_shareable = false; + internal_buffers.emplace_back(indexes_buf_size, indexes_dt, lockable, not_shareable); // 0 + internal_buffers.emplace_back(indexes_buf_size, indexes_dt, lockable, not_shareable); // 1 + internal_buffers.emplace_back(indexes_buf_size, indexes_dt, lockable, not_shareable); // 2 const auto& input = params.input_layouts[0]; const int64_t total_tokens = input.get_partial_shape()[0].get_length(); @@ -1669,11 +1670,11 @@ class PagedAttentionOptImpl : public SDPAImplBase { // Softmax intermediate output internal_buffers.emplace_back(softmax_buf_elements_count, indexes_dt); // 3 // Precalculated accumulated sequence length offsets for each subsequence - internal_buffers.emplace_back(subsequences_number * element_size, indexes_dt, lockable); // 4 + internal_buffers.emplace_back(subsequences_number * element_size, indexes_dt, lockable, not_shareable); // 4 if (desc->has_score_aggregation) { // Cumulative window size sum buffer - internal_buffers.emplace_back((subsequences_number + 1) * element_size, indexes_dt, lockable); // 5 + internal_buffers.emplace_back((subsequences_number + 1) * element_size, indexes_dt, lockable, not_shareable); // 5 } if (stage == PagedAttentionStage::PREFILL) { @@ -1695,7 +1696,7 @@ class PagedAttentionOptImpl : public SDPAImplBase { const auto multi_tokens_mode = stage == PagedAttentionStage::MIXED; if (multi_tokens_mode && !can_use_micro_sdpa) { - internal_buffers.emplace_back(total_tokens, softmax_accumulator_type, lockable); // 9 + internal_buffers.emplace_back(total_tokens, softmax_accumulator_type, lockable, not_shareable); // 9 } #ifdef ENABLE_ONEDNN_FOR_GPU @@ -1703,7 +1704,7 @@ class PagedAttentionOptImpl : public SDPAImplBase { const auto wg_tile_q = 8; // This is set as the minimum size of query block for sharing between sdpa_micro_prefill and mixed. const auto target_seq_len = std::max(paged_attention_aligned_seq_len, static_cast(1)); const auto indexes_buf_size = ceil_div(target_seq_len, wg_tile_q) * 2; - internal_buffers.emplace_back(indexes_buf_size * 4, indexes_dt, lockable); + internal_buffers.emplace_back(indexes_buf_size * 4, indexes_dt, lockable, not_shareable); } #endif diff --git a/src/plugins/intel_gpu/src/graph/include/primitive_inst.h b/src/plugins/intel_gpu/src/graph/include/primitive_inst.h index ddf93f2d38a17a..d67f2fcecabadf 100644 --- a/src/plugins/intel_gpu/src/graph/include/primitive_inst.h +++ b/src/plugins/intel_gpu/src/graph/include/primitive_inst.h @@ -47,12 +47,14 @@ class PrimitiveInstTestHelper; struct ImplementationManager; struct BufferDescriptor { - explicit BufferDescriptor(const layout& l, bool lockable = false) : m_lockable(lockable), m_layout(l) {} - BufferDescriptor(const ov::PartialShape& shape, ov::element::Type type, bool lockable = false) - : BufferDescriptor(layout(shape, type, format::bfyx), lockable) {} - BufferDescriptor(size_t elements_count, ov::element::Type type, bool lockable = false) - : BufferDescriptor(layout({static_cast(elements_count)}, type, format::bfyx), lockable) {} + explicit BufferDescriptor(const layout& l, bool lockable = false, bool shareable = true) + : m_lockable(lockable), m_shareable(shareable), m_layout(l) {} + BufferDescriptor(const ov::PartialShape& shape, ov::element::Type type, bool lockable = false, bool shareable = true) + : BufferDescriptor(layout(shape, type, format::bfyx), lockable, shareable) {} + BufferDescriptor(size_t elements_count, ov::element::Type type, bool lockable = false, bool shareable = true) + : BufferDescriptor(layout({static_cast(elements_count)}, type, format::bfyx), lockable, shareable) {} bool m_lockable = false; + bool m_shareable = true; // Whether this buffer can be shared via memory pool across primitives layout m_layout; }; @@ -322,7 +324,6 @@ class primitive_inst { bool mem_allocated() const { return _mem_allocated; } bool is_dynamic() const { return _is_dynamic; } bool can_share_buffer() const { return _can_share_buffer; } - bool can_share_internal_buffer() const { return _can_share_internal_buffer; } bool is_constant() const { return _is_constant; } bool needs_completion_event() const { return _needs_completion_event; } bool has_unfused_subgraph() const { return (_unfused_subgraph != nullptr); } @@ -441,7 +442,6 @@ class primitive_inst { size_t _fused_mem_offset = 0; bool _can_be_optimized = false; bool _can_share_buffer = true; - bool _can_share_internal_buffer = true; bool _is_constant = false; bool _needs_completion_event = false; @@ -451,7 +451,7 @@ class primitive_inst { std::vector allocate_outputs(kernel_impl_params* updated_params = nullptr, bool reset_mem = true, bool runtime_alloc = false); - memory::ptr allocate_internal_buffer(const layout& layout, size_t idx, bool reset = true, bool lockable = false); + memory::ptr allocate_internal_buffer(const layout& layout, size_t idx, bool reset = true, bool lockable = false, bool shareable = true); void allocate_shape_info_memory(); static std::vector build_exec_deps( std::vector> const& mem_deps); diff --git a/src/plugins/intel_gpu/src/graph/include/program_node.h b/src/plugins/intel_gpu/src/graph/include/program_node.h index 556f516790b005..89505b7ddcbf83 100644 --- a/src/plugins/intel_gpu/src/graph/include/program_node.h +++ b/src/plugins/intel_gpu/src/graph/include/program_node.h @@ -321,10 +321,6 @@ struct program_node { bool can_share_buffer() const { return share_buffer; } void can_share_buffer(bool share) { share_buffer = share; } - // check/set if the node's internal buffer can be shared - bool can_share_internal_buffer() const { return share_internal_buffer; } - void can_share_internal_buffer(bool share) { share_internal_buffer = share; } - // Sets padding support for all axis void support_padding_all(bool support); // Sets padding support for specified axis diff --git a/src/plugins/intel_gpu/src/graph/paged_attention.cpp b/src/plugins/intel_gpu/src/graph/paged_attention.cpp index 6d80388bbb6e8a..ecb6987029983f 100644 --- a/src/plugins/intel_gpu/src/graph/paged_attention.cpp +++ b/src/plugins/intel_gpu/src/graph/paged_attention.cpp @@ -13,7 +13,6 @@ GPU_DEFINE_PRIMITIVE_TYPE_ID(paged_attention) paged_attention_node::typed_program_node(const std::shared_ptr prim, program& prog) : parent(prim, prog) { - can_share_internal_buffer(false); } layout paged_attention_inst::calc_output_layout(const paged_attention_node& /*node*/, kernel_impl_params const& impl_param) { diff --git a/src/plugins/intel_gpu/src/graph/primitive_inst.cpp b/src/plugins/intel_gpu/src/graph/primitive_inst.cpp index 4ac03d728e3f3c..2d8dbc10a3123f 100644 --- a/src/plugins/intel_gpu/src/graph/primitive_inst.cpp +++ b/src/plugins/intel_gpu/src/graph/primitive_inst.cpp @@ -691,11 +691,11 @@ void primitive_inst::realloc_intermediates() { // we'll need additional handle for that purpose like need_reset_output_memory const bool need_reset = false; if (i < _intermediates_memory.size()) { - _intermediates_memory[i] = allocate_internal_buffer(buffer_descs[i].m_layout, i, need_reset, need_lockable); + _intermediates_memory[i] = allocate_internal_buffer(buffer_descs[i].m_layout, i, need_reset, need_lockable, buffer_descs[i].m_shareable); _max_intermediates_memory_sizes[i] = _intermediates_memory[i]->size(); } else { // i-th layout has not been allocated yet - _intermediates_memory.push_back(allocate_internal_buffer(buffer_descs[i].m_layout, i, need_reset, need_lockable)); + _intermediates_memory.push_back(allocate_internal_buffer(buffer_descs[i].m_layout, i, need_reset, need_lockable, buffer_descs[i].m_shareable)); _max_intermediates_memory_sizes.push_back(_intermediates_memory[i]->size()); } GPU_DEBUG_CODE(memalloc_info += @@ -2356,7 +2356,6 @@ primitive_inst::primitive_inst(network & network, program_node const& node, bool , _fused_mem_offset((_fused_mem_count > 0 && node.get_first_fused_dep_idx() > 0) ? static_cast(node.get_first_fused_dep_idx()) : 0) , _can_be_optimized(node.can_be_optimized()) , _can_share_buffer(node.can_share_buffer()) - , _can_share_internal_buffer(node.can_share_internal_buffer()) , _is_constant(node.is_constant()) , _needs_completion_event(is_any_user_cpu(node.get_users()) || node.is_output()) { // When dynamic shape node has huge upper boundary which causes bigger mem size than system max allocable mem size, do not allocate in build time. @@ -2428,7 +2427,7 @@ primitive_inst::primitive_inst(network & network, program_node const& node, bool OPENVINO_ASSERT(_max_output_layout_count.size() == get_node().get_output_layouts().size()); } -memory::ptr primitive_inst::allocate_internal_buffer(const layout& layout, size_t idx, bool reset, bool lockable) { +memory::ptr primitive_inst::allocate_internal_buffer(const layout& layout, size_t idx, bool reset, bool lockable, bool shareable) { if (_impl == nullptr || _outputs.empty() || _outputs[0] == nullptr) return nullptr; @@ -2492,7 +2491,7 @@ memory::ptr primitive_inst::allocate_internal_buffer(const layout& layout, size_ *_node, layout, alloc_type, - can_share_internal_buffer(), + shareable, _runtime_memory_dependencies, reset, _intermediates_memory.size() > idx ? _intermediates_memory[idx].get() : nullptr); @@ -2513,7 +2512,7 @@ void primitive_inst::allocate_internal_buffers(bool reset) { for (size_t i = 0; i < buffer_descs.size(); ++i) { if (buffer_descs[i].m_layout.get_linear_size() == 0) continue; - intermediates_memory.push_back(allocate_internal_buffer(buffer_descs[i].m_layout, i, reset)); + intermediates_memory.push_back(allocate_internal_buffer(buffer_descs[i].m_layout, i, reset, buffer_descs[i].m_lockable, buffer_descs[i].m_shareable)); _max_intermediates_memory_sizes.push_back(intermediates_memory[i]->size()); } _intermediates_memory = intermediates_memory; From 25cfd3468a55e53922ee6171c3ec0bbf9f453db8 Mon Sep 17 00:00:00 2001 From: Dan Liu Date: Fri, 24 Apr 2026 14:13:08 +0800 Subject: [PATCH 050/545] [NPU] Move vm_runtime_api to utils/vm (#35227) ### Details: - Move vm_runtime_api to utils/vm folder as OBJECT library to reduce build time and not install this vm utils library to ov package. - Move `zero utils`, `vm`(npu_vm_runtime_api) and `level-zero-ext`(interface lib) out of ENABLE_NPU_PLUGIN_ENGINE control. ### Tickets: - *CVS-183328* ### AI Assistance: - *AI assistance used: no / yes* - *If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).* --- .../src/compiler_adapter/CMakeLists.txt | 2 +- .../include/dynamic_graph.hpp | 2 +- .../utils/vm}/npu_vm_runtime_api.hpp | 0 .../intel_npu/src/utils/src/CMakeLists.txt | 1 + .../src/utils/src/vcl/CMakeLists.txt | 2 -- .../intel_npu/src/utils/src/vm/CMakeLists.txt | 30 +++++++++++++++++++ .../src/vm}/npu_vm_runtime_api.cpp | 2 +- 7 files changed, 34 insertions(+), 5 deletions(-) rename src/plugins/intel_npu/src/{compiler_adapter/include => utils/include/intel_npu/utils/vm}/npu_vm_runtime_api.hpp (100%) create mode 100644 src/plugins/intel_npu/src/utils/src/vm/CMakeLists.txt rename src/plugins/intel_npu/src/{compiler_adapter/src => utils/src/vm}/npu_vm_runtime_api.cpp (97%) diff --git a/src/plugins/intel_npu/src/compiler_adapter/CMakeLists.txt b/src/plugins/intel_npu/src/compiler_adapter/CMakeLists.txt index 3aa5b01104f52b..08161856f57573 100644 --- a/src/plugins/intel_npu/src/compiler_adapter/CMakeLists.txt +++ b/src/plugins/intel_npu/src/compiler_adapter/CMakeLists.txt @@ -8,7 +8,7 @@ file(GLOB_RECURSE SOURCES *.cpp *.hpp *.h) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES}) -add_library(${TARGET_NAME} STATIC ${SOURCES} $) +add_library(${TARGET_NAME} STATIC ${SOURCES} $ $) ov_build_target_faster(${TARGET_NAME} PCH_HEADER "src/precomp.hpp" diff --git a/src/plugins/intel_npu/src/compiler_adapter/include/dynamic_graph.hpp b/src/plugins/intel_npu/src/compiler_adapter/include/dynamic_graph.hpp index 896e6609e82962..2c3c6d7a3f02cc 100644 --- a/src/plugins/intel_npu/src/compiler_adapter/include/dynamic_graph.hpp +++ b/src/plugins/intel_npu/src/compiler_adapter/include/dynamic_graph.hpp @@ -10,8 +10,8 @@ #include "intel_npu/common/idynamic_graph.hpp" #include "intel_npu/common/network_metadata.hpp" +#include "intel_npu/utils/vm/npu_vm_runtime_api.hpp" #include "intel_npu/utils/zero/zero_init.hpp" -#include "npu_vm_runtime_api.hpp" #include "openvino/runtime/so_ptr.hpp" namespace intel_npu { diff --git a/src/plugins/intel_npu/src/compiler_adapter/include/npu_vm_runtime_api.hpp b/src/plugins/intel_npu/src/utils/include/intel_npu/utils/vm/npu_vm_runtime_api.hpp similarity index 100% rename from src/plugins/intel_npu/src/compiler_adapter/include/npu_vm_runtime_api.hpp rename to src/plugins/intel_npu/src/utils/include/intel_npu/utils/vm/npu_vm_runtime_api.hpp diff --git a/src/plugins/intel_npu/src/utils/src/CMakeLists.txt b/src/plugins/intel_npu/src/utils/src/CMakeLists.txt index ec6493205c168d..434523deda87e6 100644 --- a/src/plugins/intel_npu/src/utils/src/CMakeLists.txt +++ b/src/plugins/intel_npu/src/utils/src/CMakeLists.txt @@ -7,4 +7,5 @@ add_subdirectory(logger) if(ENABLE_NPU_PLUGIN_ENGINE) add_subdirectory(vcl) add_subdirectory(zero) + add_subdirectory(vm) endif() diff --git a/src/plugins/intel_npu/src/utils/src/vcl/CMakeLists.txt b/src/plugins/intel_npu/src/utils/src/vcl/CMakeLists.txt index ee78f0c0597d18..7d712cae96922a 100644 --- a/src/plugins/intel_npu/src/utils/src/vcl/CMakeLists.txt +++ b/src/plugins/intel_npu/src/utils/src/vcl/CMakeLists.txt @@ -12,8 +12,6 @@ set_target_properties( ${TARGET_NAME} PROPERTIES INTERPROCEDURAL_OPTIMIZATION_RELEASE ${ENABLE_LTO}) ov_add_clang_format_target(${TARGET_NAME}_clang FOR_TARGETS ${TARGET_NAME}) -add_library(openvino::npu_vcl_utils ALIAS ${TARGET_NAME}) - target_include_directories(${TARGET_NAME} PUBLIC $ diff --git a/src/plugins/intel_npu/src/utils/src/vm/CMakeLists.txt b/src/plugins/intel_npu/src/utils/src/vm/CMakeLists.txt new file mode 100644 index 00000000000000..6c077c0b1853b6 --- /dev/null +++ b/src/plugins/intel_npu/src/utils/src/vm/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright (C) 2018-2026 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +set(TARGET_NAME openvino_npu_vm_utils) + +file(GLOB_RECURSE SOURCES *.cpp) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES}) + +add_library(${TARGET_NAME} OBJECT ${SOURCES}) + +set_target_properties(${TARGET_NAME} PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE ${ENABLE_LTO} + EXPORT_NAME npu_vm_utils) + +ov_add_clang_format_target(${TARGET_NAME}_clang FOR_TARGETS ${TARGET_NAME}) + +target_include_directories(${TARGET_NAME} + PUBLIC + $ + $ + $ + $ + $ + $ +) + +if(NOT BUILD_SHARED_LIBS) + target_compile_definitions(${TARGET_NAME} PRIVATE OPENVINO_STATIC_LIBRARY) +endif() diff --git a/src/plugins/intel_npu/src/compiler_adapter/src/npu_vm_runtime_api.cpp b/src/plugins/intel_npu/src/utils/src/vm/npu_vm_runtime_api.cpp similarity index 97% rename from src/plugins/intel_npu/src/compiler_adapter/src/npu_vm_runtime_api.cpp rename to src/plugins/intel_npu/src/utils/src/vm/npu_vm_runtime_api.cpp index 4ae9f6dccff0f2..de616c8f04d36f 100644 --- a/src/plugins/intel_npu/src/compiler_adapter/src/npu_vm_runtime_api.cpp +++ b/src/plugins/intel_npu/src/utils/src/vm/npu_vm_runtime_api.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // -#include "npu_vm_runtime_api.hpp" +#include "intel_npu/utils/vm/npu_vm_runtime_api.hpp" #include "openvino/util/file_util.hpp" #include "openvino/util/shared_object.hpp" From 257665c9e23b9bfd62f358ce5042c3a55bed1193 Mon Sep 17 00:00:00 2001 From: Vladislav Golubev Date: Fri, 24 Apr 2026 09:17:34 +0200 Subject: [PATCH 051/545] [CPU][Debug Caps] Enable perf counters collection for exec graph (#35429) ### Details: Currently, `OV_CPU_EXEC_GRAPH_PATH` debug env variable collects exec graphs without performance numbers. In this PR, the performance statistic collection is enabled for this debug scenario. Also, exec graph serialization is moved to the Graph destructor ### Tickets: - *N\A* ### AI Assistance: - *AI assistance used: no* --- .../exec_graph_serialization.md | 21 +++++++++++++- src/plugins/intel_cpu/src/config.cpp | 5 ++-- src/plugins/intel_cpu/src/graph.cpp | 1 + src/plugins/intel_cpu/src/graph_dumper.cpp | 29 +++++++++++++++---- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/plugins/intel_cpu/docs/debug_capabilities/exec_graph_serialization.md b/src/plugins/intel_cpu/docs/debug_capabilities/exec_graph_serialization.md index 1139bb0e937004..e0490054fd9148 100644 --- a/src/plugins/intel_cpu/docs/debug_capabilities/exec_graph_serialization.md +++ b/src/plugins/intel_cpu/docs/debug_capabilities/exec_graph_serialization.md @@ -2,8 +2,9 @@ Execution graph serialization is disabled by default and controlled by environment variable **OV_CPU_EXEC_GRAPH_PATH**: ```sh - OV_CPU_EXEC_GRAPH_PATH=