Skip to content

[CORE] Align constant offsets to 8 B in .bin serialization#36733

Open
LeoHeller wants to merge 5 commits into
openvinotoolkit:masterfrom
LeoHeller:fix/constant-bin-alignment
Open

[CORE] Align constant offsets to 8 B in .bin serialization#36733
LeoHeller wants to merge 5 commits into
openvinotoolkit:masterfrom
LeoHeller:fix/constant-bin-alignment

Conversation

@LeoHeller

Copy link
Copy Markdown

Details:

  • ConstantWriter::write() previously wrote constant data at whatever stream position the topological traversal produced, yielding unaligned offsets when constants of different element sizes are serialised consecutively.
  • At load time, the runtime mmaps the .bin and constructs a SharedBuffer whose get_ptr<T>() performs reinterpret_cast<T*>(mmap_base + offset). An unaligned offset makes this undefined behaviour.
  • This patch inserts up to 7 zero-bytes of padding before each constant write to bring the offset to an 8-byte boundary (the maximum scalar alignment for any OpenVINO element type). Uses the existing ov::util::align_padding_size utility.
  • Deduplication is checked before padding so that a duplicate hit does not leave orphaned padding bytes in the stream.
  • Only newly serialised .bin files get aligned offsets; old files load unchanged (their offsets are not retroactively fixed, but the reader does not validate alignment, so they remain loadable).

Tickets:

AI Assistance:

  • AI assistance used: yes
  • AI was used to trace the full serialisation-to-deserialisation call chain across 6+ source files and identify all downstream UB sites. Human validated every finding against the actual source, compiled and ran the reproducer with UBSan to confirm the misaligned-load diagnostic, and reviewed the final diff.

ConstantWriter::write() previously wrote constant data at whatever
stream position the topological traversal produced, yielding unaligned
offsets when constants of different element sizes (bool 1 B, i32 4 B,
i64 8 B) were serialized consecutively.  At load time the runtime
mmaps the .bin and constructs a SharedBuffer (subclass of AlignedBuffer)
whose get_ptr<T>() does reinterpret_cast<T*> at the stored offset.
When that offset is not a multiple of alignof(T) the reinterpret_cast
and subsequent dereference are undefined behaviour.

Fix inserts up to 7 zero-bytes of padding before each constant write
to bring the offset to an 8-byte boundary (the maximum scalar alignment
for any OpenVINO element type).  Deduplication is checked before padding
so that a duplicate hit does not leave orphaned padding bytes.

Backward-compatible: only newly serialised .bin files get aligned
offsets; old files continue to load (their offsets are unchanged).

Uses the existing ov::util::align_padding_size utility already employed
by CompiledBlobHeader for the same purpose.
@LeoHeller LeoHeller requested a review from a team as a code owner July 6, 2026 10:18
@github-actions github-actions Bot added the category: Core OpenVINO Core (aka ngraph) label Jul 6, 2026
@sys-openvino-ci sys-openvino-ci added the ExternalPR External contributor label Jul 6, 2026
@mlukasze

mlukasze commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

please add tests to cover the change

Add a round-trip test that serializes a model with mixed element-size
constants (bool 1 B, i32 4 B, i64 8 B), reloads it, and asserts every
constant's raw data pointer satisfies uintptr_t % element_type.size() == 0.

Without the alignment fix this test fails for the i64 constant whose
data lands at offset 5 in the .bin file.
@LeoHeller

Copy link
Copy Markdown
Author

please add tests to cover the change

Done.

The test asserted an exact .bin file size of 2 bytes to verify two
distinct 1-byte constants were not deduplicated.  Alignment padding
now adds 7 bytes between them.  Replace the brittle size assertion
with a check that the two reloaded constants have different data
pointers, proving no false deduplication occurred.
@t-jankowski

Copy link
Copy Markdown
Contributor

build_jenkins

m_out_xml_path is std::filesystem::path in SerializePassTest but
readModel expects std::string.  MSVC does not implicitly convert
filesystem::path for overload resolution.  Add .string() to match
the existing call site at line 94.
auto c = std::dynamic_pointer_cast<Constant>(node);
if (!c || c->get_element_type() == element::string)
continue;
auto alignment = c->get_element_type().size();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alignment value on writing is unconditional, while here it depends on type size. Would it pass without above changes in ConstantWriter::write?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it would fail without the fix. it lands at offset 5 without the fix, and 5 % 8 != 0.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly in the future one could pass the element type/size to the write callsite to use minimal padding, but for now always padding to 8 seems more elegant and simpler.

@LeoHeller

Copy link
Copy Markdown
Author

Tests now pass locally, hopefully also in CI.

@LeoHeller LeoHeller requested a review from t-jankowski July 6, 2026 14:02
@praasz praasz self-assigned this Jul 7, 2026
@praasz praasz added this to the 2026.4 milestone Jul 7, 2026
@praasz

praasz commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@LeoHeller
Except issue reported by sanitizer, Is lack of align make some issue like corrupted/wrong data in constant nodes?

@LeoHeller

Copy link
Copy Markdown
Author

@LeoHeller Except issue reported by sanitizer, Is lack of align make some issue like corrupted/wrong data in constant nodes?

No. I believe on ARM64 it can deliver SIGBUS but usually it is fine. However we build openvino with the address sanitizer which then complains.

Could you please take a look at the failed CI job, it seems to be unrelated to my changes?
FileNotFoundError: [Errno 2] No such file or directory: '/mount/build-artifacts/dldt/master/pre_commit/ada82834a36cf9d5e37c8ee52405691efb2e8556/public_linux_ubuntu_24_04_x86_64_release/workflow_link.txt'

@t-jankowski

Copy link
Copy Markdown
Contributor

Could you please take a look at the failed CI job, it seems to be unrelated to my changes? FileNotFoundError: [Errno 2] No such file or directory: '/mount/build-artifacts/dldt/master/pre_commit/ada82834a36cf9d5e37c8ee52405691efb2e8556/public_linux_ubuntu_24_04_x86_64_release/workflow_link.txt'

Yes, it's unrelated.

We find this issue worth solving - thanks for catching it. Currently we're preparing for 2026.3 release and it's vacation seasons - we'll get back following weeks.

@t-jankowski t-jankowski self-assigned this Jul 7, 2026
@praasz

praasz commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

build_jenkins

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

category: Core OpenVINO Core (aka ngraph) ExternalPR External contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Unaligned constant offsets in .bin serialization cause undefined behavior with mmap

5 participants