Skip to content

Add some additional Tensor APIs #117739

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 18, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,18 @@ void System.IDisposable.Dispose() { }
System.Numerics.Tensors.ReadOnlyTensorSpan<T> System.Numerics.Tensors.IReadOnlyTensor<System.Numerics.Tensors.TensorSpan<T>, T>.AsReadOnlyTensorSpan(params scoped System.ReadOnlySpan<System.Buffers.NRange> ranges) { throw null; }
System.Numerics.Tensors.ReadOnlyTensorDimensionSpan<T> System.Numerics.Tensors.IReadOnlyTensor<System.Numerics.Tensors.TensorSpan<T>, T>.GetDimensionSpan(int dimension) { throw null; }
ref readonly T System.Numerics.Tensors.IReadOnlyTensor<System.Numerics.Tensors.TensorSpan<T>, T>.GetPinnableReference() { throw null; }
System.ReadOnlySpan<T> System.Numerics.Tensors.IReadOnlyTensor<System.Numerics.Tensors.TensorSpan<T>, T>.GetSpan(scoped System.ReadOnlySpan<nint> startIndexes, int length) { throw null; }
System.ReadOnlySpan<T> System.Numerics.Tensors.IReadOnlyTensor<System.Numerics.Tensors.TensorSpan<T>, T>.GetSpan(scoped System.ReadOnlySpan<System.Buffers.NIndex> startIndexes, int length) { throw null; }
System.Numerics.Tensors.TensorSpan<T> System.Numerics.Tensors.IReadOnlyTensor<System.Numerics.Tensors.TensorSpan<T>, T>.ToDenseTensor() { throw null; }

bool System.Numerics.Tensors.ITensor.IsReadOnly { get { throw null; } }
object? System.Numerics.Tensors.ITensor.this[params scoped System.ReadOnlySpan<System.Buffers.NIndex> indexes] { get { throw null; } set { } }
object? System.Numerics.Tensors.ITensor.this[params scoped System.ReadOnlySpan<nint> indexes] { get { throw null; } set { } }
void System.Numerics.Tensors.ITensor.Fill(object value) { }
static System.Numerics.Tensors.TensorSpan<T> ITensor<TensorSpan<T>, T>.Create(scoped System.ReadOnlySpan<nint> lengths, bool pinned) { throw null; }
static System.Numerics.Tensors.TensorSpan<T> ITensor<TensorSpan<T>, T>.Create(scoped System.ReadOnlySpan<nint> lengths, scoped System.ReadOnlySpan<nint> strides, bool pinned) { throw null; }
static System.Numerics.Tensors.TensorSpan<T> ITensor<TensorSpan<T>, T>.CreateUninitialized(scoped System.ReadOnlySpan<nint> lengths, bool pinned) { throw null; }
static System.Numerics.Tensors.TensorSpan<T> ITensor<TensorSpan<T>, T>.CreateUninitialized(scoped System.ReadOnlySpan<nint> lengths, scoped System.ReadOnlySpan<nint> strides, bool pinned) { throw null; }
static System.Numerics.Tensors.TensorSpan<T> ITensor<TensorSpan<T>, T>.CreateFromShape(scoped System.ReadOnlySpan<nint> lengths, bool pinned) { throw null; }
static System.Numerics.Tensors.TensorSpan<T> ITensor<TensorSpan<T>, T>.CreateFromShape(scoped System.ReadOnlySpan<nint> lengths, scoped System.ReadOnlySpan<nint> strides, bool pinned) { throw null; }
static System.Numerics.Tensors.TensorSpan<T> ITensor<TensorSpan<T>, T>.CreateFromShapeUninitialized(scoped System.ReadOnlySpan<nint> lengths, bool pinned) { throw null; }
static System.Numerics.Tensors.TensorSpan<T> ITensor<TensorSpan<T>, T>.CreateFromShapeUninitialized(scoped System.ReadOnlySpan<nint> lengths, scoped System.ReadOnlySpan<nint> strides, bool pinned) { throw null; }
System.Numerics.Tensors.TensorSpan<T> System.Numerics.Tensors.ITensor<System.Numerics.Tensors.TensorSpan<T>, T>.AsTensorSpan() { throw null; }
System.Numerics.Tensors.TensorSpan<T> System.Numerics.Tensors.ITensor<System.Numerics.Tensors.TensorSpan<T>, T>.AsTensorSpan(params scoped System.ReadOnlySpan<nint> startIndexes) { throw null; }
System.Numerics.Tensors.TensorSpan<T> System.Numerics.Tensors.ITensor<System.Numerics.Tensors.TensorSpan<T>, T>.AsTensorSpan(params scoped System.ReadOnlySpan<System.Buffers.NIndex> startIndexes) { throw null; }
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@
<Compile Include="System\Numerics\Tensors\netcore\TensorShape.cs" />
<Compile Include="System\Numerics\Tensors\netcore\TensorSpan_1.cs" />
<Compile Include="System\Numerics\Tensors\netcore\TensorSpanDebugView.cs" />
<Compile Include="System\Runtime\InteropServices\TensorMarshal.cs" />
</ItemGroup>

