main
3988 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
2e870ab6bd |
Update roadmap with new dates and task adjustments (#8295)
### Motivation and Context Fixes # Signed-off-by: Andreas Fehlner <fehlner@arcor.de> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
6e05100f77 | Remove dormant install_test.yml (#8316) | ||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
764a4280ee | fix(reference): handle empty and non-contiguous Gather indices (#8306) | ||
|
|
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>
|
||
|
|
07e5d9552b | docs(defs): clarify Einsum spec supports upper-case letters as distinct symbols (#8258) | ||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
4d04a9b88c |
docs: correct Protobuf build version guidance (#8286)
### Motivation and Context Fixes # Signed-off-by: Andreas Fehlner <fehlner@arcor.de> |
||
|
|
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 |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |