Repository navigation
Fix tensor shape consistency and native-width traversal - #135060
tannergooding merged 1 commit into
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Added When you commit this breaking change:
Tagging @dotnet/compat for awareness of the breaking change. |
|
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. |
|
Tagging subscribers to this area: @dotnet/area-system-numerics-tensors |
There was a problem hiding this comment.
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. |
Breaking Change DocumentationA 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 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.
|
#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>
<!-- --> # 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>
Summary
Make tensor shape handling consistent across broadcasting, elementwise operations, copies, equality, reshaping, axis operations, and views:
[0], without changing their storedRank,Lengths, orStrides. 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.Anyspan kernels to traverse the input length and handle empty inputs correctly.TensorPrimitivesfor dense spans, dense suffixes, and bounded gathered blocks, preserving first-NaN, tie, signed-zero, and magnitude semantics.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
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,IndexOfMaxmeasured 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.