Skip to content

Fix FNO recompiling for every grid size under torch.compile(dynamic=True) - #2238

Open
aarochu wants to merge 1 commit into
NVIDIA:mainfrom
aarochu:fix/fno-dynamic-resolution-recompile
Open

aarochu wants to merge 1 commit into
NVIDIA:mainfrom
aarochu:fix/fno-dynamic-resolution-recompile

Conversation

@aarochu

@aarochu aarochu commented Nov 8, 2026

Copy link
Copy Markdown

PhysicsNeMo Pull Request

Description

Closes #2207.

If you compile FNO with torch.compile(dynamic=True) and feed it different grid sizes, it recompiles every time. That's the opposite of what you want from a resolution-independent model. The cause is the coordinate grid in the encoders' _meshgrid: it uses torch.linspace(0, 1, size), and Dynamo specializes on linspace's step count. With TORCH_LOGS=dynamic you can see it guarding Eq(s53, 16) right at that line, and then tensor 'x' size mismatch at index 2 on the next size.

The fix adds a small _unit_grid helper that builds the same grid as torch.arange(size) / max(size - 1, 1) (using torch.sym_max so the size stays symbolic). All four encoders (1D–4D) now use it. Nothing else changes:

  • Values match linspace to within 1 float32 ULP (I checked every size from 1 to 512). The endpoints are exactly 0 and 1, and size=1 still gives [0.].
  • The existing forward tests against saved .pth reference outputs still pass, as do the JIT/AMP/CUDA-graph optim tests and the xFNO tests.

Before/after for the repro from the issue (CPU, torch 2.13, sizes 16/24/32/40 for 1D/2D, smaller for 3D/4D):

dim graphs on main graphs with this PR
1 4 1
2 4 1
3 4 1
4 4 1

I ran it with the eager, aot_eager and inductor backends. Compiled outputs match eager in every case. The issue mentions 3D hitting an Inductor ValueRangeError on CUDA. I don't have a GPU on this machine, so I couldn't check that path, but 3D compiles to a single graph with Inductor on CPU. If it still trips on CUDA, that looks like an Inductor bug rather than something on our side.

I also added test_fno_compile_dynamic_resolution. It compiles with dynamic=True on the eager backend, runs three sizes, and asserts that there's one graph and the outputs match eager. It runs on CPU too, unlike the existing fullgraph check, which is GPU-only and never changes size. On main it fails for all four dimensions.

Checklist

Dependencies

None.

The FNO encoders built their coordinate grid with torch.linspace, and
Dynamo specializes linspace's step count. A model compiled with
dynamic=True therefore recompiled for every new input resolution
(4 sizes -> 4 graphs) until it hit the recompile limit.

Build the grid with torch.arange(n) / max(n - 1, 1) instead, which keeps
the size symbolic. Values match linspace to within 1 float32 ULP and the
saved reference outputs still pass.

Closes NVIDIA#2207

Signed-off-by: aarochu <aaronchu022@gmail.com>
@copy-pr-bot

copy-pr-bot Bot commented Nov 8, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

CODEOWNERS review map

Current for commit 1513d397045e. An approval covers every file listed for that owner; one owner is sufficient for shared files.

⏳ @loliverhennigh — 1 file(s)
  • physicsnemo/nn/module/fno_layers.py

No CODEOWNER

  • CHANGELOG.md
  • test/models/fno/test_fno.py

Comment /codeowners-info to refresh.

@greptile-apps

greptile-apps Bot commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in Greptile

[Medium impact] The change reaches FNO runs across one through four dimensions.

The PR appears safe to merge, with a non-blocking request to test grid values directly.

Findings

  1. P2 Grid values remain unchecked ▶

Summary

Replaces torch.linspace with a shared _unit_grid helper so coordinate-grid sizes can stay symbolic under torch.compile(dynamic=True).

  • FNO encoders reuse one compiled graph across changing grid sizes.

Acknowledged behavior: aarochu explicitly accepts small float32 rounding differences from the replacement formula.

Reviews (1) · Last reviewed commit: "Keep FNO grid size dynamic under torch.c..." · Reviewed by Greptile

Comment on lines +193 to +195
for size in (6, 7, 8):
invar = torch.randn(2, 2, *([size] * dimension), device=device)
torch.testing.assert_close(compiled(invar), model(invar))

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.

P2 Grid values remain unchecked

Both calls in this comparison use _unit_grid, and the test only tries sizes 6, 7, and 8. An incorrect grid could therefore pass, including a broken [0.] result for size 1. Add a focused test against torch.linspace for sizes 1, 2, and several larger sizes, with a tight float32 tolerance and exact endpoint checks.

This branch has not been deployed

No deployments
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.

FNO recompiles for every new input resolution with torch.compile(dynamic=True)

1 participant