fix[next]: stop KeyError from masking compiled-program failures - #2733
Draft
havogt wants to merge 1 commit into
Draft
fix[next]: stop KeyError from masking compiled-program failures#2733havogt wants to merge 1 commit into
havogt wants to merge 1 commit into
Conversation
`CompiledProgramsPool.__call__` handled the cache miss inside the
`except KeyError` handler of the dispatch lookup. Everything raised there —
`future.result()` re-raising a worker exception, `artifact.load()` doing
I/O — was chained to the `KeyError`, so the traceback led with an opaque
key tuple instead of the actual cause:
KeyError: ((np.int32(557), ...), -5810085652480803948, None)
During handling of the above exception, another exception occurred:
...
OSError: [Errno 116] Stale file handle
Look the key up with `.get()` instead, so a miss raises nothing and there
is no exception context to chain to, and wrap `artifact.load()` failures in
an error naming the program.
`.get()` costs ~5ns per call on Python >=3.11 against ~2800ns for a full
dispatch; nothing measurable on 3.10.
This was referenced Jul 29, 2026
There was a problem hiding this comment.
🟢 Ready to approve
The changes are localized, align with the stated failure-mode goal, and are covered by targeted regression tests that validate the traceback/cause behavior.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
This PR improves error reporting in gt4py.next’s OTF compiled-program dispatch so that real compilation/load failures are no longer masked by an initial KeyError from the dispatch dictionary lookup. This directly targets clearer tracebacks and faster diagnosis of underlying I/O / artifact-load problems in production deployments.
Changes:
- Refactors
CompiledProgramsPool.__call__to avoidKeyError-based miss handling (usesdict.get()+Nonecheck), preventing unrelated exceptions from being chained to a dict-missKeyError. - Adds
_load_artifact()to wrapartifact.load()failures with a targetedRuntimeError(... ) from <cause>while preserving worker exceptions fromfuture.result()as-is. - Adds unit tests ensuring (a) load errors surface as the cause, (b) worker exceptions propagate without
KeyErrorcontext, and (c) non-JIT cache misses don’t showKeyErrorin the traceback.
File summaries
| File | Description |
|---|---|
| src/gt4py/next/otf/compiled_program.py | Removes KeyError-driven miss path and wraps artifact load failures to prevent KeyError from dominating tracebacks. |
| tests/next_tests/unit_tests/otf_tests/test_compiled_program.py | Adds regression tests for traceback chaining behavior across load failures, worker failures, and non-JIT misses. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Low
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
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.
Description
Draft — split out of #2731 (now closed) so the fix and the error-message improvement can be
reviewed separately. #2734 stacks on top with the message change.
When a compiled program cannot be obtained, the traceback leads with a
KeyErroron thedispatch dict even though the actual cause is something else. Real incident (ICON/icon4py,
10 MPI ranks, shared OTF cache on Lustre scratch):
The genuine cause was
OSError: [Errno 116] Stale file handlewhile loading the artifact, butthe
KeyErrorwith its opaque key tuple is what everyone reads first — it looks like acache-key/dispatch bug, and it is not. That cost hours of misdirected debugging.
CompiledProgramsPool.__call__did all cache-miss handling inside theexcept KeyErrorhandler, so everything raised there —
future.result()re-raising a worker exception,artifact.load()doing I/O — was chained to theKeyError.Changes
self.compiled_programs.get(key)plus anif ... is None. Nothingis raised on a miss, so there is no exception context for the real failure to be chained to.
The miss branch itself is unchanged.
_load_artifactwrapsartifact.load()failures asRuntimeError("Failed to load the compiled program '<name>'.") from <cause>, so theOSErroris what the reader sees. Worker exceptions from
future.result()are deliberately notwrapped — they propagate as themselves.
Hot path
__call__runs per program invocation (thousands per second in ICON), so the cost of thesuccessful lookup matters. Isolated dict lookup, key present:
try: d[key] except KeyError:d.get(key)key in d+d[key]So
.get()costs ~5 ns on ≥3.11, where the non-raisingtrybecame free, and nothing on 3.10.That is 0.2 % of the ~2800 ns a full dispatch takes; a whole-dispatch microbenchmark (program
call with the executable replaced by a no-op, under
-O) cannot resolve it above run-to-runnoise. Taken deliberately in exchange for a much smaller diff — an earlier revision kept the
try/exceptand restructured__call__so the handler was left before any miss handling(identical hit-path bytecode, zero cost); see
the discussion with
@egparedes.
Note that just adding
raise ... from eat the raise sites does not fix this:from esets__suppress_context__on the outer exception, but the intermediateOSErrorstill carries__context__ = KeyErrorand is printed as the cause, so theKeyErrorstill leads thetraceback. Hiding it that way would require setting
inner.__context__ = Noneat every site.Open question
_finish_compilation_jobstill hasassert key not in self.compiled_programs, which never runsunder icon4py's
PYTHONOPTIMIZE=2. Left as an assert: it is only reachable right after aconfirmed dict miss, so it cannot be violated by user input.
Out of scope: the underlying Lustre
ESTALE, which belongs with the OTF cache write/replacelogic (#2691). This is only about which error the user is shown.
Requirements
Three unit tests in
tests/next_tests/unit_tests/otf_tests/test_compiled_program.py(load
OSError, worker exception, genuine miss); all three fail onmainand pass here.