microsoft / microsoft/onnxruntime
confusing error log indicate a ghost Slice node fail
- Dominant language
- C++
- Stars
- 21.9k
- Forks
- 4.2k
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 184
Description
### Describe the issue
I was using Range and Gather node to replace Slice node, because Slice node cannot accept data with dynamic shape during convert to tensorRT format. And I used a 1-d constant tensor as Start of Range node, which is wrong, but I got a confusing error log:
```
FAIL : Non-zero status code returned while running Slice node. Name:'Slice' Status Message: slice.cc:194 FillVectorsFromInput Ends must be a 1-D array
```
While there is not `Slice` node in my onnx model, and I prepare a tiny demo to reproduce this:
### To reproduce
```python
import onnx
import onnx_graphsurgeon as gs
import numpy as np
import pdb
# Define the model filename
OUTPUT_FILENAME = "slice_with_range_gather.onnx"
# 1. Create an empty GraphSurgeon graph
graph = gs.Graph(opset=11) # Use opset 11 or higher
# 2. Define the model's input
# Input tensor with shape [N, T], where N and T are dynamic
inp = gs.Variable(name="input_tensor", dtype=np.float32, shape=["N", "T"])
graph.inputs = [inp]
# 3. Get the shape of the input tensor
# The output of the Shape op is a 1D tensor, e.g., [N, T]
shape_out = gs.Variable(name="shape_output", dtype=np.int64)
graph.layer(op="Shape", inputs=[inp], outputs=[shape_out])
# 4. Extract the T dimension (to be used as the limit for Range)
# We need T-1 as the limit for the Range operator.
# First, use Gather to extract T.
t_dim_1d = gs.Variable(name="t_dim_1d", dtype=np.int64)
# indices=[1] means taking the element at index 1 from shape_output ([N, T]), which is T.
graph.layer(op="Gather",
inputs=[shape_out, gs.Constant(
name="gather_indices", values=np.array([1], dtype=np.int64))],
outputs=[t_dim_1d])
# Calculate T-1 from T
limit_1d = gs.Variable(name="limit_1d", dtype=np.int64)
graph.layer(op="Sub",
inputs=[t_dim_1d, gs.Constant(
name="const_one_sub", values=np.array([1], dtype=np.int64))],
outputs=[limit_1d])
# 5. Convert the inputs for Range to 0D scalars (Crucial Step!)
# The Range operator in some backends like TensorRT requires its inputs to be 0D scalars.
# We need: start=1, limit=T-1, delta=2
start_scalar = gs.Constant(
name="start_scalar", values=np.array(1, dtype=np.int64))
delta_scalar = gs.Constant(
name="delta_scalar", values=np.array(2, dtype=np.int64))
# Squeeze converts the 1D tensor [T-1] to a 0D scalar T-1.
limit_scalar = gs.Variable(name="limit_scalar", dtype=np.int64)
graph.layer(op="Squeeze",
inputs=[limit_1d], # Only one input: the data to be squeezed
outputs=[limit_scalar],
attrs={"axes": [0]}) # Provide axes as an attribute
# 6. Use Range to generate the index sequence
# Range(start=1, limit=T-1, delta=2) will produce [1, 3, 5, ...]
indices = gs.Variable(name="indices_output", dtype=np.int64)
graph.layer(op="Range",
# NOTE: we use limit_1d to reproduce this, but it should be limit_scalar
inputs=[start_scalar, limit_1d, delta_scalar],
outputs=[indices])
# 7. Use Gather to perform the slicing
# Gather will extract data from input_tensor along the specified axis based on the indices.
# axis=1 means we are operating on the T dimension.
sliced_output = gs.Variable(name="output_tensor", dtype=np.float32)
graph.layer(op="Gather",
inputs=[inp, indices],
attrs={"axis": 1},
outputs=[sliced_output])
# 8. Define the model's output
graph.outputs = [sliced_output]
# 9. Clean up and topologically sort the graph
graph.cleanup().toposort()
# 10. Export to an ONNX file
onnx_model = gs.export_onnx(graph, ir_version=7)
onnx.save(onnx_model, OUTPUT_FILENAME)
print(f"Successfully created ONNX model: {OUTPUT_FILENAME}")
print("You can now open it with Netron to visualize the graph.")
# --- Optional: Verify the model ---
def verify_model():
print("\n--- Verifying the model with ONNX Runtime ---")
try:
import onnxruntime
sess = onnxruntime.InferenceSession(OUTPUT_FILENAME)
# Create a sample input, e.g., with shape [2, 10]
N, T = 2, 10
test_input = np.arange(N * T, dtype=np.float32).reshape(N, T)
print("Input Tensor (shape {}):\n{}".format(
test_input.shape, test_input))
# Python slicing as the ground truth
expected_output = test_input[:, 1:-1:2]
print(
"\nExpected Output (from Python slicing a[:, 1:-1:2]):\n{}".format(expected_output))
# ONNX model inference
input_name = sess.get_inputs()[0].name
output_name = sess.get_outputs()[0].name
result = sess.run([output_name], {input_name: test_input})[0]
print("\nActual ONNX Model Output:\n{}".format(result))
# Compare the results
np.testing.assert_allclose(result, expected_output, rtol=1e-5)
print("\nVerification successful! The outputs match.")
except ImportError:
print("\nSkipping verification: onnxruntime is not installed.")
except Exception as e:
print(f"\nVerification failed: {e}")
verify_model()
```
### Urgency
not urgent
### Platform
Linux
### OS Version
CentOS-8
### ONNX Runtime Installation
Released Package
### ONNX Runtime Version or Commit ID
1.16.3
### ONNX Runtime API
Python
### Architecture
X64
### Execution Provider
CUDA
### Execution Provider Library Version
CUDA11.7
Contributor guide
Assessment
This issue has not been assessed yet.