Repository: dotnet/runtime
Pull request #113618: Tensor<T> - Fixed missing memory offset from ReadOnlyTensorSpan
Author: login: michaelgsharp
Labels: name: area-System.Numerics.Tensors
Created: 2025-03-17T17:06:02Z | Merged: 2025-03-17T22:05:21Z

When creating #113166 I didn't have any testing for the implicit conversions and so missed a few places where the memory offset was not being passed through. This fixes that, and makes the memory offset no longer have a default value on the internal constructor so it has to be manually specified every time to not miss it anymore. It also adds testing to catch this case moving forward.


Diff:
diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.Factory.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.Factory.cs
index 1e3904fb7504d2..29b48f4a613e03 100644
--- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.Factory.cs
+++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.Factory.cs
@@ -64,7 +64,7 @@ public static Tensor<T> Create<T>(T[] values, scoped ReadOnlySpan<nint> lengths,
         /// <exception cref="ArgumentOutOfRangeException"></exception>
         public static Tensor<T> Create<T>(T[] values, scoped ReadOnlySpan<nint> lengths, scoped ReadOnlySpan<nint> strides, bool pinned = false)
         {
-            return new Tensor<T>(values, lengths, strides, pinned);
+            return new Tensor<T>(values, lengths, strides, memoryOffset: 0, pinned);
         }
 
         /// <summary>
@@ -77,7 +77,7 @@ public static Tensor<T> Create<T>(T[] values, scoped ReadOnlySpan<nint> lengths,
         public static Tensor<T> Create<T>(IEnumerable<T> values, scoped ReadOnlySpan<nint> lengths, bool pinned = false)
         {
             T[] data = values.ToArray();
-            return new Tensor<T>(data, lengths.IsEmpty ? [data.Length] : lengths, pinned);
+            return new Tensor<T>(data, lengths.IsEmpty ? [data.Length] : lengths, memoryOffset: 0, pinned);
         }
 
         /// <summary>
@@ -90,7 +90,7 @@ public static Tensor<T> Create<T>(IEnumerable<T> values, scoped ReadOnlySpan<nin
         public static Tensor<T> Create<T>(IEnumerable<T> values, scoped ReadOnlySpan<nint> lengths, scoped ReadOnlySpan<nint> strides, bool pinned = false)
         {
             T[] data = values.ToArray();
-            return new Tensor<T>(data, lengths.IsEmpty ? [data.Length] : lengths, strides, pinned);
+            return new Tensor<T>(data, lengths.IsEmpty ? [data.Length] : lengths, strides, memoryOffset: 0, pinned);
         }
 
         #region Normal
@@ -115,7 +115,7 @@ public static Tensor<T> CreateAndFillGaussianNormalDistribution<T>(Random random
             nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
             T[] values = new T[linearLength];
             GaussianDistribution<T>(values, linearLength, random);
-            return new Tensor<T>(values, lengths, false);
+            return new Tensor<T>(values, lengths, memoryOffset: 0, isPinned: false);
         }
 
         private static void GaussianDistribution<T>(in Span<T> values, nint linearLength, Random random)
@@ -154,7 +154,7 @@ public static Tensor<T> CreateAndFillUniformDistribution<T>(Random random, param
             for (int i = 0; i < values.Length; i++)
                 values[i] = T.CreateChecked(random.NextDouble());
 
-            return new Tensor<T>(values, lengths, false);
+            return new Tensor<T>(values, lengths, memoryOffset: 0, isPinned: false);
         }
 
         /// <summary>
@@ -175,7 +175,7 @@ public static Tensor<T> CreateUninitialized<T>(scoped ReadOnlySpan<nint> lengths
         {
             nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
             T[] values = GC.AllocateUninitializedArray<T>((int)linearLength, pinned);
-            return new Tensor<T>(values, lengths, strides, pinned);
+            return new Tensor<T>(values, lengths, strides, memoryOffset: 0, pinned);
         }
 
         /// <summary>
diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs
index 74c6efe94f94cf..4878350449c319 100644
--- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs
+++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs
@@ -49,10 +49,10 @@ internal Tensor()
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        internal Tensor(T[]? values, ReadOnlySpan<nint> lengths, bool isPinned = false, int memoryOffset = 0) : this(values, lengths, Array.Empty<nint>(), isPinned, memoryOffset) { }
+        internal Tensor(T[]? values, ReadOnlySpan<nint> lengths, int memoryOffset, bool isPinned = false) : this(values, lengths, Array.Empty<nint>(), memoryOffset, isPinned) { }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        internal Tensor(T[]? values, ReadOnlySpan<nint> lengths, ReadOnlySpan<nint> strides, bool isPinned = false, int memoryOffset = 0)
+        internal Tensor(T[]? values, ReadOnlySpan<nint> lengths, ReadOnlySpan<nint> strides, int memoryOffset, bool isPinned = false)
         {
             if (values == null)
             {
@@ -69,6 +69,9 @@ internal Tensor(T[]? values, ReadOnlySpan<nint> lengths, ReadOnlySpan<nint> stri
             _lengths = lengths.IsEmpty ? [values.Length] : lengths.ToArray();
             _memoryOffset = memoryOffset;
 
+            if (_memoryOffset < 0 || (_memoryOffset >= values.Length && values.Length != 0 ))
+                ThrowHelper.ThrowIndexOutOfRangeException();
+
             _flattenedLength = TensorSpanHelpers.CalculateTotalLength(_lengths);
             _strides = strides.IsEmpty ? TensorSpanHelpers.CalculateStrides(_lengths, _flattenedLength) : strides.ToArray();
             TensorSpanHelpers.ValidateStrides(_strides, _lengths);
@@ -77,12 +80,12 @@ internal Tensor(T[]? values, ReadOnlySpan<nint> lengths, ReadOnlySpan<nint> stri
             if (Environment.Is64BitProcess)
             {
                 // See comment in Span<T>.Slice for how this works.
-                if ((ulong)(uint)maxElements >= (ulong)(uint)values.Length && values.Length != 0)
+                if ((ulong)(uint)maxElements >= (ulong)(uint)(values.Length - memoryOffset) && values.Length != 0)
                     ThrowHelper.ThrowArgument_InvalidStridesAndLengths();
             }
             else
             {
-                if (((uint)maxElements >= (uint)(values.Length)) && values.Length != 0)
+                if (((uint)maxElements >= (uint)(values.Length - memoryOffset)) && values.Length != 0)
                     ThrowHelper.ThrowArgument_InvalidStridesAndLengths();
             }
 
@@ -99,7 +102,7 @@ static Tensor<T> ITensor<Tensor<T>, T>.Create(scoped ReadOnlySpan<nint> lengths,
         {
             nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
             T[] values = pinned ? GC.AllocateArray<T>((int)linearLength, pinned) : (new T[linearLength]);
-            return new Tensor<T>(values, lengths.ToArray(), pinned);
+            return new Tensor<T>(values, lengths.ToArray(), memoryOffset: 0, pinned);
         }
 
         /// <summary>
@@ -112,7 +115,7 @@ static Tensor<T> ITensor<Tensor<T>, T>.Create(scoped ReadOnlySpan<nint> lengths,
         {
             nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
             T[] values = pinned ? GC.AllocateArray<T>((int)linearLength, pinned) : (new T[linearLength]);
-            return new Tensor<T>(values, lengths.ToArray(), strides.ToArray(), pinned);
+            return new Tensor<T>(values, lengths.ToArray(), strides.ToArray(), memoryOffset: 0, pinned);
         }
 
         /// <summary>
@@ -124,7 +127,7 @@ static Tensor<T> ITensor<Tensor<T>, T>.CreateUninitialized(scoped ReadOnlySpan<n
         {
             nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
             T[] values = GC.AllocateUninitializedArray<T>((int)linearLength, pinned);
-            return new Tensor<T>(values, lengths.ToArray(), pinned);
+            return new Tensor<T>(values, lengths.ToArray(), memoryOffset: 0, pinned);
         }
 
         /// <summary>
@@ -137,7 +140,7 @@ static Tensor<T> ITensor<Tensor<T>, T>.CreateUninitialized(scoped ReadOnlySpan<n
         {
             nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
             T[] values = GC.AllocateUninitializedArray<T>((int)linearLength, pinned);
-            return new Tensor<T>(values, lengths.ToArray(), strides.ToArray(), pinned);
+            return new Tensor<T>(values, lengths.ToArray(), strides.ToArray(), memoryOffset: 0, pinned);
         }
 
         // ITensor
@@ -374,24 +377,24 @@ public Tensor<T> this[Tensor<bool> filter]
                     }
                 }
 
-                return new Tensor<T>(values, [linearLength], _isPinned);
+                return new Tensor<T>(values, [linearLength], _memoryOffset, _isPinned);
             }
         }
 
         /// <summary>
         /// Defines an implicit conversion of an array to a <see cref="Tensor{T}"/>.
         /// </summary>
-        public static implicit operator Tensor<T>(T[] array) => new Tensor<T>(array, [array.Length]);
+        public static implicit operator Tensor<T>(T[] array) => new Tensor<T>(array, [array.Length], memoryOffset: 0);
 
         /// <summary>
         /// Defines an implicit conversion of a <see cref="Tensor{T}"/> to a <see cref="TensorSpan{T}"/>.
         /// </summary>
-        public static implicit operator TensorSpan<T>(Tensor<T> value) => new TensorSpan<T>(ref MemoryMarshal.GetArrayDataReference(value._values), value._lengths, value._strides, value._flattenedLength);
+        public static implicit operator TensorSpan<T>(Tensor<T> value) => value.AsTensorSpan();
 
         /// <summary>
         /// Defines an implicit conversion of a <see cref="Tensor{T}"/> to a <see cref="TensorSpan{T}"/>.
         /// </summary>
-        public static implicit operator ReadOnlyTensorSpan<T>(Tensor<T> value) => new ReadOnlyTensorSpan<T>(ref MemoryMarshal.GetArrayDataReference(value._values), value._lengths, value._strides, value.FlattenedLength);
+        public static implicit operator ReadOnlyTensorSpan<T>(Tensor<T> value) => value.AsReadOnlyTensorSpan();
 
         /// <summary>
         /// Converts this <see cref="Tensor{T}"/> to a <see cref="TensorSpan{T}"/> pointing to the same backing memory."/>
@@ -411,7 +414,7 @@ public Tensor<T> this[Tensor<bool> filter]
         /// </summary>
         /// <param name="start">The start location you want in the <see cref="TensorSpan{T}"/>.</param>
         /// <returns><see cref="TensorSpan{T}"/> based on the provided ranges.</returns>
-        public TensorSpan<T> AsTensorSpan(params scoped ReadOnlySpan<nint> start) => Slice(start);
+        public TensorSpan<T> AsTensorSpan(params scoped ReadOnlySpan<nint> start) => AsTensorSpan().Slice(start);
 
         /// <summary>
         /// Converts this <see cref="Tensor{T}"/> to a <see cref="TensorSpan{T}"/> pointing to the same backing memory based on the provided start indexes."/>
@@ -424,7 +427,7 @@ public Tensor<T> this[Tensor<bool> filter]
         /// Converts this <see cref="Tensor{T}"/> to a <see cref="ReadOnlyTensorSpan{T}"/> pointing to the same backing memory."/>
         /// </summary>
         /// <returns><see cref="ReadOnlyTensorSpan{T}"/></returns>
-        public ReadOnlyTensorSpan<T> AsReadOnlyTensorSpan() => new ReadOnlyTensorSpan<T>(ref MemoryMarshal.GetArrayDataReference(_values), _lengths, _strides, _flattenedLength);
+        public ReadOnlyTensorSpan<T> AsReadOnlyTensorSpan() => new ReadOnlyTensorSpan<T>(ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_values), _memoryOffset), _lengths, _strides, _flattenedLength);
 
         /// <summary>
         /// Converts this <see cref="Tensor{T}"/> to a <see cref="ReadOnlyTensorSpan{T}"/> pointing to the same backing memory based on the provided ranges."/>
@@ -438,7 +441,7 @@ public Tensor<T> this[Tensor<bool> filter]
         /// </summary>
         /// <param name="start">The start locations you want in the <see cref="ReadOnlyTensorSpan{T}"/></param>
         /// <returns></returns>
-        public ReadOnlyTensorSpan<T> AsReadOnlyTensorSpan(params scoped ReadOnlySpan<nint> start) => Slice(start);
+        public ReadOnlyTensorSpan<T> AsReadOnlyTensorSpan(params scoped ReadOnlySpan<nint> start) => AsTensorSpan().Slice(start);
 
         /// <summary>
         /// Converts this <see cref="Tensor{T}"/> to a <see cref="ReadOnlyTensorSpan{T}"/> pointing to the same backing memory based on the provided start indexes."/>
@@ -515,7 +518,7 @@ public Tensor<T> Slice(params ReadOnlySpan<NRange> start)
             if ((memoryOffset >= _values.Length || memoryOffset < 0) && flattenedLength != 0)
                 ThrowHelper.ThrowIndexOutOfRangeException();
 
-            Tensor<T> toReturn = new Tensor<T>(_values, lengths, Strides, _isPinned, memoryOffset);
+            Tensor<T> toReturn = new Tensor<T>(_values, lengths, Strides, memoryOffset, _isPinned);
 
             if (offsetsArray != null)
                 ArrayPool<nint>.Shared.Return(offsetsArray);
diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs
index 9e5c4c933cd602..6b9dbec121b207 100644
--- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs
+++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs
@@ -218,7 +218,7 @@ internal static ReadOnlyTensorSpan<T> LazyBroadcast<T>(in ReadOnlyTensorSpan<T>
         internal static Tensor<T> LazyBroadcast<T>(Tensor<T> input, ReadOnlySpan<nint> lengths)
         {
             if (input.Lengths.SequenceEqual(lengths))
-                return new Tensor<T>(input._values, lengths, false);
+                return new Tensor<T>(input._values, lengths, input._memoryOffset, isPinned: false);
 
             if (!TensorHelpers.IsBroadcastableTo(input.Lengths, lengths))
                 ThrowHelper.ThrowArgument_LengthsNotBroadcastCompatible();
@@ -244,7 +244,7 @@ internal static Tensor<T> LazyBroadcast<T>(Tensor<T> input, ReadOnlySpan<nint> l
                 }
             }
 
-            Tensor<T> output = new Tensor<T>(input._values, lengths, strides);
+            Tensor<T> output = new Tensor<T>(input._values, lengths, strides, input._memoryOffset);
 
             return output;
         }
@@ -2660,7 +2660,7 @@ public static Tensor<T> PermuteDimensions<T>(this Tensor<T> tensor, params ReadO
                         lengths[i] = tensor.Lengths[dimensions[i]];
                     permutation = dimensions.ToArray();
                 }
-                outTensor = new Tensor<T>(values, lengths, Array.Empty<nint>(), tensor._isPinned);
+                outTensor = new Tensor<T>(values, lengths, Array.Empty<nint>(), tensor._memoryOffset, tensor._isPinned);
 
                 ospan = outTensor.AsTensorSpan();
                 ispan = tensor.AsTensorSpan();
@@ -2771,7 +2771,7 @@ public static Tensor<T> Reshape<T>(this Tensor<T> tensor, params ReadOnlySpan<ni
             else
                 strides = TensorSpanHelpers.CalculateStrides(arrLengths);
 
-            return new Tensor<T>(tensor._values, arrLengths, strides);
+            return new Tensor<T>(tensor._values, arrLengths, strides, tensor._memoryOffset);
         }
 
         /// <summary>
@@ -2926,7 +2926,7 @@ public static Tensor<T> Resize<T>(Tensor<T> tensor, ReadOnlySpan<nint> lengths)
         {
             nint newSize = TensorSpanHelpers.CalculateTotalLength(lengths);
             T[] values = tensor.IsPinned ? GC.AllocateArray<T>((int)newSize) : (new T[newSize]);
-            Tensor<T> output = new Tensor<T>(values, lengths, false);
+            Tensor<T> output = new Tensor<T>(values, lengths, tensor._memoryOffset, isPinned: false);
             ReadOnlySpan<T> span = MemoryMarshal.CreateSpan(ref tensor.AsTensorSpan()._reference, (int)tensor._values.Length);
             Span<T> ospan = MemoryMarshal.CreateSpan(ref output.AsTensorSpan()._reference, (int)output.FlattenedLength);
             if (newSize > tensor._values.Length)
@@ -3217,7 +3217,7 @@ public static Tensor<T>[] Split<T>(scoped in ReadOnlyTensorSpan<T> tensor, int s
             for (int i = 0; i < outputs.Length; i++)
             {
                 T[] values = new T[(int)totalToCopy];
-                outputs[i] = new Tensor<T>(values, newShape);
+                outputs[i] = new Tensor<T>(values, newShape, memoryOffset: 0);
                 oIndices.Clear();
                 iIndices.Clear();
 
@@ -3299,7 +3299,7 @@ public static Tensor<T> SqueezeDimension<T>(this Tensor<T> tensor, int dimension
                 strides = TensorSpanHelpers.CalculateStrides(lengths);
             }
 
-            return new Tensor<T>(tensor._values, lengths, strides);
+            return new Tensor<T>(tensor._values, lengths, strides, tensor._memoryOffset);
         }
 
         /// <summary>
@@ -3661,7 +3661,7 @@ public static Tensor<T> Unsqueeze<T>(this Tensor<T> tensor, int dimension)
                 strides[dimension] = tensor.Strides[dimension] * tensor.Lengths[dimension];
             }
 
-            return new Tensor<T>(tensor._values, lengths, strides);
+            return new Tensor<T>(tensor._values, lengths, strides, tensor._memoryOffset);
         }
 
         /// <summary>
diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs
index 7e99a29e282b3e..fce9d8f68f474f 100644
--- a/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs
+++ b/src/libraries/System.Numerics.Tensors/tests/TensorTests.cs
@@ -1136,6 +1136,46 @@ public static void TensorStdDevTests()
             Assert.Equal(0f, Tensor.StdDev(upperLeft));
         }
 
+        [Fact]
+        public static void TensorSumTests()
+        {
+            float[] values = new float[] { 1, 2, 3, 4, 5, 6 };
+            Tensor<float> t0 = Tensor.Create<float>(new float[] { 1, 2, 3, 4, 5, 6 }, [2, 3]);
+            float sum = Tensor.Sum<float>(t0);
+            Assert.Equal(21, sum);
+
+            // Slice first row of 2 x 2 for sum
+            Tensor<float> t1 = t0.Slice(new NRange(new NIndex(0), new NIndex(1)), new NRange(new NIndex(0), new NIndex(0, true)));
+            sum = Tensor.Sum<float>(t1);
+            Assert.Equal(6, sum);
+
+            // Slice second row of 2 x 2 for sum.
+            t1 = t0.Slice(new NRange(new NIndex(1), new NIndex(2)), new NRange(new NIndex(0), new NIndex(0, true)));
+            sum = Tensor.Sum<float>(t1);
+            Assert.Equal(15, sum);
+
+            // Slice first column of 2 x 2 for sum.
+            t1 = t0.Slice(new NRange(new NIndex(0), new NIndex(2)), new NRange(new NIndex(0), new NIndex(1)));
+            sum = Tensor.Sum<float>(t1);
+            Assert.Equal(5, sum);
+
+            // Slice second column of 2 x 2 for sum.
+            t1 = t0.Slice(new NRange(new NIndex(0), new NIndex(2)), new NRange(new NIndex(1), new NIndex(2)));
+            sum = Tensor.Sum<float>(t1);
+            Assert.Equal(7, sum);
+
+            // Slice Third column of 2 x 2 for sum.
+            t1 = t0.Slice(new NRange(new NIndex(0), new NIndex(2)), new NRange(new NIndex(2), new NIndex(3)));
+            sum = Tensor.Sum<float>(t1);
+            Assert.Equal(9, sum);
+
+            Assert.Throws<IndexOutOfRangeException>(()=> new Tensor<float>(new float[] { 1, 2, 3, 4, 5, 6 }, [2, 3], -1));
+            Assert.Throws<IndexOutOfRangeException>(()=> new Tensor<float>(new float[] { 1, 2, 3, 4, 5, 6 }, [2, 3], 100));
+            Assert.Throws<IndexOutOfRangeException>(()=> new Tensor<float>(new float[] { 1, 2, 3, 4, 5, 6 }, [2, 3], int.MinValue));
+            Assert.Throws<IndexOutOfRangeException>(()=> new Tensor<float>(new float[] { 1, 2, 3, 4, 5, 6 }, [2, 3], int.MaxValue));
+            Assert.Throws<ArgumentException>(()=> new Tensor<float>(new float[] { 1, 2, 3, 4, 5, 6 }, [2, 3], 2));
+        }
+
         public static float StdDev(float[] values)
         {
             float mean = Mean(values);
@@ -2649,7 +2689,7 @@ public void TensorFilteredUpdateTest()
         [Fact]
         public void TensorObjectFillTests()
         {
-            ITensor tensor = (ITensor)new Tensor<int>(new int[4], new nint[] { 2, 2 });
+            ITensor tensor = (ITensor)new Tensor<int>(new int[4], new nint[] { 2, 2 }, 0);
             tensor.Fill(5);
 
             Assert.Equal(5, tensor[0, 0]);
@@ -2670,7 +2710,7 @@ public void TensorObjectFillTests()
         [Fact]
         public void TensorObjectIndexerTests()
         {
-            ITensor tensor = new Tensor<int>(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 });
+            ITensor tensor = new Tensor<int>(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 }, 0);
 
             Assert.Equal(1, tensor[new nint[] { 0, 0 }]);
             Assert.Equal(2, tensor[new nint[] { 0, 1 }]);
@@ -2701,7 +2741,7 @@ public void TensorObjectIndexerTests()
         [Fact]
         public void TensorGetPinnedHandleTests()
         {
-            Tensor<int> tensor = new Tensor<int>(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 });
+            Tensor<int> tensor = new Tensor<int>(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 }, 0);
 
             using MemoryHandle handle = tensor.GetPinnedHandle();
             unsafe


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This pull request fixes the missing memory offset issue in ReadOnlyTensorSpan by requiring an explicit memory offset in internal constructors and updating all related factory, slicing, and conversion methods. The changes ensure that the memory offset is consistently propagated throughout tensor creation, slicing, and conversion operations, and new tests have been added for verifying the correctness of summing and slicing operations.

### Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

| File | Description |
| ---- | ----------- |
| src/libraries/System.Numerics.Tensors/tests/TensorTests.cs | Added tests for Tensor.Sum and updated tensor creation to include memory offset |
| src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs | Updated constructors, conversion operators, and slicing methods to require and propagate memory offset |
| src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.Factory.cs | Modified factory methods to pass the mandatory memory offset |
| src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs | Adjusted extension methods to maintain memory offset consistency in tensor broadcasting, reshaping, and other operations |




state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:07:02Z
user: login: tarekgh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T17:15:19Z
user: login: tarekgh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T17:18:28Z
user: login: tarekgh
body: Added a small suggestions, LGTM otherwise.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-17T17:19:35Z
user: login: carlossanlop
body: Out of curiosity, how did you catch this? Do you have an open issue that needs to be linked in the PR description, or did you find it by testing manually?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:21:28Z
user: login: michaelgsharp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:26:12Z
user: login: michaelgsharp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:26:20Z
user: login: michaelgsharp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:31:18Z
user: login: carlossanlop
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:35:50Z
user: login: carlossanlop
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:36:01Z
user: login: michaelgsharp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:37:33Z

Review comments:
diff_hunk: @@ -99,7 +99,7 @@ static Tensor<T> ITensor<Tensor<T>, T>.Create(scoped ReadOnlySpan<nint> lengths,
         {
             nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
             T[] values = pinned ? GC.AllocateArray<T>((int)linearLength, pinned) : (new T[linearLength]);
-            return new Tensor<T>(values, lengths.ToArray(), pinned);
+            return new Tensor<T>(values, lengths.ToArray(), 0, pinned);
path: src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs
user: login: tarekgh
body: >0 [](http://example.com/codeflow?start=60&length=1)

nit: could you please refix this parameter with `memoryOffset: ` in all usages for the sake of readability?
created_at: 2025-03-17T17:15:19Z
author_association: MEMBER
diff_hunk: @@ -218,7 +218,7 @@ internal static ReadOnlyTensorSpan<T> LazyBroadcast<T>(in ReadOnlyTensorSpan<T>
         internal static Tensor<T> LazyBroadcast<T>(Tensor<T> input, ReadOnlySpan<nint> lengths)
         {
             if (input.Lengths.SequenceEqual(lengths))
-                return new Tensor<T>(input._values, lengths, false);
+                return new Tensor<T>(input._values, lengths, input._memoryOffset, false);
path: src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs
user: login: tarekgh
body: >false [](http://example.com/codeflow?start=82&length=5)

nit: add `pinned: `
created_at: 2025-03-17T17:18:28Z
author_association: MEMBER
diff_hunk: @@ -2701,7 +2735,7 @@ public void TensorObjectIndexerTests()
         [Fact]
         public void TensorGetPinnedHandleTests()
         {
-            Tensor<int> tensor = new Tensor<int>(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 });
+            Tensor<int> tensor = new Tensor<int>(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 }, 0);
path: src/libraries/System.Numerics.Tensors/tests/TensorTests.cs
user: login: carlossanlop
body: Do you have tests that pass anything else except 0? Negative numbers, positive numbers, int max, int min?
created_at: 2025-03-17T17:21:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -99,7 +99,7 @@ static Tensor<T> ITensor<Tensor<T>, T>.Create(scoped ReadOnlySpan<nint> lengths,
         {
             nint linearLength = TensorSpanHelpers.CalculateTotalLength(lengths);
             T[] values = pinned ? GC.AllocateArray<T>((int)linearLength, pinned) : (new T[linearLength]);
-            return new Tensor<T>(values, lengths.ToArray(), pinned);
+            return new Tensor<T>(values, lengths.ToArray(), 0, pinned);
path: src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/Tensor.cs
user: login: michaelgsharp
body: Fixed.
created_at: 2025-03-17T17:26:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -218,7 +218,7 @@ internal static ReadOnlyTensorSpan<T> LazyBroadcast<T>(in ReadOnlyTensorSpan<T>
         internal static Tensor<T> LazyBroadcast<T>(Tensor<T> input, ReadOnlySpan<nint> lengths)
         {
             if (input.Lengths.SequenceEqual(lengths))
-                return new Tensor<T>(input._values, lengths, false);
+                return new Tensor<T>(input._values, lengths, input._memoryOffset, false);
path: src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorExtensions.cs
user: login: michaelgsharp
body: Fixed.
created_at: 2025-03-17T17:26:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2701,7 +2735,7 @@ public void TensorObjectIndexerTests()
         [Fact]
         public void TensorGetPinnedHandleTests()
         {
-            Tensor<int> tensor = new Tensor<int>(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 });
+            Tensor<int> tensor = new Tensor<int>(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 }, 0);
path: src/libraries/System.Numerics.Tensors/tests/TensorTests.cs
user: login: michaelgsharp
body: Since the constructor that takes that value is internal I didn't have much testing around it. I'll add some checks/testing real fast though.
created_at: 2025-03-17T17:31:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2701,7 +2735,7 @@ public void TensorObjectIndexerTests()
         [Fact]
         public void TensorGetPinnedHandleTests()
         {
-            Tensor<int> tensor = new Tensor<int>(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 });
+            Tensor<int> tensor = new Tensor<int>(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 }, 0);
path: src/libraries/System.Numerics.Tensors/tests/TensorTests.cs
user: login: carlossanlop
body: Oh, right, sorry. Internal constructor. Nevermind.
created_at: 2025-03-17T17:35:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2701,7 +2735,7 @@ public void TensorObjectIndexerTests()
         [Fact]
         public void TensorGetPinnedHandleTests()
         {
-            Tensor<int> tensor = new Tensor<int>(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 });
+            Tensor<int> tensor = new Tensor<int>(new int[] { 1, 2, 3, 4 }, new nint[] { 2, 2 }, 0);
path: src/libraries/System.Numerics.Tensors/tests/TensorTests.cs
user: login: michaelgsharp
body: Added
created_at: 2025-03-17T17:37:33Z
author_association: CONTRIBUTOR

PR comments:
user: login: dotnet-policy-service[bot]
created_at: 2025-03-17T17:06:50Z
body: Tagging subscribers to this area: @dotnet/area-system-numerics-tensors
See info in [area-owners.md](https://github.com/dotnet/runtime/blob/main/docs/area-owners.md) if you want to be subscribed.
<!-- Policy app identification https://img.shields.io/static/v1?label=PullRequestIssueManagement. -->
author_association: CONTRIBUTOR
user: login: michaelgsharp
created_at: 2025-03-17T17:29:55Z
body: @carlossanlop I'm working on some more complex slicing operations and during that work I discovered this issue. I decided this was important enough that I'd split it off into its own PR instead of trying to piggy back it on the slicing PR since thats going to be bigger/more complex.
author_association: CONTRIBUTOR
user: login: carlossanlop
created_at: 2025-03-17T17:35:01Z
body: > I'm working on some more complex slicing operations and during that work I discovered this issue. I decided this was important enough that I'd split it off into its own PR instead of trying to piggy back it on the slicing PR since thats going to be bigger/more complex.

Cool. Sounds good. I mainly just wanted to learn the process.
author_association: CONTRIBUTOR