Skip to content

slice_scatter: leave the input unchanged for an empty slice - #145

Open
john-rocky wants to merge 5 commits into
apple:mainfrom
john-rocky:fix/empty-slice-assign
Open

john-rocky wants to merge 5 commits into
apple:mainfrom
john-rocky:fix/empty-slice-assign

Conversation

@john-rocky

Copy link
Copy Markdown
Contributor

Summary

An assignment to an empty slice, such as mask[:, 8:, :, :] = 1.0 on a dim of size 8, exports to a slice_scatter whose src has a zero-size dim. PyTorch returns the input unchanged. The lowering emitted a coreai.slice_update with that empty update, and to_coreai() then died with a segmentation fault (exit 139).

replace_slice_scatter now returns the input when src has a static zero-size dim.

Reading an empty slice (the SIGTRAP in the issue notes) takes another path and is not changed here.

Testing

TestSliceScatter::test_slice_assignment assigns to [:, 4:] and [:, 8:] of a constant and of an input-derived tensor and compares the output with PyTorch. Before the fix, the 8: cases exit 139. After it, they pass.

uv run pytest tests/ops/test_ops.py -k TestSliceScatter   # 22 passed
uv run pytest tests/ -n 4                                 # 6558 passed; 3 failed in tests/debugging/, as on main
uv run --extra dev ruff check .                           # All checks passed
uv run --extra dev ruff format --check .                  # all files formatted

Fixes #142

An assignment to an empty slice, such as mask[:, 8:] = 1.0 on a dim of
size 8, exports to a slice_scatter whose src has a zero-size dim. PyTorch
returns the input unchanged. The lowering emitted a slice_update with the
empty update, and to_coreai() then died with a segmentation fault.
@gokulkrishna98
gokulkrishna98 self-requested a review November 9, 2026 21:53
@gokulkrishna98

Copy link
Copy Markdown
Contributor

nit: The PR looks good, could you also check the fix for validity on dynamic shapes ?

test_slice_assignment_dynamic exports the test_slice_assignment model
with every dim but the sliced one dynamic, then runs the program at
three sizes. test_slice_assignment_from_dynamic_end assigns to
x[:, n:] with n = x.shape[1] dynamic. In both, src keeps a static
zero-size dim, so replace_slice_scatter returns the input.

Without that check, test_slice_assignment_from_dynamic_end crashes in
to_coreai() (exit 139), and test_slice_assignment_dynamic aborts with
--compute-unit-kind=gpu.

Tested:
- pytest tests/ops/test_ops.py -k slice_assignment with
  --compute-unit-kind=gpu and with --compute-unit-kind=cpu: 9 passed
  each.
- pytest tests/ -n 4: 6566 passed, 126 skipped, 18 xfailed, 1 xpassed.
- ruff check and ruff format --check: clean.
@john-rocky

Copy link
Copy Markdown
Contributor Author

Thanks for the review. The fix is valid with dynamic shapes, and c081281 adds two tests for it.

The fix leaves the input unchanged only when src has a static zero-size dim (0 in src.type.shape). I found two ways to get there with dynamic shapes, and both match PyTorch on the interpreter, CPU and GPU:

  • test_slice_assignment_dynamic: mask[:, 8:, :, :] = 1.0 with every dim but dim 1 dynamic. The program is exported once and run at three sizes. Without the fix, this test aborts with --compute-unit-kind=gpu (exit 134).
  • test_slice_assignment_from_dynamic_end: mask[:, n:, :, :] = 1.0 with n = x.shape[1] dynamic. Without the fix, to_coreai() crashes here (exit 139), as in the static case.

When the slice length is dynamic, as in mask[:, y.shape[0]:, :, :] = 1.0, src has no static zero-size dim, so the fix does not apply and the lowering emits the same slice_update as on main. If y.shape[0] is x.shape[1] or more at run time, the slice is empty in PyTorch, and the interpreter and CPU match it.

One thing outside this PR: in that case, with --compute-unit-kind=gpu on macOS 27.0, the assignment writes to the last index of dim 1 instead of leaving the input unchanged. main does the same. To see it, export with x of shape (1, 8, 8, 1) and y of shape (4,), with dim 1 of x and dim 0 of y dynamic, then run with x of the same shape and y of shape (8,).

With a static start on a dynamic dim, such as mask[:, 8:, :, :] = 1.0 with dim 1 dynamic, I found no way to export a program in which the slice can be empty. torch.export raises an error for Dim(min=1), Dim(min=9) and Dim(min=1, max=8). Dim(min=10) exports, and there the slice is never empty.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

to_coreai() segfaults on an assignment to an empty slice

2 participants