<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net9.0'))">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System.Numerics.Tensors
{
/// <summary>Performs primitive tensor operations over spans of memory.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using static System.Numerics.Tensors.TensorPrimitives;

namespace System.Numerics.Tensors
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ public interface IReadOnlyTensor<TSelf, T> : IReadOnlyTensor
/// <remarks>This method is intended to support .NET compilers and is not intended to be called by user code.</remarks>
ref readonly T GetPinnableReference();

/// <summary>Return a span that starts at the specified index and contains the specified number of items.</summary>
/// <param name="startIndexes">The index at which the span should start.</param>
/// <param name="length">The length for the span to return.</param>
/// <returns>A span that consists of <paramref name="length" /> elements from the current tensor starting at <paramref name="startIndexes" />.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <para><paramref name="startIndexes" /> does not contain <see cref="IReadOnlyTensor.Rank" /> elements.</para>
/// -or-
/// <para><paramref name="length" /> is negative, greater than <see cref="IReadOnlyTensor.FlattenedLength" />, or would cause the span to contain elements that should be skipped due to <see cref="IReadOnlyTensor.Strides" />.</para>
/// </exception>
/// <exception cref="IndexOutOfRangeException"><paramref name="startIndexes" /> is not a valid index into the tensor.</exception>
ReadOnlySpan<T> GetSpan(scoped ReadOnlySpan<nint> startIndexes, int length);

/// <inheritdoc cref="GetSpan(ReadOnlySpan{nint}, int)" />
ReadOnlySpan<T> GetSpan(scoped ReadOnlySpan<NIndex> startIndexes, int length);

/// <summary>Forms a slice out of the current tensor that begins at a specified index.</summary>
/// <param name="startIndexes">The indexes at which to begin the slice.</param>
/// <returns>A tensor that consists of all elements of the current tensor from <paramref name="startIndexes" /> to the end of the tensor.</returns>
Expand Down Expand Up @@ -112,5 +127,17 @@ public interface IReadOnlyTensor<TSelf, T> : IReadOnlyTensor
/// <para>If the <paramref name="destination" /> length is shorter than the source, no items are copied and the method returns <c>false</c>.</para>
/// </remarks>
bool TryFlattenTo(scoped Span<T> destination);

/// <summary>Tries to return a span that starts at the specified index and contains the specified number of items.</summary>
/// <param name="startIndexes">The index at which the span should start.</param>
/// <param name="length">The desired length of the span to retrieve.</param>
/// <param name="span">On successful return, a span that consists of <paramref name="length" /> elements from the current tensor starting at <paramref name="startIndexes" />.</param>
/// <returns><c>true</c> if a span was successfully retrieved; otherwise, <c>false</c> which indicates <paramref name="length" /> was invalid.</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndexes" /> does not contain <see cref="IReadOnlyTensor.Rank" /> elements.</exception>
/// <exception cref="IndexOutOfRangeException"><paramref name="startIndexes" /> is not a valid index into the tensor.</exception>
bool TryGetSpan(scoped ReadOnlySpan<nint> startIndexes, int length, out ReadOnlySpan<T> span);

/// <inheritdoc cref="TryGetSpan(ReadOnlySpan{nint}, int, out ReadOnlySpan{T})" />
bool TryGetSpan(scoped ReadOnlySpan<NIndex> startIndexes, int length, out ReadOnlySpan<T> span);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Diagnostics.CodeAnalysis;

namespace System.Numerics.Tensors
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public interface ITensor<TSelf, T> : ITensor, IReadOnlyTensor<TSelf, T>
/// <para>If <paramref name="pinned"/> is true the underlying buffer is created permanently pinned, otherwise the underlying buffer is not pinned.</para>
/// <para>The underlying buffer is initialized to default values.</para>
/// </remarks>
static abstract TSelf Create(scoped ReadOnlySpan<nint> lengths, bool pinned = false);
static abstract TSelf CreateFromShape(scoped ReadOnlySpan<nint> lengths, bool pinned = false);

/// <summary>Creates a new tensor with the specified lengths and strides.</summary>
/// <param name="lengths">The lengths of each dimension.</param>
Expand All @@ -35,7 +35,7 @@ public interface ITensor<TSelf, T> : ITensor, IReadOnlyTensor<TSelf, T>
/// <para>If <paramref name="pinned"/> is true the underlying buffer is created permanently pinned, otherwise the underlying buffer is not pinned.</para>
/// <para>The underlying buffer is initialized to default values.</para>
/// </remarks>
static abstract TSelf Create(scoped ReadOnlySpan<nint> lengths, scoped ReadOnlySpan<nint> strides, bool pinned = false);
static abstract TSelf CreateFromShape(scoped ReadOnlySpan<nint> lengths, scoped ReadOnlySpan<nint> strides, bool pinned = false);

/// <summary>Creates a new tensor with the specified lengths and strides.</summary>
/// <param name="lengths">The lengths of each dimension.</param>
Expand All @@ -44,7 +44,7 @@ public interface ITensor<TSelf, T> : ITensor, IReadOnlyTensor<TSelf, T>
/// <para>If <paramref name="pinned"/> is true the underlying buffer is created permanently pinned, otherwise the underlying buffer is not pinned.</para>
/// <para>The underlying buffer is not initialized.</para>
/// </remarks>
static abstract TSelf CreateUninitialized(scoped ReadOnlySpan<nint> lengths, bool pinned = false);
static abstract TSelf CreateFromShapeUninitialized(scoped ReadOnlySpan<nint> lengths, bool pinned = false);

/// <summary>Creates a new tensor with the specified lengths and strides. If <paramref name="pinned"/> is true the underlying buffer is created permanently pinned, otherwise the underlying buffer is not pinned. The underlying buffer is not initialized.</summary>
/// <param name="lengths">The lengths of each dimension.</param>
Expand All @@ -54,7 +54,7 @@ public interface ITensor<TSelf, T> : ITensor, IReadOnlyTensor<TSelf, T>
/// If <paramref name="pinned"/> is true the underlying buffer is created permanently pinned, otherwise the underlying buffer is not pinned.
/// The underlying buffer is not initialized.
/// </remarks>
static abstract TSelf CreateUninitialized(scoped ReadOnlySpan<nint> lengths, scoped ReadOnlySpan<nint> strides, bool pinned = false);
static abstract TSelf CreateFromShapeUninitialized(scoped ReadOnlySpan<nint> lengths, scoped ReadOnlySpan<nint> strides, bool pinned = false);

/// <inheritdoc cref="IReadOnlyTensor{TSelf, T}.this[ReadOnlySpan{nint}]" />
new ref T this[params scoped ReadOnlySpan<nint> indexes] { get; }
Expand Down Expand Up @@ -93,5 +93,17 @@ public interface ITensor<TSelf, T> : ITensor, IReadOnlyTensor<TSelf, T>

/// <inheritdoc cref="IReadOnlyTensor{TSelf, T}.GetPinnableReference" />
new ref T GetPinnableReference();

/// <inheritdoc cref="IReadOnlyTensor{TSelf, T}.GetSpan(ReadOnlySpan{nint}, int)" />
new Span<T> GetSpan(scoped ReadOnlySpan<nint> startIndexes, int length);

/// <inheritdoc cref="IReadOnlyTensor{TSelf, T}.GetSpan(ReadOnlySpan{NIndex}, int)" />
new Span<T> GetSpan(scoped ReadOnlySpan<NIndex> startIndexes, int length);

/// <inheritdoc cref="IReadOnlyTensor{TSelf, T}.TryGetSpan(ReadOnlySpan{nint}, int, out ReadOnlySpan{T})" />
bool TryGetSpan(scoped ReadOnlySpan<nint> startIndexes, int length, out Span<T> span);

/// <inheritdoc cref="IReadOnlyTensor{TSelf, T}.TryGetSpan(ReadOnlySpan{NIndex}, int, out ReadOnlySpan{T})" />
bool TryGetSpan(scoped ReadOnlySpan<NIndex> startIndexes, int length, out Span<T> span);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public ReadOnlyTensorSpan<T> this[nint index]
ThrowHelper.ThrowArgumentOutOfRangeException();
}

