Repository navigation
Description
Description
SparseValues.load() in onnx_graphsurgeon/ir/tensor.py has two bugs.
1. Values and indices stored in raw_data are ignored. load() reads only the typed repeated fields (int64_data, float_data, ...). Tensors created with onnx.numpy_helper.from_array, and most exporters, store their data in raw_data, so those fields are empty.
- On onnx-graphsurgeon 0.6.1 (PyPI), the sparse tensor silently loads as all zeros.
- On current
main, the size check added in the 11.3 release catches the empty fields, and loading fails withSparse tensor w declares 2 indices, but stores 0.
2. The dense result is always float64. The dense buffer is created with np.zeros(...) without a dtype. For a sparse INT64 initializer this:
- loses precision for values above 2**53 (
2**60 + 1comes back as1.152921504606847e+18), - changes
Constant.dtypefromint64before loading tofloat64after loading, - makes
gs.export_onnxwrite aDOUBLEdense initializer, which then fails ONNX type checking against its consumer.
Environment
TensorRT Version: N/A, ONNX-GraphSurgeon only. Reproduced on main (16ed063, onnx-graphsurgeon 0.6.2) and on onnx-graphsurgeon 0.6.1 from PyPI.
NVIDIA GPU: N/A
NVIDIA Driver Version: N/A
CUDA Version: N/A
CUDNN Version: N/A
Operating System: Linux
Python Version (if applicable): 3.13 (onnx 1.23.2, numpy 2.5.3)
Tensorflow Version (if applicable): N/A
PyTorch Version (if applicable): N/A
Baremetal or Container (if so, version): Container
Relevant Files
Model link: N/A, the script below builds the model directly.
Steps To Reproduce
Commands or scripts:
import numpy as np
import onnx
from onnx import helper, numpy_helper, TensorProto
import onnx_graphsurgeon as gs
dense_shape = [2, 3]
def make_model(values, indices):
sparse = helper.make_sparse_tensor(values, indices, dense_shape)
node = helper.make_node("Identity", ["w"], ["y"])
graph = helper.make_graph(
[node], "g", [],
[helper.make_tensor_value_info("y", TensorProto.INT64, dense_shape)],
sparse_initializer=[sparse],
)
return helper.make_model(graph)
# Case 1: values/indices stored in raw_data (what numpy_helper.from_array produces)
model = make_model(
numpy_helper.from_array(np.array([7, 9], dtype=np.int64), "w"),
numpy_helper.from_array(np.array([1, 4], dtype=np.int64), "idx"),
)
graph = gs.import_onnx(model)
try:
print(graph.tensors()["w"].values)
except Exception as err:
print("raw_data case:", type(err).__name__, err)
# Case 2: values stored in int64_data
model = make_model(
helper.make_tensor("w", TensorProto.INT64, [2], [7, 2**60 + 1]),
helper.make_tensor("idx", TensorProto.INT64, [2], [1, 4]),
)
graph = gs.import_onnx(model)
w = graph.tensors()["w"]
print("dtype before load:", w.dtype)
print("values:", w.values.ravel().tolist(), "dtype after load:", w.values.dtype)
try:
onnx.checker.check_model(gs.export_onnx(graph), full_check=True)
print("exported model OK")
except Exception as err:
print("exported model invalid:", str(err).strip())Output on onnx-graphsurgeon 0.6.1 (PyPI):
[[0. 0. 0.]
[0. 0. 0.]]
dtype before load: int64
values: [0.0, 7.0, 0.0, 0.0, 1.152921504606847e+18, 0.0] dtype after load: float64
exported model invalid: [ShapeInferenceError] Inference error(s): (op_type:Identity): [TypeInferenceError] Inferred elem type differs from existing elem type: (11) vs (7)
Output on main:
raw_data case: OnnxGraphSurgeonException Sparse tensor w declares 2 indices, but stores 0
dtype before load: int64
values: [0.0, 7.0, 0.0, 0.0, 1.152921504606847e+18, 0.0] dtype after load: float64
exported model invalid: [ShapeInferenceError] Inference error(s): (op_type:Identity): [TypeInferenceError] Inferred elem type differs from existing elem type: (11) vs (7)
Expected: both cases load as an int64 array of shape (2, 3) with the exact values ([[0, 7, 0], [0, 9, 0]] for case 1), and the exported model passes the checker.
Full traceback for case 1 on main:
Traceback (most recent call last):
File "repro.py", line 6, in <module>
gs.import_onnx(helper.make_model(graph)).tensors()["w"].values
File "onnx_graphsurgeon/ir/tensor.py", line 460, in values
self._values = self._values.load()
File "onnx_graphsurgeon/ir/tensor.py", line 314, in load
G_LOGGER.critical(
File "onnx_graphsurgeon/logger/logger.py", line 242, in critical
raise OnnxGraphSurgeonException(message) from None # Erase exception chain
onnx_graphsurgeon.util.exception.OnnxGraphSurgeonException: Sparse tensor w declares 2 indices, but stores 0
Have you tried the latest release?: Yes. It reproduces on the latest onnx-graphsurgeon from PyPI (0.6.1) and on current main.
Can this model run on other frameworks?: Yes. onnx.checker.check_model accepts the case 1 model, and ONNX Runtime 1.29.0 loads it and returns [[0, 7, 0], [0, 9, 0]] (saved with ir_version=10, since that ONNX Runtime version supports IR version 13 at most).
Proposed fix
Decode values and indices with onnx.numpy_helper.to_array, which handles every storage form, and allocate the dense buffer with the dtype of the values:
values_data = np.asarray(onnx.numpy_helper.to_array(self.tensor.values)).ravel()
indices_data = np.asarray(onnx.numpy_helper.to_array(self.tensor.indices)).ravel().astype(np.int64, copy=False)
...
values = np.zeros(dense_size, dtype=values_data.dtype)The existing bounds and size checks stay as they are. I'd like to send a PR with this fix and regression tests in tests/test_importers.py.
Activity
NVIDIA#4910 - [onnx-graphsurgeon] Fix sparse initializer loading for …