Repository: onnx/onnx

Issue:
numpy_helper.to_array modifies TensorProto on big endian systems
# Bug Report

### Is the issue related to model conversion?
Yes, but debugged to an issue within ONNX

### Describe the bug
Using tf2onnx to export a TF model to ONNX, some items were byteswapped incorrectly. Tracing of the code showed that the TensorProto holding the value had the correct contents, but at some point reexamination of the same TensorProto showed byteeswapped data. Further investigation showed that `numpy_helper.to_array()` was byteswapping the data within the `TensorProto` as opposed to returning a byteswapped copy of the data, yielding the inconsistent format. A local fix produced correct data.

### System information
<!--
- OS Platform and Distribution (*e.g. Linux Ubuntu 20.04*):
- ONNX version (*e.g. 1.13*):
- Python version:
- GCC/Compiler version (if compiling from source):
- CMake version:
- Protobuf version:
- Visual Studio version (if applicable):-->
Linux RHEL 9.3 on s390x
ONNX 1.15.0
Python 3.10
gcc (compiling ONNX via pip install) 11.4.0
CMake 3.22.1
protobuf 3.20.3

### Reproduction instructions
Simplified testcase:
```python
from onnx import numpy_helper, TensorProto
import numpy as np

t = np.array(1, dtype=np.int32)
onnx_tensor = numpy_helper.from_array(t, "Tensor")
assert t == numpy_helper.to_array(onnx_tensor)
assert t == numpy_helper.to_array(onnx_tensor) # Currently blows up on big endian
```

### Expected behavior
In the above testcase, the expectation is that `onnx_tensor` is treated as immutable during `to_array()`

### Notes




Discussion:
I tested a small patch in `numpy_helper.to_array()` to not modify the `TensorProto` and it not only fixes the above testcase, but also fixes my issue in tf2onnx.
Thanks! A pull request is welcome and appreciated.

Resolving patch:
diff --git a/onnx/numpy_helper.py b/onnx/numpy_helper.py
--- a/onnx/numpy_helper.py
+++ b/onnx/numpy_helper.py
@@ -252,40 +252,41 @@ def to_array(tensor: TensorProto, base_dir: str = "") -> np.ndarray:  # noqa: PL
 
     if tensor.HasField("raw_data"):
         # Raw_bytes support: using frombuffer.
+        raw_data = tensor.raw_data
         if sys.byteorder == "big":
             # Convert endian from little to big
-            convert_endian(tensor)
+            raw_data = np.frombuffer(raw_data, dtype=np_dtype).byteswap().tobytes()
 
         # manually convert bf16 since there's no numpy support
         if tensor_dtype == TensorProto.BFLOAT16:
-            data = np.frombuffer(tensor.raw_data, dtype=np.int16)
+            data = np.frombuffer(raw_data, dtype=np.int16)
             return bfloat16_to_float32(data, dims)
 
         if tensor_dtype == TensorProto.FLOAT8E4M3FN:
-            data = np.frombuffer(tensor.raw_data, dtype=np.int8)
+            data = np.frombuffer(raw_data, dtype=np.int8)
             return float8e4m3_to_float32(data, dims)
 
         if tensor_dtype == TensorProto.FLOAT8E4M3FNUZ:
-            data = np.frombuffer(tensor.raw_data, dtype=np.int8)
+            data = np.frombuffer(raw_data, dtype=np.int8)
             return float8e4m3_to_float32(data, dims, uz=True)
 
         if tensor_dtype == TensorProto.FLOAT8E5M2:
-            data = np.frombuffer(tensor.raw_data, dtype=np.int8)
+            data = np.frombuffer(raw_data, dtype=np.int8)
             return float8e5m2_to_float32(data, dims)
 
         if tensor_dtype == TensorProto.FLOAT8E5M2FNUZ:
-            data = np.frombuffer(tensor.raw_data, dtype=np.int8)
+            data = np.frombuffer(raw_data, dtype=np.int8)
             return float8e5m2_to_float32(data, dims, fn=True, uz=True)
 
         if tensor_dtype == TensorProto.UINT4:
-            data = np.frombuffer(tensor.raw_data, dtype=np.uint8)
+            data = np.frombuffer(raw_data, dtype=np.uint8)
             return unpack_int4(data, dims, signed=False)
 
         if tensor_dtype == TensorProto.INT4:
-            data = np.frombuffer(tensor.raw_data, dtype=np.int8)
+            data = np.frombuffer(raw_data, dtype=np.int8)
             return unpack_int4(data, dims, signed=True)
 
-        return np.frombuffer(tensor.raw_data, dtype=np_dtype).reshape(dims)  # type: ignore[no-any-return]
+        return np.frombuffer(raw_data, dtype=np_dtype).reshape(dims)  # type: ignore[no-any-return]
 
     # float16 is stored as int32 (uint16 type); Need view to get the original value
     if tensor_dtype == TensorProto.FLOAT16:


Test patch: