Skip to content

Fix tensor shape consistency and native-width traversal - #135060

Merged
tannergooding merged 1 commit into
dotnet:mainfrom
tannergooding:tannergooding-tensor-span-kernel-issues
Oct 2, 2026
Merged

tannergooding merged 1 commit into
dotnet:mainfrom
tannergooding:tannergooding-tensor-span-kernel-issues

Conversation

@tannergooding

Copy link
Copy Markdown
Member

Summary

Make tensor shape handling consistent across broadcasting, elementwise operations, copies, equality, reshaping, axis operations, and views:

  • Treat default rank-zero empty values as effective shape [0], without changing their stored Rank, Lengths, or Strides. Only redundant leading singleton axes can be added or removed; explicit zero axes and non-leading singleton axes remain significant. Stack and concatenate interpret axes using the first input's effective shape.
  • Fix all ten internal Any span kernels to traverse the input length and handle empty inputs correctly.
  • Keep empty view origins within source storage, handle zero-containing shape products without intermediate overflow, and retain native-sized counts and offsets instead of narrowing them prematurely.
  • Preserve overlap-safe ordering for native-width dense copies. Index-of-min/max reductions reuse TensorPrimitives for dense spans, dense suffixes, and bounded gathered blocks, preserving first-NaN, tie, signed-zero, and magnitude semantics.
  • Share concatenation validation and copying, avoid revalidating prepared stack inputs, and remove redundant shape comparisons after alignment. Caller-supplied destinations retain shape and overlap validation before writes.

This includes behavioral compatibility changes, not new public API. Some formerly inconsistent shape combinations are now accepted or rejected according to the shared rules. For example, default empty values can explicitly broadcast to [2, 0], while binary operations with [0, 2] reject the incompatible trailing dimension. Unsupported overlapping layouts and self-overlapping output layouts throw before writing. The README and affected API remarks document the shape and storage contracts; breaking-change documentation is required after merge.

Validation

  • Release build of the tensor solution passed across its target frameworks.
  • Full suites passed normally and with hardware intrinsics disabled: 6,266 net11.0 tests and 200 net481 tests per run, with no failures or skips.
  • Regression coverage includes default/ranked empties, leading-padding alignment, dense and strided destinations, rejection before writes, native-width logical counts, forced chunk boundaries, NaNs/ties, high ranks, and protected-memory boundaries.
  • Two consecutive independent shape/storage audit passes covered 149,246 cases each, with no failures; the second used software intrinsics.

Performance

Local BenchmarkDotNet comparisons used Windows x64, a Ryzen 9 7950X, and the .NET 11 RC SDK/runtime.

For a native-width broadcast view with shape [nint.MaxValue / 1024, 1024], strides [0, 1], and a NaN at the end of the first dense suffix, IndexOfMax measured 295 ns with dense-suffix primitive dispatch versus 4,050 ns with an elementwise native-width fallback. This compares dispatch strategies and early NaN termination, not full traversal of a multi-gigabyte allocation or throughput against upstream's length-narrowing path.

Concatenation, stack, and dispatch comparisons retained the same allocation counts. Separate-process timings varied substantially and some apparent regressions reversed with run order. A same-process comparison using separately loaded assemblies measured row dispatch at 326 -> 309 ns for rank 2 and 351 -> 356 ns for rank 6, within measurement variability. No general throughput improvement is claimed for the validation simplification or metadata snapshots.

Note

This pull request was authored with assistance from GitHub Copilot.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@tannergooding tannergooding added area-System.Numerics.Tensors breaking-change Issue or PR that represents a breaking API or functional change over a previous release. needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet labels Oct 1, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Added needs-breaking-change-doc-created label because this PR has the breaking-change label.

When you commit this breaking change:

  1. Create and link to this PR and the issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.
  2. Ask a committer to mail the .NET Breaking Change Notification DL.

Tagging @dotnet/compat for awareness of the breaking change.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-numerics-tensors
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The broad behavioral compatibility changes and unsafe native-width memory traversal require final maintainer review despite substantial regression coverage.

Review effort: Balanced
Findings: None

What changed in this PR

Standardizes tensor shape semantics for rank-zero empties and leading singleton dimensions while enabling safe native-width traversal.

Changes:

  • Aligns broadcasting, reshape, stack, concatenate, equality, slicing, and axis behavior.
  • Adds native-width copying and min/max index traversal with overlap and SIMD semantics preserved.
  • Expands regression coverage and documents shape/storage contracts.
