3988 Commits

Author SHA1 Message Date
Takeshi Watanabe c12d3a8b3e Fix LoadProtoFromPath's byte-at-a-time file read (#8345)
std::string data{istreambuf_iterator{stream}, istreambuf_iterator{}}
copies a file one character at a time (each increment pays a stream
buffer-boundary check), instead of one bulk read. For small models this
is noise; for a large one it is seconds -- measured costing the bulk of
a ~16s gap between onnx-optimizer's path-based loadModel/saveModel and
the equivalent Python-side onnx.load on an 833MB model
(https://github.com/onnxsim/onnxsim/issues/633's investigation into
loadModel's path-based entry points, used by its own SimplifyPath fast
path).

Sizes the file up front via std::filesystem::file_size and does a single
read() into a pre-sized string; falls back to the old iterator-based
read if the size can't be determined (e.g. a pipe). Correctness is
checked via gcount() rather than the stream's good()/eof() flags, since
a read() that consumes exactly to EOF can set eofbit on a stream
implementation even though every requested byte was read.

(cherry picked from commit 4784075aa7d40a771eaf70c12d0eeaee3d5d3a17)



### Motivation and Context

Fixes #

Signed-off-by: take-cheeze <takechi101010@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-21 16:22:41 +08:00
Christian Bourjau 39ba0b628b Remove pixi-shim.py (#8264)
The shim was a nice try, but #8257 indicates that its hacky approach
(using the system's python interpreter to call pixi to call Python tools
such as `reuse` using a different interpreter) creates mysterious bugs
in the evoked tools. I don't think that anybody is actually using it as
intended (i.e., as a fallback if pixi is not installed). I think we
should just remove it. Non-pixi users can still continue to use
lintrunner as they do today.

The idea of the shim was to have a platform-agnostic way of calling into
our environment. It essentially aliases `pixi run` when pixi is not
installed. However, to correctly do this accross all supported platforms
and shells is a project by itself. I think people would be better of
using their own specific workarounds if they really, really want to.

---------

Signed-off-by: Christian Bourjau <christian.bourjau@quantco.com>
Co-authored-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-21 09:47:33 +02:00
Andreas Fehlner e3b033e7a4 fix: guard ExtendSupportedTypes against missing output in version converter (#8274)
### Motivation and Context

`ExtendSupportedTypes::adapt_type_extension` backs the opset 9 → 8
down-conversion
adapters for `Flatten`, `Constant`, `MatMul`, `Gemm`, `PRelu`,
`Greater`, and `Less`.
It assumes every node it processes has exactly one output. That
assumption was
already implicitly enforced by `Node::output()` (which asserts
`outputs_.size() == 1`), but a node with the wrong output count only
produced a
generic, file/line-only assertion message with no indication of which op
was
involved or how many outputs were actually found — unhelpful when a
malformed
or hand-crafted `ModelProto` reaches the version converter.

This PR makes the invariant explicit and gives it a descriptive message
before
the output is used, instead of relying on the implicit check inside
`Node::output()`.

### What changed

- `onnx/version_converter/adapters/extend_supported_types.h`: add an
`ONNX_ASSERTM` that reports the adapter name and the actual output count
  when a node passed to `adapt_type_extension` does not have exactly one
  output, and use `outputs[0]` (now that arity is validated) instead of
  `node->output()`.
- `onnx/test/version_converter_test.py`: add
  `test_extend_supported_types_rejects_missing_output`, which converts a
deliberately malformed model (a `Flatten` node with no output) from
opset 9
  to opset 8 and asserts the new, descriptive error is raised.

### Testing

- `pytest onnx/test/version_converter_test.py -k extend_supported_types`

---------

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-21 06:08:08 +00:00
Andreas Fehlner 1c1a28d61d Close check_tensor size-validation gap for six field families (#8319)
## Summary
- `check_tensor` (`onnx/checker.cc`) validates that a tensor's data
field holds enough entries for the declared shape for the
`int32_data`-backed types and the packed sub-byte `raw_data` path
(UINT4/INT4/FLOAT4E2M1, UINT2/INT2), but six other field families only
checked that the field was **non-empty**, not that it actually held
enough data for the declared shape:
  - `float_data` (FLOAT, COMPLEX64)
  - `double_data` (DOUBLE, COMPLEX128)
  - `int64_data` (INT64)
  - `uint64_data` (UINT32, UINT64)
  - `string_data` (STRING)
- `raw_data` for regular, non-packed types (e.g. a FLOAT tensor storing
its data as raw bytes)
- This meant a tensor could declare a large shape while its backing data
field held far fewer values/bytes than that implies, which
`checker.check_model`/`checker.check_tensor` would silently accept.
- Add explicit minimum-size checks for all six, following the same
pattern already used for `int32_data`. COMPLEX64/COMPLEX128 need 2
value-field entries per element (interleaved real/imaginary). Byte-count
multiplications (`nelem * bytes_per_element`) use the existing
`checked_mul_overflow` helper (already used by `safe_dim_product`) to
avoid overflow on adversarially large declared shapes.

## Test plan
- [x] Added regression tests in `tests/python/checker_test.py`:
`test_check_tensor_float_double_data_too_small`,
`test_check_tensor_complex_data_too_small`,
`test_check_tensor_int64_uint64_data_too_small`,
`test_check_tensor_string_data_too_small`,
`test_check_tensor_raw_data_regular_types_too_small` — each checks both
the too-small-rejects and exact-size-passes cases.
- [x] `pixi run pytest tests/python/checker_test.py` — 65 passed.
- [x] `pixi run pytest tests/python` (full suite) — 6288 passed, 4034
skipped, 2 xfailed, no failures.
- [x] `pixi run gtest` — 143 passed.
- [x] `lintrunner` — clean.
- [x] Full rebuild via `pixi run install` (ONNX_BUILD_TESTS=1,
ONNX_HARDENING=ON, ONNX_WERROR=ON) succeeded.

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
Co-authored-by: Yuanyuan Chen <cyyever@outlook.com>
2026-08-20 21:03:53 +02:00
Kiwi 299ec33f2c fix(docs): restore operator examples (#8332)
### Motivation and Context

Fixes #8314

The operator docs generator passed the default ONNX domain as an empty
string, but the example lookup did not recognize it. It also assumed
non-default examples lived in domain subpackages and depended on
filenames matching operator names. This omitted existing examples from
generated pages and placed GreaterOrEqual and LessOrEqual examples under
Greater and Less in `docs/Operators.md`.

This treats the empty string as the default domain, limits top-level
fallback to the preview domains, aligns legacy module and exporter names
where safe, and keeps Range's legacy module through an explicit alias.
Example source is rendered directly inside trusted code fences so the
Sphinx build can highlight it.

Tested:

- `python -m pytest -q tests/python/onnx_sphinx_test.py` (12 passed)
- `python -m pytest -q tests/python/backend_test.py -k 'batchnorm or
greater_equal or instancenorm or less_equal or test_range or
softmaxcrossentropy'` (51 passed, 51 skipped)
- `ONNX_ML=1 python onnx/defs/gen_doc.py`
- `lintrunner -r HEAD`
- `cd docs/docsgen && make html`

---------

Signed-off-by: kiwigitops <kiwisclubco@gmail.com>
Co-authored-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-20 10:20:56 -07:00
Artur Cygan bf0bd61413 fix(reference): reject out-of-range Concat axes (#8327)
The Concat reference implementation implicitly extends input rank for
axes beyond the valid range, allocating a model-controlled tuple. Reject
axes outside `[-rank, rank-1]` before evaluation.

Reproducer:
[model.onnx.zip](https://github.com/user-attachments/files/31177450/model.onnx.zip)

`model.onnx` contains a single opset-5 `Concat` node with one rank-1
input and `axis = 2147483647`. It is loaded and executed through public
Python APIs:

```python
import numpy as np
import onnx
from onnx.reference import ReferenceEvaluator

model = onnx.load("model.onnx")
ReferenceEvaluator(model).run(None, {"value0": np.zeros((1,), dtype=np.float32)})
```

### Security Impact
A malicious ONNX model can exhaust memory during Concat evaluation by
specifying an extremely large axis, causing denial of service. Model
execution through the reference evaluator is required.

### Motivation and Context
This bug was found by Artur Cygan of Trail of Bits in collaboration with
OpenAI (Patch the Planet initiative).

Signed-off-by: Artur Cygan <artur.cygan@trailofbits.com>
Co-authored-by: Yuanyuan Chen <cyyever@outlook.com>
2026-08-20 10:14:43 -07:00
Andreas Fehlner 8018cbb7ff docs(community): add GitHub Organization Seat Policy (#8223)
## Summary

Adds a **GitHub Organization Seat Policy** with explicit removal
triggers, motivated by the limited number of org member seats available.
The policy distinguishes GitHub org membership (a limited "seat") from
Contributor/Approver governance status: inactivity, resignation, or a
Code of Conduct ruling ends both; removal solely due to seat pressure
ends only org membership, leaving governance status and SC voting
eligibility intact.

This is a **Work in Progress** section and is **not yet ratified** by
the Steering Committee.

## Motivation

The ONNX GitHub organization has a limited number of member seats.
Without a defined removal policy, inactive members occupy seats
indefinitely. This PR proposes: inactivity for 12 consecutive months
triggers removal of both org membership and governance status at an
annual review. The SIG Architecture & Infra chairs may free capacity
earlier by removing the least-recently-active members' org membership
only. Members removed solely for seat capacity keep their
Contributor/Approver status and voting eligibility and can be re-added
once a seat frees up; members removed for inactivity, resignation, or a
Code of Conduct ruling must requalify through the normal sponsorship
process.


## Context

This is one of three PRs splitting #8124 into smaller, independently
reviewable pieces. This is the piece most likely to need extended
Steering Committee discussion, so it's kept isolated from the other two
so it doesn't block them:

1. Release Manager role (#8221)
2. Contributor Ladder table + GitHub org membership process (#8222)
3. **This PR** — GitHub Organization Seat Policy

Note: this PR and #8222 both insert content near the same location in
`community/readme.md` (just before "Organizational Structure"), so
whichever merges second may need a quick rebase — no content overlap
otherwise.

## Status

Draft / RFC — the seat threshold and annual review timing are
placeholders pending SC discussion.

See #8124 for the original combined discussion and open questions.

## DCO fix

Two commits (`docs(community): tie seats to SIG Teams, make audits
demand-triggered` and `docs(community): clarify seat and voting
eligibility are independent`) were missing the required `Signed-off-by`
trailer and failed the DCO check. History was rewritten to add the
missing sign-offs to those two commits only, preserving the original
trees, authors, and commit messages otherwise; the resulting tree is
byte-identical to before. DCO compliance is a project security
requirement, not just process: the sign-off is a Developer Certificate
of Origin attestation that the submitter has the right to contribute the
code under the project's license, which underpins the provenance and IP
chain of custody for everything merged into ONNX. All commits now pass
DCO.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-20 12:17:46 +02:00
Yuanyuan Chen 19e8e7d6c9 ci: ignore link-checker URLs that block automated requests (#8338)
### Motivation and Context

The **Check URLs** workflow is failing on `main` — every scheduled and
push run for the last several days
([latest](https://github.com/onnx/onnx/actions/runs/32334272449)):

```
🔍 2079 Total  🔗 1331 Unique   2053 OK  🚫 6 Errors  👻 20 Excluded  🔀 58 Redirects
```

All 6 are `403 Forbidden` from live pages that reject the link checker,
not broken links:

| URL | Referenced from |
|---|---|
| `https://lfaifoundation.slack.com/` | `CONTRIBUTING.md:13`,
`README.md:70` |
| `https://lfaifoundation.slack.com/archives/C016UBNDBL2` |
`docs/OnnxReleases.md:136` |
| `https://lfaifoundation.slack.com/archives/C018VGGJUGK` |
`docs/OnnxReleases.md:15`, `:136` |
| `https://gitlab.kitware.com/cmake/cmake/-/issues/25145` |
`CMakeLists.txt:148` |

The Slack links 302-redirect to a login page that then answers 403. The
Kitware issue is reachable — it returns 200 to a plain request and 403
to a browser-like one, i.e. fingerprint-based bot filtering, not a dead
page. In both cases the destination is correct and should stay in the
docs.

### Changes

Add all four to `.lycheeignore`, which already carries entries for
exactly this category (`join.slack.com`, Cloudflare-fronted pages,
StackOverflow).

The Slack workspace is matched by host —
`https://lfaifoundation\.slack\.com/.*` — rather than URL by URL. Every
link to that workspace is blocked the same way, and
`docs/OnnxReleases.md` gains channel archive links over time; matching
per-URL would just break the workflow again on the next one. Verified
the pattern matches the five failing occurrences and nothing else in the
repo (in particular it does not swallow the existing `join.slack.com`
invite entry).

The Kitware link is listed individually since it is the only one.

### Validation

`lychee` is not installable in my environment, so I could not run the
checker locally. Verified instead that:

- each URL is live and returns 403 only to automated requests
- the regex matches exactly the 5 failing occurrences and no other URL
in the tree

CI on this PR will exercise the real check, since `.lycheeignore`
changes are picked up by the same workflow. Worth confirming the run
goes green before merging.

### Maintainer label note

Suggested labels: `topic: CI`.

---------

Signed-off-by: Yuanyuan Chen <cyyever@outlook.com>
2026-08-20 14:27:27 +08:00
Владимир 0b5258f97b Fix Unique backend test output shapes (#8180)
### Motivation and Context

Unique output shapes depend on the input values. this PR makes them
dynamic in backend tests and regenerates the affected models.

Fixes https://github.com/onnx/onnx/issues/6133

---------

Signed-off-by: DarkBall123 <vitalya228007@gmail.com>
Co-authored-by: Christian Bourjau <cbourjau@users.noreply.github.com>
Co-authored-by: Yuanyuan Chen <cyyever@outlook.com>
Co-authored-by: Andreas Fehlner <fehlner@arcor.de>
Co-authored-by: Christian Bourjau <christian.bourjau@quantco.com>
2026-08-20 05:47:19 +00:00
FuZoe 7a4496a476 Add local_window_size attribute to Attention opset 25 (#8108)
### Motivation

Fixes #7914.

This adds `local_window_size` to Attention opset 25 for autoregressive
sliding-window attention, including static and dynamic KV-cache use
cases.

### Scope and semantics

This version intentionally defines a **causal left window**:

- `local_window_size > 0` requires `is_causal=1`.
- A query at absolute position `p = offset + i` attends key `j` iff `0
<= p - j < local_window_size`.
- `local_window_size = -1` (default) disables the window and preserves
opset-24 behavior.
- `0` and values below `-1` are rejected.

The predicate is inherently left-looking and causal. Requiring
`is_causal=1` makes that behavior explicit instead of letting the window
silently override `is_causal=0`. The motivating models use
autoregressive left windows. Symmetric or bidirectional local attention
would need separately agreed left/right-window semantics; this PR does
not define those semantics.

### Implementation

- Adds the opset-25 schema, function body, Python reference
implementation, and 25-to-24 converter rule.
- Preserves standard right-aligned ONNX broadcasting for rank-1/2/3/4
and unknown-rank masks.
- Supports internal and external caches with offset-aware window masks.
- Validates window values, causal mode, cache pairing/mixing, and 4-D
head attributes.
- Keeps the shared Attention helper and opset-24 behavior unchanged.
- Casts causal, window, and external-cache padding masks with `CastLike`
so floating-point attention masks retain the attention bias element
type.

### Coverage

Coverage includes conventional mask ranks, internal/external caches, 3-D
MQA, 4-D GQA, distinct V head size, boolean masks, softcap, softmax
precision, mode-3 QK output, and fully masked rows.

Local validation:

- Attention backend reference regression: 218 passed
- Attention version-converter tests: 16 passed
- Attention shape-inference tests: 42 passed
- Reference cache-validation tests for omitted and disabled windows: 2
passed
- Full editable C++ build; lintrunner reports only two pre-existing
function-name warnings in `attention.py`

---------

Signed-off-by: FuZoe <fxq4533@163.com>
Co-authored-by: Yuanyuan Chen <cyyever@outlook.com>
2026-08-20 05:35:46 +00:00
Yuanyuan Chen c07b818143 test: fix typo flagged by the typos lint hook (#8339)
### Motivation and Context

The `Lint` job is failing on `main` and therefore on every open PR (e.g.
#8108):

```
typos....................................................................Failed
  error: `mis` should be `miss`, `mist`
      ╭▸ tests/python/printer_test.py:168:22
  168 │         # Printing a mis-sized tensor would emit text that re-parses as valid.
```

My fault — the comment came in with #8337. `typos` runs as a `prek` hook
via `pixi run lint`, not through `lintrunner`, which is why it slipped
past my local checks.

### Changes

Reword the comment to "wrongly sized".

### Validation

- `typos` clean on all files touched by #8337.
- `pytest tests/python/printer_test.py`: 34 passed.
- `lintrunner` clean.

### Maintainer label note

Suggested labels: `topic: CI`.

Signed-off-by: Yuanyuan Chen <cyyever@outlook.com>
2026-08-20 13:29:47 +08:00
Yuanyuan Chen 863d78e4df fix(printer): decode all fixed-width raw_data types (#8337)
### Motivation and Context

Follow-up to #8308, which added 16-bit `raw_data` decoding to the
textual printer. Three gaps remained:

1. **`raw_data` length was not validated against `dims`.** The 16-bit
decoder consumed whatever bytes were present, so `dims=[2]` with 6 bytes
printed three elements and round-tripped silently (`checker.cc` only
rejects `int32_data().size() < nelem`), while `dims=[4]` with 2 bytes
printed text that later failed `check_model` with a confusing
"int32_data size too small".

2. **Most fixed-width types still printed `...`.** INT8, UINT8, BOOL,
INT16, UINT16, UINT32, UINT64 and the FLOAT8 types are all
byte-addressable and already accepted by the parser, and the printer's
own non-raw branch already prints them — but their `raw_data` was
undecodable. Since `numpy_helper.from_array` produces `raw_data`,
`onnx.save_model(m, p, format="onnxtxt")` → `onnx.load_model(p,
format="onnxtxt")` was broken for every quantized model.

3. **The non-raw branch printed nothing for types it did not handle**,
so a FLOAT8 or INT4 tensor printed `float8e4m3fn[3] w = ` and re-parsed
into an empty tensor — silent data loss with no error.

### Changes

- Lift the raw-decode tail out of `DEFINE_PARSE_DATA` into a shared
`ParseRawData<T>` template in `tensor_proto_util.h`, plus a non-template
`RawDataElementCount` for the length check. `ParseData`'s raw branch
becomes a one-line delegate (the macro loses ~30 lines), and the printer
instantiates the same template for the element types `ParseData` does
not cover.
- The decode itself is unchanged — bulk `memcpy`, then a per-element
`std::reverse` only on big-endian hosts, exactly as `ParseData` already
did.
- Require `raw_data.size()` to match `dims` exactly **in the printer**
(`exact_fit`). `ParseData` keeps its existing leniency — too-short
fatal, trailing bytes ignored — so no existing caller changes behavior.
- Route INT8, UINT8/BOOL/FLOAT8\*, INT16, UINT16/FLOAT16/BFLOAT16, INT32
into `int32_data`; UINT32/UINT64 into `uint64_data`; INT64 into
`int64_data` — matching what the non-raw branch and the parser already
do for each type.
- Add the FLOAT8 types to the non-raw `int32_data` case, and give that
branch the same `...` fallback the raw branch already had.

FLOAT16/BFLOAT16 output is unchanged — still the unsigned 16-bit pattern
the parser accepts.

Sub-byte packed types (INT2/INT4/UINT2/UINT4/FLOAT4E2M1) still print
`...`: their element count cannot be derived from `sizeof()`, and the
packing rule (`ceil(n*k/8)`) exists in C++ only inline in `checker.cc`.
Complex and string need a different element form.

### Behavior change

`to_text` now raises `InferenceError` on any fixed-width tensor whose
`raw_data` does not match `dims`, where FLOAT16/BFLOAT16 previously
printed a best-effort initializer and the newly-decoded types previously
printed `...`. This makes all fixed-width types consistent, and matches
`ParseData`'s long-standing behavior for too-short data.

Two things worth a maintainer's call:

- A printer that refuses to print a malformed tensor cuts against its
role as a debugging aid, and under `ONNX_DISABLE_EXCEPTIONS` this path
`abort()`s rather than throwing. Happy to emit a placeholder for the bad
tensor instead of failing the whole document.
- This validation arguably belongs in `checker.cc`. `check_tensor`'s
`expected_bytes` switch covers only the sub-byte types and `default:
break`s on every fixed-width one, so `check_model` currently **passes**
a FLOAT16 tensor with `dims=[4]` and 2 bytes of `raw_data`. The printer
is the only thing in the tree that catches this. Tightening the checker
has ecosystem blast radius, so I left it alone — flagging it so the gap
does not stay invisible.

### Known gaps, not addressed here

- Sub-byte packed types are excluded because `RawDataElementCount` takes
a `size_t element_size`, which cannot express half a byte — not because
the round-trip is out of reach. `parser.cc` already accepts
INT2/INT4/UINT2/UINT4/FLOAT4E2M1 into `int32_data`, and the packing
formula already exists inline at `checker.cc:184` and `:188`. A
`bits_per_element` parameter would cover them.
- `data_type → destination field` is still enumerated independently in
three places: the printer's raw and non-raw switches and `parser.cc`.
This PR had to add the five FLOAT8 cases to both printer switches, which
is the drift that mapping would prevent. C++ has no analogue of Python's
`helper.tensor_dtype_to_field`.
- A complex initializer in `float_data`/`double_data` still prints `...`
even though `parser.cc` accepts exactly that form.

### Validation

- `pytest tests/python/printer_test.py`: 34 passed. Full `pytest
tests/`: 6346 passed (3 failures pre-exist on `be2925b6` and are
unrelated). `onnx_gtests`: 147 passed.
- `ParseRawData` and `RawDataElementCount` are new shared symbols on the
`ParseData` path, so `tests/cpp/tensor_test.cc` gains six direct tests:
little-endian decode, trailing bytes tolerated by default, `exact_fit`
rejecting them, insufficient data, float bit patterns, and the empty
tensor.
- Confirmed `ParseData`'s leniency survives the refactor: a `Reshape`
shape initializer with two trailing bytes still infers `[2,3]` rather
than raising.
- Verified `to_text` → `parse_graph` → `numpy_helper.to_array`
round-trips for all 12 fixed-width dtypes, including `int8 -128`,
`uint16 65535`, `uint64 2**64-1`, and float/double
`inf`/`-inf`/`nan`/`-0`.
- Reverting only `printer.cc` makes the new tests fail, so they lock in
the fix.
- `lintrunner` and `clang-tidy` clean on both changed files.

Suggested labels: `topic: bug fix`, `module: parser`.

Signed-off-by: Yuanyuan Chen <cyyever@outlook.com>
2026-08-20 04:57:41 +00:00
OllieinCanada be2925b62f fix(printer): print float16 initializer data (#8308)
### Motivation and Context

Fixes #7053

The textual ONNX printer currently emits `...` for float16 and bfloat16
initializers stored in `raw_data`, so printed models lose their
initializer values and cannot round-trip through the parser.

### Changes

- decode 16-bit raw initializer words in the format required by the ONNX
text parser
- print typed `int32_data` for float16 and bfloat16 tensors as well
- add a float16 initializer print/parse round-trip regression test

The representation is bit-preserving: for `[1.0, -2.0, 0.5]`, the
printer emits the corresponding 16-bit words `{15360,49152,14336}`,
which the parser accepts for a float16 tensor.

### Validation

- reproduced the pre-fix behavior: `float16[3] weights = ...`
- built the native extension and C++ tests under Linux with the
prescribed Pixi environment, hardening enabled, and warnings denied
- `pytest tests/python/printer_test.py -q`: 8 passed
- Ruff check, Ruff format, and clang-format pass for the changed files
- `git diff --check` passes

Signed-off-by is included in every commit as required. This PR was
prepared with AI assistance and manually reviewed.

### Maintainer label note

GitHub does not permit outside contributors to apply upstream labels.
Suggested labels for the required PR-label check: `topic: bug fix` and
`module: parser`.

---------

Signed-off-by: Oliver Slapinski <olliefromcanada@gmail.com>
Co-authored-by: Oliver Slapinski <olliefromcanada@gmail.com>
Co-authored-by: Yuanyuan Chen <cyyever@outlook.com>
2026-08-20 11:06:37 +08:00
BW-SK 41e74c50e6 docs: clarify spurious unknown variable in 'Slice' operator (#8335)
Specifically, there is no "start" variable, and the sentencte doesn't
quite make sense.

I've tried to follow
https://github.com/onnx/onnx/blob/2e870ab6bd9bcb6f8d97db56ea2b36417ae85abe/CONTRIBUTING.md#generate-operator-documentation
as best as I could, but there were some errors when trying to install
pixi.

It compiles using `pip install -e . -v`, and since this PR only touches
the documentation, I think this should be fine.

I assume the [SIG
Operators](https://github.com/onnx/sigs/tree/main/operators) are the
relevant SIG for review here.

### Motivation and Context

I'm using onnx for my first time, and on a small project, and still have
to read the documentation very closely. That's why this malformed
sentence in the documentation stuck out to me.

### Fixes

This commit fixes the variable name, the sentence, and re-creates
Operators.md (via python onnx/defs/gen_doc.py ).

Signed-off-by: Ben.W <Ben.W@shiratech.ai>
Co-authored-by: Yuanyuan Chen <cyyever@outlook.com>
2026-08-20 01:29:40 +00:00
Artur Cygan aac6d3f891 fix(version_converter): prevent initializer-name use-after-free (#8334)
The Upsample 9-to-8 adapter contains a use-after-free: it passes an
initializer-owned name by reference to `Graph::eraseInitializer`, which
destroys the string before subsequent bookkeeping reuses it. Copy the
name before erasure and add regression coverage using a heap-backed
initializer name.

Reproducer:
[model.onnx.zip](https://github.com/user-attachments/files/31218118/model.onnx.zip)

The reproducer contains an opset-9 Upsample node with initializer-backed
scales and triggers the use-after-free when converted to opset 8.

```python
import onnx

model = onnx.load("model.onnx")
onnx.checker.check_model(model)
onnx.version_converter.convert_version(model, 8)
```

### Security Impact
A checker-accepted ONNX model can trigger this use-after-free during
explicit conversion from opset 9 to 8, potentially crashing the process
and causing denial of service. Exploitability appears low because the
dangling reference is only read immediately after the free, with no
intervening allocation that could replace its model-controlled contents;
we found no write primitive, data disclosure, or path to code execution.

### Motivation and Context
This bug was found by Artur Cygan of Trail of Bits in collaboration with
OpenAI (Patch the Planet initiative).

Signed-off-by: Artur Cygan <artur.cygan@trailofbits.com>
Co-authored-by: Yuanyuan Chen <cyyever@outlook.com>
2026-08-20 01:28:50 +00:00
Kiwi 6dd911b293 Support higher-rank ScatterElements evaluation (#8299)
### Description

The reference implementation handled only inputs with one to four
dimensions and raised `NotImplementedError` for valid higher-rank
tensors.

This adds a dimension-agnostic update path for higher-rank inputs while
keeping the existing specialized paths, plus a rank-five
`ReferenceEvaluator` regression test.

### Testing

- `pytest -q tests/python/reference_evaluator_test.py` (248 passed, 8
skipped)
- `pytest -q tests/python/backend_reference_test.py -k scatter_elements`
(7 passed, 7 skipped)
- `ruff check onnx/reference/ops/op_scatter_elements.py
tests/python/reference_evaluator_test.py`
- `ruff format --check onnx/reference/ops/op_scatter_elements.py
tests/python/reference_evaluator_test.py`

Signed-off-by: kiwigitops <kiwisclubco@gmail.com>
2026-08-19 13:13:45 -07:00
Kiwi 4f397cdced fix(reference): handle zero-length ReverseSequence slices (#8304)
## Summary

- preserve a batch slice when its `sequence_lens` entry is zero
- add a `ReferenceEvaluator` regression test covering zero and nonzero
lengths

The reference implementation currently builds a full reverse slice for a
zero length and then tries to assign it into an empty destination,
raising a broadcast error. A zero-length prefix is a no-op, so the
evaluator can leave that copied slice unchanged.

## Testing

- `python -m pytest tests/python/reference_evaluator_test.py -q` (251
passed, 8 skipped)
- `lintrunner`

Signed-off-by: kiwigitops <kiwisclubco@gmail.com>
2026-08-19 13:13:20 -07:00
Artur Cygan 2771008735 fix(shape_inference): bound inferred rank materialization (#8321)
Shape inference can materialize an unbounded number of dimensions when a
model declares an extremely large shape-vector length. This affects both
generic shape-input inference and Col2Im, allowing a very small model to
exhaust memory. This change applies a shared rank-materialization limit
and adds regression tests for both paths.

Reproducers:
[models.zip](https://github.com/user-attachments/files/31149903/models.zip)

### Security Impact
A malicious or malformed ONNX model can exhaust memory and CPU during
shape inference, potentially crashing or making applications that
inspect uploaded models unavailable. No model execution is required;
loading the model and running shape inference is sufficient.

### Motivation and Context
This bug was found by Artur Cygan of Trail of Bits in collaboration with
OpenAI (Patch the Planet initiative).

Signed-off-by: Artur Cygan <artur.cygan@trailofbits.com>
2026-08-19 13:11:59 -07:00
Artur Cygan 31232fa08f fix(shape_inference): reject zero scalar SplitToSequence split (#8322)
SplitToSequence shape inference performs modulo by a scalar split value
without checking whether it is zero. Reject zero scalar splits before
the calculation.

### Security impact
A malicious or malformed ONNX model can crash an application during
shape inference through integer division by zero, causing denial of
service. Model execution is not required.

### Motivation and Context
This bug was found by Artur Cygan of Trail of Bits in collaboration with
OpenAI (Patch the Planet initiative).

Signed-off-by: Artur Cygan <artur.cygan@trailofbits.com>
2026-08-19 12:52:59 -07:00
Andreas Fehlner 2e870ab6bd Update roadmap with new dates and task adjustments (#8295)
### Motivation and Context

Fixes #

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-17 11:34:14 -07:00
Artur Cygan 7d95a87860 fix(shape_inference): prevent use-after-scope during function inference (#8303)
`ShapeInferenceImplBase` stores the model-local function map by
reference, but `InferFunctionOutputTypes` passed a temporary empty map.
Accessing that dangling reference results in an ASan
stack-use-after-scope report.

Reproducer:
[model.onnx.zip](https://github.com/user-attachments/files/31066618/model.onnx.zip)

Fix: Keep the map alive for the duration of inference and add a
regression test for a function containing an unsupported operator.

### Security Impact
A specially crafted ONNX model can cause function output type inference
to access an object after it has gone out of scope. This may crash
applications that inspect untrusted models or produce unpredictable
results; AddressSanitizer detects it as a stack-use-after-scope. We have
not demonstrated data disclosure or code execution.

### Motivation and Context
This bug was found by Artur Cygan of Trail of Bits in collaboration with
OpenAI (Patch the Planet initiative).

Signed-off-by: Artur Cygan <artur.cygan@trailofbits.com>
2026-08-17 11:33:13 -07:00
Andreas Fehlner 5aaf09bc06 Raise on malformed proto bytes instead of ignoring parse failure (#8318)
## Summary
- `ParseProtoFromPyBytes` (`onnx/py_utils.h`) returns `false` when the
input is malformed, truncated, or exceeds the proto size limit.
- Every call site in `onnx/cpp2py_export.cc` that uses it as a plain
statement was discarding that return value, so passing bad bytes from
Python (e.g. to `checker.check_model`, `shape_inference.infer_shapes`,
`version_converter.convert_version`, the inliner bindings, etc.)
silently proceeded with a partially-populated or empty proto instead of
raising an error.
- Add `ParseProtoFromPyBytesOrThrow`, which raises `ValueError` on parse
failure, and route all ~15 statement-form call sites through it.
- The `ONNX_DEFINE_TYPE_CASTER` macro's `from_python` still uses the raw
boolean-returning `ParseProtoFromPyBytes`, since nanobind's type-caster
protocol needs the `bool` itself to report a cast failure rather than an
exception.

## Test plan
- [x] Added `test_check_model_malformed_bytes_raises` in
`tests/python/checker_test.py`, asserting `checker.check_model(b"...")`
with unparseable bytes raises `ValueError` instead of silently
succeeding or producing a confusing downstream error.
- [x] `pixi run pytest tests/python/checker_test.py` — 61 passed.
- [x] `pixi run pytest tests/python/shape_inference_test.py
tests/python/basic_test.py tests/python/parser_test.py` — 1066 passed,
53 skipped, 2 xfailed, no failures.
- [x] Full rebuild via `pixi run install` (ONNX_BUILD_TESTS=1,
ONNX_HARDENING=ON, ONNX_WERROR=ON) succeeded.

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-17 19:06:28 +02:00
Kiwi 5871d0f9c1 Fix ReduceMax on empty boolean tensors (#8312)
### Description

The reference evaluator currently uses `-inf` for empty non-integer
`ReduceMax` inputs. For boolean inputs, casting `-inf` produces `True`,
which is the maximum boolean value rather than the minimum. Handle
boolean inputs explicitly with `False` and add a backend test for the
empty boolean case.

### Motivation and Context

An opset 20 `ReduceMax` over an empty boolean dimension currently
returns all `True`. The operator specifies the minimum value of the data
type for an empty reduction, so the expected boolean result is `False`.

### Testing

- Generated `test_reduce_max_empty_set_bool` and evaluated it with
`ReferenceEvaluator`
- `python -m pytest -q onnx/tests/python/backend_reference_test.py -k
reduce_max` (10 passed, 10 skipped)
- `ruff check onnx/reference/ops/op_reduce_max.py
onnx/backend/test/case/node/reducemax.py`
- `ruff format --check onnx/reference/ops/op_reduce_max.py
onnx/backend/test/case/node/reducemax.py`

---------

Signed-off-by: kiwigitops <kiwisclubco@gmail.com>
2026-08-17 09:28:36 -07:00
Andreas Fehlner 6e05100f77 Remove dormant install_test.yml (#8316) 2026-08-17 08:39:42 -07:00
Artur Cygan 948fef9bb7 fix(shape_inference): handle missing function inputs in subgraphs (#8305)
Shape inference can dereference a null pointer when a missing
model-local function input shares its name with an initializer inside a
nested subgraph.

Reproducer:
[model.onnx.zip](https://github.com/user-attachments/files/31086307/model.onnx.zip)

### Security Impact
A specially crafted, checker-accepted ONNX model can reliably crash
applications performing shape inference or full model validation,
causing denial of service. No data disclosure or code execution has been
demonstrated.

### Motivation and Context
This bug was found by Artur Cygan of Trail of Bits in collaboration with
OpenAI (Patch the Planet initiative).

Signed-off-by: Artur Cygan <artur.cygan@trailofbits.com>
2026-08-17 10:21:50 +08:00
Takeshi Watanabe bde53407c2 Make Graph::isNameUnique() O(1) for the common case (#8310)
Graph::isNameUnique() did a full O(n) linear scan on every call: a
std::find over initializer_names_, plus, for every node, two
std::find_if scans doing string comparisons against all of its inputs
and outputs, plus a per-node attributeNames() allocation just to find
the rare nodes with a g/gs (If/Loop/Scan) subgraph attribute.
Graph::getNextUniqueName() calls isNameUnique() in a loop to mint a
fresh name, and onnx-optimizer's fuse passes (e.g. fuse_bn_into_conv)
call it several times per fusion, so on a graph with many independent
fusable blocks this scan ran repeatedly against the whole graph.

Add a maintained std::unordered_set<std::string> used_names_ to Graph,
kept in sync at the choke points that actually assign or clear a name
(Value::setUniqueName -- the only place unique_name_/has_unique_name_
are ever set -- plus Graph::freeValue, addInitializer, eraseInitializer,
and clearInitializers). isNameUnique() now answers with a single hash
lookup for this graph's own names. Default (never explicitly renamed)
values cannot collide with each other or with this set, since their
display name is derived from a per-graph unique id counter that only
ever increases.

Also add a maintained std::unordered_set<const Node*>
subgraph_bearing_nodes_, kept in sync via a new
Node::onAttributesChanged() hook (called from
Attributes<Derived>::set/removeAttribute/copyAttributes, all three
mutation paths, and cleared in Graph::freeNode()), so isNameUnique()'s
recursion into nested If/Loop/Scan subgraph bodies only visits the
handful of nodes that actually carry a subgraph attribute instead of
scanning every node in the graph -- typically none, for graphs with no
control-flow ops.

Finally, add Attributes<Derived>::forEachAttributeNameAndKind(), a
non-allocating (name, kind) visitor over the same underlying attribute
storage, and use it in both isNameUnique()'s subgraph recursion and
Graph::forSelfAndEachSubGraphImpl() (the shared helper also used by
forEachNode(), e.g. via Value::replaceAllUsesWith(), a hot path during
rewriting) in place of attributeNames() + kindOf(), which heap-allocated
a fresh std::vector<Symbol> per node per call regardless of whether the
graph has any subgraphs at all.

Measured via onnxsim (which vendors onnx-optimizer, which vendors this
IR): total simplification time on a synthetic 900-node
Conv+BatchNorm+Relu benchmark dropped from ~8.8s to ~3.24s (~2.7x), with
identical output.

Part of https://github.com/onnxsim/onnxsim/pull/619


Claude-Session: https://claude.ai/code/session_01N97HSjtXFKjrfK1NG7LpPc



### Motivation and Context

Fixes #

---------

Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: take-cheeze <takechi101010@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-16 12:31:01 +08:00
Andreas Fehlner 7061d0d4c3 docs: clarify release and reproducibility guidance (#8284)
## Summary

- align the release-manager guide with the three-month cadence and
Trusted Publishing workflow
- describe `SOURCE_DATE_EPOCH` as reproducibility support without
promising identical artifacts across uncontrolled environments
- move irreversible PyPI cleanup out of the release-manager checklist
into a separate Architecture & Infra SIG administration guide

## Testing

- `git diff --check`
- verified local documentation targets and trailing whitespace
- Pixi documentation hooks could not run because dependency downloads
failed TLS certificate verification

---------

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-15 20:04:26 +00:00
Kiwi 764a4280ee fix(reference): handle empty and non-contiguous Gather indices (#8306) 2026-08-15 14:44:30 +00:00
Copilot 3e2cf56f58 Fix b_zero_point for int8 in QLinearMatMul 3D test case (#7991)
The 3D test case for `QLinearMatMul` used `b_zero_point = [114]` for
int8, but int8 values require a `-127` adjustment (same as the 2D case).
This produced incorrect expected outputs for the int8 3D variants.

### Changes

- **`onnx/backend/test/case/node/qlinearmatmul.py`**: Apply `[114 -
127]` for int8 `b_zero_point` in the 3D test (was `[114]`), matching the
existing 2D logic. Update expected int8 output from `[[[-86, -128,
-128], [115, 39, -121]], ...]` to `[[[41, -12, -9], [1, -75, -128]],
...]` — now consistent with the 2D int8 result tiled across the batch
dimension.
- **Backend test data**: Regenerate `test_qlinearmatmul_3D_int8_float32`
and `test_qlinearmatmul_3D_int8_float16` pb files (`input_5.pb`,
`output_0.pb`). uint8 variants unchanged.


### Motivation and Context

<!-- START COPILOT CODING AGENT SUFFIX -->



<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
>     Please apply the following diffs and create a pull request.
> Once the PR is ready, give it a title based on the messages of the
fixes being applied.
> 
> [{"message":"For int8 quantization type in the 3D test case, the
b_zero_point should be adjusted by subtracting 127 (i.e., [114 - 127])
to be consistent with the 2D test case logic on line 55. Without this
adjustment, the zero point value is incorrect for int8, which will cause
the test to produce incorrect
results.","fixFiles":[{"filePath":"onnx/backend/test/case/node/qlinearmatmul.py","diff":"diff
--git a/onnx/backend/test/case/node/qlinearmatmul.py
b/onnx/backend/test/case/node/qlinearmatmul.py\n---
a/onnx/backend/test/case/node/qlinearmatmul.py\n+++
b/onnx/backend/test/case/node/qlinearmatmul.py\n@@ -109,7 +109,9 @@\n b
= b.astype(quant_type)\n \n b_scale = np.array([0.00705],
dtype=dtype)\n- b_zero_point = np.array([114], dtype=quant_type)\n+
b_zero_point = np.array(\n+ [114 - 127] if quant_type == np.int8 else
[114], dtype=quant_type\n+ )\n \n y_scale = np.array([0.0107],
dtype=dtype)\n y_zero_point = np.array(\n"}]}]
>   


</details>

---------

Signed-off-by: Yuanyuan Chen <cyyever@outlook.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: andife <20612932+andife@users.noreply.github.com>
Co-authored-by: Andreas Fehlner <fehlner@arcor.de>
Co-authored-by: gramalingam <10075881+gramalingam@users.noreply.github.com>
Co-authored-by: Yuanyuan Chen <cyyever@outlook.com>
2026-08-15 05:03:27 +00:00
Copilot 07e5d9552b docs(defs): clarify Einsum spec supports upper-case letters as distinct symbols (#8258) 2026-08-15 04:44:47 +00:00
Andreas Fehlner a7dce11c21 docs: refresh community governance docs, add History section (#8297)
### Description

`community/readme.md` and `community/sc-election-guidelines.md` still
described the 2018 open-governance bootstrap (founding Steering
Committee composition, "first elections to occur after 1 year", an
election process "to be published within 3 months") in the
present/future tense, as if it hadn't happened yet — even though the
Steering Committee has been elected annually for years and
`sc-election-guidelines.md` has existed the whole time.

This PR moves that one-time bootstrap context into a new `## History`
section instead of deleting it, and keeps the Steering Committee/SIG
sections focused on the current, durable process. It also fixes a few
other things noticed along the way.

### Changes

- Add a `History` section to `community/readme.md` covering the 2018
founding Steering Committee composition and the transition to annual
elections, cross-linked from the TL;DR and Steering Committee Structure
sections.
- Update the SIG list to the current 6 SIGs (add Compilers and
Optimizations, rename Operator Standardization to Operators) and point
to the [sigs repository](https://github.com/onnx/sigs) as the live
source of truth.
- Cut two stale, years-old placeholders that had since been settled in
practice: "The Steering Committee will monitor how the community behaves
and apply constraints if needed in the future" (SIG decision making),
and "The Steering Committee is currently reviewing options for
collecting contributor information... shortly"
(`sc-election-guidelines.md`).
- Fill in the previously empty `## Candidacy process` section in
`sc-election-guidelines.md`, based on the process actually used in the
[steering-committee
repository](https://github.com/onnx/steering-committee/tree/main/elections),
without duplicating the per-year details (nomination form link, etc.)
that already live there.
- Reword the `sc-election-guidelines.md` introduction out of the future
tense describing the 2018 launch, consistent with the History section
above.
- Minor grammar fixes (double spaces, a garbled sentence, missing
periods).

### Motivation and Context

Noticed while reviewing the governance docs that several sections read
as if ONNX open governance had just launched and
elections/SIGs/candidacy hadn't happened yet, which is misleading for
anyone new reading the doc today. No change in substance to current
process — this is a documentation freshness pass.

---------

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-15 05:04:01 +02:00
Andreas Fehlner dc835a8148 Bump nanobind to v2.14.0 (#8302)
## Summary
- Bump the nanobind pin in `sbom.cdx.json` from 2.13.0 to 2.14.0 (the
pin CMake's `sbom_get_dep()` reads for the `FetchContent`-based build;
released 2026-08-07, past Renovate's `minimumReleaseAge` window)
- Fix `NOTICE`, which still listed nanobind 2.12.0 (already stale from a
prior bump), to match the current pinned version

## Breaking changes in nanobind 2.14.0 to be aware of
- **Stricter integer argument conversion**: implicit conversion to
integer-typed arguments now requires the input to implement Python's
`__index__` protocol. Inputs previously accepted via looser coercion
(e.g. non-`float`-subclass floats, numeric strings like `"123"`) are now
rejected. `onnx/cpp2py_export.cc` has a few plain `int`/`int64_t`
bindings (e.g. `convert_version`'s `target`, `opsetImports`,
`irVersion`); these take ordinary Python ints in normal usage, so this
shouldn't be a practical break, but it's worth a close look during
review/CI.
- **ABI version bumped 20 → 21** (nanobind's `NB_DOMAIN`/stable ABI
versioning), consistent with a minor version bump.
- Also includes STL-caster performance improvements and
memory-leak/stub-generation fixes — see the [nanobind
changelog](https://nanobind.readthedocs.io/en/latest/changelog.html) for
full details.

## Scope note: pixi.lock is intentionally untouched
`pixi.lock` still resolves nanobind to the conda package `2.13.0`. When
`find_package(nanobind CONFIG QUIET)` succeeds (any pixi-based build,
e.g. `pixi_build.yml` / `pixi run install`), CMake uses that
pre-installed nanobind and never consults `sbom.cdx.json`, so pixi-based
builds stay on 2.13.0 for now. Per `renovate.json5`, `pixi.lock` is
refreshed by a separate scheduled "lock-file maintenance" job rather
than hand-edited in dependency-bump PRs, so it will pick up 2.14.0 on
its own schedule. This PR's `sbom.cdx.json` bump does take effect for
the non-pixi `FetchContent` path used by the actual release wheel/sdist
builds (`release_linux_cibw.yml`, `release_macos_cibw.yml`,
`release_windows_cibw.yml`, `release_sdist.yml`).

## Test plan
- [ ] CI build/test matrix (Linux/macOS/Windows) passes with nanobind
2.14.0
- [ ] `pytest` passes, in particular any tests exercising
`convert_version`/`opsetImports`/`irVersion` argument coercion
- [ ] `lintrunner` passes

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-14 20:49:46 +02:00
Andreas Fehlner fa2fe73a5e Fix shape inference overflow (#8277)
## Summary

This change hardens ONNX shape inference against signed integer overflow
and invalid arithmetic involving model-provided dimensions, attributes,
and initializer values.

Shape inference operates on `int64_t` values that can be controlled by
an untrusted model. Previously, several inference paths performed
unchecked addition, subtraction, multiplication, and division. Extreme
values could therefore produce undefined behavior, invalid inferred
shapes, division-by-zero errors, or sanitizer/compiler-dependent
failures.

## Security impact

Malformed or adversarial models could trigger arithmetic errors while
shape inference was running. The affected behavior was primarily denial
of service or incorrect shape inference; no direct memory corruption or
code execution was identified.

The new implementation converts these failures into explicit
`InferenceError` exceptions.

## Changes

- Added centralized checked helpers for:
  - signed addition;
  - signed subtraction;
  - signed multiplication;
  - division by zero;
  - the `INT64_MIN / -1` division overflow case.
- Updated `Dim` arithmetic operators to use the checked helpers.
- Hardened current and legacy inference implementations for:
  - Conv and pooling effective-kernel calculations;
  - Conv, Pool, ConvTranspose, and MaxUnpool output dimensions;
  - Col2Im block-size calculations;
  - SpaceToDepth and DepthToSpace block-area calculations;
  - Tile repeat multiplication;
  - Gather and Pad rank/dimension arithmetic;
  - DFT signal-size calculations;
  - Attention packed-dimension and sequence-length calculations.
- Added validation for invalid divisors and non-positive block/head
parameters where required.
- Added regression tests for oversized dilation, tile repeats, Col2Im
kernel products, block-area multiplication, and padding arithmetic.
- Covered both current operator schemas and legacy schema
implementations.

## Compatibility

For valid models whose dimensions fit within signed 64-bit limits,
inferred shapes remain unchanged. Invalid or mathematically
unrepresentable shapes now fail explicitly instead of relying on
undefined signed arithmetic behavior.

## Testing

- `git diff --check` passes.
- Added shape-inference regression tests covering the newly guarded
paths.

---------

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-14 09:49:45 -07:00
Kiwi 8e0bc8e7d6 fix(reference): handle scalar NonZero inputs (#8296)
### Description

Handle scalar NonZero inputs in ReferenceEvaluator without calling
`np.nonzero`, which rejects 0-D arrays in NumPy 2.x. The scalar result
now follows ONNX's specified `(0, N)` shape, where `N` is zero or one.

Add focused coverage for both zero and nonzero scalar values.

### Motivation and Context

The NonZero schema explicitly differs from NumPy for scalar inputs.
ReferenceEvaluator currently raises `ValueError` with NumPy 2.x instead
of producing an output.

### Testing

- `pytest -q
tests/python/reference_evaluator_test.py::TestReferenceEvaluator::test_eval_nonzero_scalar_true
tests/python/reference_evaluator_test.py::TestReferenceEvaluator::test_eval_nonzero_scalar_false`
- `pytest -q tests/python/backend_reference_test.py -k nonzero`
- `ruff check onnx/reference/ops/op_non_zero.py
tests/python/reference_evaluator_test.py`
- `ruff format --check onnx/reference/ops/op_non_zero.py
tests/python/reference_evaluator_test.py`

Signed-off-by: kiwigitops <kiwisclubco@gmail.com>
2026-08-14 08:17:30 -07:00
Kiwi 57a52d912e Fix GatherElements output shape for empty indices (#8298)
### Motivation and Context

`GatherElements` outputs must have the same shape as `indices`. The
reference implementation returned a flat `(0,)` array whenever `indices`
was empty, so higher-rank inputs with a zero-length dimension produced
the wrong output shape.

This allocates empty results with `indices.shape` while preserving the
data dtype, and adds a regression through the public
`ReferenceEvaluator` interface.

Testing:
- `pytest
tests/python/reference_evaluator_test.py::TestReferenceEvaluator::test_gather_elements_empty_indices
-q`
- `pytest tests/python/backend_reference_test.py -k gather_elements -q`
- Ruff check and format verification on the touched files

Signed-off-by: kiwigitops <kiwisclubco@gmail.com>
2026-08-14 08:16:58 -07:00
renovate[bot] 39f600f4ca Update dependency wjakob/nanobind to v2.14.0 (#8300)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [wjakob/nanobind](https://redirect.github.com/wjakob/nanobind) | minor
| `2.13.0` → `2.14.0` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the warning logs for
more information.

This PR was automatically created by [Renovate
Bot](https://docs.renovatebot.com/) to keep build dependencies up to
date.

Renovate manages dependencies **not** handled by Dependabot: manylinux
Docker images, nanobind (via sbom.cdx.json), googletest (release tarball
+ SHA256), and the Pixi lockfile.

Please review the linked changelog/release notes before merging. A
one-week stabilization period (`minimumReleaseAge`) has already elapsed
since the new version was published.

---

### Release Notes

<details>
<summary>wjakob/nanobind (wjakob/nanobind)</summary>

###
[`v2.14.0`](https://redirect.github.com/wjakob/nanobind/compare/v2.13.0...v2.14.0)

[Compare
Source](https://redirect.github.com/wjakob/nanobind/compare/v2.13.0...v2.14.0)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/onnx/onnx).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4yNC4wIiwidXBkYXRlZEluVmVyIjoiNDQuMjQuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsibW9kdWxlOiBkZXBlbmRlbmNpZXMiLCJydW4gcmVsZWFzZSBDSXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-14 09:19:31 +02:00
Christian Bourjau 8bd4ddc60e Fix warnings from docs-build and ensure we fail on warnings in the future (#8294)
This will turn our docs build job red (see first commit) if there are
any broken internal anchors etc. Repository-links in the markdown files
are re-written on-the-fly to urls pointing to the appropriate file on
github.com (all those links are currently broken in the docs hosted on
onnx.ai).

Related to #8291

---------

Signed-off-by: Christian Bourjau <christian.bourjau@quantco.com>
2026-08-12 15:36:00 +02:00
Andreas Fehlner 4d04a9b88c docs: correct Protobuf build version guidance (#8286)
### Motivation and Context

Fixes #

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-11 13:11:05 +02:00
G. Ramalingam 6a1de3b746 Make opaque type unconditional (#8269)
ONNX allows users to define custom ops using custom domains. It is
useful to allow users to define custom types as well. We have had the
ability to define custom types (called Opaque types), but the support
was previously limited to ONNX_ML (conditionally supported only if the
build flag for ONNX_ML is on).

This PR makes this support unconditional, making user-defined (custom)
types possible in ONNX (without ONNX_ML) as well.

---------

Signed-off-by: Ganesan Ramalingam <grama@microsoft.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: gramalingam <10075881+gramalingam@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7f853424-17c9-49b2-bc5b-3424da24f0b0
2026-08-10 16:51:23 -07:00
G. Ramalingam 0aa6e1a193 Update hasInput and hasOutput methods of InferenceContextImpl (#8271)
Fix an issue with the implementation of the hasInput/hasOutput methods
of InferenceContextImpl. In its current form, hasOutput does not
correctly handle missing optional outputs that are followed by other
outputs (non-trailing missing optional outputs).

The issue shows up in the op in https://github.com/onnx/onnx/pull/8108
(if one wishes to do some sanity checks on the allowed combinations of
optional outputs).

Other fixes:
* The update uncovered a bug in the inference method for QuantizeLinear
(which confused absence of input with absence of input type).
* Update the shape-inference implementation to distinguish between a
missing (optional) input and an input with unknown type (which arises in
the context of FunctionProto where a named value may represent an
optional formal parameter, which may be absent in a particular callsite.

---------

Signed-off-by: Ganesan Ramalingam <grama@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-10 15:19:00 -07:00
Andreas Fehlner 9969eea1ee docs: require labels on agent-authored PRs (#8285)
## Summary

- require agent-authored pull requests to use existing labels that
accurately describe their scope
- document the required `topic:` or `module:` label enforced by CI
- provide `topic: documentation` as the documentation-only example

## Testing

- `git diff --check`
- verified the referenced label-check workflow exists

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-10 17:24:00 +02:00
Andreas Fehlner 3f11fc7bd3 docs: fix cross-platform environment variable syntax (#8283)
## Summary

- use PowerShell environment-variable syntax when generating operator
documentation
- use robust CMD environment assignments without embedding quote
characters
- use POSIX `export` in the macOS build instructions

## Testing

- `git diff --check`
- manually verified the PowerShell and CMD assignments set the expected
values
- Pixi documentation hooks could not run because dependency downloads
failed TLS certificate verification

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-10 15:20:16 +02:00
Christian Bourjau 1e58fd5ec2 Move tests from /onnx/test to /tests (#8253)
We are currently shipping our unit tests (`onnx.test`) in the Python
package. These tests, which are different from `onnx.backend.test`, are
not relevant for downstream users. Furthermore, they are not usable with
the regular dependencies of the `onnx` package (i.e., we actually ship a
broken package today if one were to be pedantic about this).

With this PR I'd like to move these out of the package (i.e., `onnx/`)
into the root-level `tests/` which is more idiomatic in Python these
days. I don't see why a downstream package would import anything from
`onnx.test`, and I wasn't able to find an open-source project doing so
(see
https://grep.app/search?regexp=true&q=%28%28from%29%7C%28import%29%29%5Csonnx%5C.test).

---------

Signed-off-by: Christian Bourjau <christian.bourjau@quantco.com>
Signed-off-by: Christian Bourjau <cbourjau@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-09 17:33:38 -07:00
Andreas Fehlner 0dd7987d73 fix: avoid non-portable pointer NTTP in ScopedResource (#8279)
## Summary

`ScopedResource` (added in #7790's TOCTOU fix,
`onnx/common/scoped_resource.h`) uses a pointer value as a non-type
template argument:

```cpp
template <auto Invalid, void (*Close)(decltype(Invalid))>
class ScopedResource { ... };

using ScopedHandle = ScopedResource<INVALID_HANDLE_VALUE, close_handle>;
```

`INVALID_HANDLE_VALUE` expands to `((HANDLE)(LONG_PTR)-1)` — a
reinterpret_cast-style conversion of `-1` to a pointer. That is not a
valid converted constant expression for a pointer-typed non-type
template parameter per `[temp.arg.nontype]`. MSVC's default (permissive)
mode accepts it silently, but conforming compilers reject it. Building
this header with clang-cl fails:

```
onnx/common/scoped_resource.h(59,37): error: non-type template argument is not a constant expression
using ScopedHandle = ScopedResource<INVALID_HANDLE_VALUE, close_handle>;
                                    ^~~~~~~~~~~~~~~~~~~~
...
note: cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression
```

This surfaced in review comments on the (already-merged) #7790. There is
currently no CI job that would catch it (no clang-cl leg, and the MSVC
job doesn't build with `/permissive-`), so it's a latent portability bug
in code that is actively used by `checker.cc`.

## Fix

Replace the `<auto Invalid, void(*Close)(...)>` non-type template
parameters with a `Traits` type parameter (`HandleTraits` / `FdTraits`)
supplying `invalid()` and `close()` as ordinary static member functions.
This sidesteps the pointer-NTTP restriction entirely rather than working
around it, and keeps the same call-site API (`ScopedHandle guard(h)`,
`ScopedFd guard(fd)`).

## Testing

- Compiled the header standalone with clang-cl (`/std:c++17 /EHsc /W4
/WX`) exercising both `ScopedHandle` and `ScopedFd` — clean, no
warnings.
- `clang-format` reports no diff.

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-08 12:51:18 +02:00
Andreas Fehlner b062c05c8b docs: correct model validation assurances (#8276)
## Description

Correct the security assurance case so it accurately describes the
boundary between model loading and semantic validation.

- clarify that loading and deserialization do not implicitly call
`onnx.checker.check_model`
- document that binary proto2 parsing accepts and preserves unknown
fields for forward compatibility
- mark Fail-Safe Defaults and Complete Mediation as partially satisfied
- correct the CWE-20 mitigation and distinguish optional shape inference
with `full_check=True`
- update the assurance case to version 1.2 (August 2026)

## Motivation

`onnx.load_model` and `onnx.load_model_from_string` deserialize models
but do not run the semantic checker. In addition, binary proto2 unknown
fields are accepted rather than rejected. The previous assurance
statements could therefore lead consumers to assume that a successfully
loaded model had already passed semantic, type, and shape validation.

## Validation

- commit hooks passed
- `git diff --check` passed
- documentation-only change; no runtime behavior changed

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-08 08:35:54 +02:00
Christian Bourjau 74ff8571fc Validate offset in save_external_data function (#8260)
The offset is motivated by alignment. Anything larger than 64kB is
rejected. This issue was repeatedly reported in advisories, but does not
warrant secrecy since it is actually trivial to find using widely
available coding agents (validated with Opus 4.8 which found this bug in
seconds with minimal prompting).

This PR fixes an issue in which deserializing or serializing a model
with maliciously crafted offsets could cause resource exhaustion on the
host.

Signed-off-by: Christian Bourjau <christian.bourjau@quantco.com>
Co-authored-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-05 18:28:18 +02:00
Andreas Fehlner aa42afa48b fix: use int64_t and overflow-checked arithmetic in shape inference (#8031)
## Summary

Fixes silent integer overflow in ONNX shape/data-propagation arithmetic
and Concat shape inference.

### Problems fixed

| Location | Problem |
|---|---|
| `onnx/defs/math/utils.cc` — `MathOpTwoIntegers` | Was `int` (32-bit);
promoted to `int64_t` but Add/Mul used helpers with non-negative-only
MSVC fallbacks; Sub had raw `a - b` (signed UB on overflow) |
| `onnx/common/safe_math.h` — `checked_add_overflow` | MSVC fallback:
`INT64_MAX - a` is UB when `a < 0`; assert fires on any negative input |
| `onnx/common/safe_math.h` — `checked_mul_overflow` | MSVC fallback:
assert + UB negating `INT64_MIN`; now used for tensor *element* values
which can be negative |
| `onnx/defs/tensor/defs.cc` — `Concat` `total_length` | Promoted from
`int` to `int64_t` but the `+=` accumulation was still an unchecked
signed add |

### Changes

**`onnx/common/safe_math.h`**
- `checked_mul_overflow`: MSVC fallback is now signed-safe — handles
`INT64_MIN` explicitly (cannot be negated) and uses abs-based division
for all other pairs.
- `checked_add_overflow`: MSVC fallback replaced with the
unsigned-arithmetic trick (`uint64_t` add wraps without UB; sign-bit XOR
detects overflow). The cast back to `int64_t` is implementation-defined
in C++17 but two's-complement on every MSVC target; mandated by the
standard from C++20.
- `checked_sub_overflow`: new helper following the same unsigned
pattern, for `__builtin_sub_overflow` on GCC/Clang and the XOR trick on
MSVC.

**`onnx/defs/math/utils.cc`**
- `MathOpTwoIntegers`: Add and Mul already used overflow helpers; Sub
now uses `checked_sub_overflow` and raises `InferenceError` on overflow.

**`onnx/defs/tensor/defs.cc`**
- `Concat` shape inference: validates each `dim_value` is non-negative
and uses `checked_add_overflow` to accumulate `total_length`, calling
`fail_shape_inference` on overflow.

## Test plan

- [x] CI passes (C++ gtests, Python pytest, clang-tidy)
- [x] Manual: `Concat` with dims summing past `INT64_MAX` raises
`InferenceError`
- [x] Manual: `Add`/`Sub`/`Mul` data propagation with values near
`INT64_MIN`/`INT64_MAX` raises `InferenceError`

---------

Signed-off-by: Andreas Fehlner <fehlner@arcor.de>
Co-authored-by: Yuanyuan Chen <cyyever@outlook.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Christian Bourjau <cbourjau@users.noreply.github.com>
Co-authored-by: G. Ramalingam <grama@microsoft.com>
2026-08-05 17:31:45 +02:00
mcollinswisc 665de4200a Add tests for direction attribute of RNN ops (#7935)
### Motivation and Context

`GRU`, `LSTM`, and (simple) `RNN` ops define a `direction` attribute for
reversed or bi-directional RNNs. There were no backend test cases
covering this. The `ReferenceEvaluator` was ignoring the `direction`
attribute (giving incorrect results for "reverse") and raising a
`NotImplementedError` when given inputs with `num_directions != 1` (for
bidirectional).

This change adds test cases for all valid `direction` values for these
three ops, and implements this feature in `ReferenceEvaluator`.

### Testing

Change adds tests cases that get included in
`onnx/test/test_backend_reference.py`.

Also ran ONNXRuntime against these new test cases: ONNXRuntime already
supports the `direction` attribute. Done with script at:
https://gist.github.com/mcollinswisc/abe6ea4a7765c28d68eae265e44f896e

Passing result (with `onnxruntime==1.26.0`):


[check_rnn_ort_result.txt](https://github.com/user-attachments/files/27580284/check_rnn_ort_result.txt)

### Notes

Made some related fixes along the way given what code needed to be
edited anyway:

* Typo in LSTM operator schema: "hidde_size" -> "hidden_size"
* Fixes for the `layout` attribute:
* Test helpers & ReferenceEvaluator implementations were getting a
batch_size that didn't account for the `layout` attribute (before this
fix, X was being transposed based on`layout` *after* `batch_size` was
inferred from its axes
* `initial_h` (and `initial_c` for LSTM) inputs were not being
transposed when `layout = 1`
* ReferenceEvaluator implementations were incorrectly trying to
`np.squeeze` the `sequence_lens` inputs. (Not valid when `batch_size >
1`.) Though it's not supported anyway: not addressing that input in this
change, just removing the incorrect `np.squeeze`.
* LSTM reference implementation (and helper in backend tests) now
includes `Y_c` output.

---------

Signed-off-by: Maxwell Collins <mcollins@cs.wisc.edu>
Signed-off-by: mcollinswisc <maxwelldecollins@gmail.com>
Signed-off-by: Maxwell D Collins <mcollins@cs.wisc.edu>
Co-authored-by: Andreas Fehlner <fehlner@arcor.de>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-05 08:14:32 -07:00
Yuanyuan Chen 643dd20bdd Enable more clang-tidy checks (#8259)
### Description

Enables more clang-tidy check families. Follow-up to #8148 / #8150; no
behavior change.

Already clean on the tree (verified with `-warnings-as-errors="*"` over
all 666 TUs): `cert-*` (except `cert-err58-cpp`), `concurrency-*`,
`portability-*` (except `portability-avoid-pragma-once` and
`portability-template-virtual-member-function`), and nine more
`google-*` checks.

Enabled with their warnings fixed:
- `readability-math-missing-parentheses` — explicit parentheses around
`*` and `/` in mixed arithmetic.
- `readability-inconsistent-declaration-parameter-name` — `printer.cc`'s
`DEF_OP` macro now takes the parameter name, so the generated
`operator<<` definitions match the names in `printer.h` (header
unchanged).

Also disabled the checks that only run on C++20 or later
(`modernize-use-std-format`, `modernize-use-starts-ends-with`, etc.).

Signed-off-by: Yuanyuan Chen <cyyever@outlook.com>
Co-authored-by: Andreas Fehlner <fehlner@arcor.de>
2026-08-05 01:52:25 +00:00
Yuanyuan Chen 1da65ecfb7 Fix raw_data heap overflow in ParseData and modernize to C++17 STL (#8109)
### Motivation and Context

The Tensor* ParseData overload sized the result by floor(len/sizeof(T))
but copied the full raw_data length, overflowing the result buffer when
the byte length was not a multiple of the element size (reachable via
the Upsample 9->8 version-converter adapter). Reject such lengths via
ONNX_ASSERTM, matching the validation the TensorProto* overload already
has.

Also replace the C-style byte handling with C++17 STL in both overloads:
std::reverse for the endian swap, std::copy_n for the byte copy,
range-based for, std::byte for the byte views, and read raw_data via
const& to drop the full-buffer copy.

---------

Signed-off-by: cyy <cyyever@outlook.com>
Signed-off-by: Yuanyuan Chen <cyyever@outlook.com>
Co-authored-by: Andreas Fehlner <fehlner@arcor.de>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-04 16:29:59 -07:00