microsoft / microsoft/onnxruntime

confusing error log indicate a ghost Slice node fail

Open
#26,788 1 comment 0 reactions 1 assignee Claimed by @justinchuby View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.