nint linearOffset = _tensor._shape.GetLinearOffset(index, _dimension);
nint linearOffset = _tensor._shape.GetLinearOffsetForDimension(index, _dimension);
return new ReadOnlyTensorSpan<T>(ref Unsafe.Add(ref _tensor._reference, linearOffset), _sliceShape);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,15 +238,15 @@ public unsafe ReadOnlyTensorSpan(T* data, nint dataLength, scoped ReadOnlySpan<n
_reference = ref Unsafe.AsRef<T>(data);
}

internal ReadOnlyTensorSpan(ref T data, nint dataLength, scoped ReadOnlySpan<nint> lengths, scoped ReadOnlySpan<nint> strides, bool pinned)
internal ReadOnlyTensorSpan(ref readonly T data, nint dataLength, scoped ReadOnlySpan<nint> lengths, scoped ReadOnlySpan<nint> strides, bool pinned)
{
_shape = TensorShape.Create(ref data, dataLength, lengths, strides, pinned);
_reference = ref data;
_shape = TensorShape.Create(in data, dataLength, lengths, strides, pinned);
_reference = ref Unsafe.AsRef(in data);
}

internal ReadOnlyTensorSpan(ref T reference, scoped in TensorShape shape)
internal ReadOnlyTensorSpan(ref readonly T reference, scoped in TensorShape shape)
{
_reference = ref reference;
_reference = ref Unsafe.AsRef(in reference);
_shape = shape;
}

