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
OS: Linux x86_64
Target: llvm
TVM commit: 2a2b293c02269f4d9f3526c5b03a7548578e78e8 (current main)
Steps to reproduce (end-to-end ONNX)
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).
Expected behavior
An ONNX
Slicewhosestartsis 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 — shouldreturn 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 thesame wrong values.
Root cause is op-level: the ONNX frontend lowers runtime (non-constant)
startstorelax.dynamic_strided_slicewithout normalizing negative values (negativeaxesare normalized;starts/endsarepassed through raw), and
dynamic_strided_sliceitself does not handle negativebegin— any negativebegin, in-range or out-of-bound, static or symbolic dim, produces zeros/garbage, while the same inputsthrough static
strided_sliceare correct. Negativeendworks. Constantstartstake a frontend shortcut to the static op, which is why the bug only shows with runtimestarts.Environment
Steps to reproduce (end-to-end ONNX)
Op-level isolation (no ONNX):
relax.op.dynamic_strided_slice(x, const([-2]), const([8]), const([1]))onx: (8,)returns zeros;relax.op.strided_slice(x, [0], [-2], [8], [1])returns the correct last-2elements. 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) insidedynamic_strided_slice, or normalize in the ONNX frontend before lowering (mirroring what it already does for negativeaxes).