Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/core/dev_api/openvino/xml_util/constant_writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include <iostream>
#include <map>
#include <string_view>
#include <vector>

#include "openvino/core/attribute_visitor.hpp"
#include "openvino/core/type/element_type.hpp"
Expand All @@ -29,6 +31,8 @@ class OPENVINO_API ConstantWriter {
ov::element::Type src_type = ov::element::dynamic,
bool ptr_is_temporary = false);

virtual FilePosition write(const std::vector<std::string_view>& chunks, size_t& new_size);

uint64_t get_data_hash() const {
return m_data_hash;
}
Expand All @@ -40,6 +44,7 @@ class OPENVINO_API ConstantWriter {
size_t& compressed_size);

ConstWritePositions m_hash_to_file_positions;
std::map<HashValue, FilePosition> m_string_hash_to_file_positions;
std::reference_wrapper<std::ostream> m_binary_output;
bool m_enable_compression;
FilePosition m_blob_offset; // blob offset inside output stream
Expand Down
29 changes: 29 additions & 0 deletions src/core/src/xml_util/constant_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,35 @@ ConstantWriter::FilePosition ConstantWriter::write(const char* ptr,
return offset;
}

ConstantWriter::FilePosition ConstantWriter::write(const std::vector<std::string_view>& chunks, size_t& new_size) {
new_size = 0;
for (const auto& sv : chunks)
new_size += sv.size();

const FilePosition write_pos = m_binary_output.get().tellp();
const FilePosition offset = write_pos - m_blob_offset;

if (m_enable_compression) {
HashValue hash = 0;
for (const auto& sv : chunks) {
hash = util::u64_hash_combine(hash, ov::runtime::compute_hash(sv.data(), sv.size()));
}

const auto [it, inserted] = m_string_hash_to_file_positions.emplace(hash, offset);
if (!inserted) {
return it->second;
}

m_data_hash = util::u64_hash_combine(m_data_hash, hash);
} else {
m_data_hash = util::u64_hash_combine(m_data_hash, new_size);
}

for (const auto& sv : chunks)
m_binary_output.get().write(sv.data(), sv.size());
return offset;
}