Expand Down Expand Up @@ -382,6 +382,26 @@ public ref readonly T GetPinnableReference()
return ref ret;
}

/// <inheritdoc cref="IReadOnlyTensor{TSelf, T}.GetSpan(ReadOnlySpan{nint}, int)" />
public ReadOnlySpan<T> GetSpan(scoped ReadOnlySpan<nint> startIndexes, int length)
{
if (!TryGetSpan(startIndexes, length, out ReadOnlySpan<T> span))
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
return span;
}

/// <inheritdoc cref="IReadOnlyTensor{TSelf, T}.GetSpan(ReadOnlySpan{NIndex}, int)" />
public ReadOnlySpan<T> GetSpan(scoped ReadOnlySpan<NIndex> startIndexes, int length)
{
if (!TryGetSpan(startIndexes, length, out ReadOnlySpan<T> span))
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
return span;
}

/// <inheritdoc cref="IReadOnlyTensor{TSelf, T}.Slice(ReadOnlySpan{nint})" />
public ReadOnlyTensorSpan<T> Slice(params scoped ReadOnlySpan<nint> startIndexes)
{
Expand Down Expand Up @@ -438,6 +458,38 @@ public bool TryFlattenTo(scoped Span<T> destination)
return false;
}

/// <inheritdoc cref="IReadOnlyTensor{TSelf, T}.TryGetSpan(ReadOnlySpan{nint}, int, out ReadOnlySpan{T})" />
public bool TryGetSpan(scoped ReadOnlySpan<nint> startIndexes, int length, out ReadOnlySpan<T> span)
{
// This validates that startIndexes is valid and will throw ArgumentOutOfRangeException or IndexOutOfRangeException if it is not.
nint longestContiguousLength = _shape.GetLongestContiguousLength<TensorShape.GetOffsetAndLengthForNInt, nint>(startIndexes, out nint linearOffset);

if ((length < 0) || (length > longestContiguousLength))
{
span = default;
return false;
}

span = MemoryMarshal.CreateReadOnlySpan(in Unsafe.Add(ref _reference, linearOffset), length);
return true;
}

/// <inheritdoc cref="IReadOnlyTensor{TSelf, T}.TryGetSpan(ReadOnlySpan{NIndex}, int, out ReadOnlySpan{T})" />
public bool TryGetSpan(scoped ReadOnlySpan<NIndex> startIndexes, int length, out ReadOnlySpan<T> span)
{
// This validates that startIndexes is valid and will throw ArgumentOutOfRangeException or IndexOutOfRangeException if it is not.
nint longestContiguousLength = _shape.GetLongestContiguousLength<TensorShape.GetOffsetAndLengthForNIndex, NIndex>(startIndexes, out nint linearOffset);

if ((length < 0) || (length > longestContiguousLength))
{
span = default;
return false;
}

span = MemoryMarshal.CreateReadOnlySpan(in Unsafe.Add(ref _reference, linearOffset), length);
return true;
}

#if NET9_0_OR_GREATER
//
// IReadOnlyTensor
Expand Down Expand Up @@ -465,7 +517,7 @@ ReadOnlyTensorSpan<T> IReadOnlyTensor<ReadOnlyTensorSpan<T>, T>.ToDenseTensor()

if (!IsDense)
{
Tensor<T> tmp = Tensor.Create<T>(Lengths, IsPinned);
Tensor<T> tmp = Tensor.CreateFromShape<T>(Lengths, IsPinned);
CopyTo(tmp);
result = tmp;
}
Expand Down
Loading
Loading