Conversation
_ExportPassBase.call picks the FakeTensorMode to retrace under by scanning self.inputs(graph_module). extract_input unwraps a placeholder's FakeTensor to its materialized .constant, so a submodule whose placeholders are all lifted tensor constants yields no FakeTensor to scan, and a fresh mode is created even though the graph already carries one. The retrace then writes vals from the new mode onto the traced nodes while the placeholders keep the original, leaving two modes in one graph. detect_fake_mode asserts on that mixture when _get_updated_range_constraints later walks the node vals. Fall back to the mode recorded on the placeholders before creating one. The change is strictly narrowing: graphs where the input scan already found a mode are untouched, and a new mode is still created when the placeholders carry no FakeTensor or disagree. Reported against DINOv2 on WebGPU, where the positional-encoding path partitions into a constant-only subgraph. The failure surfaced as "An error occurred when running the 'FuseBatchNormPass' pass" on a model with no batch norm at all, since that pass is merely the first one in the Vulkan pipeline to retrace. Verified: dinov2_vits14 now exports through WebGPUPartitioner to an 84 MB .pte with 27 VulkanBackend delegates; the added tests fail without the fix and pass with it; exporting DINOv2 through XNNPACK produces byte-identical .pte files with the fix enabled and disabled. Authored with assistance from Claude Code.
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/22891
Note: Links to docs will display an error until the docs builds have been completed. ❌ 5 New FailuresAs of commit 276ed55 with merge base b03dec2 ( NEW FAILURES - The following jobs have failed:
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
This PR needs a
|
There was a problem hiding this comment.
🟢 Approval recommended
The change is narrowly scoped, preserves existing behavior when a mode is already found via inputs(), and includes targeted regression tests for the reported failure mode.
Pull request overview
This PR fixes an EXIR export-pass replay failure for submodules whose FX graphs contain only constant placeholders by reusing the existing FakeTensorMode already recorded on placeholder node metadata, instead of minting a new mode during retrace.
Changes:
- Add
_ExportPassBase._get_fake_mode_from_placeholders()to reuse a single placeholderFakeTensorModewheninputs()yields noFakeTensor(e.g., all placeholders were unwrapped to.constant). - Update
_ExportPassBase.call()to fall back to the placeholder mode before creating a freshFakeTensorMode. - Add regression tests covering the constant-only placeholder case and the “placeholders disagree on mode” guard.
File summaries
| File | Description |
|---|---|
| exir/pass_base.py | Reuses placeholder FakeTensorMode when inputs() returns no FakeTensor, preventing mixed fake modes after retrace. |
| exir/tests/test_pass_infra.py | Adds regression tests ensuring constant-only graphs keep a single fake mode and that mode reuse is skipped when placeholders disagree. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # lifted constants yields no FakeTensor above even though the graph | ||
| # carries a mode. Creating a fresh one here would leave the retraced | ||
| # nodes in a different mode from the untouched placeholders. | ||
| fake_tensor_mode = self._get_fake_mode_from_placeholders(graph_module) |
There was a problem hiding this comment.
how did this work for xnnpack?
Not covered: the Vulkan .pte has not been executed (no VulkanBackend in the test runtime), so runtime numerics for DINOv2 on Vulkan/WebGPU remain unverified and are out of scope for this change.
And does this now work on both vulkan and wgpu e2e?
|
@claude review this PR, and verify if there are any existing assumption around fakemode == none which can be broken by this. |
|
Also in a different PR, do you want to add dinov2 on wgpu and/or vk as a model level test ci? perhaps on a trunk or something? |
|
Claude finished @digantdesai's task in 3m 55s —— View job Review: reuse graph fake mode for constant-only submodulesTask
The fix itself is correct and the diagnosis is accurate: But you asked specifically about assumptions around if fake_tensor_mode is None:
fake_tensor_mode = FakeTensorMode(allow_non_fake_inputs=True)
dispatcher_mode = nullcontext() # <- old path for const-only graphs
else:
fake_tensor_mode.allow_non_fake_inputs = True
dispatcher_mode = enable_python_dispatcher() # <- new path for const-only graphs1. 2. The reused mode is mutated in place: Assumptions I checked that are NOT broken:
Minor / nits:
Re @digantdesai's inline question ("how did this work for xnnpack?"): XNNPACK partitioning doesn't produce the all-constant-placeholder submodule that trips this, so its passes' Recommendation: logic is sound and narrowly scoped. Before merge I'd suggest (a) a sentence in the description noting the dispatcher-context flip, and (b) a decision on whether to restore Note: I reviewed statically; I did not run |
Problem
Exporting DINOv2 (or any model that partitions into a constant-only subgraph) fails in
to_edge_transform_and_lower():Reported for DINOv2 on WebGPU. The error is doubly misleading: it names a batch-norm pass on a ViT with no batch norm, and the actual cause is in
exir/pass_base.py, not in the pass or the backend.Root cause
_ExportPassBase.callpicks theFakeTensorModeto retrace under by scanningself.inputs(graph_module):extract_inputunwraps a placeholder'sFakeTensorto its materialized.constantwhen one exists, returning a real tensor. A submodule whose placeholders are all lifted tensor constants therefore yields noFakeTensorto scan, and a fresh mode is created — even though the graph already carries exactly one consistent mode.The retrace then writes vals from the new mode onto the traced nodes while the placeholders keep their originals, leaving two modes in one graph.
_get_updated_range_constraints→detect_fake_modeasserts on the mixture.Instrumenting the failing DINOv2 submodule shows it precisely:
FuseBatchNormPassappears only because it is the first pass in the Vulkan pipeline that callssuper().call()to retrace (fuse_batch_norm.py, "To regenerate metadata and shape information, retrace module"). Any pass in that position would fail identically.Fix
Fall back to the mode already recorded on the placeholders before creating a new one. Strictly narrowing: graphs where the input scan already found a mode are untouched, and a new mode is still created when the placeholders carry no
FakeTensoror disagree on one.Verification
dinov2_vits14exports throughWebGPUPartitionerto an 84 MB.ptewith 27VulkanBackenddelegates; the 4 remaining CPU ops (floor,index,mul,any) match the partitioner's own skip list.AssertionError: 2 != 1) with the fallback stubbed out.exir/tests/test_pass_infra.py: 19/19 pass..ptefiles with the fix enabled and disabled, confirming unaffected paths are unperturbed.Not covered: the Vulkan
.ptehas not been executed (noVulkanBackendin the test runtime), so runtime numerics for DINOv2 on Vulkan/WebGPU remain unverified and are out of scope for this change.Authored with assistance from Claude Code.