std::unique_ptr<char[]> ConstantWriter::compress_data_to_fp16(const char* ptr,
size_t size,
const element::Type& src_type,
Expand Down
31 changes: 10 additions & 21 deletions src/core/src/xml_util/xml_serialize_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <functional>
#include <pugixml.hpp>
#include <string_view>

#include "openvino/core/descriptor_tensor.hpp"
#include "openvino/core/except.hpp"
Expand Down Expand Up @@ -707,9 +708,7 @@ void XmlSerializer::on_adapter(const std::string& name, ov::ValueAccessor<void>&
if (name == "value" && translate_type_name(m_node_type_name) == "Const") {
auto a1 = ov::as_type<ov::AttributeAdapter<std::shared_ptr<ov::StringAlignedBuffer>>>(&adapter);
auto a2 = ov::as_type<ov::AttributeAdapter<std::shared_ptr<ov::SharedStringAlignedBuffer>>>(&adapter);
size_t new_size = 0;
size_t inter_size = 0;
// write a header of packed string tensor

std::shared_ptr<uint8_t> header_ptr = nullptr;
size_t header_size = 0;
if (a1) {
Expand All @@ -718,22 +717,16 @@ void XmlSerializer::on_adapter(const std::string& name, ov::ValueAccessor<void>&
a2->get_header(header_ptr, header_size);
}

int64_t offset = get_constant_write_handler().write(
reinterpret_cast<const char*>(header_ptr.get()),
header_size,
inter_size,
m_compress_to_fp16,
m_output_element_type,
true); // header_ptr is allocated in AttributeAdapter that has limited life time
new_size += inter_size;

// write raw strings part
size_t num_elements = 0;
if (a1) {
num_elements = a1->get()->get_num_elements();
} else {
num_elements = a2->get()->get_num_elements();
}

std::vector<std::string_view> chunks;
chunks.reserve(1 + num_elements);
chunks.emplace_back(reinterpret_cast<const char*>(header_ptr.get()), header_size);
for (size_t ind = 0; ind < num_elements; ++ind) {
const char* raw_string_ptr;
size_t raw_string_size;
Expand All @@ -742,16 +735,12 @@ void XmlSerializer::on_adapter(const std::string& name, ov::ValueAccessor<void>&
} else {
a2->get_raw_string_by_index(raw_string_ptr, raw_string_size, ind);
}
chunks.emplace_back(raw_string_ptr, raw_string_size);
}

get_constant_write_handler().write(raw_string_ptr,
raw_string_size,
inter_size,
m_compress_to_fp16,
m_output_element_type,
m_data_is_temporary);
size_t new_size = 0;
int64_t offset = get_constant_write_handler().write(chunks, new_size);

new_size += inter_size;
}
m_xml_node.append_attribute("offset").set_value(static_cast<unsigned long long>(offset));
m_xml_node.append_attribute("size").set_value(static_cast<unsigned long long>(new_size));
}
Expand Down
68 changes: 68 additions & 0 deletions src/core/tests/pass/serialization/const_compression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,71 @@ TEST_F(SerializationConstantCompressionTest, EmptyAndNotEmptyConstantsDifferentV
const auto& [success, message] = compare_functions(model_initial, model_imported, true, true, false, true, true);
ASSERT_TRUE(success) << message;
}

TEST_F(SerializationConstantCompressionTest, StringConstantsRoundTrip) {
// Three distinct string constant tensors: verify each one round-trips with exact string content.
auto vocab_A = ov::op::v0::Constant::create(ov::element::string,
ov::Shape{4},
std::vector<std::string>{"UNKNOWN", "cat", "dog", "fish"});

auto vocab_B = ov::op::v0::Constant::create(ov::element::string,
ov::Shape{4},
std::vector<std::string>{"UNKNOWN", "red", "green", "blue"});

auto vocab_C = ov::op::v0::Constant::create(ov::element::string,
ov::Shape{3},
std::vector<std::string>{"sports", "politics", "tech"});

auto model_initial =
std::make_shared<ov::Model>(ov::OutputVector{vocab_A, vocab_B, vocab_C}, ov::ParameterVector{});

ov::pass::Serialize(m_out_xml_path_1, m_out_bin_path_1).run_on_model(model_initial);

ov::Core core;
auto model_imported = core.read_model(m_out_xml_path_1, m_out_bin_path_1);

std::vector<std::shared_ptr<ov::op::v0::Constant>> consts;
for (const auto& result : model_imported->get_results()) {
if (const auto c = std::dynamic_pointer_cast<ov::op::v0::Constant>(result->get_input_node_shared_ptr(0))) {
if (c->get_element_type() == ov::element::string) {
consts.push_back(c);
}
}
}
ASSERT_EQ(consts.size(), 3u);

std::vector<std::vector<std::string>> actual;
for (const auto& c : consts) {
actual.push_back(c->get_vector<std::string>());
}

std::vector<std::vector<std::string>> expected{
{"UNKNOWN", "cat", "dog", "fish"},
{"UNKNOWN", "red", "green", "blue"},
{"sports", "politics", "tech"},
};

EXPECT_EQ(actual, expected);
}

TEST_F(SerializationConstantCompressionTest, IdenticalStringConstantsRoundTrip) {
const std::vector<std::string> vocab{"UNKNOWN", "cat", "dog", "fish"};

auto A = ov::op::v0::Constant::create(ov::element::string, ov::Shape{4}, vocab);
auto B = ov::op::v0::Constant::create(ov::element::string, ov::Shape{4}, vocab);

auto model = std::make_shared<ov::Model>(ov::OutputVector{A, B}, ov::ParameterVector{});

ov::pass::Serialize(m_out_xml_path_1, m_out_bin_path_1).run_on_model(model);

ov::Core core;
auto model_imported = core.read_model(m_out_xml_path_1, m_out_bin_path_1);

for (const auto& result : model_imported->get_results()) {
if (const auto c = std::dynamic_pointer_cast<ov::op::v0::Constant>(result->get_input_node_shared_ptr(0))) {
if (c->get_element_type() == ov::element::string) {
EXPECT_EQ(c->get_vector<std::string>(), vocab);
}
}
}
}
23 changes: 23 additions & 0 deletions src/core/tests/xml_util/custom_ir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ class WeightlessWriter : public ov::util::ConstantWriter {
// use new_size not modified and return offset 0 to store these in modifed IR (xmL) only
return 0;
}

FilePosition write(const std::vector<std::string_view>&, size_t& new_size) override {
new_size = 0;
return 0;
}
};

// Custom serializer to store weights in the map during serialization (which not exists in original model)
Expand All @@ -141,6 +146,24 @@ class WeightMapWriter : public ov::util::ConstantWriter {
return static_cast<FilePosition>(w_id);
}

FilePosition write(const std::vector<std::string_view>& chunks, size_t& new_size) override {
new_size = 0;
for (const auto& sv : chunks) {
new_size += sv.size();
}

auto weights = std::make_shared<ov::AlignedBuffer>(new_size);
char* dst = weights->get_ptr<char>();
for (const auto& sv : chunks) {
std::memcpy(dst, sv.data(), sv.size());
dst += sv.size();
}

auto w_id = reinterpret_cast<size_t>(weights.get());
m_weights_map.get().emplace(w_id, std::move(weights));
return static_cast<FilePosition>(w_id);
}

private:
std::reference_wrapper<WeightsMap> m_weights_map;
};
Expand Down
16 changes: 16 additions & 0 deletions src/plugins/intel_cpu/src/utils/graph_serializer/serializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include <memory>
#include <ostream>
#include <string>
#include <string_view>
#include <vector>

#include "openvino/core/model.hpp"
#include "openvino/core/node.hpp"
Expand Down Expand Up @@ -49,6 +51,20 @@ class WeightlessWriter : public util::ConstantWriter {
return offset;
}

WeightlessWriter::FilePosition write(const std::vector<std::string_view>& chunks, size_t& new_size) override {
if (!m_skip_weights) {
return util::ConstantWriter::write(chunks, new_size);
}
new_size = 0;
for (const auto& sv : chunks) {
new_size += sv.size();
}

const FilePosition offset = m_offset;
m_offset += new_size;
return offset;
}

void skip_weights(bool skip_weights) {
m_skip_weights = skip_weights;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright (C) 2018-2026 Intel Corporation.
// Copyright (C) 2018-2026 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#pragma once

#include <string_view>

#include "openvino/pass/serialize.hpp"
#include "openvino/xml_util/xml_serialize_util.hpp"

Expand All @@ -20,6 +22,11 @@ class WeightlessWriter : public ov::util::ConstantWriter {
FilePosition write(const char*, size_t, size_t&, bool, ov::element::Type, bool) override {
return 0;
}

FilePosition write(const std::vector<std::string_view>&, size_t& new_size) override {
new_size = 0;
return 0;
}
};

/**
Expand Down
Loading