File Description
TensorTests.cs Adds extensive shape, traversal, overlap, and boundary tests.
TensorSpan_1.cs Uses validated view-shape construction.
TensorShape.cs Centralizes normalization, shape equivalence, and empty-view handling.
TensorOperation.cs Adds native-width copy/reduction paths and fixes Any kernels.
TensorDimensionSpan_1.cs Supports effective rank for default empties.
Tensor.cs Applies consistent semantics across tensor operations.
ReadOnlyTensorSpan_1.cs Uses validated view-shape construction.
ReadOnlyTensorDimensionSpan_1.cs Supports effective rank for read-only empty views.
TensorPrimitives.IIndexOfMinMaxOperator.cs Exposes internals needed for shared reduction dispatch.
README.md Documents shape, storage, and native-width behavior.

@tannergooding
tannergooding merged commit be7b579 into dotnet:main Oct 2, 2026
104 of 108 checks passed
@tannergooding
tannergooding deleted the tannergooding-tensor-span-kernel-issues branch October 2, 2026 11:19
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Breaking Change Documentation

A breaking change draft has been prepared for this PR.

👉 Click here to create the issue in dotnet/docs

The issue body is available in the issue-draft.md workflow artifact. Copy it into the blank issue opened by the link before submitting.

After creating the issue, please email a link to it to the .NET Breaking Change Notifications alias (dotnetbcn@microsoft.com).

/cc @tannergooding

Note

This documentation was generated with AI assistance from Copilot.

Generated by Breaking Change Documentation for #135060 · gpt56 · 36.4 AIC · ⌖ 1.45 AIC · ⊞ 19.7K · ◷

tannergooding added a commit that referenced this pull request Oct 6, 2026
#135281)

