Fix DynamicLayer state leak after dynamo export - #2626
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a global state leak in the ONNX dynamo-export path by ensuring a temporary transformers.cache_utils.DynamicLayer.lazy_initialization monkeypatch is reliably restored after export, and corrects the patch logic to preserve distinct key/value cache shapes.
Changes:
- Converted
_patch_dynamic_layer_for_export()into acontextlib.contextmanagerthat restores the originalDynamicLayer.lazy_initializationin afinallyblock. - Fixed the patched cache initialization to build
self.valuesfromvalue_states(falling back tokey_statesonly whenvalue_states is None). - Added regression tests to validate method restoration (normal and exception paths) and to ensure key/value shapes remain distinct.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
olive/passes/onnx/conversion.py |
Makes the DynamicLayer patch scoped + exception-safe; fixes value cache initialization; wraps export in the patch context. |
test/passes/onnx/test_conversion.py |
Adds targeted unit tests for restoration and key/value shape preservation under the patch. |
test/passes/onnx/test_common.py |
Adds a regression assertion ensuring OnnxConversion(...use_dynamo_exporter=True) does not leave the patch applied. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Split the combined "with pytest.raises(...), _patch_dynamic_layer_for_export():" statement into nested with-blocks. CodeQL doesn't model pytest.raises as catching the exception when combined with another context manager in one with-statement, so it flagged the assert after the block as unreachable and the variable only read there as unused. Suppress ruff's SIM117 (which would otherwise recombine them) with a qualified noqa, matching the repo's existing noqa convention for similar pytest.raises cases.
CodeQL's control-flow analysis does not model pytest.raises as capable of suppressing an exception raised inside its with block, so it flags code after the with block as unreachable and the variable read there as unused. Nesting the with statements (previous fix) did not resolve this since CodeQL still doesn't understand pytest.raises semantics. Rewrite test_dynamic_layer_export_patch_restores_method_on_error to use a native try/except, which CodeQL's CFG analysis correctly understands, resolving the false positive alerts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e10674c1-6909-4b09-9f5a-d41b28c89d2d
Jambay Kinley (jambayk)
approved these changes
Aug 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Describe your changes
OnnxConversionwithuse_dynamo_exporter=Truemonkeypatchestransformers.cache_utils.DynamicLayer.lazy_initializationat the class level (via_patch_dynamic_layer_for_export()) in order to make ONNX export work, but never restored the original method afterward. Because the patch is class-level (not instance-level), it silently affected every subsequentDynamicLayer/DynamicCacheinstance created later in the same process, for any model class.The patched implementation also had its own bug: it always initialized
self.valuesfromkey_states(ignoringvalue_states). This is harmless when key/value head dimensions match, but corrupts the KV cache shape for architectures where they differ (e.g. DeepSeek-V3's MLA, whereqk_rope_head_dim + qk_nope_head_dim!=v_head_dim), producing errors such as:This was discovered as a cross-test pollution bug: running
test/passes/onnx/test_common.py::test_resave_model(any model, dynamo export) before a DeepSeek-V3 test in the same pytest process (as CI does, since it runs the wholetest/directory in one process) leaves the patchedlazy_initializationin place and corrupts the later test's cache handling — even though the two tests use entirely unrelated models.Fix
_patch_dynamic_layer_for_export()into acontextlib.contextmanagerthat saves the originalDynamicLayer.lazy_initialization, applies the patch, yields, and restores the original in afinallyblock, so the patch is undone even if export raises.value_states(falling back tokey_statesonly ifvalue_states is None) instead of always reusingkey_statesfor both keys and values.torch.onnx.export(...)call site inwith patch_context:(usingcontextlib.nullcontext()for the transformers<5.0branch that doesn't need this patch).Verification
DynamicLayer.lazy_initializationis restored after a normal patched export path and correctly preserves distinct key/value shapes, and one asserting it is restored even when the patched block raises.test/passes/onnx/test_common.pytogether with a DeepSeek-V3 GPTQ MoE test in the same pytest process; confirmed the failure occurs before this fix and is resolved after it.lintrunnerclean.Checklist before requesting a review
lintrunner -a(Optional) Issue link
Discovered while investigating a CI-only
deepseek_v3failure reported on #2610 (unrelated to that PR's changes; caused by this pre-existing global-state leak, only reproducible when the wholetest/suite runs in one process, as CI does).