apache / apache/tvm

[Bug][Relax] ONNX Slice with runtime negative starts gives wrong results (zeros) — dynamic_strided_slice mishandles negative begin

Open
#20,263 0 comments 0 reactions 0 assignees View on GitHub
needs-triage type: bug
Dominant language
Python
Stars
13.7k
Forks
4k
Avg merge
2d 1h
Merged PRs (30d)
112

Description

### Expected behavior

An ONNX `Slice` whose `starts` is a runtime input (not an initializer) with a negative value — e.g. `starts=[-2]` to take the last 2 elements of a dynamic axis, the standard GPT-2-style pattern — should
return the last 2 elements. Negative indices are part of the ONNX Slice spec.

### Actual behavior

Silent wrong results: the output has the correct shape but contains **zeros** (out-of-bounds memory that happens to be zeroed). Both official build pipelines (default and `get_default_pipeline`) produce the
same wrong values.

Root cause is op-level: the ONNX frontend lowers runtime (non-constant) `starts` to `relax.dynamic_strided_slice` **without normalizing negative values** (negative `axes` are normalized; `starts`/`ends` are
passed through raw), and `dynamic_strided_slice` itself does not handle negative `begin` — any negative `begin`, in-range or out-of-bound, static or symbolic dim, produces zeros/garbage, while the same inputs
through static `strided_slice` are correct. Negative `end` works. Constant `starts` take a frontend shortcut to the static op, which is why the bug only shows with runtime `starts`.

### Environment

```text
OS: Linux x86_64
Target: llvm
TVM commit: 2a2b293c02269f4d9f3526c5b03a7548578e78e8 (current main)
```

### Steps to reproduce (end-to-end ONNX)

```python
import numpy as np, onnx, tvm
from onnx import helper, TensorProto
from tvm import relax
from tvm.relax.frontend.onnx import from_onnx

X = helper.make_tensor_value_info('x', TensorProto.FLOAT, [8])
S = helper.make_tensor_value_info('starts', TensorProto.INT64, [1])
E = helper.make_tensor_value_info('ends', TensorProto.INT64, [1])
Y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2])
node = helper.make_node('Slice', ['x', 'starts', 'ends'], ['y'])
graph = helper.make_graph([node], 'g', [X, S, E], [Y])
model = helper.make_model(graph, opset_imports=[helper.make_opsetid('', 13)])
model.ir_version = 8

mod = from_onnx(model)
data = np.clip(np.random.RandomState(7).randn(8), -1, 1).astype("float32")
exe = tvm.relax.build(mod, target=tvm.target.Target("llvm"), exec_mode="compiled")
got = relax.VirtualMachine(exe, tvm.cpu())["main"](
tvm.runtime.tensor(data, tvm.cpu()),
tvm.runtime.tensor(np.array([-2], "int64"), tvm.cpu()),
tvm.runtime.tensor(np.array([np.iinfo(np.int64).max], "int64"), tvm.cpu())).numpy()
print(got) # [0., 0.] — expected data[-2:]
```

Op-level isolation (no ONNX): `relax.op.dynamic_strided_slice(x, const([-2]), const([8]), const([1]))` on `x: (8,)` returns zeros; `relax.op.strided_slice(x, [0], [-2], [8], [1])` returns the correct last-2
elements. Larger negative begins (e.g. `-58`) read garbage (`5.7e+16`) — out-of-bounds reads, potential segfault.

Fix direction: either normalize negative `begin`/`end` (add dim, then clip) inside `dynamic_strided_slice`, or normalize in the ONNX frontend before lowering (mirroring what it already does for negative
`axes`).

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with relax.op.dynamic_strided_slice and the ONNX Slice lowering described in the issue, then reproduce the provided runtime-starts case and compare it with static strided_slice. Trace how negative begin values are handled for static and symbolic dimensions; done means negative runtime starts return the expected elements without out-of-bounds reads in both build pipelines.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
compilers, machine-learning
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.