lrcyyds1 opened a new issue, #20262:
URL: https://github.com/apache/tvm/issues/20262
### Expected behavior
Per the `tvm.relax.op.strided_slice` contract, `assume_inbound` defaults
to `false`, in which case *"out of bound indices will be clipped to the
bound"*. So a slice with an out-of-bound `begin`/`end` on a
**symbolic** dim should behave exactly like the same slice on a static
dim: clip the indices, then slice.
### Actual behavior
On a static dim, out-of-bound `begin`/`end` are clipped correctly. On a
**symbolic** dim, the clip is applied to the **loop extent only** — the **index
expression keeps the unclipped begin** — so the first
steps of the slice read out of bounds, and the result silently contains
garbage:
- `default` (compiled): reads uninitialized memory (e.g. `1.88e+09` for
float32 inputs in `[-1, 1]`)
- `cpu_generic` (fused): different garbage (`2.22e+09`) — the two official
pipelines disagree
- `bytecode` VM: zeros
All three execution backends violate the documented clipping contract;
what you get depends on memory content (with a larger out-of-bound offset, e.g.
`begin = m + 50`, this is a potential segfault, not just
wrong values).
### Environment
```text
OS: Linux x86_64
Target: llvm
TVM commit: 2a2b293c02269f4d9f3526c5b03a7548578e78e8 (current main)
```
### Steps to reproduce
```python
import numpy as np
import tvm
from tvm import relax
from tvm import tirx as tir
def build(begin_fn, end_fn, stride, symbolic):
dim = tir.Var("m", "int64") if symbolic else 8
bb = relax.BlockBuilder()
x = relax.Var("x", relax.TensorType([dim], "float32"))
with bb.function("main", params=[x]):
with bb.dataflow():
y = bb.emit(relax.op.strided_slice(
x, [0], [begin_fn(dim)], [end_fn(dim)], [stride]), "y")
gv = bb.emit_output(y)
bb.emit_func_output(gv)
return bb.get()
def run(mod, X):
exe = tvm.relax.build(mod, target=tvm.target.Target("llvm"),
exec_mode="compiled")
return relax.VirtualMachine(exe, tvm.cpu())["main"](
tvm.runtime.tensor(X, tvm.cpu())).numpy()
X = np.clip(np.random.RandomState(7).randn(8), -1, 1).astype("float32")
# Symbolic dim, out-of-bound begin/end — should clip to a full reversal
mod = build(lambda d: d + 1, lambda d: -d - 1, -1, symbolic=True)
print(run(mod, X)) # [garbage, garbage, x[7], x[6], ...] — OOB reads
# Static dim, equally out-of-bound — clips correctly
mod_static = build(lambda d: 100, lambda d: -100, -1, symbolic=False)
print(run(mod_static, X)) # == X[::-1], correct
```
### Root cause
After `LegalizeOps`, the generated TIR for the symbolic case is:
```tir
out[v_ax0] = x[m + 1 - v_ax0]
```
The **extent** of `v_ax0` uses the min/max clip formulas (so the output
shape is correct), but the **index expression** uses the raw, unclipped `begin`
(`m + 1`). The first iterations therefore read `x[m+1]`,
`x[m]`, ... before landing in bounds. The static path folds the clip at
compile time, which is why only the symbolic path is broken.
Fix direction: clamp the runtime `begin` (and `end`) into `[0, dim-1]` /
`[-1, dim-1]` before generating the index expression, the same way the extent
formulas already do.
### Impact
`strided_slice` with symbolic dims and out-of-bound endpoints is reachable
from real frontends (ONNX `Slice` with dynamic `starts`/`ends` on a dynamic
batch/sequence axis). The failure is silent wrong-code
plus out-of-bounds reads on all three execution backends.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]