Skip to content

fix: validate cached ONNX before reuse to prevent empty-graph TensorRT build crash#34

Open
forkni wants to merge 1 commit into
dotsimulate:mainfrom
forkni:fix/onnx-cache-validation-upstream
Open

fix: validate cached ONNX before reuse to prevent empty-graph TensorRT build crash#34
forkni wants to merge 1 commit into
dotsimulate:mainfrom
forkni:fix/onnx-cache-validation-upstream

Conversation

@forkni

@forkni forkni commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

A user hit a crash during TensorRT engine build:

AttributeError: 'NoneType' object has no attribute 'graph'
  ... polygraphy/backend/onnx/util.py, in get_num_nodes
      return _get_num_graph_nodes(model.graph)
Exception: Acceleration has failed: 'NoneType' object has no attribute 'graph'

Root cause: a prior build was interrupted mid-torch.onnx.export() (OOM, crash, manual
kill). torch.onnx.export() writes directly to the final onnx_path with no temp-file-then-
rename, so the interrupted run left a syntactically-valid-but-empty (or truncated) protobuf on
disk. EngineBuilder.build()'s cache check is existence-only (os.path.exists(onnx_path)), so
the next run silently reused the corrupt file instead of re-exporting. That empty/0-node graph
then reached Optimizer.fold_constants() → polygraphy's fold_constants → ONNX Runtime's
symbolic shape inference, which refuses opset < 7 graphs and returns None — crashing
get_num_nodes(None) with the opaque AttributeError above, several layers away from the
actual cause.

Fix (two layers, so every model type is covered)

  • builder.py: add _onnx_cache_valid(path) — loads the ONNX structure-only (no external
    weight data, so it's cheap even for large models), and rejects it if the file is empty, has
    0 graph nodes, or has no opset ≥ 7. Gate the export cache-hit on this check instead of on
    os.path.exists() alone; if a cached file fails validation, delete it and re-export instead
    of silently reusing (or crashing on) garbage.
  • models.py: guard Optimizer.fold_constants() itself against a 0-node graph, rather than
    only guarding BaseModel.optimize(). CLIP.optimize() overrides BaseModel.optimize()
    entirely and calls fold_constants() directly, so a guard placed only in
    BaseModel.optimize() wouldn't catch a corrupt CLIP ONNX. Placing it in fold_constants()
    covers CLIP, UNet, VAE encoder, and VAE decoder from the one shared call site.

With this fix, an interrupted-export leftover is detected and transparently re-exported instead
of crashing on the next run.

Test plan

🤖 Generated with Claude Code

…T build crash

An interrupted/failed ONNX export leaves a syntactically-valid-but-empty
protobuf at onnx_path. The export cache check was existence-only, so the
empty file was reused, feeding a 0-node/opset-less graph into polygraphy
fold_constants -> ORT shape inference bails on opset<7 -> returns None ->
get_num_nodes(None) crashes as "'NoneType' object has no attribute 'graph'".

builder.py: add _onnx_cache_valid() (size>0, >=1 node, opset>=7, loaded
structure-only) and gate the export cache-hit on it; delete + re-export a
bad cache entry.

models.py: guard Optimizer.fold_constants() against a 0-node graph. This
is the single call site shared by both BaseModel.optimize() and CLIP's
optimize() override (which calls fold_constants() directly, bypassing
BaseModel.optimize()), so placing the guard here -- rather than only in
optimize() -- covers every model type (CLIP/UNet/VAE encoder/decoder).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
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