Fixes Issue <!-- Issue Number -->
[#133459](#133459),
[#134691](#134691),
[#128555](#128555),
and
[#121463](#121463).

main PR <!-- Link to PR if any that fixed this in the main branch. -->
[#134914](#134914)
and
[#135060](#135060).

# Description

<!-- Give a brief summary of the issue and how the pull request is
fixing it. -->

Backport both merged Tensor fixes to `release/11.0` in their original
order:

- `137a6701515dd7427e78bc46bcdd520c8d6e3a76`: fix array element-type
validation, slice pinning, shape/storage validation, broadcasting,
overlap handling, resize zero-fill, enumeration, and numerical
operations. Restore efficient dense and contiguous-slice dispatch,
including the tangent range-reduction accuracy correction needed by that
dispatch.
- `be7b579e4230520b5c47cd956513147e13f737a6`: make shape alignment
consistent across operations, treat default rank-zero empties as
effective shape `[0]` without changing their stored metadata, fix all
ten internal `Any` span kernels, and preserve native-width counts,
overlap-safe copy ordering, and valid empty-view origins.

Both cherry-picks applied without conflicts or backport-specific source
changes. The combined implementation, regression tests, README, and API
remarks match the merged changes; release-specific compatibility
suppressions are left unchanged. No public API is added.

# Customer Impact

<!-- What is the impact to customers of not taking this fix? -->

Without these fixes, incompatible `System.Array` storage can be
interpreted as the wrong element type and read beyond its bounds.
Pinning a tensor slice can pass the parent's data, rather than the
slice's data, to native consumers such as inference engines. Other
affected operations can produce incorrect results, mishandle empty or
broadcast shapes, omit the required resize zero-fill, or corrupt
overlapping output.

The existing cropped/gapped `FlattenTo` performance regression and
per-element overhead in dense and contiguous-slice operations also
remain. Native-backed spans with more than `int.MaxValue` logical
elements retain paths that prematurely narrow counts or offsets. Taking
both fixes together keeps the initial correctness/dispatch changes
paired with the subsequent shape-consistency and native-width
corrections.

# Regression

<!-- Is this fixing a problem that was introduced in the most recent
release, ie., fixing a regression? -->

Yes, in part, but these are not all newly introduced .NET 11
regressions. The array type-safety issue and cropped `FlattenTo`
regression were introduced by the .NET 10 Tensor rewrite in
[#114927](#114927)
and remain in `release/11.0`. Slice pinning was reproduced with the
10.0.9 package. The remaining changes address existing correctness and
consistency defects rather than one recent servicing regression.

# Testing

<!-- What kind of testing has been done with the fix. -->

Local Windows x64 validation on the actual `release/11.0` backport:

- `.\build.cmd clr+libs -rc release -lc release` on the unmodified
release baseline: passed, zero warnings/errors.
- `.\dotnet.cmd build
src\libraries\System.Numerics.Tensors\System.Numerics.Tensors.slnx -c
Release /p:RuntimeConfiguration=Release` after both cherry-picks:
passed, zero warnings/errors. This built the configured `net11.0`,
`net10.0`, `netstandard2.0`, and `net462` implementations and both test
targets.
- `.\dotnet.cmd build
src\libraries\System.Numerics.Tensors\tests\System.Numerics.Tensors.Tests.csproj
-t:Test -c Release /p:RuntimeConfiguration=Release`: passed in the
default configuration.
- The same full test command with `/p:testnobuild=true --no-restore` was
rerun under each environment override below: all passed. Fresh XML
results were checked for the full counts, with no failures, errors, or
skips.

| Configuration | Environment override | net11.0 passed | net481 passed
|
| --- | --- | ---: | ---: |
| Default intrinsics | None | 6,266 | 200 |
| 256-bit vectors | `DOTNET_PreferredVectorBitWidth=256` | 6,266 | 200 |
| 128-bit vectors | `DOTNET_PreferredVectorBitWidth=128` | 6,266 | 200 |
| AVX2/FMA disabled | `DOTNET_EnableAVX2=0` | 6,266 | 200 |
| All intrinsics disabled | `DOTNET_EnableHWIntrinsic=0` | 6,266 | 200 |

Coverage includes incompatible arrays, pinning, dense/strided/broadcast
layouts, rejection before writes, empty shapes/views, high ranks,
native-width logical counts, forced copy/reduction chunk boundaries,
NaNs/ties/signed zero, and protected-memory boundaries.

Local BenchmarkDotNet ShortRun comparisons used the same built Release
runtime with distinct baseline/backport Tensor assemblies on a Ryzen 9
7950X. Assembly loading and the active vector/FMA configuration were
verified. The 10 default-intrinsic jobs and four non-FMA tangent jobs
all produced measured results:

| Scenario | Release baseline | Combined backport |
| --- | ---: | ---: |
| Dense float Add, 64x16 | 16.37 us | 0.114 us |
| Gapped-row float Add, 64x16, strides `[32, 1]` | 16.25 us | 2.05 us |
| Gapped-row float FlattenTo, same layout | 7.92 us | 0.875 us |
| Float Tan, 4,096 values in `[-1, 1]`, FMA | 1.80 us | 1.72 us |
| Float Tan, 4,096 values in `[-50, 50]`, FMA | 1.63 us | 1.53 us |
| Float Tan, same small range, no FMA | 4.98 us | 5.08 us |
| Float Tan, same wide range, no FMA | 5.60 us | 9.19 us |

No managed allocation was measured in these samples. Small timing
differences are not claimed as precise gains or proof of performance
neutrality; the non-FMA wide-range slowdown is an accuracy tradeoff,
discussed below. These checks do not replace the more extensive
measurements in the main PRs.

Linux/macOS, Arm64, Mono, NativeAOT, and browser/WASM were not locally
validated.

# Risk

<!-- Please assess the risk of taking this fix. Provide details backing
up your assessment. -->

Low-to-moderate risk. The changes are consistent backports of two fixes
already merged on main, applied without conflicts or release-specific
source adaptations. The extensive regression tests passed in all five
local configurations, and the scope is confined to
`System.Numerics.Tensors`. Remaining risk is in the intentional behavior
changes and edge cases described below, rather than the patch size.

Intentional behavior changes include accepting/rejecting shape
combinations consistently, interpreting default empties as effective
shape `[0]`, rejecting incompatible array element types and unsupported
overlapping layouts before writes, and zero-filling newly exposed
resized elements. Consumers relying on the previous inconsistent
behavior can be affected. There is no compatibility switch.

On hardware without FMA, cancellation-sensitive tangent vectors now use
scalar evaluation to avoid inaccurate results. The wide-range local
sample increased from 5.60 to 9.19 us, approximately 64%; this is an
acknowledged throughput cost for correctness, not a performance-neutral
change. FMA-capable hardware retains fused vector range reduction.

Breaking-change documentation is already tracked by
[dotnet/docs#56290](dotnet/docs#56290) and
[dotnet/docs#56305](dotnet/docs#56305). Both
currently name .NET 12 Preview 1; their shipment-version metadata needs
to be aligned with the approved .NET 11 delivery if this backport is
accepted.

# Package authoring no longer needed in .NET 9

IMPORTANT: Starting with .NET 9, you no longer need to edit a NuGet
package's csproj to enable building and bump the version.
Keep in mind that we still need package authoring in .NET 8 and older
versions.

> [!NOTE]
> This pull request description was generated with assistance from
GitHub Copilot.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
tannergooding added a commit that referenced this pull request Oct 22, 2026
<!-- -->

# Summary

Extend the dense-layout fast paths in `System.Numerics.Tensors` while
preserving tensor shape, overlap, traversal-order, and logical-element
semantics. This builds on the tensor correctness and
contiguous-operation work in #134914 and the
shape-consistency/native-width fixes in #135060.

- Use span clearing for dense `ResizeTo` destinations when the source is
strided, while retaining logical clearing for non-dense destinations.
- Process large dense tensors in `int`-sized chunks and reuse contiguous
runs or slices for copy, reverse, reductions, predicates, equality,
random fill, concatenation, and stack operations.
- Add a rank-one linear-offset fast path and cover strided, broadcast,
singleton, empty, overlap, and ordering cases.
- Guard multi-gigabyte tests with available-memory preflight and extra
headroom, isolate allocations in remote processes, and skip expected
out-of-memory outcomes.

No public API signatures are changed.

# Performance

The complete direct comparison is the 42-job BenchmarkDotNet run (21
matched parent/updated cases) against current `main`. It used the same
Windows x64 Ryzen 9 7950X host for both assemblies. BenchmarkDotNet was
0.16.0-preview.2; the selected CoreRun reported .NET 12.0.0-dev.

| Scenario | Parent | Updated | Time reduction | Allocation reduction |
| --- | ---: | ---: | ---: | ---: |
| `EnumerateStrided`, 16 columns | 2.662 us | 2.206 us | 17.1% (1.21x
faster) | - |
| `EnumerateStrided`, 256 columns | 42.838 us | 34.642 us | 19.1% (1.24x
faster) | - |
| `AllDense`, 16 columns | 4.4322 us | 0.5002 us | 88.7% (8.86x faster)
| - |
| `AllDense`, 256 columns | 66.9839 us | 7.6185 us | 88.6% (8.79x
faster) | - |
| `ReverseBetweenRuns`, 16 columns | 4.5807 us | 1.3011 us | 71.6%
(3.52x faster) | - |
| `ReverseBetweenRuns`, 256 columns | 55.9756 us | 2.7795 us | 95.0%
(20.14x faster) | - |
| `StackInterleaved`, rank 2, 16 columns | 95.6232 us / 632 B | 3.2084
us / 0 B | 96.6% (29.80x faster) | 632 B (100%) |
| `StackInterleaved`, rank 2, 256 columns | 1,752.133 us / 632 B |
64.4305 us / 0 B | 96.3% (27.19x faster) | 632 B (100%) |
| `StackInterleaved`, rank 6, 16 columns | 227.9439 us / 279,704 B |
3.5561 us / 0 B | 98.4% (64.10x faster) | 279,704 B (100%) |
| `StackInterleaved`, rank 6, 256 columns | 3,972.9322 us / 4,457,624 B
| 56.3874 us / 0 B | 98.6% (70.46x faster) | 4,457,624 B (100%) |
| `ConcatenateInner`, rank 2, 16 columns | 6.1363 us | 0.3139 us | 94.9%
(19.55x faster) | - |
| `ConcatenateInner`, rank 2, 256 columns | 7.4425 us | 2.1339 us |
71.3% (3.49x faster) | - |
| `ConcatenateInner`, rank 6, 16 columns | 14.8374 us / 15,360 B |
0.3298 us / 0 B | 97.8% (44.99x faster) | 15,360 B (100%) |
| `ConcatenateInner`, rank 6, 256 columns | 14.2225 us / 15,360 B |
2.0137 us / 0 B | 85.8% (7.06x faster) | 15,360 B (100%) |
| `Resize`, strided value source to dense destination, zero tail | 95.91
ns | 98.92 ns | 3.1% (1.03x slower) | - |
| `Resize`, strided value source to dense destination, 16-element tail |
115.29 ns | 103.81 ns | 10.0% (1.11x faster) | - |
| `Resize`, strided reference source to dense destination, zero tail |
105.87 ns | 105.02 ns | 0.8% (1.01x faster) | - |
| `Resize`, strided reference source to dense destination, 16-element
tail | 117.60 ns | 111.01 ns | 5.6% (1.06x faster) | - |
| Tiny `ConcatenateInner`, 0 elements | 28.30 ns | 27.42 ns | 3.1%
(1.03x faster) | - |
| Tiny `ConcatenateInner`, 1 element | 147.29 ns | 36.73 ns | 75.1%
(4.01x faster) | - |
| Tiny `ConcatenateInner`, 16 elements | 153.85 ns | 44.23 ns | 71.3%
(3.48x faster) | - |

The percentage is elapsed-time reduction for faster cases or increase
for slower cases; the multiplier is baseline/measured (`x faster`) or
measured/baseline (`x slower`). Allocation reduction is shown only where
allocation counts were recorded; `-` means no allocation comparison is
available, not zero allocation. The zero-tail value `Resize` means were
95.91 ns and 98.92 ns, with overlapping 99.9% confidence intervals, so
treat the 3.1% increase as neutral rather than a regression. The table
includes all 21 pairs in this run. Its parent and updated assembly
SHA256s were
`226C5564EF9474F212F65E486E2165909BD347883D816E38FFAEAD943D42B652` and
`45DA47373EFEBC54552DB99752AAD50769A32D959BB1C8AED05402D76E9BCBA8`.

The additional results below are grouped by operation category. Each
category identifies the comparison baseline and measured build where
known; these are category-specific comparisons, not one cumulative
parent-to-final run. Absolute timings can vary across machines and
runtime/build configurations, and the final assembled binaries may
differ from the measured builds, so treat the values as measured
evidence rather than exact predictions for another environment.

<details>
<summary>General operations, copying, resizing, concatenation, and large
dense operations</summary>

Measured build:
`A0A35DBFC54D35663C105C60C0400588133FAB77D21624E9FA143C4B998E153B`;
compared with its category baseline. The dense and strided cases use
64x256 inputs except where specified.

| Scenario | Comparison baseline | Measured build | Time reduction |
| --- | ---: | ---: | ---: |
| Independent-run copy, 64x16 | 1.351 us | 0.794 us | 41.2% (1.70x
faster) |
| Independent-run copy, 64x256 | 1.905 us | 1.518 us | 20.3% (1.25x
faster) |
| Padded `IndexOfMax`, 64x256 | 4.142 us | 2.695 us | 34.9% (1.54x
faster) |
| Stride-two `IndexOfMax`, 16,384 elements | 90.045 us | 40.600 us |
54.9% (2.22x faster) |
| Padded `IndexOfMinMagnitude`, 64x256 | 5.471 us | 4.944 us | 9.6%
(1.11x faster) |
| Resize dense to padded, 64x256 | 59.613 us | 1.391 us | 97.7% (42.86x
faster) |
| Resize padded to padded, 64x256 | 100.128 us | 1.366 us | 98.6%
(73.30x faster) |
| Resize sparse to padded, 64x256 | 87.186 us | 42.538 us | 51.2% (2.05x
faster) |
| Resize references to padded, 64x256 | 124.696 us | 2.370 us | 98.1%
(52.61x faster) |
| Flattened integer concatenate, 64x256 | 82.674 us | 1.441 us | 98.3%
(57.37x faster) |
| Flattened reference concatenate, 64x256 | 109.370 us | 2.390 us |
97.8% (45.76x faster) |
| Dense `CopyTo` control, 1,024 elements | 72.96 ns | 76.77 ns | 5.2%
(1.05x slower) |
| Dense `CopyTo` control, 16,384 elements | 923.03 ns | 928.88 ns | 0.6%
(1.01x slower) |

The 1,024-element `CopyTo` control retained about 4 ns of overhead; the
16,384-element control was approximately neutral. Tiny arithmetic,
random-fill, and equality controls stayed close to the comparison
baseline, so these measurements do not establish that every path
improved.

Large dense elementwise probes used `int.MaxValue + 65` byte elements,
setup outside timing, three trials per binary, and full-buffer output
verification. The medians were:

| Operation | Comparison baseline | Measured build | Time reduction |
| --- | ---: | ---: | ---: |
| `OnesComplement` | 13,719.061 ms | 82.811 ms | 99.4% (165.67x faster)
|
| Add, scalar on right | 13,829.494 ms | 81.907 ms | 99.4% (168.84x
faster) |
| Subtract, scalar on left | 13,759.440 ms | 85.083 ms | 99.4% (161.72x
faster) |
| Add, tensor/tensor | 38,793.776 ms | 85.598 ms | 99.8% (453.21x
faster) |

The scalar-right add result is about 169x less elapsed time in the
measured build; the tensor/tensor add result is about 453x less. These
probe processes were not affinity-pinned. Above-2-GB clear/fill probes
measured `Clear` at 4,183.191 ms to 37.196 ms and `Fill` at 9,469.552 ms
to 87.177 ms.

</details>

<details>
<summary>Reductions, predicates, and out-of-place reversal</summary>

Ordered-operation measured build:
`ED50A796A284CAA5E6DBD2F9BCE630619EB86937C536BE2FC599D4153919AE8A`,
compared with its category baseline. The main cases used 64x256 inputs;
tiny cases list their element count. All measured copying and reduction
paths allocated 0 B.

| Scenario | Comparison baseline | Ordered-operation build | Time
reduction | Allocation reduction |
| --- | ---: | ---: | ---: | ---: |
| Sum, dense | 114.926 us | 37.514 us | 67.4% (3.06x faster) | 0 B |
| Sum, padded | 114.515 us | 38.018 us | 66.8% (3.01x faster) | 0 B |
| Max, dense | 118.259 us | 40.434 us | 65.8% (2.92x faster) | 0 B |
| Max, padded | 117.830 us | 40.260 us | 65.8% (2.93x faster) | 0 B |
| Dot, dense | 156.899 us | 37.183 us | 76.3% (4.22x faster) | 0 B |
| Dot, padded | 155.815 us | 37.345 us | 76.0% (4.17x faster) | 0 B |
| Dot, broadcast | 156.622 us | 39.199 us | 75.0% (4.00x faster) | 0 B |
| Standard deviation, padded | 175.545 us | 75.640 us | 56.9% (2.32x
faster) | 0 B |
| Out-of-place dense integer reversal | 148.837 us | 1.551 us | 99.0%
(95.96x faster) | 0 B |
| Empty Sum | 13.119 ns | 9.328 ns | 28.9% (1.41x faster) | 0 B |
| Empty Dot | 41.887 ns | 19.186 ns | 54.2% (2.18x faster) | 0 B |
| One-element Sum | 19.935 ns | 9.987 ns | 49.9% (2.00x faster) | 0 B |
| One-element Dot | 56.182 ns | 22.704 ns | 59.6% (2.47x faster) | 0 B |
| 16-element Sum | 120.638 ns | 23.622 ns | 80.4% (5.11x faster) | 0 B |
| 16-element Dot | 205.423 ns | 53.494 ns | 74.0% (3.84x faster) | 0 B |

An independently repeated tiny in-place reversal control measured empty
reversal at 21.95 ns versus 21.17 ns, one element at 30.30 ns versus
28.85 ns, and 16 elements at 36.08 ns versus 31.85 ns. These were small
controls, not evidence that every tiny operation improved.

Predicate/reversal measured build:
`083C97E30D887D8B922CDCAF2AC499882813E29670595A29428E0BCCFDF8C9F8`;
exact comparison assembly:
`26C1E0DAC99A43CB41E5B1F8D6BF1B479C41622B2665E8285226A46B5C353C9D`.
Cases used 64x256 inputs unless noted; all 88 jobs allocated 0 B.

| Scenario | Comparison baseline | Predicate/reversal build | Time
reduction | Allocation reduction |
| --- | ---: | ---: | ---: | ---: |
| `EqualsAll`, dense | 61.189 us | 5.475 us | 91.1% (11.18x faster) | 0
B |
| `EqualsAny`, late dense match | 61.132 us | 5.397 us | 91.2% (11.33x
faster) | 0 B |
| `EqualsAll`, padded | 60.893 us | 4.020 us | 93.4% (15.15x faster) | 0
B |
| `EqualsAll`, broadcast | 60.546 us | 6.689 us | 89.0% (9.05x faster) |
0 B |
| `GreaterThanAll`, scalar padded | 116.648 us | 3.919 us | 96.6%
(29.76x faster) | 0 B |
| `EqualsAny`, scalar dense, no match | 116.602 us | 3.233 us | 97.2%
(36.07x faster) | 0 B |
| Immediate predicate exit, dense | 48.36 ns | 22.73 ns | 53.0% (2.13x
faster) | 0 B |
| Immediate predicate exit, padded | 53.59 ns | 46.27 ns | 13.7% (1.16x
faster) | 0 B |
| Immediate predicate exit, broadcast | 77.03 ns | 71.05 ns | 7.8%
(1.08x faster) | 0 B |
| Immediate scalar padded `Any` exit | 22.24 ns | 13.14 ns | 40.9%
(1.69x faster) | 0 B |
| Reverse padded to dense | 151.907 us | 1.830 us | 98.8% (83.01x
faster) | 0 B |
| Reverse dense to padded | 151.692 us | 1.941 us | 98.7% (78.15x
faster) | 0 B |
| Reverse independent runs | 149.980 us | 2.286 us | 98.5% (65.61x
faster) | 0 B |
| Reverse references | 175.810 us | 30.434 us | 82.7% (5.78x faster) | 0
B |
| Reverse sparse to dense | 150.988 us | 56.728 us | 62.4% (2.66x
faster) | 0 B |
| Reverse dense to sparse | 151.417 us | 57.377 us | 62.1% (2.64x
faster) | 0 B |

The ordered-operation comparison also measured in-place integer reversal
at 6.346 us to 0.614 us and innermost-axis in-place reversal at 6.517 us
to 0.843 us (64x256); reference reversal measured 180.765 us to 33.268
us. Forward-path preservation controls measured dense-to-padded resize
at 1,400.5 ns versus 1,400.6 ns, padded-source resize at 1,384.8 ns
versus 1,375.6 ns, and independent copy at 1,522.63 ns versus 1,523.01
ns. These controls were approximately neutral.

</details>

<details>
<summary>In-place reversal, stack, concatenation, and small
controls</summary>

In-place reversal measured build:
`3CC6AAA2D52661562195913A4F55BBA4352A55813E2ABE0C372CC705E7208251`;
comparison build:
`083C97E30D887D8B922CDCAF2AC499882813E29670595A29428E0BCCFDF8C9F8`.

| Scenario | Comparison build | In-place reversal build | Time reduction
| Allocation reduction |
| --- | ---: | ---: | ---: | ---: |
| Integer outer axis, 4x5x4096 | 16.315 us | 3.835 us | 76.5% (4.25x
faster) | 0 B |
| Integer middle axis, 4x5x4096 | 13.105 us | 3.215 us | 75.5% (4.08x
faster) | 0 B |
| Reference outer axis, 4x5x4096 | 164.111 us | 12.964 us | 92.1%
(12.66x faster) | 0 B |
| Reference middle axis, 4x5x4096 | 126.751 us | 10.429 us | 91.8%
(12.15x faster) | 0 B |
| Reference outer axis, 4x5x16 | 613.65 ns | 130.83 ns | 78.7% (4.69x
faster) | 0 B |
| Scalar integer middle axis, 4x5x255 | 852.13 ns | 850.13 ns | 0.2%
(1.00x faster) | 0 B |
| In-place reversal of `int.MaxValue + 66` bytes | 849.184 ms | 169.800
ms | 80.0% (5.00x faster) | - |

Fitting-span 1,024- and 16,384-element reversal controls were
approximately neutral. Tiny aligned reversal measurements varied across
launch/job-role reversal; they did not establish a consistent
binary-linked regression.

For the 16-element aligned reversal control, normal job roles measured
30.56 ns versus 32.86 ns; with roles reversed, the same binaries
measured 30.76 ns versus 29.83 ns. The sign changed when job roles were
reversed, so this is reported as noise/neutral rather than a
binary-linked regression.

Stack/concatenation measured build:
`F285B2DCE5BCFC35F04681BBF1B337A4CF46F4F9CC855CF34F5F54CACC97A119`;
comparison build:
`3CC6AAA2D52661562195913A4F55BBA4352A55813E2ABE0C372CC705E7208251`. Main
cases used four inputs, rank six, and a 32x16 payload per input.

| Scenario | Comparison build | Stack/concatenation build | Time
reduction | Allocation reduction |
| --- | ---: | ---: | ---: | ---: |
| Stack into destination | 12.688 us / 18,584 B | 0.566 us / 0 B | 95.5%
(22.42x faster) | 18,584 B (100%) |
| Allocating stack | 12.839 us / 27,080 B | 0.761 us / 8,496 B | 94.1%
(16.87x faster) | 18,584 B (68.6%) |
| Innermost interleaved stack | 192.437 us / 279,704 B | 7.806 us / 0 B
| 95.9% (24.65x faster) | 279,704 B (100%) |
| Padded-source stack | 12.394 us / 18,584 B | 3.846 us / 1,024 B |
69.0% (3.22x faster) | 17,560 B (94.5%) |
| Inner-axis concatenation | 11.235 us / 15,360 B | 0.269 us / 0 B |
97.6% (41.77x faster) | 15,360 B (100%) |

Small destination-stack controls for 0, 1, and 16 elements measured
240.55/262.18/266.57 ns versus 18.69/26.32/27.81 ns; the intermediate
allocation was eliminated. Inner-axis concatenate at those sizes
measured 158.49/152.83/152.38 ns versus 21.24/33.29/32.20 ns. Allocating
stack also improved at all three sizes. Outer-axis tiny controls were
noisy and did not establish a consistent binary-linked regression.

For completeness, outer-axis stack control results in normal job roles
were 87.56/85.54 ns (empty), 74.00/83.12 ns (singleton), and 78.57/76.64
ns (16 elements), prior/current. With job roles reversed, they were
69.34/83.59 ns, 79.28/72.89 ns, and 76.23/79.27 ns, current/prior. The
role reversal changed the apparent direction for several cases; do not
claim a consistent tiny outer-axis effect.

</details>

<details>
<summary>Measured exploratory candidates not included in the
implementation</summary>

These are prototype-only measurements and are not claimed as results of
the changes in this description:

| Candidate and input | Existing implementation | Prototype | Time
reduction | Allocation reduction |
| --- | ---: | ---: | ---: | ---: |
| Stride-two enumeration, 16,384 elements | 37.291 us | 4.854 us | 87.0%
(7.68x faster) | - |
| Broadcast `Exp`, 64x256 | 7.924 us | 0.749 us | 90.5% (10.58x faster)
| - |
| Format 256 integers | 1.469 us / 13,184 B | 1.155 us / 7,040 B | 21.4%
(1.27x faster) | 6,144 B (46.6%) |
| Integer `EqualsAll`, 16,384 elements | 6.256 us | 0.823 us | 86.8%
(7.60x faster) | - |

</details>

<!-- The direct current-main run used parent assembly
226C5564EF9474F212F65E486E2165909BD347883D816E38FFAEAD943D42B652 and
updated assembly
45DA47373EFEBC54552DB99752AAD50769A32D959BB1C8AED05402D76E9BCBA8. A
focused run used a stale parent assembly and is excluded. Category
groups compare their named builds; results can vary by machine and final
build. -->

# Testing

- `.\build.cmd clr+libs -rc release` — succeeded with 0 warnings and 0
errors.
- `dotnet build /p:Configuration=Release
.\src\libraries\System.Numerics.Tensors\tests\System.Numerics.Tensors.Tests.csproj`
— succeeded with 0 warnings and 0 errors.
- Net11.0 non-outer-loop tensor tests — 6,505 passed; guarded
large-memory class — 41 passed.
- `dotnet build -f net481 /t:Test /p:Configuration=Release
.\src\libraries\System.Numerics.Tensors\tests\System.Numerics.Tensors.Tests.csproj`
— 200 passed.

The net11.0 tests were run directly on the installed .NET 11.0.0-rc.1
runtime after temporarily adjusting, then restoring, the generated test
runtimeconfig. The repository's normal test target crashed during xUnit
discovery under the locally built .NET 12 runtime, so the full net11.0
test suite was not validated through that normal test target.

Resolves #125663

> [!NOTE]
> This description was drafted with GitHub Copilot.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 2, 2026

This branch was previously deployed

1 inactive deployment
copilot-pat-pool — 62f5f162 Deployed Oct 2, 2026 by tannergooding via conclusion #9552
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Numerics.Tensors breaking-change Issue or PR that represents a breaking API or functional change over a previous release. needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants