`slanet-plus.onnx` has ONNX-prohibited nested-scope name collisions and fails to import in MIGraphX

Author: AIwork4meCreated Jul 27, 2026Updated Jul 27, 2026

Summary

The published table-recognition model slanet-plus.onnx has 16 nested-scope name collisions: formal inputs of its Loop body subgraph reuse names that are already visible from the parent graph at that point. The currently published ONNX IR prohibits this, so the artifact is rejected during ONNX import by AMD MIGraphX (reproduced on the released 2.15.0 and on a develop source build, commit 49f842b). In the tested versions, onnx.checker.check_model() passes and ONNX Runtime (CPU) executes the model, so those code paths do not reject the artifact for these collisions. This is a published-artifact portability / ONNX-conformance issue; the model remains usable in runtimes that currently accept such name collisions, and becomes visible in runtimes that validate this IR rule during import.

Affected artifact

  • Official model repository: Hugging Face opendatalab/PDF-Extract-Kit-1.0 (linked as the model source by the README of GitHub opendatalab/PDF-Extract-Kit). The .onnx is not claimed to be stored in the GitHub code repository.
  • Path: models/TabRec/SlanetPlus/slanet-plus.onnx
  • Repository revision tested: ed6b654c018d742e65a17671e379c5e6ecc87ec9 (the current repository HEAD at the time of testing; the file's introducing commit was not separately determined)
  • SHA-256: d57a942af6a2f57d6a4a0372573c696a2379bf5857c45e2ac69993f3b334514b
  • Size: 7,758,305 bytes
  • ONNX: opset ai.onnx:14, IR version 7; producer PaddlePaddle (producer_version is empty in the file, so a specific exporter version is not identifiable)

Minimal reproduction

bash
# 1. Obtain the artifact from the official HF model repo, pinned to the revision above
curl -L -o slanet-plus.onnx \
  "https://huggingface.co/opendatalab/PDF-Extract-Kit-1.0/resolve/ed6b654c018d742e65a17671e379c5e6ecc87ec9/models/TabRec/SlanetPlus/slanet-plus.onnx"
sha256sum slanet-plus.onnx   # d57a942af6a2f57d6a4a0372573c696a2379bf5857c45e2ac69993f3b334514b

# 2. Minimal, model-specific check for the known Loop-body-input collisions.
python3 - <<'PY'
import onnx
m = onnx.load("slanet-plus.onnx"); g = m.graph
# This intentionally checks only Loop body formal inputs against names visible in the
# parent graph before each Loop. A recursive audit (offered below) additionally covers
# body initializers, sparse initializers, node outputs, and nested graph attributes.
visible = set(i.name for i in g.input) | set(t.name for t in g.initializer)
hits_total = 0
for idx, node in enumerate(g.node):
    if node.op_type == "Loop":
        body = next(a.g for a in node.attribute if a.name == "body")
        hits = [vi.name for vi in body.input if vi.name in visible]
        print(f"Loop at node #{idx}: {len(hits)} body inputs collide with visible parent names")
        hits_total += len(hits)
    for o in node.output:
        visible.add(o)
print("total Loop body-input collisions:", hits_total)
PY
# -> total Loop body-input collisions: 16

# 3. (Optional) confirm a runtime that validates the rule rejects the import
migraphx-driver read slanet-plus.onnx --onnx
# -> ... parse_inputs: module "Loop_..." has parameter name "gru_cell_0.w_1"
#        existing in parent graph!   (non-zero exit)

Observed and expected behavior

  • Nested-scope check (minimal, model-specific): 16 Loop body formal inputs collide with parent-graph Constant outputs defined before the Loop.
  • onnx.load: OK. onnx.checker.check_model: PASS on onnx 1.19.1 and 1.22.0.
  • ONNX Runtime 1.27.0 (CPU): executes the model.
  • AMD MIGraphX read … --onnx: rejected at import on both tested builds:
    • released 2.15.0 — onnx_parser.cpp:366 parse_inputs: … "gru_cell_0.w_1" existing in parent graph!
    • develop source build (commit 49f842b, reported 2.17.0) — onnx_parser.cpp:409 parse_inputs: … "gru_cell_0.w_1" existing in parent graph!

Expected: nested-subgraph local definitions whose names are distinct from any name visible from an enclosing scope, so the artifact imports consistently across runtimes that follow the published ONNX IR rule.

ONNX specification / root cause

Nested subgraphs are lexically scoped: a subgraph may capture outer-scope values, but its own inputs, initializers, and node outputs must not reuse a name visible from an outer scope. The published ONNX IR states (onnx/onnx docs/IR.md, pinned to tag v1.22.0):

"… a node output name and names of inputs and initializers of the subgraph MUST be distinct from the names from the outer scopes that are visible in the nested subgraph. That is, variable shadowing is not allowed."

History (verified): the question of whether this covers subgraph inputs/initializers was raised in onnx issue #2303 and resolved by PR #6955 ("Clarify that variable shadowing is not allowed"); that wording is present from onnx v1.19.0 through the current v1.22.0. A later proposal to allow variable shadowing, RFC #7012, was closed as not planned, so the prohibition remains in the published specification. (onnx.checker, in the versions tested, does not reject this artifact for these collisions; that is not a statement about all ONNX versions or a judgment of the checker.)

The collisions most likely originate in the Paddle-originated export pipeline; the exact exporter and version are not identifiable from the artifact (producer_version is empty).

Validated candidate fix

A conservative, scope-safe local alpha-renaming resolves the conformance defect on this artifact (audit and rewrite scripts were used locally; I can provide them in a follow-up or a small reproduction repository if useful — they are not attached here). The rewrite:

  • renames only the 16 colliding nested-subgraph-local definitions (and the references that resolve to them by lexical scoping; a subgraph output name is changed only when it equals the renamed value);
  • does not touch parent-graph names, initializer tensor contents, node order, opset, IR version, or the Loop node's positional actual inputs.

Verification:

  • After rewriting: nested-scope audit reports 0 collisions; onnx.checker still passes (onnx 1.19.1 and 1.22.0); structural diff confirms only the intended name changes (opset, IR, top-level I/O names, per-graph node sequence, and initializer bytes all unchanged).
  • For one tested input (x, shape [1,3,488,488], float32, generated by np.random.default_rng(0).standard_normal((1,3,488,488)).astype(np.float32), NumPy 1.26.4; input-array SHA-256 1b577ec5b09f9626d594d2b756479b64e175c15c29f36df9bf2b0d7c9cc5b6b5), the original and rewritten models produce exactly equal outputs (np.array_equal == True; max_abs_diff == 0.0; no NaN/Inf) for both outputs. This is a single test case, not a proof of equivalence for all inputs.
  • On MIGraphX, the rewritten artifact is accepted past the nested-scope naming check on both 2.15.0 and the develop build. It then reaches a separate MIGraphX limitation triggered by the model's data-dependent Range operation (PARSE_RANGE on 2.15.0; on the develop build Range itself is handled but a downstream op on the resulting data-dependent dynamic shape fails). That limitation is independent of the nested-scope naming defect reported here.

A verified re-export that produces unique names across visible nested scopes is the preferred long-term fix.

Requested action

Could the affected artifact be replaced with either a verified re-export or a semantics-preserving alpha-renamed version? For a replacement it would be helpful to:

  • keep the official distribution locations synchronized (Hugging Face and the ModelScope mirror referenced in the README);
  • publish the new artifact checksum;
  • record the exporter version, if available.

Suggested acceptance criteria: nested-scope audit clean; onnx.checker still passes; outputs on a fixed test input match the current artifact; MIGraphX no longer fails at the nested-scope naming check on this artifact; new checksum traceable. There is no expectation that the maintainers adopt any specific binary I generated.

Environment

  • OS: Linux x86_64 (Ubuntu, kernel 6.8.0-79-generic); GPU: AMD gfx1100; ROCm 7.2.1.
  • MIGraphX: released 2.15.0; and a develop source build (commit 49f842b, git describe --always = 49f842b, reported version 2.17.0).
  • Python 3.12.3; onnx 1.19.1 and 1.22.0; onnxruntime 1.27.0 (CPU); NumPy 1.26.4.

Additional evidence

Full list of 16 Loop-body-input collisions (audit output)
model: slanet-plus.onnx  sha256: d57a942a…  opset ai.onnx:14  ir 7
subgraph: model.graph > Loop.body  — all 16 are body formal inputs; each collides with a
parent-graph Constant node-output defined before the Loop (Constant@373–388):

  gru_cell_0.w_1, linear_2.w_0, gru_cell_0.w_0, linear_6.b_0, linear_4.w_0, linear_5.w_0,
  linear_3.w_0, linear_5.b_0, linear_0.w_0, linear_6.w_0, linear_1.b_0, gru_cell_0.b_0,
  gru_cell_0.b_1, linear_4.b_0, linear_3.b_0, linear_1.w_0
MIGraphX import (original vs rewritten, both tested builds)
# released 2.15.0
migraphx-driver read slanet-plus.onnx --onnx
  -> onnx_parser.cpp:366 parse_inputs: ... "gru_cell_0.w_1" existing in parent graph!  (exit 134)
migraphx-driver read slanet-plus.rewritten.onnx --onnx
  -> accepted past the nested-scope naming check; then:
     checks.cpp:35 check_arg_empty: PARSE_RANGE: start arg dynamic shape is not supported  (exit 134)

# develop source build (commit 49f842b, reported 2.17.0)
migraphx-driver read slanet-plus.onnx --onnx
  -> onnx_parser.cpp:409 parse_inputs: ... "gru_cell_0.w_1" existing in parent graph!  (exit 134)
migraphx-driver read slanet-plus.rewritten.onnx --onnx
  -> accepted past the nested-scope naming check; then:
     checks.cpp:35 check_arg_empty: PARSE_TILE: dynamic shape is not supported  (exit 134)
Original-vs-rewritten equivalence (single test input)
input: x, shape [1,3,488,488], float32
  expr: np.random.default_rng(0).standard_normal((1,3,488,488)).astype(np.float32)
  numpy 1.26.4, seed 0
  input-array SHA-256: 1b577ec5b09f9626d594d2b756479b64e175c15c29f36df9bf2b0d7c9cc5b6b5
test cases: 1
output[0] save_infer_model/scale_0.tmp_0  [1,18,8]  float32  np.array_equal=True  max_abs_diff=0.0  NaN=no Inf=no
output[1] save_infer_model/scale_1.tmp_0  [1,18,50] float32  np.array_equal=True  max_abs_diff=0.0  NaN=no Inf=no
=> exactly equal for this tested input (single case; not a general equivalence proof)

Source: opendatalab/PDF-Extract-Kit