From 01f3cb711bd6b29f8189b898bf21c65a9efaf407 Mon Sep 17 00:00:00 2001 From: Bradley Lewis Fargo Date: Fri, 20 Mar 2026 01:35:28 -0500 Subject: [PATCH 1/5] Fix Nx.slice crash on scalar tensor bin_slice/7 called hd([]) on empty strides list for rank-0 tensors. Added scalar guard clause that returns data unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) --- nx/lib/nx/binary_backend.ex | 2 ++ nx/test/nx/scalar_slice_test.exs | 15 +++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 nx/test/nx/scalar_slice_test.exs diff --git a/nx/lib/nx/binary_backend.ex b/nx/lib/nx/binary_backend.ex index ae9c3f6267..ac91ea1299 100644 --- a/nx/lib/nx/binary_backend.ex +++ b/nx/lib/nx/binary_backend.ex @@ -1849,6 +1849,8 @@ defmodule Nx.BinaryBackend do |> then(&from_binary(out, &1)) end + defp bin_slice(data, _shape, _size, [], [], [], _output_shape), do: data + defp bin_slice(data, shape, size, start_indices, lengths, strides, output_shape) do start_indices = clamp_indices(start_indices, shape, lengths) diff --git a/nx/test/nx/scalar_slice_test.exs b/nx/test/nx/scalar_slice_test.exs new file mode 100644 index 0000000000..ce1309d1f6 --- /dev/null +++ b/nx/test/nx/scalar_slice_test.exs @@ -0,0 +1,15 @@ +defmodule Nx.ScalarSliceTest do + use ExUnit.Case, async: true + + test "slice of scalar tensor returns scalar" do + t = Nx.tensor(42) + result = Nx.slice(t, [], []) + assert Nx.to_number(result) == 42 + end + + test "slice of scalar f64 tensor" do + t = Nx.tensor(3.14, type: :f64) + result = Nx.slice(t, [], []) + assert_in_delta Nx.to_number(result), 3.14, 1.0e-10 + end +end From 67eb820df810e22b2e74635d9279f74e03a72a63 Mon Sep 17 00:00:00 2001 From: Bradley Lewis Fargo Date: Fri, 20 Mar 2026 04:04:41 -0500 Subject: [PATCH 2/5] move scalar slice tests to nx_test, add doctest to slice in nx.ex --- nx/lib/nx.ex | 9 +++++++++ nx/test/nx/scalar_slice_test.exs | 15 --------------- nx/test/nx_test.exs | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 15 deletions(-) delete mode 100644 nx/test/nx/scalar_slice_test.exs diff --git a/nx/lib/nx.ex b/nx/lib/nx.ex index fc5f55dcc3..aeb5f069af 100644 --- a/nx/lib/nx.ex +++ b/nx/lib/nx.ex @@ -13654,6 +13654,15 @@ defmodule Nx do iex> Nx.slice(Nx.tensor([[1, 2, 3], [4, 5, 6]]), [Nx.tensor(1.0), Nx.tensor(0)], [1, 1]) ** (ArgumentError) index must be integer type, got {:f, 32} for axis 0 + + ## No Op + Slicing a scalar tensor returns the scalar itself. + + iex> Nx.slice(Nx.tensor(42), [], []) + #Nx.tensor< + s32 + 42 + > """ @doc type: :indexed def slice(tensor, start_indices, lengths, opts \\ []) diff --git a/nx/test/nx/scalar_slice_test.exs b/nx/test/nx/scalar_slice_test.exs deleted file mode 100644 index ce1309d1f6..0000000000 --- a/nx/test/nx/scalar_slice_test.exs +++ /dev/null @@ -1,15 +0,0 @@ -defmodule Nx.ScalarSliceTest do - use ExUnit.Case, async: true - - test "slice of scalar tensor returns scalar" do - t = Nx.tensor(42) - result = Nx.slice(t, [], []) - assert Nx.to_number(result) == 42 - end - - test "slice of scalar f64 tensor" do - t = Nx.tensor(3.14, type: :f64) - result = Nx.slice(t, [], []) - assert_in_delta Nx.to_number(result), 3.14, 1.0e-10 - end -end diff --git a/nx/test/nx_test.exs b/nx/test/nx_test.exs index 5d42216132..d973dd9e6c 100644 --- a/nx/test/nx_test.exs +++ b/nx/test/nx_test.exs @@ -3574,4 +3574,19 @@ defmodule NxTest do assert 28 = Nx.bit_size(tensor) end end + + describe "slice of scalar tensor" do + test "returns scalar" do + t = Nx.tensor(42) + result = Nx.slice(t, [], []) + assert Nx.to_number(result) == 42 + end + + test "slice of scalar f64 tensor" do + t = Nx.tensor(3.14, type: :f64) + result = Nx.slice(t, [], []) + assert_in_delta Nx.to_number(result), 3.15, 1.0e-10 + end + end + end From 87ecde8b4a3b007f94637d346df776114945c65c Mon Sep 17 00:00:00 2001 From: Bradley Lewis Fargo Date: Fri, 20 Mar 2026 04:07:21 -0500 Subject: [PATCH 3/5] formatted --- nx/test/nx_test.exs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nx/test/nx_test.exs b/nx/test/nx_test.exs index d973dd9e6c..ff36636b3f 100644 --- a/nx/test/nx_test.exs +++ b/nx/test/nx_test.exs @@ -3580,13 +3580,12 @@ defmodule NxTest do t = Nx.tensor(42) result = Nx.slice(t, [], []) assert Nx.to_number(result) == 42 - end - + end + test "slice of scalar f64 tensor" do t = Nx.tensor(3.14, type: :f64) result = Nx.slice(t, [], []) assert_in_delta Nx.to_number(result), 3.15, 1.0e-10 end end - end From c9406de2ff16ece894de62eab63b405b5f6b17f7 Mon Sep 17 00:00:00 2001 From: Bradley Lewis Fargo Date: Fri, 20 Mar 2026 04:10:24 -0500 Subject: [PATCH 4/5] rename tensor to Tensor in doctest --- nx/lib/nx.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nx/lib/nx.ex b/nx/lib/nx.ex index aeb5f069af..ea50ba4238 100644 --- a/nx/lib/nx.ex +++ b/nx/lib/nx.ex @@ -13659,7 +13659,7 @@ defmodule Nx do Slicing a scalar tensor returns the scalar itself. iex> Nx.slice(Nx.tensor(42), [], []) - #Nx.tensor< + #Nx.Tensor< s32 42 > From 52c6c78e2aa891c16585470aa8199b0f76a81dde Mon Sep 17 00:00:00 2001 From: Bradley Lewis Fargo Date: Fri, 20 Mar 2026 04:15:24 -0500 Subject: [PATCH 5/5] Fix Nx.slice crash on scalar tensor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slicing a scalar tensor is a valid no-op — return the tensor unchanged when shape is {} and start_indices/lengths are empty. The check is done in Nx.slice itself (not in BinaryBackend) so all backends get the fix without needing separate implementations. NumPy does the same: np.array(5)[()] returns 5. Co-Authored-By: Claude Opus 4.6 (1M context) --- nx/lib/nx.ex | 14 ++++++++++++-- nx/lib/nx/binary_backend.ex | 2 -- nx/test/nx_test.exs | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/nx/lib/nx.ex b/nx/lib/nx.ex index ea50ba4238..b8a298c37e 100644 --- a/nx/lib/nx.ex +++ b/nx/lib/nx.ex @@ -13655,8 +13655,9 @@ defmodule Nx do iex> Nx.slice(Nx.tensor([[1, 2, 3], [4, 5, 6]]), [Nx.tensor(1.0), Nx.tensor(0)], [1, 1]) ** (ArgumentError) index must be integer type, got {:f, 32} for axis 0 - ## No Op - Slicing a scalar tensor returns the scalar itself. + ## Scalars + + Slicing a scalar tensor returns the scalar itself: iex> Nx.slice(Nx.tensor(42), [], []) #Nx.Tensor< @@ -13670,6 +13671,15 @@ defmodule Nx do opts = keyword!(opts, strides: 1) %T{vectorized_axes: vectorized_axes, shape: shape} = tensor = to_tensor(tensor) + # Slicing a scalar tensor is a no-op — return unchanged + if shape == {} and start_indices == [] and lengths == [] do + tensor + else + slice_non_scalar(tensor, start_indices, lengths, opts, vectorized_axes, shape) + end + end + + defp slice_non_scalar(tensor, start_indices, lengths, opts, vectorized_axes, shape) do if Enum.any?(start_indices, &(is_struct(&1, T) and &1.vectorized_axes != [])) do # if any of the indices is vectorized, we instead treat this slice as a gather [%{vectorized_axes: [{first_axis, _} | _] = vectorized_axes} | _] = diff --git a/nx/lib/nx/binary_backend.ex b/nx/lib/nx/binary_backend.ex index ac91ea1299..ae9c3f6267 100644 --- a/nx/lib/nx/binary_backend.ex +++ b/nx/lib/nx/binary_backend.ex @@ -1849,8 +1849,6 @@ defmodule Nx.BinaryBackend do |> then(&from_binary(out, &1)) end - defp bin_slice(data, _shape, _size, [], [], [], _output_shape), do: data - defp bin_slice(data, shape, size, start_indices, lengths, strides, output_shape) do start_indices = clamp_indices(start_indices, shape, lengths) diff --git a/nx/test/nx_test.exs b/nx/test/nx_test.exs index ff36636b3f..578f435dbb 100644 --- a/nx/test/nx_test.exs +++ b/nx/test/nx_test.exs @@ -3585,7 +3585,7 @@ defmodule NxTest do test "slice of scalar f64 tensor" do t = Nx.tensor(3.14, type: :f64) result = Nx.slice(t, [], []) - assert_in_delta Nx.to_number(result), 3.15, 1.0e-10 + assert_in_delta Nx.to_number(result), 3.14, 1.0e-10 end end end