Skip to content

Fix weight_id collision when materializing multiple source functions - #2828

Open
kasper0406 wants to merge 1 commit into
apple:mainfrom
kasper0406:fix/materialize-weight-id-collision
Open

Fix weight_id collision when materializing multiple source functions#2828
kasper0406 wants to merge 1 commit into
apple:mainfrom
kasper0406:fix/materialize-weight-id-collision

Conversation

@kasper0406

@kasper0406 kasper0406 commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Bug

materialize_symbolic_shape_program assigns a weight_id to cloned consts that lack one, deriving the id from the const's name alone (symbol_transform.py): f"const_{source_input_var.name}_weight_id". Const names are only unique within a function, so when two source functions each contain a same-named const holding different values/shapes (common when functions are traced separately — e.g. both produce a const named range_1d_0, one int32[8] and one int32[512]), the materialized functions claim the same weight-blob entry (the backend caches blob values keyed by weight_id in backend/mil/load.py). The serialized package is corrupt: loading fails with Attribute val has incompatible type with operation output, and the package cannot be re-loaded into pymil.

The collision is invisible when the affected consts are small (<10 elements are stored inline and never reach the blob), which is why it lurks — we hit it in production the moment a shared const name crossed the inline threshold.

Fix

Scope the invented fallback id by the source function name: const_{source_function_name}_{name}_weight_id. Consts that already carry a weight_id (e.g. assigned by const_deduplication, which groups by dtype+shape+value) are untouched, so intentional cross-function weight sharing keeps working, and every function materialized from one source still shares that source's blob entry.

Tests

  • New test_weight_id_no_collision_across_source_functions: two source functions with same-named, different-valued blob-sized consts, materialized, checked in-memory (no weight_id maps to two different weights) and end-to-end (mlprogram serialize + milproto reload, each function reads back its own values). Fails without the fix (shapes (4096,), (8192,) mismatch), passes with it.
  • test_weight_id_pass_down updated for the scoped id format (the only expectation change the fix forces).
  • Neighbouring suites pass: test_symbol_transform.py 10/10, TestMaterializeSymbolicShapeMLModel 17/17 (8 pre-existing xfails), test_cleanup_passes.py const-dedup coverage unaffected.

🤖 Generated with Claude Code

`materialize_symbolic_shape_program` assigns a `weight_id` to every const
it clones that does not have one yet, so that the materialized functions
share the weight file value of the const they were cloned from. That
`weight_id` was derived from the const name alone, but a const name is
only unique within a function, not within a program.

So materializing 2 source functions of a multifunction program that each
own a same-named const holding a different value (e.g. 2 separately
traced functions that both name their leading `arange` `range_1d_0`, one
of length 128 and one of length 512) makes those 2 unrelated consts claim
the same weight blob entry: only one value is written to the blob, both
functions point to it, and the model becomes corrupted. Loading it fails
with "Attribute val has incompatible type with operation output", and the
saved package cannot be loaded back into pymil either. The collision is
invisible for small consts, since those are stored immediately in the
proto and never reach the weight blob.

Include the source function name in the invented `weight_id`. All the
functions materialized from one source function still share the weight
file value of that source, and consts that got their `weight_id`
elsewhere (`const_deduplication` groups by dtype + shape + value) are
untouched, so deduplication across functions keeps working.

Tests:
- New `test_weight_id_no_collision_across_source_functions` in
  test_symbol_transform.py: materializes 2 source functions holding
  same-named consts that are large enough to be blob stored, lowers the
  program to mlprogram, loads it back into pymil, and checks that every
  materialized function reads back its own weight. It fails before this
  change, with `func2_materialized` reading `main`'s weight.
- `test_weight_id_pass_down` now expects the source-function-scoped id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SSuo4pE3Kr3GWNwcLKLn5v
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.

1 participant