Skip to content

cse_util.h: bound CSETensorHash's raw_data hash cost for large tensors - #15

Open
take-cheeze wants to merge 4 commits into
claude/extract-constant-move-fix4uzfrom
claude/eliminate-duplicate-initializer-bounded-hash
Open

cse_util.h: bound CSETensorHash's raw_data hash cost for large tensors#15
take-cheeze wants to merge 4 commits into
claude/extract-constant-move-fix4uzfrom
claude/eliminate-duplicate-initializer-bounded-hash

Conversation

@take-cheeze

Copy link
Copy Markdown
Member

Summary

Follow-up to the model-regression per-pass profiling that found extract_constant_to_initializer (fixed in #14) — with that bottleneck gone, eliminate_duplicate_initializer's CSETensorHash raw_data hash became the next-largest one-time cost on raw_data-heavy models with large tensors (e.g. vgg19).

CSETensorHash's raw_data path hashed the entire buffer via std::hash<std::string> on every cache miss. Unlike g_raw_hash_cache hits, a miss isn't a caching gap that can be amortized away — it's a genuine one-time-per-initializer cost, so the only lever left was making that one-time hash itself cheaper.

Changes

  • Adds HashRawDataBounded: hashes tensors ≤4KB exactly as before; for larger ones, hashes size + up to 256 evenly-spaced 64-byte windows instead of every byte, bounding the cost to a small constant regardless of tensor size (5KB or 500MB).
  • Wires it into CSETensorHash's raw_data branch in place of the full-buffer std::hash<std::string> call.

Why this is safe: CSETensorHash only needs to be a good bucketing key. CSETensorCompare's raw_data fast path — a full lhs->raw() == rhs->raw() memcmp — is the sole source of truth for equality on every hash-bucket hit, regardless of how the hash was computed. A lower-quality/sampled hash can never cause an incorrect merge; the only cost of a spurious collision is one extra, already-cheap memcmp against a candidate that turns out not to match.

Test plan

  • New test EliminateDuplicateInitializerLargeRawData (tests/test_simple.cc): deliberately constructs two 100KB tensors that differ only in a byte outside the sampled windows (a real, expected hash collision under the new bounded hash) and confirms eliminate_duplicate_initializer still keeps both — proving the hash-collision-safety invariant above, not just asserting it in a comment. Also checks a genuinely identical large pair still gets deduped to one.
  • test_simple (optimizer's own gtest suite): 5/5 passing, including the new test.
  • Full onnxsim rebuild against this commit + full pytest suite (tests/, minus torch/timm-dependent files not installed in this sandbox): 428 passed, 0 failed.

Note on base branch

This targets claude/extract-constant-move-fix4uz (PR #14's branch) rather than main, since it depends on g_raw_hash_cache/CSETensorHash machinery that currently only exists on that unmerged stack (introduced in #10). Not independently mergeable to main until that stack lands.


Generated by Claude Code

claude added 4 commits August 26, 2026 05:02
std::hash<std::string> over the full raw_data buffer was a genuine
one-time-per-initializer cost on raw_data-heavy models with large
tensors (unlike g_raw_hash_cache misses, this isn't something caching
can amortize away -- see onnxsim issue #633 follow-up profiling, where
it dominated eliminate_duplicate_initializer's time on several models).

HashRawDataBounded hashes small tensors exactly as before, and for
larger ones hashes size + up to 256 evenly-spaced 64-byte windows
instead of every byte, bounding the cost to a small constant regardless
of tensor size. This only degrades CSETensorHash's bucketing quality --
CSETensorCompare's full raw() == raw() memcmp is still the sole source
of truth for equality on every hash-bucket hit, so a spurious collision
can only cost one extra (already-cheap) memcmp, never an incorrect
merge.
Deliberately constructs two large raw_data tensors that differ only in
a byte outside HashRawDataBounded's sampled windows (a real, expected
hash collision under the new bounded hash) and confirms eliminate_
duplicate_initializer still keeps both -- CSETensorCompare's full
memcmp, not the hash, is what decides equality. Also checks a
genuinely identical large pair still gets deduped.
w3/w4 previously reused the same bytes as w1 ('base'), so
eliminate_duplicate_initializer correctly folded all three into one
survivor (w1) and the count(w3)+count(w4)==1 assertion was wrong, not
the pass. Give w3/w4 their own distinct fill byte so they form an
independent duplicate pair.
…sors

Mirrors the raw_data fix (HashRawDataBounded): CSETensorHash's typed-field
branch previously hashed via std::hash<std::string>(TensorContentDigest(...)),
paying a full BLAKE3 pass over every parsed element even though the hash is
only ever used as a bucketing key (CSETensorCompare / TensorContentDigest
equality remain the sole source of truth on any hash-bucket hit). Model-
regression CI's pass-phase bottleneck summary showed this dominating
eliminate_duplicate_initializer's cost on typed-field-heavy legacy-opset
models once the raw_data path was fixed.

Adds HashTypedFieldBounded, sharing HashBytesBounded with the raw_data path
and preserving the signed-zero canonicalization (CanonicalizeZero) that
ComputeTensorContentDigest applies for FLOAT/DOUBLE/COMPLEX64/COMPLEX128.

Adds EliminateDuplicateInitializerLargeTypedField, adversarially verified to
catch an incorrect merge when hash-bucket membership is mistakenly trusted
as equality.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4AHXs8oJU1UNbjs8LoQWu
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants