Improve a vector implementation to support alignment and non-temporal tores - #93296

Merged
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align
Oct 12, 2023
Merged

Improve a vector implementation to support alignment and non-temporal tores#93296
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align

Conversation

@tannergooding

Copy link
Copy Markdown
Member

No description provided.

@ghost

Copy link
Copy Markdown

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

Issue Details

null

Author:tannergooding
Assignees:tannergooding
Labels:

area-System.Numerics

Milestone:-

@EgorBo

Copy link
Copy Markdown
Member

If a function is idempotent (which most of them are) it's enough a single load to align data (like I did in https://github.com/dotnet/runtime/pull/93214/files) without any unrolled loops, etc - presumably, you can expose a bool IsIdempotent {get;} property in these abstraction and only align those for simplicity

@tannergooding
tannergooding marked this pull request as ready for review October 10, 2023 23:21
@tannergooding

tannergooding commented Oct 11, 2023

Copy link
Copy Markdown
MemberAuthor

Here are the before/after results for Abs, where Abs1 is the previous implementation and Abs2 is the implementation from this PR. We are faster across the board (except for 16 elements) and more than 2x faster for large inputs, with the gains really showing past 256 elements (1024 bytes).

MethodTensorLengthMeanErrorStdDev
Abs112.609 ns0.0611 ns0.0572 ns
Abs212.562 ns0.0518 ns0.0485 ns
Abs123.142 ns0.0487 ns0.0456 ns
Abs222.563 ns0.0121 ns0.0101 ns
Abs133.309 ns0.0294 ns0.0229 ns
Abs232.742 ns0.0171 ns0.0133 ns
Abs143.090 ns0.0496 ns0.0464 ns
Abs242.572 ns0.0187 ns0.0166 ns
Abs155.198 ns0.0190 ns0.0148 ns
Abs253.133 ns0.0788 ns0.0774 ns
Abs165.275 ns0.0790 ns0.0739 ns
Abs263.098 ns0.0782 ns0.0732 ns
Abs175.322 ns0.0916 ns0.0857 ns
Abs273.164 ns0.0544 ns0.0509 ns
Abs182.936 ns0.0553 ns0.0517 ns
Abs282.573 ns0.0442 ns0.0414 ns
Abs195.454 ns0.0910 ns0.0852 ns
Abs293.165 ns0.0536 ns0.0501 ns
Abs1105.431 ns0.0675 ns0.0631 ns
Abs2103.230 ns0.0308 ns0.0288 ns
Abs1115.369 ns0.0447 ns0.0418 ns
Abs2113.199 ns0.0443 ns0.0393 ns
Abs1125.571 ns0.0593 ns0.0555 ns
Abs2123.211 ns0.0320 ns0.0299 ns
Abs1135.585 ns0.0491 ns0.0459 ns
Abs2133.262 ns0.0295 ns0.0276 ns
Abs1145.766 ns0.0550 ns0.0514 ns
Abs2143.264 ns0.0292 ns0.0273 ns
Abs1155.865 ns0.0454 ns0.0354 ns
Abs2153.254 ns0.0451 ns0.0377 ns
Abs1162.790 ns0.0207 ns0.0193 ns
Abs2164.984 ns0.0479 ns0.0448 ns
Abs1326.515 ns0.0886 ns0.0785 ns
Abs2326.012 ns0.1203 ns0.1067 ns
Abs16417.126 ns0.1647 ns0.1540 ns
Abs26415.343 ns0.3172 ns0.3115 ns
Abs112838.650 ns0.7464 ns0.6982 ns
Abs212837.150 ns0.4134 ns0.3665 ns
Abs125682.113 ns0.5631 ns0.4702 ns
Abs225651.379 ns0.2897 ns0.2710 ns
Abs1512169.559 ns0.7197 ns0.6732 ns
Abs251278.668 ns0.4391 ns0.4107 ns
Abs11024336.791 ns1.9587 ns1.8321 ns
Abs21024133.500 ns0.7219 ns0.6400 ns
Abs12048698.482 ns1.3343 ns1.1142 ns
Abs22048244.729 ns1.6655 ns1.5579 ns
Abs140961,411.587 ns5.8918 ns5.2229 ns
Abs24096471.249 ns1.2273 ns1.0880 ns
Abs16553622,871.118 ns97.4602 ns91.1643 ns
Abs2655367,306.615 ns32.3811 ns30.2893 ns
Abs113107245,773.050 ns244.2154 ns228.4393 ns
Abs213107218,461.823 ns105.6972 ns93.6978 ns

@tannergooding

Copy link
Copy Markdown
MemberAuthor

Similar results for Exp

MethodTensorLengthMeanErrorStdDev
Exp115.224 ns0.1122 ns0.1050 ns
Exp214.863 ns0.0823 ns0.0770 ns
Exp127.359 ns0.1578 ns0.1689 ns
Exp226.872 ns0.0975 ns0.0912 ns
Exp139.315 ns0.1091 ns0.0852 ns
Exp239.018 ns0.1643 ns0.1537 ns
Exp144.820 ns0.1094 ns0.1075 ns
Exp244.863 ns0.1028 ns0.0962 ns
Exp159.103 ns0.0672 ns0.0629 ns
Exp256.563 ns0.1169 ns0.1036 ns
Exp169.280 ns0.2022 ns0.2076 ns
Exp266.685 ns0.1498 ns0.1538 ns
Exp179.111 ns0.0848 ns0.0793 ns
Exp276.660 ns0.1413 ns0.1321 ns
Exp185.006 ns0.0963 ns0.0901 ns
Exp284.848 ns0.1006 ns0.0941 ns
Exp1910.272 ns0.1189 ns0.1112 ns
Exp297.593 ns0.0876 ns0.0820 ns
Exp11010.188 ns0.0713 ns0.0596 ns
Exp2107.581 ns0.0935 ns0.0875 ns
Exp11110.274 ns0.1070 ns0.0949 ns
Exp2117.631 ns0.1412 ns0.1320 ns
Exp11210.246 ns0.0755 ns0.0670 ns
Exp2127.555 ns0.0742 ns0.0658 ns
Exp11310.270 ns0.1266 ns0.1184 ns
Exp2137.651 ns0.0916 ns0.0857 ns
Exp11410.252 ns0.1509 ns0.1411 ns
Exp2147.585 ns0.1017 ns0.0951 ns
Exp11510.428 ns0.0935 ns0.0875 ns
Exp2157.795 ns0.0801 ns0.0749 ns
Exp1167.271 ns0.0845 ns0.0749 ns
Exp21619.323 ns0.3291 ns0.3078 ns
Exp13218.716 ns0.2705 ns0.2530 ns
Exp23225.631 ns0.3860 ns0.3610 ns
Exp16448.860 ns0.4013 ns0.3753 ns
Exp26450.285 ns0.9766 ns0.9135 ns
Exp1128109.969 ns1.6557 ns1.5487 ns
Exp2128110.952 ns1.4802 ns1.3846 ns
Exp1256230.547 ns2.4931 ns2.0819 ns
Exp2256193.106 ns0.8509 ns0.7105 ns
Exp1512477.460 ns5.6110 ns5.2486 ns
Exp2512317.518 ns5.5631 ns5.2037 ns
Exp11024977.141 ns13.0740 ns12.2295 ns
Exp21024573.851 ns7.7362 ns7.2364 ns
Exp120481,931.447 ns12.4202 ns10.3714 ns
Exp220481,017.003 ns19.3625 ns20.7177 ns
Exp140963,892.225 ns27.1610 ns22.6807 ns
Exp240961,975.208 ns26.4692 ns23.4642 ns
Exp16553661,938.616 ns354.4827 ns296.0091 ns
Exp26553632,390.331 ns590.1723 ns552.0475 ns
Exp1131072125,798.377 ns2,260.7971 ns2,114.7510 ns
Exp213107261,126.610 ns1,091.3727 ns1,020.8707 ns

{
public static partial class TensorPrimitives
{
private const nuint NonTemporalByteThreshold = 256 * 1024;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a comment about what this is and how the value was chosen?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Suggested change
privateconstnuintNonTemporalByteThreshold=256*1024;
/// <summary>Defines the threshold, in bytes, at which non-temporal stores will be used.</summary>
/// <remarks>
/// A non-temporal store is one that allows the CPU to bypass the cache when writing to memory.
///
/// This can be beneficial when working with large amounts of memory where the writes would otherwise
/// cause large amounts of repeated updates and evictions. The hardware optimization manuals recommend
/// the threshold to be roughly half the size of the last level of on-die cache -- that is, if you have approximately
/// 4MB of L3 cache per core, you'd want this to be approx. 1-2MB, depending on if hyperthreading was enabled.
///
/// However, actually computing the amount of L3 cache per core can be tricky or error prone. Native memcpy
/// algorithms use a constant threshold that is typically around 256KB and we match that here for simplicity. This
/// threshold accounts for most processors in the last 10-15 years that had approx. 1MB L3 per core and support
/// hyperthreading, giving a per core last level cache of approx. 512KB.
/// </remarks>
privateconstnuintNonTemporalByteThreshold=256*1024;

Comment on lines +1331 to +1354
if (canAlign)
{
// Compute by how many elements we're misaligned and adjust the pointers accordingly
//
// Noting that we are only actually aligning dPtr. THis is because unaligned stores
// are more expensive than unaligned loads and aligning both is significantly more
// complex.

nuint misalignment = ((uint)(sizeof(Vector128<float>)) - ((nuint)(dPtr) % (uint)(sizeof(Vector128<float>)))) / sizeof(float);

xPtr += misalignment;
dPtr += misalignment;

Debug.Assert(((nuint)(dPtr) % (uint)(sizeof(Vector128<float>))) == 0);

remainder -= misalignment;
}

Vector128<float> vector1;
Vector128<float> vector2;
Vector128<float> vector3;
Vector128<float> vector4;

if (canAlign && (remainder > (NonTemporalByteThreshold / sizeof(float))))

@stephentoubstephentoubOct 12, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have two checks on canAlign here. Would it help to eliminate a branch if this were instead structured as:

if(canAlign){
...// do alignmentif(remainder>(NonTemporalByteThreshold/sizeof(float))){
...// handle non-temporal pathgoto AdjustingRefs;}}
...// what's currently in the else blockAdjustingRefs:
...// stuff currently after the else block

?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't think the additional complexity is worth it here and it can cause subtle issues with control flow analysis that are probably undesirable.

If we were concerned, I'd prefer switching the order so its if ((remainder > (NonTemporalByteThreshold / sizeof(float)) && canAlign) instead.


public static IEnumerable<object[]> TensorLengths =>
from length in Enumerable.Range(1, 128)
from length in Enumerable.Range(1, 256)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is good, but it's also going to double the number of test cases we're running. Not a big deal except that on netfx it seems to struggle with the theory count. We should probably subsequently change some of those theories to be loops, e.g. instead of:

[Theory][MemberData(nameof(TensorLengths))]publicvoidFoo(inttensorLength){
...}

do:

[Fact]publicvoidFoo(){foreach(inttensorLengthinTensorLengths){
...}}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll handle this in the immediately following PR covering binary/ternary.

@lewing

Copy link
Copy Markdown
Member

This appears to have broken the outerloop tests

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@lewing is there an issue or a link that can be shared so it can be investigated?

@lewing

lewing commented Oct 12, 2023

Copy link
Copy Markdown
Member

@tannergooding

Copy link
Copy Markdown
MemberAuthor

#93412 fixes the issue.

Another PR went in that removed unsafe and it didn't conflict, so the nothing caught the issue.

michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request Oct 20, 2023
… tores (dotnet#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
carlossanlop pushed a commit that referenced this pull request Oct 20, 2023
* Use FMA in TensorPrimitives (#92205)
* Simplify TensorPrimitive's AbsoluteOperator (#92577)
Vector{128/256/512} all provide Abs; no need to do this manually.
* Reduce some boilerplate in TensorPrimitive's IBinaryOperator (#92576)
Change a few of the static abstract interface methods to be virtual, as most implementations throw from these methods; we can consolidate that throwing to the base.
* Minor code cleanup in TensorPrimitives tests (#92575)
* Normalize some test naming
* Alphabetize tests
* Improve mistmatched length tests with all positions of the shorter tensor
* Alphabetize methods in TensorPrimitives.cs
* Vectorize TensorPrimitives.Min/Max{Magnitude} (#92618)
* Vectorize TensorPrimitives.Min/Max{Magnitude}
* Use AdvSimd.Max/Min
* Rename some parameters/locals for consistency
* Improve HorizontalAggregate
* Move a few helpers
* Avoid scalar path for returning found NaN
* Update TensorPrimitives aggregations to vectorize handling of remaining elements (#92672)
* Update TensorPrimitives.CosineSimilarity to vectorize handling of remaining elements
* Vectorize remainder handling for Aggregate helpers
* Flesh out TensorPrimitives XML docs (#92749)
* Flesh out TensorPrimitives XML docs
* Address PR feedback
- Remove use of FusedMultiplyAdd from all but CosineSimilarity
- Remove comments about platform/OS-specific behavior from Add/AddMultiply/Subtract/Multiply/MultiplyAdd/Divide/Negate
- Loosen comments about NaN and which exact one is returned
* Address PR feedback
* Vectorize TensorPrimitives.ConvertToHalf (#92715)
* Enable TensorPrimitives to perform in-place operations (#92820)
Some operations would produce incorrect results if the same span was passed as both an input and an output. When vectorization was employed but the span's length wasn't a perfect multiple of a vector, we'd do the standard trick of performing one last operation on the last vector's worth of data; however, that relies on the operation being idempotent, and if a previous operation has overwritten input with a new value due to the same memory being used for input and output, some operations won't be idempotent. This fixes that by masking off the already processed elements. It adds tests to validate in-place use works, and it updates the docs to carve out this valid overlapping.
* Vectorize TensorPrimitives.ConvertToSingle (#92779)
* Vectorize TensorPrimitives.ConvertToSingle
* Address PR feedback
* Throw exception in TensorPrimitives for unsupported span overlaps (#92838)
* This vectorizes TensorPrimitives.Log2 (#92897)
* Add a way to support operations that can't be vectorized on netstandard
* Updating TensorPrimitives.Log2 to be vectorized on .NET Core
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Ensure we do an arithmetic right shift in the Log2 vectorization
* Ensure the code can compile on .NET 7
* Ensure that edge cases are properly handled and don't resolve to `x`
* Ensure that Log2 special results are explicitly handled.
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Adding Log2 tests covering some special values (#92946)
* [wasm] Disable `TensorPrimitivesTests.ConvertToHalf_SpecialValues` (#92953)
Failing test: `System.Numerics.Tensors.Tests.TensorPrimitivesTests.ConvertToHalf_SpecialValues`
Issue: #92885
* Adding a vectorized implementation of TensorPrimitives.Log (#92960)
* Adding a vectorized implementation of TensorPrimitives.Log
* Make sure to hit Ctrl+S
* Consolidate some TensorPrimitivesTests logic around special values (#92982)
* Vectorize TensorPrimitives.Exp (#93018)
* Vectorize TensorPrimitives.Exp
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax (#93029)
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax
- Adds a SigmoidOperator that just wraps the ExpOperator
- Vectorizes both passes of SoftMax, on top of ExpOperator. Simplest way to do this was to augment the existing InvokeSpanScalarIntoSpan to take a transform operator.
- In doing so, found some naming inconsistencies I'd previously introduced, so I did some automatic renaming to make things more consistent.
- Added XML comments to all the internal/private surface area.
- Fleshes out some tests (and test values).
* Disable tests on mono
* Address PR feedback
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh (#93093)
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh
Tanh and Cosh are based on AOCL-LibM.
AOCL-LibM doesn't appear to have a sinh implementation, so this Sinh is just based on the sinh formula based on exp(x).
I also augmented the tests further, including:
- Added more tests for sinh/cosh/tanh
- Add an equality routine that supports comparing larger values with a tolerance
- Tightened the tolerance for most functions
- Changed some tests to be theories to be consistent with style elsewhere in the tests
- Fixed some use of Math to be MathF
* Remove unnecessary special-handling path from cosh
* Remove unnecessary special-handling path from tanh
* Redo sinh based on cosh
* Address PR feedback
* Replace confusing new T[] { ... }
* Remove a few unnecessary `unsafe` keyword uses in TensorPrimitives (#93219)
* Consolidate a few exception throws in TensorPrimitives (#93168)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value (#93169)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value
Found as part of adding more tests for Min/Max{Magnitude} to validate they match their IndexOfXx variants.
* Address PR feedback
* Improve a vector implementation to support alignment and non-temporal tores (#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
* Mark TensorPrimitives as unsafe (#93412)
* Use the improved vectorization algorithm for binary and ternary TensorPrimitives operations (#93409)
* Update InvokeSpanSpanIntoSpan<TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarIntoSpan<TTransformOperator, TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanScalarIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Improve codegen slightly by using case 0, rather than default
* Adjust the canAlign check to be latter, to reduce branch count for data under the threshold
* Add a comment explaining the NonTemporalByteThreshold
* Make sure xTransformOp.CanVectorize is checked on .NET Standard
* Use the improved vectorization algorithm for aggregate TensorPrimitives operations (#93695)
* Improve the handling of the IAggregationOperator implementations
* Update Aggregate<TTransformOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Update Aggregate<TBinaryOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Respond to PR feedback
* [wasm] Remove more active issues for #92885 (#93596)
* adding patch from pr 93556
* Vectorizes IndexOfMin/Max/Magnitude (#93469)
* resolved merge conflicts
* net core full done
* minor code cleanup
* NetStandard and PR fixes.
* minor pr changes
* Fix IndexOfMaxMagnitudeOperator
* Fix IndexOfMaxMagnitudeOperator on netcore
* updates from PR comments
* netcore fixed
* net standard updated
* add reference assembly exclusions
* made naive approach better
* resolved PR comments
* minor comment changes
* minor formatting fixes
* added inlining
* fixes from PR comments
* comments from pr
* fixed spacing
---------
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Tanner Gooding <tagoo@outlook.com>
Co-authored-by: Ankit Jain <radical@gmail.com>
Co-authored-by: Radek Doulik <radek.doulik@gmail.com>
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@EgorBo@lewing@stephentoub@gfoidl
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Improve a vector implementation to support alignment and non-temporal tores - #93296

Merged
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align
Oct 12, 2023
Merged

Improve a vector implementation to support alignment and non-temporal tores#93296
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align

Conversation

@tannergooding

Copy link
Copy Markdown
Member

No description provided.

@ghost

Copy link
Copy Markdown

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

Issue Details

null

Author:tannergooding
Assignees:tannergooding
Labels:

area-System.Numerics

Milestone:-

@EgorBo

Copy link
Copy Markdown
Member

If a function is idempotent (which most of them are) it's enough a single load to align data (like I did in https://github.com/dotnet/runtime/pull/93214/files) without any unrolled loops, etc - presumably, you can expose a bool IsIdempotent {get;} property in these abstraction and only align those for simplicity

@tannergooding
tannergooding marked this pull request as ready for review October 10, 2023 23:21
@tannergooding

tannergooding commented Oct 11, 2023

Copy link
Copy Markdown
MemberAuthor

Here are the before/after results for Abs, where Abs1 is the previous implementation and Abs2 is the implementation from this PR. We are faster across the board (except for 16 elements) and more than 2x faster for large inputs, with the gains really showing past 256 elements (1024 bytes).

MethodTensorLengthMeanErrorStdDev
Abs112.609 ns0.0611 ns0.0572 ns
Abs212.562 ns0.0518 ns0.0485 ns
Abs123.142 ns0.0487 ns0.0456 ns
Abs222.563 ns0.0121 ns0.0101 ns
Abs133.309 ns0.0294 ns0.0229 ns
Abs232.742 ns0.0171 ns0.0133 ns
Abs143.090 ns0.0496 ns0.0464 ns
Abs242.572 ns0.0187 ns0.0166 ns
Abs155.198 ns0.0190 ns0.0148 ns
Abs253.133 ns0.0788 ns0.0774 ns
Abs165.275 ns0.0790 ns0.0739 ns
Abs263.098 ns0.0782 ns0.0732 ns
Abs175.322 ns0.0916 ns0.0857 ns
Abs273.164 ns0.0544 ns0.0509 ns
Abs182.936 ns0.0553 ns0.0517 ns
Abs282.573 ns0.0442 ns0.0414 ns
Abs195.454 ns0.0910 ns0.0852 ns
Abs293.165 ns0.0536 ns0.0501 ns
Abs1105.431 ns0.0675 ns0.0631 ns
Abs2103.230 ns0.0308 ns0.0288 ns
Abs1115.369 ns0.0447 ns0.0418 ns
Abs2113.199 ns0.0443 ns0.0393 ns
Abs1125.571 ns0.0593 ns0.0555 ns
Abs2123.211 ns0.0320 ns0.0299 ns
Abs1135.585 ns0.0491 ns0.0459 ns
Abs2133.262 ns0.0295 ns0.0276 ns
Abs1145.766 ns0.0550 ns0.0514 ns
Abs2143.264 ns0.0292 ns0.0273 ns
Abs1155.865 ns0.0454 ns0.0354 ns
Abs2153.254 ns0.0451 ns0.0377 ns
Abs1162.790 ns0.0207 ns0.0193 ns
Abs2164.984 ns0.0479 ns0.0448 ns
Abs1326.515 ns0.0886 ns0.0785 ns
Abs2326.012 ns0.1203 ns0.1067 ns
Abs16417.126 ns0.1647 ns0.1540 ns
Abs26415.343 ns0.3172 ns0.3115 ns
Abs112838.650 ns0.7464 ns0.6982 ns
Abs212837.150 ns0.4134 ns0.3665 ns
Abs125682.113 ns0.5631 ns0.4702 ns
Abs225651.379 ns0.2897 ns0.2710 ns
Abs1512169.559 ns0.7197 ns0.6732 ns
Abs251278.668 ns0.4391 ns0.4107 ns
Abs11024336.791 ns1.9587 ns1.8321 ns
Abs21024133.500 ns0.7219 ns0.6400 ns
Abs12048698.482 ns1.3343 ns1.1142 ns
Abs22048244.729 ns1.6655 ns1.5579 ns
Abs140961,411.587 ns5.8918 ns5.2229 ns
Abs24096471.249 ns1.2273 ns1.0880 ns
Abs16553622,871.118 ns97.4602 ns91.1643 ns
Abs2655367,306.615 ns32.3811 ns30.2893 ns
Abs113107245,773.050 ns244.2154 ns228.4393 ns
Abs213107218,461.823 ns105.6972 ns93.6978 ns

@tannergooding

Copy link
Copy Markdown
MemberAuthor

Similar results for Exp

MethodTensorLengthMeanErrorStdDev
Exp115.224 ns0.1122 ns0.1050 ns
Exp214.863 ns0.0823 ns0.0770 ns
Exp127.359 ns0.1578 ns0.1689 ns
Exp226.872 ns0.0975 ns0.0912 ns
Exp139.315 ns0.1091 ns0.0852 ns
Exp239.018 ns0.1643 ns0.1537 ns
Exp144.820 ns0.1094 ns0.1075 ns
Exp244.863 ns0.1028 ns0.0962 ns
Exp159.103 ns0.0672 ns0.0629 ns
Exp256.563 ns0.1169 ns0.1036 ns
Exp169.280 ns0.2022 ns0.2076 ns
Exp266.685 ns0.1498 ns0.1538 ns
Exp179.111 ns0.0848 ns0.0793 ns
Exp276.660 ns0.1413 ns0.1321 ns
Exp185.006 ns0.0963 ns0.0901 ns
Exp284.848 ns0.1006 ns0.0941 ns
Exp1910.272 ns0.1189 ns0.1112 ns
Exp297.593 ns0.0876 ns0.0820 ns
Exp11010.188 ns0.0713 ns0.0596 ns
Exp2107.581 ns0.0935 ns0.0875 ns
Exp11110.274 ns0.1070 ns0.0949 ns
Exp2117.631 ns0.1412 ns0.1320 ns
Exp11210.246 ns0.0755 ns0.0670 ns
Exp2127.555 ns0.0742 ns0.0658 ns
Exp11310.270 ns0.1266 ns0.1184 ns
Exp2137.651 ns0.0916 ns0.0857 ns
Exp11410.252 ns0.1509 ns0.1411 ns
Exp2147.585 ns0.1017 ns0.0951 ns
Exp11510.428 ns0.0935 ns0.0875 ns
Exp2157.795 ns0.0801 ns0.0749 ns
Exp1167.271 ns0.0845 ns0.0749 ns
Exp21619.323 ns0.3291 ns0.3078 ns
Exp13218.716 ns0.2705 ns0.2530 ns
Exp23225.631 ns0.3860 ns0.3610 ns
Exp16448.860 ns0.4013 ns0.3753 ns
Exp26450.285 ns0.9766 ns0.9135 ns
Exp1128109.969 ns1.6557 ns1.5487 ns
Exp2128110.952 ns1.4802 ns1.3846 ns
Exp1256230.547 ns2.4931 ns2.0819 ns
Exp2256193.106 ns0.8509 ns0.7105 ns
Exp1512477.460 ns5.6110 ns5.2486 ns
Exp2512317.518 ns5.5631 ns5.2037 ns
Exp11024977.141 ns13.0740 ns12.2295 ns
Exp21024573.851 ns7.7362 ns7.2364 ns
Exp120481,931.447 ns12.4202 ns10.3714 ns
Exp220481,017.003 ns19.3625 ns20.7177 ns
Exp140963,892.225 ns27.1610 ns22.6807 ns
Exp240961,975.208 ns26.4692 ns23.4642 ns
Exp16553661,938.616 ns354.4827 ns296.0091 ns
Exp26553632,390.331 ns590.1723 ns552.0475 ns
Exp1131072125,798.377 ns2,260.7971 ns2,114.7510 ns
Exp213107261,126.610 ns1,091.3727 ns1,020.8707 ns

{
public static partial class TensorPrimitives
{
private const nuint NonTemporalByteThreshold = 256 * 1024;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a comment about what this is and how the value was chosen?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Suggested change
privateconstnuintNonTemporalByteThreshold=256*1024;
/// <summary>Defines the threshold, in bytes, at which non-temporal stores will be used.</summary>
/// <remarks>
/// A non-temporal store is one that allows the CPU to bypass the cache when writing to memory.
///
/// This can be beneficial when working with large amounts of memory where the writes would otherwise
/// cause large amounts of repeated updates and evictions. The hardware optimization manuals recommend
/// the threshold to be roughly half the size of the last level of on-die cache -- that is, if you have approximately
/// 4MB of L3 cache per core, you'd want this to be approx. 1-2MB, depending on if hyperthreading was enabled.
///
/// However, actually computing the amount of L3 cache per core can be tricky or error prone. Native memcpy
/// algorithms use a constant threshold that is typically around 256KB and we match that here for simplicity. This
/// threshold accounts for most processors in the last 10-15 years that had approx. 1MB L3 per core and support
/// hyperthreading, giving a per core last level cache of approx. 512KB.
/// </remarks>
privateconstnuintNonTemporalByteThreshold=256*1024;

Comment on lines +1331 to +1354
if (canAlign)
{
// Compute by how many elements we're misaligned and adjust the pointers accordingly
//
// Noting that we are only actually aligning dPtr. THis is because unaligned stores
// are more expensive than unaligned loads and aligning both is significantly more
// complex.

nuint misalignment = ((uint)(sizeof(Vector128<float>)) - ((nuint)(dPtr) % (uint)(sizeof(Vector128<float>)))) / sizeof(float);

xPtr += misalignment;
dPtr += misalignment;

Debug.Assert(((nuint)(dPtr) % (uint)(sizeof(Vector128<float>))) == 0);

remainder -= misalignment;
}

Vector128<float> vector1;
Vector128<float> vector2;
Vector128<float> vector3;
Vector128<float> vector4;

if (canAlign && (remainder > (NonTemporalByteThreshold / sizeof(float))))

@stephentoubstephentoubOct 12, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have two checks on canAlign here. Would it help to eliminate a branch if this were instead structured as:

if(canAlign){
...// do alignmentif(remainder>(NonTemporalByteThreshold/sizeof(float))){
...// handle non-temporal pathgoto AdjustingRefs;}}
...// what's currently in the else blockAdjustingRefs:
...// stuff currently after the else block

?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't think the additional complexity is worth it here and it can cause subtle issues with control flow analysis that are probably undesirable.

If we were concerned, I'd prefer switching the order so its if ((remainder > (NonTemporalByteThreshold / sizeof(float)) && canAlign) instead.


public static IEnumerable<object[]> TensorLengths =>
from length in Enumerable.Range(1, 128)
from length in Enumerable.Range(1, 256)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is good, but it's also going to double the number of test cases we're running. Not a big deal except that on netfx it seems to struggle with the theory count. We should probably subsequently change some of those theories to be loops, e.g. instead of:

[Theory][MemberData(nameof(TensorLengths))]publicvoidFoo(inttensorLength){
...}

do:

[Fact]publicvoidFoo(){foreach(inttensorLengthinTensorLengths){
...}}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll handle this in the immediately following PR covering binary/ternary.

@lewing

Copy link
Copy Markdown
Member

This appears to have broken the outerloop tests

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@lewing is there an issue or a link that can be shared so it can be investigated?

@lewing

lewing commented Oct 12, 2023

Copy link
Copy Markdown
Member

@tannergooding

Copy link
Copy Markdown
MemberAuthor

#93412 fixes the issue.

Another PR went in that removed unsafe and it didn't conflict, so the nothing caught the issue.

michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request Oct 20, 2023
… tores (dotnet#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
carlossanlop pushed a commit that referenced this pull request Oct 20, 2023
* Use FMA in TensorPrimitives (#92205)
* Simplify TensorPrimitive's AbsoluteOperator (#92577)
Vector{128/256/512} all provide Abs; no need to do this manually.
* Reduce some boilerplate in TensorPrimitive's IBinaryOperator (#92576)
Change a few of the static abstract interface methods to be virtual, as most implementations throw from these methods; we can consolidate that throwing to the base.
* Minor code cleanup in TensorPrimitives tests (#92575)
* Normalize some test naming
* Alphabetize tests
* Improve mistmatched length tests with all positions of the shorter tensor
* Alphabetize methods in TensorPrimitives.cs
* Vectorize TensorPrimitives.Min/Max{Magnitude} (#92618)
* Vectorize TensorPrimitives.Min/Max{Magnitude}
* Use AdvSimd.Max/Min
* Rename some parameters/locals for consistency
* Improve HorizontalAggregate
* Move a few helpers
* Avoid scalar path for returning found NaN
* Update TensorPrimitives aggregations to vectorize handling of remaining elements (#92672)
* Update TensorPrimitives.CosineSimilarity to vectorize handling of remaining elements
* Vectorize remainder handling for Aggregate helpers
* Flesh out TensorPrimitives XML docs (#92749)
* Flesh out TensorPrimitives XML docs
* Address PR feedback
- Remove use of FusedMultiplyAdd from all but CosineSimilarity
- Remove comments about platform/OS-specific behavior from Add/AddMultiply/Subtract/Multiply/MultiplyAdd/Divide/Negate
- Loosen comments about NaN and which exact one is returned
* Address PR feedback
* Vectorize TensorPrimitives.ConvertToHalf (#92715)
* Enable TensorPrimitives to perform in-place operations (#92820)
Some operations would produce incorrect results if the same span was passed as both an input and an output. When vectorization was employed but the span's length wasn't a perfect multiple of a vector, we'd do the standard trick of performing one last operation on the last vector's worth of data; however, that relies on the operation being idempotent, and if a previous operation has overwritten input with a new value due to the same memory being used for input and output, some operations won't be idempotent. This fixes that by masking off the already processed elements. It adds tests to validate in-place use works, and it updates the docs to carve out this valid overlapping.
* Vectorize TensorPrimitives.ConvertToSingle (#92779)
* Vectorize TensorPrimitives.ConvertToSingle
* Address PR feedback
* Throw exception in TensorPrimitives for unsupported span overlaps (#92838)
* This vectorizes TensorPrimitives.Log2 (#92897)
* Add a way to support operations that can't be vectorized on netstandard
* Updating TensorPrimitives.Log2 to be vectorized on .NET Core
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Ensure we do an arithmetic right shift in the Log2 vectorization
* Ensure the code can compile on .NET 7
* Ensure that edge cases are properly handled and don't resolve to `x`
* Ensure that Log2 special results are explicitly handled.
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Adding Log2 tests covering some special values (#92946)
* [wasm] Disable `TensorPrimitivesTests.ConvertToHalf_SpecialValues` (#92953)
Failing test: `System.Numerics.Tensors.Tests.TensorPrimitivesTests.ConvertToHalf_SpecialValues`
Issue: #92885
* Adding a vectorized implementation of TensorPrimitives.Log (#92960)
* Adding a vectorized implementation of TensorPrimitives.Log
* Make sure to hit Ctrl+S
* Consolidate some TensorPrimitivesTests logic around special values (#92982)
* Vectorize TensorPrimitives.Exp (#93018)
* Vectorize TensorPrimitives.Exp
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax (#93029)
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax
- Adds a SigmoidOperator that just wraps the ExpOperator
- Vectorizes both passes of SoftMax, on top of ExpOperator. Simplest way to do this was to augment the existing InvokeSpanScalarIntoSpan to take a transform operator.
- In doing so, found some naming inconsistencies I'd previously introduced, so I did some automatic renaming to make things more consistent.
- Added XML comments to all the internal/private surface area.
- Fleshes out some tests (and test values).
* Disable tests on mono
* Address PR feedback
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh (#93093)
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh
Tanh and Cosh are based on AOCL-LibM.
AOCL-LibM doesn't appear to have a sinh implementation, so this Sinh is just based on the sinh formula based on exp(x).
I also augmented the tests further, including:
- Added more tests for sinh/cosh/tanh
- Add an equality routine that supports comparing larger values with a tolerance
- Tightened the tolerance for most functions
- Changed some tests to be theories to be consistent with style elsewhere in the tests
- Fixed some use of Math to be MathF
* Remove unnecessary special-handling path from cosh
* Remove unnecessary special-handling path from tanh
* Redo sinh based on cosh
* Address PR feedback
* Replace confusing new T[] { ... }
* Remove a few unnecessary `unsafe` keyword uses in TensorPrimitives (#93219)
* Consolidate a few exception throws in TensorPrimitives (#93168)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value (#93169)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value
Found as part of adding more tests for Min/Max{Magnitude} to validate they match their IndexOfXx variants.
* Address PR feedback
* Improve a vector implementation to support alignment and non-temporal tores (#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
* Mark TensorPrimitives as unsafe (#93412)
* Use the improved vectorization algorithm for binary and ternary TensorPrimitives operations (#93409)
* Update InvokeSpanSpanIntoSpan<TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarIntoSpan<TTransformOperator, TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanScalarIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Improve codegen slightly by using case 0, rather than default
* Adjust the canAlign check to be latter, to reduce branch count for data under the threshold
* Add a comment explaining the NonTemporalByteThreshold
* Make sure xTransformOp.CanVectorize is checked on .NET Standard
* Use the improved vectorization algorithm for aggregate TensorPrimitives operations (#93695)
* Improve the handling of the IAggregationOperator implementations
* Update Aggregate<TTransformOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Update Aggregate<TBinaryOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Respond to PR feedback
* [wasm] Remove more active issues for #92885 (#93596)
* adding patch from pr 93556
* Vectorizes IndexOfMin/Max/Magnitude (#93469)
* resolved merge conflicts
* net core full done
* minor code cleanup
* NetStandard and PR fixes.
* minor pr changes
* Fix IndexOfMaxMagnitudeOperator
* Fix IndexOfMaxMagnitudeOperator on netcore
* updates from PR comments
* netcore fixed
* net standard updated
* add reference assembly exclusions
* made naive approach better
* resolved PR comments
* minor comment changes
* minor formatting fixes
* added inlining
* fixes from PR comments
* comments from pr
* fixed spacing
---------
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Tanner Gooding <tagoo@outlook.com>
Co-authored-by: Ankit Jain <radical@gmail.com>
Co-authored-by: Radek Doulik <radek.doulik@gmail.com>
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@EgorBo@lewing@stephentoub@gfoidl
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Improve a vector implementation to support alignment and non-temporal tores - #93296

Merged
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align
Oct 12, 2023
Merged

Improve a vector implementation to support alignment and non-temporal tores#93296
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align

Conversation

@tannergooding

Copy link
Copy Markdown
Member

No description provided.

@ghost

Copy link
Copy Markdown

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

Issue Details

null

Author:tannergooding
Assignees:tannergooding
Labels:

area-System.Numerics

Milestone:-

@EgorBo

Copy link
Copy Markdown
Member

If a function is idempotent (which most of them are) it's enough a single load to align data (like I did in https://github.com/dotnet/runtime/pull/93214/files) without any unrolled loops, etc - presumably, you can expose a bool IsIdempotent {get;} property in these abstraction and only align those for simplicity

@tannergooding
tannergooding marked this pull request as ready for review October 10, 2023 23:21
@tannergooding

tannergooding commented Oct 11, 2023

Copy link
Copy Markdown
MemberAuthor

Here are the before/after results for Abs, where Abs1 is the previous implementation and Abs2 is the implementation from this PR. We are faster across the board (except for 16 elements) and more than 2x faster for large inputs, with the gains really showing past 256 elements (1024 bytes).

MethodTensorLengthMeanErrorStdDev
Abs112.609 ns0.0611 ns0.0572 ns
Abs212.562 ns0.0518 ns0.0485 ns
Abs123.142 ns0.0487 ns0.0456 ns
Abs222.563 ns0.0121 ns0.0101 ns
Abs133.309 ns0.0294 ns0.0229 ns
Abs232.742 ns0.0171 ns0.0133 ns
Abs143.090 ns0.0496 ns0.0464 ns
Abs242.572 ns0.0187 ns0.0166 ns
Abs155.198 ns0.0190 ns0.0148 ns
Abs253.133 ns0.0788 ns0.0774 ns
Abs165.275 ns0.0790 ns0.0739 ns
Abs263.098 ns0.0782 ns0.0732 ns
Abs175.322 ns0.0916 ns0.0857 ns
Abs273.164 ns0.0544 ns0.0509 ns
Abs182.936 ns0.0553 ns0.0517 ns
Abs282.573 ns0.0442 ns0.0414 ns
Abs195.454 ns0.0910 ns0.0852 ns
Abs293.165 ns0.0536 ns0.0501 ns
Abs1105.431 ns0.0675 ns0.0631 ns
Abs2103.230 ns0.0308 ns0.0288 ns
Abs1115.369 ns0.0447 ns0.0418 ns
Abs2113.199 ns0.0443 ns0.0393 ns
Abs1125.571 ns0.0593 ns0.0555 ns
Abs2123.211 ns0.0320 ns0.0299 ns
Abs1135.585 ns0.0491 ns0.0459 ns
Abs2133.262 ns0.0295 ns0.0276 ns
Abs1145.766 ns0.0550 ns0.0514 ns
Abs2143.264 ns0.0292 ns0.0273 ns
Abs1155.865 ns0.0454 ns0.0354 ns
Abs2153.254 ns0.0451 ns0.0377 ns
Abs1162.790 ns0.0207 ns0.0193 ns
Abs2164.984 ns0.0479 ns0.0448 ns
Abs1326.515 ns0.0886 ns0.0785 ns
Abs2326.012 ns0.1203 ns0.1067 ns
Abs16417.126 ns0.1647 ns0.1540 ns
Abs26415.343 ns0.3172 ns0.3115 ns
Abs112838.650 ns0.7464 ns0.6982 ns
Abs212837.150 ns0.4134 ns0.3665 ns
Abs125682.113 ns0.5631 ns0.4702 ns
Abs225651.379 ns0.2897 ns0.2710 ns
Abs1512169.559 ns0.7197 ns0.6732 ns
Abs251278.668 ns0.4391 ns0.4107 ns
Abs11024336.791 ns1.9587 ns1.8321 ns
Abs21024133.500 ns0.7219 ns0.6400 ns
Abs12048698.482 ns1.3343 ns1.1142 ns
Abs22048244.729 ns1.6655 ns1.5579 ns
Abs140961,411.587 ns5.8918 ns5.2229 ns
Abs24096471.249 ns1.2273 ns1.0880 ns
Abs16553622,871.118 ns97.4602 ns91.1643 ns
Abs2655367,306.615 ns32.3811 ns30.2893 ns
Abs113107245,773.050 ns244.2154 ns228.4393 ns
Abs213107218,461.823 ns105.6972 ns93.6978 ns

@tannergooding

Copy link
Copy Markdown
MemberAuthor

Similar results for Exp

MethodTensorLengthMeanErrorStdDev
Exp115.224 ns0.1122 ns0.1050 ns
Exp214.863 ns0.0823 ns0.0770 ns
Exp127.359 ns0.1578 ns0.1689 ns
Exp226.872 ns0.0975 ns0.0912 ns
Exp139.315 ns0.1091 ns0.0852 ns
Exp239.018 ns0.1643 ns0.1537 ns
Exp144.820 ns0.1094 ns0.1075 ns
Exp244.863 ns0.1028 ns0.0962 ns
Exp159.103 ns0.0672 ns0.0629 ns
Exp256.563 ns0.1169 ns0.1036 ns
Exp169.280 ns0.2022 ns0.2076 ns
Exp266.685 ns0.1498 ns0.1538 ns
Exp179.111 ns0.0848 ns0.0793 ns
Exp276.660 ns0.1413 ns0.1321 ns
Exp185.006 ns0.0963 ns0.0901 ns
Exp284.848 ns0.1006 ns0.0941 ns
Exp1910.272 ns0.1189 ns0.1112 ns
Exp297.593 ns0.0876 ns0.0820 ns
Exp11010.188 ns0.0713 ns0.0596 ns
Exp2107.581 ns0.0935 ns0.0875 ns
Exp11110.274 ns0.1070 ns0.0949 ns
Exp2117.631 ns0.1412 ns0.1320 ns
Exp11210.246 ns0.0755 ns0.0670 ns
Exp2127.555 ns0.0742 ns0.0658 ns
Exp11310.270 ns0.1266 ns0.1184 ns
Exp2137.651 ns0.0916 ns0.0857 ns
Exp11410.252 ns0.1509 ns0.1411 ns
Exp2147.585 ns0.1017 ns0.0951 ns
Exp11510.428 ns0.0935 ns0.0875 ns
Exp2157.795 ns0.0801 ns0.0749 ns
Exp1167.271 ns0.0845 ns0.0749 ns
Exp21619.323 ns0.3291 ns0.3078 ns
Exp13218.716 ns0.2705 ns0.2530 ns
Exp23225.631 ns0.3860 ns0.3610 ns
Exp16448.860 ns0.4013 ns0.3753 ns
Exp26450.285 ns0.9766 ns0.9135 ns
Exp1128109.969 ns1.6557 ns1.5487 ns
Exp2128110.952 ns1.4802 ns1.3846 ns
Exp1256230.547 ns2.4931 ns2.0819 ns
Exp2256193.106 ns0.8509 ns0.7105 ns
Exp1512477.460 ns5.6110 ns5.2486 ns
Exp2512317.518 ns5.5631 ns5.2037 ns
Exp11024977.141 ns13.0740 ns12.2295 ns
Exp21024573.851 ns7.7362 ns7.2364 ns
Exp120481,931.447 ns12.4202 ns10.3714 ns
Exp220481,017.003 ns19.3625 ns20.7177 ns
Exp140963,892.225 ns27.1610 ns22.6807 ns
Exp240961,975.208 ns26.4692 ns23.4642 ns
Exp16553661,938.616 ns354.4827 ns296.0091 ns
Exp26553632,390.331 ns590.1723 ns552.0475 ns
Exp1131072125,798.377 ns2,260.7971 ns2,114.7510 ns
Exp213107261,126.610 ns1,091.3727 ns1,020.8707 ns

{
public static partial class TensorPrimitives
{
private const nuint NonTemporalByteThreshold = 256 * 1024;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a comment about what this is and how the value was chosen?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Suggested change
privateconstnuintNonTemporalByteThreshold=256*1024;
/// <summary>Defines the threshold, in bytes, at which non-temporal stores will be used.</summary>
/// <remarks>
/// A non-temporal store is one that allows the CPU to bypass the cache when writing to memory.
///
/// This can be beneficial when working with large amounts of memory where the writes would otherwise
/// cause large amounts of repeated updates and evictions. The hardware optimization manuals recommend
/// the threshold to be roughly half the size of the last level of on-die cache -- that is, if you have approximately
/// 4MB of L3 cache per core, you'd want this to be approx. 1-2MB, depending on if hyperthreading was enabled.
///
/// However, actually computing the amount of L3 cache per core can be tricky or error prone. Native memcpy
/// algorithms use a constant threshold that is typically around 256KB and we match that here for simplicity. This
/// threshold accounts for most processors in the last 10-15 years that had approx. 1MB L3 per core and support
/// hyperthreading, giving a per core last level cache of approx. 512KB.
/// </remarks>
privateconstnuintNonTemporalByteThreshold=256*1024;

Comment on lines +1331 to +1354
if (canAlign)
{
// Compute by how many elements we're misaligned and adjust the pointers accordingly
//
// Noting that we are only actually aligning dPtr. THis is because unaligned stores
// are more expensive than unaligned loads and aligning both is significantly more
// complex.

nuint misalignment = ((uint)(sizeof(Vector128<float>)) - ((nuint)(dPtr) % (uint)(sizeof(Vector128<float>)))) / sizeof(float);

xPtr += misalignment;
dPtr += misalignment;

Debug.Assert(((nuint)(dPtr) % (uint)(sizeof(Vector128<float>))) == 0);

remainder -= misalignment;
}

Vector128<float> vector1;
Vector128<float> vector2;
Vector128<float> vector3;
Vector128<float> vector4;

if (canAlign && (remainder > (NonTemporalByteThreshold / sizeof(float))))

@stephentoubstephentoubOct 12, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have two checks on canAlign here. Would it help to eliminate a branch if this were instead structured as:

if(canAlign){
...// do alignmentif(remainder>(NonTemporalByteThreshold/sizeof(float))){
...// handle non-temporal pathgoto AdjustingRefs;}}
...// what's currently in the else blockAdjustingRefs:
...// stuff currently after the else block

?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't think the additional complexity is worth it here and it can cause subtle issues with control flow analysis that are probably undesirable.

If we were concerned, I'd prefer switching the order so its if ((remainder > (NonTemporalByteThreshold / sizeof(float)) && canAlign) instead.


public static IEnumerable<object[]> TensorLengths =>
from length in Enumerable.Range(1, 128)
from length in Enumerable.Range(1, 256)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is good, but it's also going to double the number of test cases we're running. Not a big deal except that on netfx it seems to struggle with the theory count. We should probably subsequently change some of those theories to be loops, e.g. instead of:

[Theory][MemberData(nameof(TensorLengths))]publicvoidFoo(inttensorLength){
...}

do:

[Fact]publicvoidFoo(){foreach(inttensorLengthinTensorLengths){
...}}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll handle this in the immediately following PR covering binary/ternary.

@lewing

Copy link
Copy Markdown
Member

This appears to have broken the outerloop tests

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@lewing is there an issue or a link that can be shared so it can be investigated?

@lewing

lewing commented Oct 12, 2023

Copy link
Copy Markdown
Member

@tannergooding

Copy link
Copy Markdown
MemberAuthor

#93412 fixes the issue.

Another PR went in that removed unsafe and it didn't conflict, so the nothing caught the issue.

michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request Oct 20, 2023
… tores (dotnet#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
carlossanlop pushed a commit that referenced this pull request Oct 20, 2023
* Use FMA in TensorPrimitives (#92205)
* Simplify TensorPrimitive's AbsoluteOperator (#92577)
Vector{128/256/512} all provide Abs; no need to do this manually.
* Reduce some boilerplate in TensorPrimitive's IBinaryOperator (#92576)
Change a few of the static abstract interface methods to be virtual, as most implementations throw from these methods; we can consolidate that throwing to the base.
* Minor code cleanup in TensorPrimitives tests (#92575)
* Normalize some test naming
* Alphabetize tests
* Improve mistmatched length tests with all positions of the shorter tensor
* Alphabetize methods in TensorPrimitives.cs
* Vectorize TensorPrimitives.Min/Max{Magnitude} (#92618)
* Vectorize TensorPrimitives.Min/Max{Magnitude}
* Use AdvSimd.Max/Min
* Rename some parameters/locals for consistency
* Improve HorizontalAggregate
* Move a few helpers
* Avoid scalar path for returning found NaN
* Update TensorPrimitives aggregations to vectorize handling of remaining elements (#92672)
* Update TensorPrimitives.CosineSimilarity to vectorize handling of remaining elements
* Vectorize remainder handling for Aggregate helpers
* Flesh out TensorPrimitives XML docs (#92749)
* Flesh out TensorPrimitives XML docs
* Address PR feedback
- Remove use of FusedMultiplyAdd from all but CosineSimilarity
- Remove comments about platform/OS-specific behavior from Add/AddMultiply/Subtract/Multiply/MultiplyAdd/Divide/Negate
- Loosen comments about NaN and which exact one is returned
* Address PR feedback
* Vectorize TensorPrimitives.ConvertToHalf (#92715)
* Enable TensorPrimitives to perform in-place operations (#92820)
Some operations would produce incorrect results if the same span was passed as both an input and an output. When vectorization was employed but the span's length wasn't a perfect multiple of a vector, we'd do the standard trick of performing one last operation on the last vector's worth of data; however, that relies on the operation being idempotent, and if a previous operation has overwritten input with a new value due to the same memory being used for input and output, some operations won't be idempotent. This fixes that by masking off the already processed elements. It adds tests to validate in-place use works, and it updates the docs to carve out this valid overlapping.
* Vectorize TensorPrimitives.ConvertToSingle (#92779)
* Vectorize TensorPrimitives.ConvertToSingle
* Address PR feedback
* Throw exception in TensorPrimitives for unsupported span overlaps (#92838)
* This vectorizes TensorPrimitives.Log2 (#92897)
* Add a way to support operations that can't be vectorized on netstandard
* Updating TensorPrimitives.Log2 to be vectorized on .NET Core
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Ensure we do an arithmetic right shift in the Log2 vectorization
* Ensure the code can compile on .NET 7
* Ensure that edge cases are properly handled and don't resolve to `x`
* Ensure that Log2 special results are explicitly handled.
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Adding Log2 tests covering some special values (#92946)
* [wasm] Disable `TensorPrimitivesTests.ConvertToHalf_SpecialValues` (#92953)
Failing test: `System.Numerics.Tensors.Tests.TensorPrimitivesTests.ConvertToHalf_SpecialValues`
Issue: #92885
* Adding a vectorized implementation of TensorPrimitives.Log (#92960)
* Adding a vectorized implementation of TensorPrimitives.Log
* Make sure to hit Ctrl+S
* Consolidate some TensorPrimitivesTests logic around special values (#92982)
* Vectorize TensorPrimitives.Exp (#93018)
* Vectorize TensorPrimitives.Exp
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax (#93029)
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax
- Adds a SigmoidOperator that just wraps the ExpOperator
- Vectorizes both passes of SoftMax, on top of ExpOperator. Simplest way to do this was to augment the existing InvokeSpanScalarIntoSpan to take a transform operator.
- In doing so, found some naming inconsistencies I'd previously introduced, so I did some automatic renaming to make things more consistent.
- Added XML comments to all the internal/private surface area.
- Fleshes out some tests (and test values).
* Disable tests on mono
* Address PR feedback
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh (#93093)
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh
Tanh and Cosh are based on AOCL-LibM.
AOCL-LibM doesn't appear to have a sinh implementation, so this Sinh is just based on the sinh formula based on exp(x).
I also augmented the tests further, including:
- Added more tests for sinh/cosh/tanh
- Add an equality routine that supports comparing larger values with a tolerance
- Tightened the tolerance for most functions
- Changed some tests to be theories to be consistent with style elsewhere in the tests
- Fixed some use of Math to be MathF
* Remove unnecessary special-handling path from cosh
* Remove unnecessary special-handling path from tanh
* Redo sinh based on cosh
* Address PR feedback
* Replace confusing new T[] { ... }
* Remove a few unnecessary `unsafe` keyword uses in TensorPrimitives (#93219)
* Consolidate a few exception throws in TensorPrimitives (#93168)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value (#93169)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value
Found as part of adding more tests for Min/Max{Magnitude} to validate they match their IndexOfXx variants.
* Address PR feedback
* Improve a vector implementation to support alignment and non-temporal tores (#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
* Mark TensorPrimitives as unsafe (#93412)
* Use the improved vectorization algorithm for binary and ternary TensorPrimitives operations (#93409)
* Update InvokeSpanSpanIntoSpan<TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarIntoSpan<TTransformOperator, TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanScalarIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Improve codegen slightly by using case 0, rather than default
* Adjust the canAlign check to be latter, to reduce branch count for data under the threshold
* Add a comment explaining the NonTemporalByteThreshold
* Make sure xTransformOp.CanVectorize is checked on .NET Standard
* Use the improved vectorization algorithm for aggregate TensorPrimitives operations (#93695)
* Improve the handling of the IAggregationOperator implementations
* Update Aggregate<TTransformOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Update Aggregate<TBinaryOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Respond to PR feedback
* [wasm] Remove more active issues for #92885 (#93596)
* adding patch from pr 93556
* Vectorizes IndexOfMin/Max/Magnitude (#93469)
* resolved merge conflicts
* net core full done
* minor code cleanup
* NetStandard and PR fixes.
* minor pr changes
* Fix IndexOfMaxMagnitudeOperator
* Fix IndexOfMaxMagnitudeOperator on netcore
* updates from PR comments
* netcore fixed
* net standard updated
* add reference assembly exclusions
* made naive approach better
* resolved PR comments
* minor comment changes
* minor formatting fixes
* added inlining
* fixes from PR comments
* comments from pr
* fixed spacing
---------
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Tanner Gooding <tagoo@outlook.com>
Co-authored-by: Ankit Jain <radical@gmail.com>
Co-authored-by: Radek Doulik <radek.doulik@gmail.com>
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@EgorBo@lewing@stephentoub@gfoidl
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Improve a vector implementation to support alignment and non-temporal tores - #93296

Merged
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align
Oct 12, 2023
Merged

Improve a vector implementation to support alignment and non-temporal tores#93296
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align

Conversation

@tannergooding

Copy link
Copy Markdown
Member

No description provided.

@ghost

Copy link
Copy Markdown

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

Issue Details

null

Author:tannergooding
Assignees:tannergooding
Labels:

area-System.Numerics

Milestone:-

@EgorBo

Copy link
Copy Markdown
Member

If a function is idempotent (which most of them are) it's enough a single load to align data (like I did in https://github.com/dotnet/runtime/pull/93214/files) without any unrolled loops, etc - presumably, you can expose a bool IsIdempotent {get;} property in these abstraction and only align those for simplicity

@tannergooding
tannergooding marked this pull request as ready for review October 10, 2023 23:21
@tannergooding

tannergooding commented Oct 11, 2023

Copy link
Copy Markdown
MemberAuthor

Here are the before/after results for Abs, where Abs1 is the previous implementation and Abs2 is the implementation from this PR. We are faster across the board (except for 16 elements) and more than 2x faster for large inputs, with the gains really showing past 256 elements (1024 bytes).

MethodTensorLengthMeanErrorStdDev
Abs112.609 ns0.0611 ns0.0572 ns
Abs212.562 ns0.0518 ns0.0485 ns
Abs123.142 ns0.0487 ns0.0456 ns
Abs222.563 ns0.0121 ns0.0101 ns
Abs133.309 ns0.0294 ns0.0229 ns
Abs232.742 ns0.0171 ns0.0133 ns
Abs143.090 ns0.0496 ns0.0464 ns
Abs242.572 ns0.0187 ns0.0166 ns
Abs155.198 ns0.0190 ns0.0148 ns
Abs253.133 ns0.0788 ns0.0774 ns
Abs165.275 ns0.0790 ns0.0739 ns
Abs263.098 ns0.0782 ns0.0732 ns
Abs175.322 ns0.0916 ns0.0857 ns
Abs273.164 ns0.0544 ns0.0509 ns
Abs182.936 ns0.0553 ns0.0517 ns
Abs282.573 ns0.0442 ns0.0414 ns
Abs195.454 ns0.0910 ns0.0852 ns
Abs293.165 ns0.0536 ns0.0501 ns
Abs1105.431 ns0.0675 ns0.0631 ns
Abs2103.230 ns0.0308 ns0.0288 ns
Abs1115.369 ns0.0447 ns0.0418 ns
Abs2113.199 ns0.0443 ns0.0393 ns
Abs1125.571 ns0.0593 ns0.0555 ns
Abs2123.211 ns0.0320 ns0.0299 ns
Abs1135.585 ns0.0491 ns0.0459 ns
Abs2133.262 ns0.0295 ns0.0276 ns
Abs1145.766 ns0.0550 ns0.0514 ns
Abs2143.264 ns0.0292 ns0.0273 ns
Abs1155.865 ns0.0454 ns0.0354 ns
Abs2153.254 ns0.0451 ns0.0377 ns
Abs1162.790 ns0.0207 ns0.0193 ns
Abs2164.984 ns0.0479 ns0.0448 ns
Abs1326.515 ns0.0886 ns0.0785 ns
Abs2326.012 ns0.1203 ns0.1067 ns
Abs16417.126 ns0.1647 ns0.1540 ns
Abs26415.343 ns0.3172 ns0.3115 ns
Abs112838.650 ns0.7464 ns0.6982 ns
Abs212837.150 ns0.4134 ns0.3665 ns
Abs125682.113 ns0.5631 ns0.4702 ns
Abs225651.379 ns0.2897 ns0.2710 ns
Abs1512169.559 ns0.7197 ns0.6732 ns
Abs251278.668 ns0.4391 ns0.4107 ns
Abs11024336.791 ns1.9587 ns1.8321 ns
Abs21024133.500 ns0.7219 ns0.6400 ns
Abs12048698.482 ns1.3343 ns1.1142 ns
Abs22048244.729 ns1.6655 ns1.5579 ns
Abs140961,411.587 ns5.8918 ns5.2229 ns
Abs24096471.249 ns1.2273 ns1.0880 ns
Abs16553622,871.118 ns97.4602 ns91.1643 ns
Abs2655367,306.615 ns32.3811 ns30.2893 ns
Abs113107245,773.050 ns244.2154 ns228.4393 ns
Abs213107218,461.823 ns105.6972 ns93.6978 ns

@tannergooding

Copy link
Copy Markdown
MemberAuthor

Similar results for Exp

MethodTensorLengthMeanErrorStdDev
Exp115.224 ns0.1122 ns0.1050 ns
Exp214.863 ns0.0823 ns0.0770 ns
Exp127.359 ns0.1578 ns0.1689 ns
Exp226.872 ns0.0975 ns0.0912 ns
Exp139.315 ns0.1091 ns0.0852 ns
Exp239.018 ns0.1643 ns0.1537 ns
Exp144.820 ns0.1094 ns0.1075 ns
Exp244.863 ns0.1028 ns0.0962 ns
Exp159.103 ns0.0672 ns0.0629 ns
Exp256.563 ns0.1169 ns0.1036 ns
Exp169.280 ns0.2022 ns0.2076 ns
Exp266.685 ns0.1498 ns0.1538 ns
Exp179.111 ns0.0848 ns0.0793 ns
Exp276.660 ns0.1413 ns0.1321 ns
Exp185.006 ns0.0963 ns0.0901 ns
Exp284.848 ns0.1006 ns0.0941 ns
Exp1910.272 ns0.1189 ns0.1112 ns
Exp297.593 ns0.0876 ns0.0820 ns
Exp11010.188 ns0.0713 ns0.0596 ns
Exp2107.581 ns0.0935 ns0.0875 ns
Exp11110.274 ns0.1070 ns0.0949 ns
Exp2117.631 ns0.1412 ns0.1320 ns
Exp11210.246 ns0.0755 ns0.0670 ns
Exp2127.555 ns0.0742 ns0.0658 ns
Exp11310.270 ns0.1266 ns0.1184 ns
Exp2137.651 ns0.0916 ns0.0857 ns
Exp11410.252 ns0.1509 ns0.1411 ns
Exp2147.585 ns0.1017 ns0.0951 ns
Exp11510.428 ns0.0935 ns0.0875 ns
Exp2157.795 ns0.0801 ns0.0749 ns
Exp1167.271 ns0.0845 ns0.0749 ns
Exp21619.323 ns0.3291 ns0.3078 ns
Exp13218.716 ns0.2705 ns0.2530 ns
Exp23225.631 ns0.3860 ns0.3610 ns
Exp16448.860 ns0.4013 ns0.3753 ns
Exp26450.285 ns0.9766 ns0.9135 ns
Exp1128109.969 ns1.6557 ns1.5487 ns
Exp2128110.952 ns1.4802 ns1.3846 ns
Exp1256230.547 ns2.4931 ns2.0819 ns
Exp2256193.106 ns0.8509 ns0.7105 ns
Exp1512477.460 ns5.6110 ns5.2486 ns
Exp2512317.518 ns5.5631 ns5.2037 ns
Exp11024977.141 ns13.0740 ns12.2295 ns
Exp21024573.851 ns7.7362 ns7.2364 ns
Exp120481,931.447 ns12.4202 ns10.3714 ns
Exp220481,017.003 ns19.3625 ns20.7177 ns
Exp140963,892.225 ns27.1610 ns22.6807 ns
Exp240961,975.208 ns26.4692 ns23.4642 ns
Exp16553661,938.616 ns354.4827 ns296.0091 ns
Exp26553632,390.331 ns590.1723 ns552.0475 ns
Exp1131072125,798.377 ns2,260.7971 ns2,114.7510 ns
Exp213107261,126.610 ns1,091.3727 ns1,020.8707 ns

{
public static partial class TensorPrimitives
{
private const nuint NonTemporalByteThreshold = 256 * 1024;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a comment about what this is and how the value was chosen?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Suggested change
privateconstnuintNonTemporalByteThreshold=256*1024;
/// <summary>Defines the threshold, in bytes, at which non-temporal stores will be used.</summary>
/// <remarks>
/// A non-temporal store is one that allows the CPU to bypass the cache when writing to memory.
///
/// This can be beneficial when working with large amounts of memory where the writes would otherwise
/// cause large amounts of repeated updates and evictions. The hardware optimization manuals recommend
/// the threshold to be roughly half the size of the last level of on-die cache -- that is, if you have approximately
/// 4MB of L3 cache per core, you'd want this to be approx. 1-2MB, depending on if hyperthreading was enabled.
///
/// However, actually computing the amount of L3 cache per core can be tricky or error prone. Native memcpy
/// algorithms use a constant threshold that is typically around 256KB and we match that here for simplicity. This
/// threshold accounts for most processors in the last 10-15 years that had approx. 1MB L3 per core and support
/// hyperthreading, giving a per core last level cache of approx. 512KB.
/// </remarks>
privateconstnuintNonTemporalByteThreshold=256*1024;

Comment on lines +1331 to +1354
if (canAlign)
{
// Compute by how many elements we're misaligned and adjust the pointers accordingly
//
// Noting that we are only actually aligning dPtr. THis is because unaligned stores
// are more expensive than unaligned loads and aligning both is significantly more
// complex.

nuint misalignment = ((uint)(sizeof(Vector128<float>)) - ((nuint)(dPtr) % (uint)(sizeof(Vector128<float>)))) / sizeof(float);

xPtr += misalignment;
dPtr += misalignment;

Debug.Assert(((nuint)(dPtr) % (uint)(sizeof(Vector128<float>))) == 0);

remainder -= misalignment;
}

Vector128<float> vector1;
Vector128<float> vector2;
Vector128<float> vector3;
Vector128<float> vector4;

if (canAlign && (remainder > (NonTemporalByteThreshold / sizeof(float))))

@stephentoubstephentoubOct 12, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have two checks on canAlign here. Would it help to eliminate a branch if this were instead structured as:

if(canAlign){
...// do alignmentif(remainder>(NonTemporalByteThreshold/sizeof(float))){
...// handle non-temporal pathgoto AdjustingRefs;}}
...// what's currently in the else blockAdjustingRefs:
...// stuff currently after the else block

?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't think the additional complexity is worth it here and it can cause subtle issues with control flow analysis that are probably undesirable.

If we were concerned, I'd prefer switching the order so its if ((remainder > (NonTemporalByteThreshold / sizeof(float)) && canAlign) instead.


public static IEnumerable<object[]> TensorLengths =>
from length in Enumerable.Range(1, 128)
from length in Enumerable.Range(1, 256)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is good, but it's also going to double the number of test cases we're running. Not a big deal except that on netfx it seems to struggle with the theory count. We should probably subsequently change some of those theories to be loops, e.g. instead of:

[Theory][MemberData(nameof(TensorLengths))]publicvoidFoo(inttensorLength){
...}

do:

[Fact]publicvoidFoo(){foreach(inttensorLengthinTensorLengths){
...}}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll handle this in the immediately following PR covering binary/ternary.

@lewing

Copy link
Copy Markdown
Member

This appears to have broken the outerloop tests

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@lewing is there an issue or a link that can be shared so it can be investigated?

@lewing

lewing commented Oct 12, 2023

Copy link
Copy Markdown
Member

@tannergooding

Copy link
Copy Markdown
MemberAuthor

#93412 fixes the issue.

Another PR went in that removed unsafe and it didn't conflict, so the nothing caught the issue.

michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request Oct 20, 2023
… tores (dotnet#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
carlossanlop pushed a commit that referenced this pull request Oct 20, 2023
* Use FMA in TensorPrimitives (#92205)
* Simplify TensorPrimitive's AbsoluteOperator (#92577)
Vector{128/256/512} all provide Abs; no need to do this manually.
* Reduce some boilerplate in TensorPrimitive's IBinaryOperator (#92576)
Change a few of the static abstract interface methods to be virtual, as most implementations throw from these methods; we can consolidate that throwing to the base.
* Minor code cleanup in TensorPrimitives tests (#92575)
* Normalize some test naming
* Alphabetize tests
* Improve mistmatched length tests with all positions of the shorter tensor
* Alphabetize methods in TensorPrimitives.cs
* Vectorize TensorPrimitives.Min/Max{Magnitude} (#92618)
* Vectorize TensorPrimitives.Min/Max{Magnitude}
* Use AdvSimd.Max/Min
* Rename some parameters/locals for consistency
* Improve HorizontalAggregate
* Move a few helpers
* Avoid scalar path for returning found NaN
* Update TensorPrimitives aggregations to vectorize handling of remaining elements (#92672)
* Update TensorPrimitives.CosineSimilarity to vectorize handling of remaining elements
* Vectorize remainder handling for Aggregate helpers
* Flesh out TensorPrimitives XML docs (#92749)
* Flesh out TensorPrimitives XML docs
* Address PR feedback
- Remove use of FusedMultiplyAdd from all but CosineSimilarity
- Remove comments about platform/OS-specific behavior from Add/AddMultiply/Subtract/Multiply/MultiplyAdd/Divide/Negate
- Loosen comments about NaN and which exact one is returned
* Address PR feedback
* Vectorize TensorPrimitives.ConvertToHalf (#92715)
* Enable TensorPrimitives to perform in-place operations (#92820)
Some operations would produce incorrect results if the same span was passed as both an input and an output. When vectorization was employed but the span's length wasn't a perfect multiple of a vector, we'd do the standard trick of performing one last operation on the last vector's worth of data; however, that relies on the operation being idempotent, and if a previous operation has overwritten input with a new value due to the same memory being used for input and output, some operations won't be idempotent. This fixes that by masking off the already processed elements. It adds tests to validate in-place use works, and it updates the docs to carve out this valid overlapping.
* Vectorize TensorPrimitives.ConvertToSingle (#92779)
* Vectorize TensorPrimitives.ConvertToSingle
* Address PR feedback
* Throw exception in TensorPrimitives for unsupported span overlaps (#92838)
* This vectorizes TensorPrimitives.Log2 (#92897)
* Add a way to support operations that can't be vectorized on netstandard
* Updating TensorPrimitives.Log2 to be vectorized on .NET Core
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Ensure we do an arithmetic right shift in the Log2 vectorization
* Ensure the code can compile on .NET 7
* Ensure that edge cases are properly handled and don't resolve to `x`
* Ensure that Log2 special results are explicitly handled.
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Adding Log2 tests covering some special values (#92946)
* [wasm] Disable `TensorPrimitivesTests.ConvertToHalf_SpecialValues` (#92953)
Failing test: `System.Numerics.Tensors.Tests.TensorPrimitivesTests.ConvertToHalf_SpecialValues`
Issue: #92885
* Adding a vectorized implementation of TensorPrimitives.Log (#92960)
* Adding a vectorized implementation of TensorPrimitives.Log
* Make sure to hit Ctrl+S
* Consolidate some TensorPrimitivesTests logic around special values (#92982)
* Vectorize TensorPrimitives.Exp (#93018)
* Vectorize TensorPrimitives.Exp
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax (#93029)
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax
- Adds a SigmoidOperator that just wraps the ExpOperator
- Vectorizes both passes of SoftMax, on top of ExpOperator. Simplest way to do this was to augment the existing InvokeSpanScalarIntoSpan to take a transform operator.
- In doing so, found some naming inconsistencies I'd previously introduced, so I did some automatic renaming to make things more consistent.
- Added XML comments to all the internal/private surface area.
- Fleshes out some tests (and test values).
* Disable tests on mono
* Address PR feedback
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh (#93093)
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh
Tanh and Cosh are based on AOCL-LibM.
AOCL-LibM doesn't appear to have a sinh implementation, so this Sinh is just based on the sinh formula based on exp(x).
I also augmented the tests further, including:
- Added more tests for sinh/cosh/tanh
- Add an equality routine that supports comparing larger values with a tolerance
- Tightened the tolerance for most functions
- Changed some tests to be theories to be consistent with style elsewhere in the tests
- Fixed some use of Math to be MathF
* Remove unnecessary special-handling path from cosh
* Remove unnecessary special-handling path from tanh
* Redo sinh based on cosh
* Address PR feedback
* Replace confusing new T[] { ... }
* Remove a few unnecessary `unsafe` keyword uses in TensorPrimitives (#93219)
* Consolidate a few exception throws in TensorPrimitives (#93168)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value (#93169)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value
Found as part of adding more tests for Min/Max{Magnitude} to validate they match their IndexOfXx variants.
* Address PR feedback
* Improve a vector implementation to support alignment and non-temporal tores (#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
* Mark TensorPrimitives as unsafe (#93412)
* Use the improved vectorization algorithm for binary and ternary TensorPrimitives operations (#93409)
* Update InvokeSpanSpanIntoSpan<TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarIntoSpan<TTransformOperator, TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanScalarIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Improve codegen slightly by using case 0, rather than default
* Adjust the canAlign check to be latter, to reduce branch count for data under the threshold
* Add a comment explaining the NonTemporalByteThreshold
* Make sure xTransformOp.CanVectorize is checked on .NET Standard
* Use the improved vectorization algorithm for aggregate TensorPrimitives operations (#93695)
* Improve the handling of the IAggregationOperator implementations
* Update Aggregate<TTransformOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Update Aggregate<TBinaryOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Respond to PR feedback
* [wasm] Remove more active issues for #92885 (#93596)
* adding patch from pr 93556
* Vectorizes IndexOfMin/Max/Magnitude (#93469)
* resolved merge conflicts
* net core full done
* minor code cleanup
* NetStandard and PR fixes.
* minor pr changes
* Fix IndexOfMaxMagnitudeOperator
* Fix IndexOfMaxMagnitudeOperator on netcore
* updates from PR comments
* netcore fixed
* net standard updated
* add reference assembly exclusions
* made naive approach better
* resolved PR comments
* minor comment changes
* minor formatting fixes
* added inlining
* fixes from PR comments
* comments from pr
* fixed spacing
---------
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Tanner Gooding <tagoo@outlook.com>
Co-authored-by: Ankit Jain <radical@gmail.com>
Co-authored-by: Radek Doulik <radek.doulik@gmail.com>
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@EgorBo@lewing@stephentoub@gfoidl
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Improve a vector implementation to support alignment and non-temporal tores - #93296

Merged
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align
Oct 12, 2023
Merged

Improve a vector implementation to support alignment and non-temporal tores#93296
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align

Conversation

@tannergooding

Copy link
Copy Markdown
Member

No description provided.

@ghost

Copy link
Copy Markdown

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

Issue Details

null

Author:tannergooding
Assignees:tannergooding
Labels:

area-System.Numerics

Milestone:-

@EgorBo

Copy link
Copy Markdown
Member

If a function is idempotent (which most of them are) it's enough a single load to align data (like I did in https://github.com/dotnet/runtime/pull/93214/files) without any unrolled loops, etc - presumably, you can expose a bool IsIdempotent {get;} property in these abstraction and only align those for simplicity

@tannergooding
tannergooding marked this pull request as ready for review October 10, 2023 23:21
@tannergooding

tannergooding commented Oct 11, 2023

Copy link
Copy Markdown
MemberAuthor

Here are the before/after results for Abs, where Abs1 is the previous implementation and Abs2 is the implementation from this PR. We are faster across the board (except for 16 elements) and more than 2x faster for large inputs, with the gains really showing past 256 elements (1024 bytes).

MethodTensorLengthMeanErrorStdDev
Abs112.609 ns0.0611 ns0.0572 ns
Abs212.562 ns0.0518 ns0.0485 ns
Abs123.142 ns0.0487 ns0.0456 ns
Abs222.563 ns0.0121 ns0.0101 ns
Abs133.309 ns0.0294 ns0.0229 ns
Abs232.742 ns0.0171 ns0.0133 ns
Abs143.090 ns0.0496 ns0.0464 ns
Abs242.572 ns0.0187 ns0.0166 ns
Abs155.198 ns0.0190 ns0.0148 ns
Abs253.133 ns0.0788 ns0.0774 ns
Abs165.275 ns0.0790 ns0.0739 ns
Abs263.098 ns0.0782 ns0.0732 ns
Abs175.322 ns0.0916 ns0.0857 ns
Abs273.164 ns0.0544 ns0.0509 ns
Abs182.936 ns0.0553 ns0.0517 ns
Abs282.573 ns0.0442 ns0.0414 ns
Abs195.454 ns0.0910 ns0.0852 ns
Abs293.165 ns0.0536 ns0.0501 ns
Abs1105.431 ns0.0675 ns0.0631 ns
Abs2103.230 ns0.0308 ns0.0288 ns
Abs1115.369 ns0.0447 ns0.0418 ns
Abs2113.199 ns0.0443 ns0.0393 ns
Abs1125.571 ns0.0593 ns0.0555 ns
Abs2123.211 ns0.0320 ns0.0299 ns
Abs1135.585 ns0.0491 ns0.0459 ns
Abs2133.262 ns0.0295 ns0.0276 ns
Abs1145.766 ns0.0550 ns0.0514 ns
Abs2143.264 ns0.0292 ns0.0273 ns
Abs1155.865 ns0.0454 ns0.0354 ns
Abs2153.254 ns0.0451 ns0.0377 ns
Abs1162.790 ns0.0207 ns0.0193 ns
Abs2164.984 ns0.0479 ns0.0448 ns
Abs1326.515 ns0.0886 ns0.0785 ns
Abs2326.012 ns0.1203 ns0.1067 ns
Abs16417.126 ns0.1647 ns0.1540 ns
Abs26415.343 ns0.3172 ns0.3115 ns
Abs112838.650 ns0.7464 ns0.6982 ns
Abs212837.150 ns0.4134 ns0.3665 ns
Abs125682.113 ns0.5631 ns0.4702 ns
Abs225651.379 ns0.2897 ns0.2710 ns
Abs1512169.559 ns0.7197 ns0.6732 ns
Abs251278.668 ns0.4391 ns0.4107 ns
Abs11024336.791 ns1.9587 ns1.8321 ns
Abs21024133.500 ns0.7219 ns0.6400 ns
Abs12048698.482 ns1.3343 ns1.1142 ns
Abs22048244.729 ns1.6655 ns1.5579 ns
Abs140961,411.587 ns5.8918 ns5.2229 ns
Abs24096471.249 ns1.2273 ns1.0880 ns
Abs16553622,871.118 ns97.4602 ns91.1643 ns
Abs2655367,306.615 ns32.3811 ns30.2893 ns
Abs113107245,773.050 ns244.2154 ns228.4393 ns
Abs213107218,461.823 ns105.6972 ns93.6978 ns

@tannergooding

Copy link
Copy Markdown
MemberAuthor

Similar results for Exp

MethodTensorLengthMeanErrorStdDev
Exp115.224 ns0.1122 ns0.1050 ns
Exp214.863 ns0.0823 ns0.0770 ns
Exp127.359 ns0.1578 ns0.1689 ns
Exp226.872 ns0.0975 ns0.0912 ns
Exp139.315 ns0.1091 ns0.0852 ns
Exp239.018 ns0.1643 ns0.1537 ns
Exp144.820 ns0.1094 ns0.1075 ns
Exp244.863 ns0.1028 ns0.0962 ns
Exp159.103 ns0.0672 ns0.0629 ns
Exp256.563 ns0.1169 ns0.1036 ns
Exp169.280 ns0.2022 ns0.2076 ns
Exp266.685 ns0.1498 ns0.1538 ns
Exp179.111 ns0.0848 ns0.0793 ns
Exp276.660 ns0.1413 ns0.1321 ns
Exp185.006 ns0.0963 ns0.0901 ns
Exp284.848 ns0.1006 ns0.0941 ns
Exp1910.272 ns0.1189 ns0.1112 ns
Exp297.593 ns0.0876 ns0.0820 ns
Exp11010.188 ns0.0713 ns0.0596 ns
Exp2107.581 ns0.0935 ns0.0875 ns
Exp11110.274 ns0.1070 ns0.0949 ns
Exp2117.631 ns0.1412 ns0.1320 ns
Exp11210.246 ns0.0755 ns0.0670 ns
Exp2127.555 ns0.0742 ns0.0658 ns
Exp11310.270 ns0.1266 ns0.1184 ns
Exp2137.651 ns0.0916 ns0.0857 ns
Exp11410.252 ns0.1509 ns0.1411 ns
Exp2147.585 ns0.1017 ns0.0951 ns
Exp11510.428 ns0.0935 ns0.0875 ns
Exp2157.795 ns0.0801 ns0.0749 ns
Exp1167.271 ns0.0845 ns0.0749 ns
Exp21619.323 ns0.3291 ns0.3078 ns
Exp13218.716 ns0.2705 ns0.2530 ns
Exp23225.631 ns0.3860 ns0.3610 ns
Exp16448.860 ns0.4013 ns0.3753 ns
Exp26450.285 ns0.9766 ns0.9135 ns
Exp1128109.969 ns1.6557 ns1.5487 ns
Exp2128110.952 ns1.4802 ns1.3846 ns
Exp1256230.547 ns2.4931 ns2.0819 ns
Exp2256193.106 ns0.8509 ns0.7105 ns
Exp1512477.460 ns5.6110 ns5.2486 ns
Exp2512317.518 ns5.5631 ns5.2037 ns
Exp11024977.141 ns13.0740 ns12.2295 ns
Exp21024573.851 ns7.7362 ns7.2364 ns
Exp120481,931.447 ns12.4202 ns10.3714 ns
Exp220481,017.003 ns19.3625 ns20.7177 ns
Exp140963,892.225 ns27.1610 ns22.6807 ns
Exp240961,975.208 ns26.4692 ns23.4642 ns
Exp16553661,938.616 ns354.4827 ns296.0091 ns
Exp26553632,390.331 ns590.1723 ns552.0475 ns
Exp1131072125,798.377 ns2,260.7971 ns2,114.7510 ns
Exp213107261,126.610 ns1,091.3727 ns1,020.8707 ns

{
public static partial class TensorPrimitives
{
private const nuint NonTemporalByteThreshold = 256 * 1024;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a comment about what this is and how the value was chosen?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Suggested change
privateconstnuintNonTemporalByteThreshold=256*1024;
/// <summary>Defines the threshold, in bytes, at which non-temporal stores will be used.</summary>
/// <remarks>
/// A non-temporal store is one that allows the CPU to bypass the cache when writing to memory.
///
/// This can be beneficial when working with large amounts of memory where the writes would otherwise
/// cause large amounts of repeated updates and evictions. The hardware optimization manuals recommend
/// the threshold to be roughly half the size of the last level of on-die cache -- that is, if you have approximately
/// 4MB of L3 cache per core, you'd want this to be approx. 1-2MB, depending on if hyperthreading was enabled.
///
/// However, actually computing the amount of L3 cache per core can be tricky or error prone. Native memcpy
/// algorithms use a constant threshold that is typically around 256KB and we match that here for simplicity. This
/// threshold accounts for most processors in the last 10-15 years that had approx. 1MB L3 per core and support
/// hyperthreading, giving a per core last level cache of approx. 512KB.
/// </remarks>
privateconstnuintNonTemporalByteThreshold=256*1024;

Comment on lines +1331 to +1354
if (canAlign)
{
// Compute by how many elements we're misaligned and adjust the pointers accordingly
//
// Noting that we are only actually aligning dPtr. THis is because unaligned stores
// are more expensive than unaligned loads and aligning both is significantly more
// complex.

nuint misalignment = ((uint)(sizeof(Vector128<float>)) - ((nuint)(dPtr) % (uint)(sizeof(Vector128<float>)))) / sizeof(float);

xPtr += misalignment;
dPtr += misalignment;

Debug.Assert(((nuint)(dPtr) % (uint)(sizeof(Vector128<float>))) == 0);

remainder -= misalignment;
}

Vector128<float> vector1;
Vector128<float> vector2;
Vector128<float> vector3;
Vector128<float> vector4;

if (canAlign && (remainder > (NonTemporalByteThreshold / sizeof(float))))

@stephentoubstephentoubOct 12, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have two checks on canAlign here. Would it help to eliminate a branch if this were instead structured as:

if(canAlign){
...// do alignmentif(remainder>(NonTemporalByteThreshold/sizeof(float))){
...// handle non-temporal pathgoto AdjustingRefs;}}
...// what's currently in the else blockAdjustingRefs:
...// stuff currently after the else block

?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't think the additional complexity is worth it here and it can cause subtle issues with control flow analysis that are probably undesirable.

If we were concerned, I'd prefer switching the order so its if ((remainder > (NonTemporalByteThreshold / sizeof(float)) && canAlign) instead.


public static IEnumerable<object[]> TensorLengths =>
from length in Enumerable.Range(1, 128)
from length in Enumerable.Range(1, 256)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is good, but it's also going to double the number of test cases we're running. Not a big deal except that on netfx it seems to struggle with the theory count. We should probably subsequently change some of those theories to be loops, e.g. instead of:

[Theory][MemberData(nameof(TensorLengths))]publicvoidFoo(inttensorLength){
...}

do:

[Fact]publicvoidFoo(){foreach(inttensorLengthinTensorLengths){
...}}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll handle this in the immediately following PR covering binary/ternary.

@lewing

Copy link
Copy Markdown
Member

This appears to have broken the outerloop tests

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@lewing is there an issue or a link that can be shared so it can be investigated?

@lewing

lewing commented Oct 12, 2023

Copy link
Copy Markdown
Member

@tannergooding

Copy link
Copy Markdown
MemberAuthor

#93412 fixes the issue.

Another PR went in that removed unsafe and it didn't conflict, so the nothing caught the issue.

michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request Oct 20, 2023
… tores (dotnet#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
carlossanlop pushed a commit that referenced this pull request Oct 20, 2023
* Use FMA in TensorPrimitives (#92205)
* Simplify TensorPrimitive's AbsoluteOperator (#92577)
Vector{128/256/512} all provide Abs; no need to do this manually.
* Reduce some boilerplate in TensorPrimitive's IBinaryOperator (#92576)
Change a few of the static abstract interface methods to be virtual, as most implementations throw from these methods; we can consolidate that throwing to the base.
* Minor code cleanup in TensorPrimitives tests (#92575)
* Normalize some test naming
* Alphabetize tests
* Improve mistmatched length tests with all positions of the shorter tensor
* Alphabetize methods in TensorPrimitives.cs
* Vectorize TensorPrimitives.Min/Max{Magnitude} (#92618)
* Vectorize TensorPrimitives.Min/Max{Magnitude}
* Use AdvSimd.Max/Min
* Rename some parameters/locals for consistency
* Improve HorizontalAggregate
* Move a few helpers
* Avoid scalar path for returning found NaN
* Update TensorPrimitives aggregations to vectorize handling of remaining elements (#92672)
* Update TensorPrimitives.CosineSimilarity to vectorize handling of remaining elements
* Vectorize remainder handling for Aggregate helpers
* Flesh out TensorPrimitives XML docs (#92749)
* Flesh out TensorPrimitives XML docs
* Address PR feedback
- Remove use of FusedMultiplyAdd from all but CosineSimilarity
- Remove comments about platform/OS-specific behavior from Add/AddMultiply/Subtract/Multiply/MultiplyAdd/Divide/Negate
- Loosen comments about NaN and which exact one is returned
* Address PR feedback
* Vectorize TensorPrimitives.ConvertToHalf (#92715)
* Enable TensorPrimitives to perform in-place operations (#92820)
Some operations would produce incorrect results if the same span was passed as both an input and an output. When vectorization was employed but the span's length wasn't a perfect multiple of a vector, we'd do the standard trick of performing one last operation on the last vector's worth of data; however, that relies on the operation being idempotent, and if a previous operation has overwritten input with a new value due to the same memory being used for input and output, some operations won't be idempotent. This fixes that by masking off the already processed elements. It adds tests to validate in-place use works, and it updates the docs to carve out this valid overlapping.
* Vectorize TensorPrimitives.ConvertToSingle (#92779)
* Vectorize TensorPrimitives.ConvertToSingle
* Address PR feedback
* Throw exception in TensorPrimitives for unsupported span overlaps (#92838)
* This vectorizes TensorPrimitives.Log2 (#92897)
* Add a way to support operations that can't be vectorized on netstandard
* Updating TensorPrimitives.Log2 to be vectorized on .NET Core
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Ensure we do an arithmetic right shift in the Log2 vectorization
* Ensure the code can compile on .NET 7
* Ensure that edge cases are properly handled and don't resolve to `x`
* Ensure that Log2 special results are explicitly handled.
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Adding Log2 tests covering some special values (#92946)
* [wasm] Disable `TensorPrimitivesTests.ConvertToHalf_SpecialValues` (#92953)
Failing test: `System.Numerics.Tensors.Tests.TensorPrimitivesTests.ConvertToHalf_SpecialValues`
Issue: #92885
* Adding a vectorized implementation of TensorPrimitives.Log (#92960)
* Adding a vectorized implementation of TensorPrimitives.Log
* Make sure to hit Ctrl+S
* Consolidate some TensorPrimitivesTests logic around special values (#92982)
* Vectorize TensorPrimitives.Exp (#93018)
* Vectorize TensorPrimitives.Exp
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax (#93029)
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax
- Adds a SigmoidOperator that just wraps the ExpOperator
- Vectorizes both passes of SoftMax, on top of ExpOperator. Simplest way to do this was to augment the existing InvokeSpanScalarIntoSpan to take a transform operator.
- In doing so, found some naming inconsistencies I'd previously introduced, so I did some automatic renaming to make things more consistent.
- Added XML comments to all the internal/private surface area.
- Fleshes out some tests (and test values).
* Disable tests on mono
* Address PR feedback
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh (#93093)
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh
Tanh and Cosh are based on AOCL-LibM.
AOCL-LibM doesn't appear to have a sinh implementation, so this Sinh is just based on the sinh formula based on exp(x).
I also augmented the tests further, including:
- Added more tests for sinh/cosh/tanh
- Add an equality routine that supports comparing larger values with a tolerance
- Tightened the tolerance for most functions
- Changed some tests to be theories to be consistent with style elsewhere in the tests
- Fixed some use of Math to be MathF
* Remove unnecessary special-handling path from cosh
* Remove unnecessary special-handling path from tanh
* Redo sinh based on cosh
* Address PR feedback
* Replace confusing new T[] { ... }
* Remove a few unnecessary `unsafe` keyword uses in TensorPrimitives (#93219)
* Consolidate a few exception throws in TensorPrimitives (#93168)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value (#93169)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value
Found as part of adding more tests for Min/Max{Magnitude} to validate they match their IndexOfXx variants.
* Address PR feedback
* Improve a vector implementation to support alignment and non-temporal tores (#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
* Mark TensorPrimitives as unsafe (#93412)
* Use the improved vectorization algorithm for binary and ternary TensorPrimitives operations (#93409)
* Update InvokeSpanSpanIntoSpan<TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarIntoSpan<TTransformOperator, TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanScalarIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Improve codegen slightly by using case 0, rather than default
* Adjust the canAlign check to be latter, to reduce branch count for data under the threshold
* Add a comment explaining the NonTemporalByteThreshold
* Make sure xTransformOp.CanVectorize is checked on .NET Standard
* Use the improved vectorization algorithm for aggregate TensorPrimitives operations (#93695)
* Improve the handling of the IAggregationOperator implementations
* Update Aggregate<TTransformOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Update Aggregate<TBinaryOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Respond to PR feedback
* [wasm] Remove more active issues for #92885 (#93596)
* adding patch from pr 93556
* Vectorizes IndexOfMin/Max/Magnitude (#93469)
* resolved merge conflicts
* net core full done
* minor code cleanup
* NetStandard and PR fixes.
* minor pr changes
* Fix IndexOfMaxMagnitudeOperator
* Fix IndexOfMaxMagnitudeOperator on netcore
* updates from PR comments
* netcore fixed
* net standard updated
* add reference assembly exclusions
* made naive approach better
* resolved PR comments
* minor comment changes
* minor formatting fixes
* added inlining
* fixes from PR comments
* comments from pr
* fixed spacing
---------
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Tanner Gooding <tagoo@outlook.com>
Co-authored-by: Ankit Jain <radical@gmail.com>
Co-authored-by: Radek Doulik <radek.doulik@gmail.com>
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@EgorBo@lewing@stephentoub@gfoidl
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Improve a vector implementation to support alignment and non-temporal tores - #93296

Merged
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align
Oct 12, 2023
Merged

Improve a vector implementation to support alignment and non-temporal tores#93296
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align

Conversation

@tannergooding

Copy link
Copy Markdown
Member

No description provided.

@ghost

Copy link
Copy Markdown

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

Issue Details

null

Author:tannergooding
Assignees:tannergooding
Labels:

area-System.Numerics

Milestone:-

@EgorBo

Copy link
Copy Markdown
Member

If a function is idempotent (which most of them are) it's enough a single load to align data (like I did in https://github.com/dotnet/runtime/pull/93214/files) without any unrolled loops, etc - presumably, you can expose a bool IsIdempotent {get;} property in these abstraction and only align those for simplicity

@tannergooding
tannergooding marked this pull request as ready for review October 10, 2023 23:21
@tannergooding

tannergooding commented Oct 11, 2023

Copy link
Copy Markdown
MemberAuthor

Here are the before/after results for Abs, where Abs1 is the previous implementation and Abs2 is the implementation from this PR. We are faster across the board (except for 16 elements) and more than 2x faster for large inputs, with the gains really showing past 256 elements (1024 bytes).

MethodTensorLengthMeanErrorStdDev
Abs112.609 ns0.0611 ns0.0572 ns
Abs212.562 ns0.0518 ns0.0485 ns
Abs123.142 ns0.0487 ns0.0456 ns
Abs222.563 ns0.0121 ns0.0101 ns
Abs133.309 ns0.0294 ns0.0229 ns
Abs232.742 ns0.0171 ns0.0133 ns
Abs143.090 ns0.0496 ns0.0464 ns
Abs242.572 ns0.0187 ns0.0166 ns
Abs155.198 ns0.0190 ns0.0148 ns
Abs253.133 ns0.0788 ns0.0774 ns
Abs165.275 ns0.0790 ns0.0739 ns
Abs263.098 ns0.0782 ns0.0732 ns
Abs175.322 ns0.0916 ns0.0857 ns
Abs273.164 ns0.0544 ns0.0509 ns
Abs182.936 ns0.0553 ns0.0517 ns
Abs282.573 ns0.0442 ns0.0414 ns
Abs195.454 ns0.0910 ns0.0852 ns
Abs293.165 ns0.0536 ns0.0501 ns
Abs1105.431 ns0.0675 ns0.0631 ns
Abs2103.230 ns0.0308 ns0.0288 ns
Abs1115.369 ns0.0447 ns0.0418 ns
Abs2113.199 ns0.0443 ns0.0393 ns
Abs1125.571 ns0.0593 ns0.0555 ns
Abs2123.211 ns0.0320 ns0.0299 ns
Abs1135.585 ns0.0491 ns0.0459 ns
Abs2133.262 ns0.0295 ns0.0276 ns
Abs1145.766 ns0.0550 ns0.0514 ns
Abs2143.264 ns0.0292 ns0.0273 ns
Abs1155.865 ns0.0454 ns0.0354 ns
Abs2153.254 ns0.0451 ns0.0377 ns
Abs1162.790 ns0.0207 ns0.0193 ns
Abs2164.984 ns0.0479 ns0.0448 ns
Abs1326.515 ns0.0886 ns0.0785 ns
Abs2326.012 ns0.1203 ns0.1067 ns
Abs16417.126 ns0.1647 ns0.1540 ns
Abs26415.343 ns0.3172 ns0.3115 ns
Abs112838.650 ns0.7464 ns0.6982 ns
Abs212837.150 ns0.4134 ns0.3665 ns
Abs125682.113 ns0.5631 ns0.4702 ns
Abs225651.379 ns0.2897 ns0.2710 ns
Abs1512169.559 ns0.7197 ns0.6732 ns
Abs251278.668 ns0.4391 ns0.4107 ns
Abs11024336.791 ns1.9587 ns1.8321 ns
Abs21024133.500 ns0.7219 ns0.6400 ns
Abs12048698.482 ns1.3343 ns1.1142 ns
Abs22048244.729 ns1.6655 ns1.5579 ns
Abs140961,411.587 ns5.8918 ns5.2229 ns
Abs24096471.249 ns1.2273 ns1.0880 ns
Abs16553622,871.118 ns97.4602 ns91.1643 ns
Abs2655367,306.615 ns32.3811 ns30.2893 ns
Abs113107245,773.050 ns244.2154 ns228.4393 ns
Abs213107218,461.823 ns105.6972 ns93.6978 ns

@tannergooding

Copy link
Copy Markdown
MemberAuthor

Similar results for Exp

MethodTensorLengthMeanErrorStdDev
Exp115.224 ns0.1122 ns0.1050 ns
Exp214.863 ns0.0823 ns0.0770 ns
Exp127.359 ns0.1578 ns0.1689 ns
Exp226.872 ns0.0975 ns0.0912 ns
Exp139.315 ns0.1091 ns0.0852 ns
Exp239.018 ns0.1643 ns0.1537 ns
Exp144.820 ns0.1094 ns0.1075 ns
Exp244.863 ns0.1028 ns0.0962 ns
Exp159.103 ns0.0672 ns0.0629 ns
Exp256.563 ns0.1169 ns0.1036 ns
Exp169.280 ns0.2022 ns0.2076 ns
Exp266.685 ns0.1498 ns0.1538 ns
Exp179.111 ns0.0848 ns0.0793 ns
Exp276.660 ns0.1413 ns0.1321 ns
Exp185.006 ns0.0963 ns0.0901 ns
Exp284.848 ns0.1006 ns0.0941 ns
Exp1910.272 ns0.1189 ns0.1112 ns
Exp297.593 ns0.0876 ns0.0820 ns
Exp11010.188 ns0.0713 ns0.0596 ns
Exp2107.581 ns0.0935 ns0.0875 ns
Exp11110.274 ns0.1070 ns0.0949 ns
Exp2117.631 ns0.1412 ns0.1320 ns
Exp11210.246 ns0.0755 ns0.0670 ns
Exp2127.555 ns0.0742 ns0.0658 ns
Exp11310.270 ns0.1266 ns0.1184 ns
Exp2137.651 ns0.0916 ns0.0857 ns
Exp11410.252 ns0.1509 ns0.1411 ns
Exp2147.585 ns0.1017 ns0.0951 ns
Exp11510.428 ns0.0935 ns0.0875 ns
Exp2157.795 ns0.0801 ns0.0749 ns
Exp1167.271 ns0.0845 ns0.0749 ns
Exp21619.323 ns0.3291 ns0.3078 ns
Exp13218.716 ns0.2705 ns0.2530 ns
Exp23225.631 ns0.3860 ns0.3610 ns
Exp16448.860 ns0.4013 ns0.3753 ns
Exp26450.285 ns0.9766 ns0.9135 ns
Exp1128109.969 ns1.6557 ns1.5487 ns
Exp2128110.952 ns1.4802 ns1.3846 ns
Exp1256230.547 ns2.4931 ns2.0819 ns
Exp2256193.106 ns0.8509 ns0.7105 ns
Exp1512477.460 ns5.6110 ns5.2486 ns
Exp2512317.518 ns5.5631 ns5.2037 ns
Exp11024977.141 ns13.0740 ns12.2295 ns
Exp21024573.851 ns7.7362 ns7.2364 ns
Exp120481,931.447 ns12.4202 ns10.3714 ns
Exp220481,017.003 ns19.3625 ns20.7177 ns
Exp140963,892.225 ns27.1610 ns22.6807 ns
Exp240961,975.208 ns26.4692 ns23.4642 ns
Exp16553661,938.616 ns354.4827 ns296.0091 ns
Exp26553632,390.331 ns590.1723 ns552.0475 ns
Exp1131072125,798.377 ns2,260.7971 ns2,114.7510 ns
Exp213107261,126.610 ns1,091.3727 ns1,020.8707 ns

{
public static partial class TensorPrimitives
{
private const nuint NonTemporalByteThreshold = 256 * 1024;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a comment about what this is and how the value was chosen?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Suggested change
privateconstnuintNonTemporalByteThreshold=256*1024;
/// <summary>Defines the threshold, in bytes, at which non-temporal stores will be used.</summary>
/// <remarks>
/// A non-temporal store is one that allows the CPU to bypass the cache when writing to memory.
///
/// This can be beneficial when working with large amounts of memory where the writes would otherwise
/// cause large amounts of repeated updates and evictions. The hardware optimization manuals recommend
/// the threshold to be roughly half the size of the last level of on-die cache -- that is, if you have approximately
/// 4MB of L3 cache per core, you'd want this to be approx. 1-2MB, depending on if hyperthreading was enabled.
///
/// However, actually computing the amount of L3 cache per core can be tricky or error prone. Native memcpy
/// algorithms use a constant threshold that is typically around 256KB and we match that here for simplicity. This
/// threshold accounts for most processors in the last 10-15 years that had approx. 1MB L3 per core and support
/// hyperthreading, giving a per core last level cache of approx. 512KB.
/// </remarks>
privateconstnuintNonTemporalByteThreshold=256*1024;

Comment on lines +1331 to +1354
if (canAlign)
{
// Compute by how many elements we're misaligned and adjust the pointers accordingly
//
// Noting that we are only actually aligning dPtr. THis is because unaligned stores
// are more expensive than unaligned loads and aligning both is significantly more
// complex.

nuint misalignment = ((uint)(sizeof(Vector128<float>)) - ((nuint)(dPtr) % (uint)(sizeof(Vector128<float>)))) / sizeof(float);

xPtr += misalignment;
dPtr += misalignment;

Debug.Assert(((nuint)(dPtr) % (uint)(sizeof(Vector128<float>))) == 0);

remainder -= misalignment;
}

Vector128<float> vector1;
Vector128<float> vector2;
Vector128<float> vector3;
Vector128<float> vector4;

if (canAlign && (remainder > (NonTemporalByteThreshold / sizeof(float))))

@stephentoubstephentoubOct 12, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have two checks on canAlign here. Would it help to eliminate a branch if this were instead structured as:

if(canAlign){
...// do alignmentif(remainder>(NonTemporalByteThreshold/sizeof(float))){
...// handle non-temporal pathgoto AdjustingRefs;}}
...// what's currently in the else blockAdjustingRefs:
...// stuff currently after the else block

?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't think the additional complexity is worth it here and it can cause subtle issues with control flow analysis that are probably undesirable.

If we were concerned, I'd prefer switching the order so its if ((remainder > (NonTemporalByteThreshold / sizeof(float)) && canAlign) instead.


public static IEnumerable<object[]> TensorLengths =>
from length in Enumerable.Range(1, 128)
from length in Enumerable.Range(1, 256)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is good, but it's also going to double the number of test cases we're running. Not a big deal except that on netfx it seems to struggle with the theory count. We should probably subsequently change some of those theories to be loops, e.g. instead of:

[Theory][MemberData(nameof(TensorLengths))]publicvoidFoo(inttensorLength){
...}

do:

[Fact]publicvoidFoo(){foreach(inttensorLengthinTensorLengths){
...}}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll handle this in the immediately following PR covering binary/ternary.

@lewing

Copy link
Copy Markdown
Member

This appears to have broken the outerloop tests

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@lewing is there an issue or a link that can be shared so it can be investigated?

@lewing

lewing commented Oct 12, 2023

Copy link
Copy Markdown
Member

@tannergooding

Copy link
Copy Markdown
MemberAuthor

#93412 fixes the issue.

Another PR went in that removed unsafe and it didn't conflict, so the nothing caught the issue.

michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request Oct 20, 2023
… tores (dotnet#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
carlossanlop pushed a commit that referenced this pull request Oct 20, 2023
* Use FMA in TensorPrimitives (#92205)
* Simplify TensorPrimitive's AbsoluteOperator (#92577)
Vector{128/256/512} all provide Abs; no need to do this manually.
* Reduce some boilerplate in TensorPrimitive's IBinaryOperator (#92576)
Change a few of the static abstract interface methods to be virtual, as most implementations throw from these methods; we can consolidate that throwing to the base.
* Minor code cleanup in TensorPrimitives tests (#92575)
* Normalize some test naming
* Alphabetize tests
* Improve mistmatched length tests with all positions of the shorter tensor
* Alphabetize methods in TensorPrimitives.cs
* Vectorize TensorPrimitives.Min/Max{Magnitude} (#92618)
* Vectorize TensorPrimitives.Min/Max{Magnitude}
* Use AdvSimd.Max/Min
* Rename some parameters/locals for consistency
* Improve HorizontalAggregate
* Move a few helpers
* Avoid scalar path for returning found NaN
* Update TensorPrimitives aggregations to vectorize handling of remaining elements (#92672)
* Update TensorPrimitives.CosineSimilarity to vectorize handling of remaining elements
* Vectorize remainder handling for Aggregate helpers
* Flesh out TensorPrimitives XML docs (#92749)
* Flesh out TensorPrimitives XML docs
* Address PR feedback
- Remove use of FusedMultiplyAdd from all but CosineSimilarity
- Remove comments about platform/OS-specific behavior from Add/AddMultiply/Subtract/Multiply/MultiplyAdd/Divide/Negate
- Loosen comments about NaN and which exact one is returned
* Address PR feedback
* Vectorize TensorPrimitives.ConvertToHalf (#92715)
* Enable TensorPrimitives to perform in-place operations (#92820)
Some operations would produce incorrect results if the same span was passed as both an input and an output. When vectorization was employed but the span's length wasn't a perfect multiple of a vector, we'd do the standard trick of performing one last operation on the last vector's worth of data; however, that relies on the operation being idempotent, and if a previous operation has overwritten input with a new value due to the same memory being used for input and output, some operations won't be idempotent. This fixes that by masking off the already processed elements. It adds tests to validate in-place use works, and it updates the docs to carve out this valid overlapping.
* Vectorize TensorPrimitives.ConvertToSingle (#92779)
* Vectorize TensorPrimitives.ConvertToSingle
* Address PR feedback
* Throw exception in TensorPrimitives for unsupported span overlaps (#92838)
* This vectorizes TensorPrimitives.Log2 (#92897)
* Add a way to support operations that can't be vectorized on netstandard
* Updating TensorPrimitives.Log2 to be vectorized on .NET Core
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Ensure we do an arithmetic right shift in the Log2 vectorization
* Ensure the code can compile on .NET 7
* Ensure that edge cases are properly handled and don't resolve to `x`
* Ensure that Log2 special results are explicitly handled.
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Adding Log2 tests covering some special values (#92946)
* [wasm] Disable `TensorPrimitivesTests.ConvertToHalf_SpecialValues` (#92953)
Failing test: `System.Numerics.Tensors.Tests.TensorPrimitivesTests.ConvertToHalf_SpecialValues`
Issue: #92885
* Adding a vectorized implementation of TensorPrimitives.Log (#92960)
* Adding a vectorized implementation of TensorPrimitives.Log
* Make sure to hit Ctrl+S
* Consolidate some TensorPrimitivesTests logic around special values (#92982)
* Vectorize TensorPrimitives.Exp (#93018)
* Vectorize TensorPrimitives.Exp
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax (#93029)
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax
- Adds a SigmoidOperator that just wraps the ExpOperator
- Vectorizes both passes of SoftMax, on top of ExpOperator. Simplest way to do this was to augment the existing InvokeSpanScalarIntoSpan to take a transform operator.
- In doing so, found some naming inconsistencies I'd previously introduced, so I did some automatic renaming to make things more consistent.
- Added XML comments to all the internal/private surface area.
- Fleshes out some tests (and test values).
* Disable tests on mono
* Address PR feedback
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh (#93093)
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh
Tanh and Cosh are based on AOCL-LibM.
AOCL-LibM doesn't appear to have a sinh implementation, so this Sinh is just based on the sinh formula based on exp(x).
I also augmented the tests further, including:
- Added more tests for sinh/cosh/tanh
- Add an equality routine that supports comparing larger values with a tolerance
- Tightened the tolerance for most functions
- Changed some tests to be theories to be consistent with style elsewhere in the tests
- Fixed some use of Math to be MathF
* Remove unnecessary special-handling path from cosh
* Remove unnecessary special-handling path from tanh
* Redo sinh based on cosh
* Address PR feedback
* Replace confusing new T[] { ... }
* Remove a few unnecessary `unsafe` keyword uses in TensorPrimitives (#93219)
* Consolidate a few exception throws in TensorPrimitives (#93168)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value (#93169)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value
Found as part of adding more tests for Min/Max{Magnitude} to validate they match their IndexOfXx variants.
* Address PR feedback
* Improve a vector implementation to support alignment and non-temporal tores (#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
* Mark TensorPrimitives as unsafe (#93412)
* Use the improved vectorization algorithm for binary and ternary TensorPrimitives operations (#93409)
* Update InvokeSpanSpanIntoSpan<TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarIntoSpan<TTransformOperator, TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanScalarIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Improve codegen slightly by using case 0, rather than default
* Adjust the canAlign check to be latter, to reduce branch count for data under the threshold
* Add a comment explaining the NonTemporalByteThreshold
* Make sure xTransformOp.CanVectorize is checked on .NET Standard
* Use the improved vectorization algorithm for aggregate TensorPrimitives operations (#93695)
* Improve the handling of the IAggregationOperator implementations
* Update Aggregate<TTransformOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Update Aggregate<TBinaryOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Respond to PR feedback
* [wasm] Remove more active issues for #92885 (#93596)
* adding patch from pr 93556
* Vectorizes IndexOfMin/Max/Magnitude (#93469)
* resolved merge conflicts
* net core full done
* minor code cleanup
* NetStandard and PR fixes.
* minor pr changes
* Fix IndexOfMaxMagnitudeOperator
* Fix IndexOfMaxMagnitudeOperator on netcore
* updates from PR comments
* netcore fixed
* net standard updated
* add reference assembly exclusions
* made naive approach better
* resolved PR comments
* minor comment changes
* minor formatting fixes
* added inlining
* fixes from PR comments
* comments from pr
* fixed spacing
---------
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Tanner Gooding <tagoo@outlook.com>
Co-authored-by: Ankit Jain <radical@gmail.com>
Co-authored-by: Radek Doulik <radek.doulik@gmail.com>
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@EgorBo@lewing@stephentoub@gfoidl
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Improve a vector implementation to support alignment and non-temporal tores - #93296

Merged
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align
Oct 12, 2023
Merged

Improve a vector implementation to support alignment and non-temporal tores#93296
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align

Conversation

@tannergooding

Copy link
Copy Markdown
Member

No description provided.

@ghost

Copy link
Copy Markdown

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

Issue Details

null

Author:tannergooding
Assignees:tannergooding
Labels:

area-System.Numerics

Milestone:-

@EgorBo

Copy link
Copy Markdown
Member

If a function is idempotent (which most of them are) it's enough a single load to align data (like I did in https://github.com/dotnet/runtime/pull/93214/files) without any unrolled loops, etc - presumably, you can expose a bool IsIdempotent {get;} property in these abstraction and only align those for simplicity

@tannergooding
tannergooding marked this pull request as ready for review October 10, 2023 23:21
@tannergooding

tannergooding commented Oct 11, 2023

Copy link
Copy Markdown
MemberAuthor

Here are the before/after results for Abs, where Abs1 is the previous implementation and Abs2 is the implementation from this PR. We are faster across the board (except for 16 elements) and more than 2x faster for large inputs, with the gains really showing past 256 elements (1024 bytes).

MethodTensorLengthMeanErrorStdDev
Abs112.609 ns0.0611 ns0.0572 ns
Abs212.562 ns0.0518 ns0.0485 ns
Abs123.142 ns0.0487 ns0.0456 ns
Abs222.563 ns0.0121 ns0.0101 ns
Abs133.309 ns0.0294 ns0.0229 ns
Abs232.742 ns0.0171 ns0.0133 ns
Abs143.090 ns0.0496 ns0.0464 ns
Abs242.572 ns0.0187 ns0.0166 ns
Abs155.198 ns0.0190 ns0.0148 ns
Abs253.133 ns0.0788 ns0.0774 ns
Abs165.275 ns0.0790 ns0.0739 ns
Abs263.098 ns0.0782 ns0.0732 ns
Abs175.322 ns0.0916 ns0.0857 ns
Abs273.164 ns0.0544 ns0.0509 ns
Abs182.936 ns0.0553 ns0.0517 ns
Abs282.573 ns0.0442 ns0.0414 ns
Abs195.454 ns0.0910 ns0.0852 ns
Abs293.165 ns0.0536 ns0.0501 ns
Abs1105.431 ns0.0675 ns0.0631 ns
Abs2103.230 ns0.0308 ns0.0288 ns
Abs1115.369 ns0.0447 ns0.0418 ns
Abs2113.199 ns0.0443 ns0.0393 ns
Abs1125.571 ns0.0593 ns0.0555 ns
Abs2123.211 ns0.0320 ns0.0299 ns
Abs1135.585 ns0.0491 ns0.0459 ns
Abs2133.262 ns0.0295 ns0.0276 ns
Abs1145.766 ns0.0550 ns0.0514 ns
Abs2143.264 ns0.0292 ns0.0273 ns
Abs1155.865 ns0.0454 ns0.0354 ns
Abs2153.254 ns0.0451 ns0.0377 ns
Abs1162.790 ns0.0207 ns0.0193 ns
Abs2164.984 ns0.0479 ns0.0448 ns
Abs1326.515 ns0.0886 ns0.0785 ns
Abs2326.012 ns0.1203 ns0.1067 ns
Abs16417.126 ns0.1647 ns0.1540 ns
Abs26415.343 ns0.3172 ns0.3115 ns
Abs112838.650 ns0.7464 ns0.6982 ns
Abs212837.150 ns0.4134 ns0.3665 ns
Abs125682.113 ns0.5631 ns0.4702 ns
Abs225651.379 ns0.2897 ns0.2710 ns
Abs1512169.559 ns0.7197 ns0.6732 ns
Abs251278.668 ns0.4391 ns0.4107 ns
Abs11024336.791 ns1.9587 ns1.8321 ns
Abs21024133.500 ns0.7219 ns0.6400 ns
Abs12048698.482 ns1.3343 ns1.1142 ns
Abs22048244.729 ns1.6655 ns1.5579 ns
Abs140961,411.587 ns5.8918 ns5.2229 ns
Abs24096471.249 ns1.2273 ns1.0880 ns
Abs16553622,871.118 ns97.4602 ns91.1643 ns
Abs2655367,306.615 ns32.3811 ns30.2893 ns
Abs113107245,773.050 ns244.2154 ns228.4393 ns
Abs213107218,461.823 ns105.6972 ns93.6978 ns

@tannergooding

Copy link
Copy Markdown
MemberAuthor

Similar results for Exp

MethodTensorLengthMeanErrorStdDev
Exp115.224 ns0.1122 ns0.1050 ns
Exp214.863 ns0.0823 ns0.0770 ns
Exp127.359 ns0.1578 ns0.1689 ns
Exp226.872 ns0.0975 ns0.0912 ns
Exp139.315 ns0.1091 ns0.0852 ns
Exp239.018 ns0.1643 ns0.1537 ns
Exp144.820 ns0.1094 ns0.1075 ns
Exp244.863 ns0.1028 ns0.0962 ns
Exp159.103 ns0.0672 ns0.0629 ns
Exp256.563 ns0.1169 ns0.1036 ns
Exp169.280 ns0.2022 ns0.2076 ns
Exp266.685 ns0.1498 ns0.1538 ns
Exp179.111 ns0.0848 ns0.0793 ns
Exp276.660 ns0.1413 ns0.1321 ns
Exp185.006 ns0.0963 ns0.0901 ns
Exp284.848 ns0.1006 ns0.0941 ns
Exp1910.272 ns0.1189 ns0.1112 ns
Exp297.593 ns0.0876 ns0.0820 ns
Exp11010.188 ns0.0713 ns0.0596 ns
Exp2107.581 ns0.0935 ns0.0875 ns
Exp11110.274 ns0.1070 ns0.0949 ns
Exp2117.631 ns0.1412 ns0.1320 ns
Exp11210.246 ns0.0755 ns0.0670 ns
Exp2127.555 ns0.0742 ns0.0658 ns
Exp11310.270 ns0.1266 ns0.1184 ns
Exp2137.651 ns0.0916 ns0.0857 ns
Exp11410.252 ns0.1509 ns0.1411 ns
Exp2147.585 ns0.1017 ns0.0951 ns
Exp11510.428 ns0.0935 ns0.0875 ns
Exp2157.795 ns0.0801 ns0.0749 ns
Exp1167.271 ns0.0845 ns0.0749 ns
Exp21619.323 ns0.3291 ns0.3078 ns
Exp13218.716 ns0.2705 ns0.2530 ns
Exp23225.631 ns0.3860 ns0.3610 ns
Exp16448.860 ns0.4013 ns0.3753 ns
Exp26450.285 ns0.9766 ns0.9135 ns
Exp1128109.969 ns1.6557 ns1.5487 ns
Exp2128110.952 ns1.4802 ns1.3846 ns
Exp1256230.547 ns2.4931 ns2.0819 ns
Exp2256193.106 ns0.8509 ns0.7105 ns
Exp1512477.460 ns5.6110 ns5.2486 ns
Exp2512317.518 ns5.5631 ns5.2037 ns
Exp11024977.141 ns13.0740 ns12.2295 ns
Exp21024573.851 ns7.7362 ns7.2364 ns
Exp120481,931.447 ns12.4202 ns10.3714 ns
Exp220481,017.003 ns19.3625 ns20.7177 ns
Exp140963,892.225 ns27.1610 ns22.6807 ns
Exp240961,975.208 ns26.4692 ns23.4642 ns
Exp16553661,938.616 ns354.4827 ns296.0091 ns
Exp26553632,390.331 ns590.1723 ns552.0475 ns
Exp1131072125,798.377 ns2,260.7971 ns2,114.7510 ns
Exp213107261,126.610 ns1,091.3727 ns1,020.8707 ns

{
public static partial class TensorPrimitives
{
private const nuint NonTemporalByteThreshold = 256 * 1024;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a comment about what this is and how the value was chosen?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Suggested change
privateconstnuintNonTemporalByteThreshold=256*1024;
/// <summary>Defines the threshold, in bytes, at which non-temporal stores will be used.</summary>
/// <remarks>
/// A non-temporal store is one that allows the CPU to bypass the cache when writing to memory.
///
/// This can be beneficial when working with large amounts of memory where the writes would otherwise
/// cause large amounts of repeated updates and evictions. The hardware optimization manuals recommend
/// the threshold to be roughly half the size of the last level of on-die cache -- that is, if you have approximately
/// 4MB of L3 cache per core, you'd want this to be approx. 1-2MB, depending on if hyperthreading was enabled.
///
/// However, actually computing the amount of L3 cache per core can be tricky or error prone. Native memcpy
/// algorithms use a constant threshold that is typically around 256KB and we match that here for simplicity. This
/// threshold accounts for most processors in the last 10-15 years that had approx. 1MB L3 per core and support
/// hyperthreading, giving a per core last level cache of approx. 512KB.
/// </remarks>
privateconstnuintNonTemporalByteThreshold=256*1024;

Comment on lines +1331 to +1354
if (canAlign)
{
// Compute by how many elements we're misaligned and adjust the pointers accordingly
//
// Noting that we are only actually aligning dPtr. THis is because unaligned stores
// are more expensive than unaligned loads and aligning both is significantly more
// complex.

nuint misalignment = ((uint)(sizeof(Vector128<float>)) - ((nuint)(dPtr) % (uint)(sizeof(Vector128<float>)))) / sizeof(float);

xPtr += misalignment;
dPtr += misalignment;

Debug.Assert(((nuint)(dPtr) % (uint)(sizeof(Vector128<float>))) == 0);

remainder -= misalignment;
}

Vector128<float> vector1;
Vector128<float> vector2;
Vector128<float> vector3;
Vector128<float> vector4;

if (canAlign && (remainder > (NonTemporalByteThreshold / sizeof(float))))

@stephentoubstephentoubOct 12, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have two checks on canAlign here. Would it help to eliminate a branch if this were instead structured as:

if(canAlign){
...// do alignmentif(remainder>(NonTemporalByteThreshold/sizeof(float))){
...// handle non-temporal pathgoto AdjustingRefs;}}
...// what's currently in the else blockAdjustingRefs:
...// stuff currently after the else block

?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't think the additional complexity is worth it here and it can cause subtle issues with control flow analysis that are probably undesirable.

If we were concerned, I'd prefer switching the order so its if ((remainder > (NonTemporalByteThreshold / sizeof(float)) && canAlign) instead.


public static IEnumerable<object[]> TensorLengths =>
from length in Enumerable.Range(1, 128)
from length in Enumerable.Range(1, 256)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is good, but it's also going to double the number of test cases we're running. Not a big deal except that on netfx it seems to struggle with the theory count. We should probably subsequently change some of those theories to be loops, e.g. instead of:

[Theory][MemberData(nameof(TensorLengths))]publicvoidFoo(inttensorLength){
...}

do:

[Fact]publicvoidFoo(){foreach(inttensorLengthinTensorLengths){
...}}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll handle this in the immediately following PR covering binary/ternary.

@lewing

Copy link
Copy Markdown
Member

This appears to have broken the outerloop tests

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@lewing is there an issue or a link that can be shared so it can be investigated?

@lewing

lewing commented Oct 12, 2023

Copy link
Copy Markdown
Member

@tannergooding

Copy link
Copy Markdown
MemberAuthor

#93412 fixes the issue.

Another PR went in that removed unsafe and it didn't conflict, so the nothing caught the issue.

michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request Oct 20, 2023
… tores (dotnet#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
carlossanlop pushed a commit that referenced this pull request Oct 20, 2023
* Use FMA in TensorPrimitives (#92205)
* Simplify TensorPrimitive's AbsoluteOperator (#92577)
Vector{128/256/512} all provide Abs; no need to do this manually.
* Reduce some boilerplate in TensorPrimitive's IBinaryOperator (#92576)
Change a few of the static abstract interface methods to be virtual, as most implementations throw from these methods; we can consolidate that throwing to the base.
* Minor code cleanup in TensorPrimitives tests (#92575)
* Normalize some test naming
* Alphabetize tests
* Improve mistmatched length tests with all positions of the shorter tensor
* Alphabetize methods in TensorPrimitives.cs
* Vectorize TensorPrimitives.Min/Max{Magnitude} (#92618)
* Vectorize TensorPrimitives.Min/Max{Magnitude}
* Use AdvSimd.Max/Min
* Rename some parameters/locals for consistency
* Improve HorizontalAggregate
* Move a few helpers
* Avoid scalar path for returning found NaN
* Update TensorPrimitives aggregations to vectorize handling of remaining elements (#92672)
* Update TensorPrimitives.CosineSimilarity to vectorize handling of remaining elements
* Vectorize remainder handling for Aggregate helpers
* Flesh out TensorPrimitives XML docs (#92749)
* Flesh out TensorPrimitives XML docs
* Address PR feedback
- Remove use of FusedMultiplyAdd from all but CosineSimilarity
- Remove comments about platform/OS-specific behavior from Add/AddMultiply/Subtract/Multiply/MultiplyAdd/Divide/Negate
- Loosen comments about NaN and which exact one is returned
* Address PR feedback
* Vectorize TensorPrimitives.ConvertToHalf (#92715)
* Enable TensorPrimitives to perform in-place operations (#92820)
Some operations would produce incorrect results if the same span was passed as both an input and an output. When vectorization was employed but the span's length wasn't a perfect multiple of a vector, we'd do the standard trick of performing one last operation on the last vector's worth of data; however, that relies on the operation being idempotent, and if a previous operation has overwritten input with a new value due to the same memory being used for input and output, some operations won't be idempotent. This fixes that by masking off the already processed elements. It adds tests to validate in-place use works, and it updates the docs to carve out this valid overlapping.
* Vectorize TensorPrimitives.ConvertToSingle (#92779)
* Vectorize TensorPrimitives.ConvertToSingle
* Address PR feedback
* Throw exception in TensorPrimitives for unsupported span overlaps (#92838)
* This vectorizes TensorPrimitives.Log2 (#92897)
* Add a way to support operations that can't be vectorized on netstandard
* Updating TensorPrimitives.Log2 to be vectorized on .NET Core
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Ensure we do an arithmetic right shift in the Log2 vectorization
* Ensure the code can compile on .NET 7
* Ensure that edge cases are properly handled and don't resolve to `x`
* Ensure that Log2 special results are explicitly handled.
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Adding Log2 tests covering some special values (#92946)
* [wasm] Disable `TensorPrimitivesTests.ConvertToHalf_SpecialValues` (#92953)
Failing test: `System.Numerics.Tensors.Tests.TensorPrimitivesTests.ConvertToHalf_SpecialValues`
Issue: #92885
* Adding a vectorized implementation of TensorPrimitives.Log (#92960)
* Adding a vectorized implementation of TensorPrimitives.Log
* Make sure to hit Ctrl+S
* Consolidate some TensorPrimitivesTests logic around special values (#92982)
* Vectorize TensorPrimitives.Exp (#93018)
* Vectorize TensorPrimitives.Exp
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax (#93029)
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax
- Adds a SigmoidOperator that just wraps the ExpOperator
- Vectorizes both passes of SoftMax, on top of ExpOperator. Simplest way to do this was to augment the existing InvokeSpanScalarIntoSpan to take a transform operator.
- In doing so, found some naming inconsistencies I'd previously introduced, so I did some automatic renaming to make things more consistent.
- Added XML comments to all the internal/private surface area.
- Fleshes out some tests (and test values).
* Disable tests on mono
* Address PR feedback
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh (#93093)
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh
Tanh and Cosh are based on AOCL-LibM.
AOCL-LibM doesn't appear to have a sinh implementation, so this Sinh is just based on the sinh formula based on exp(x).
I also augmented the tests further, including:
- Added more tests for sinh/cosh/tanh
- Add an equality routine that supports comparing larger values with a tolerance
- Tightened the tolerance for most functions
- Changed some tests to be theories to be consistent with style elsewhere in the tests
- Fixed some use of Math to be MathF
* Remove unnecessary special-handling path from cosh
* Remove unnecessary special-handling path from tanh
* Redo sinh based on cosh
* Address PR feedback
* Replace confusing new T[] { ... }
* Remove a few unnecessary `unsafe` keyword uses in TensorPrimitives (#93219)
* Consolidate a few exception throws in TensorPrimitives (#93168)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value (#93169)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value
Found as part of adding more tests for Min/Max{Magnitude} to validate they match their IndexOfXx variants.
* Address PR feedback
* Improve a vector implementation to support alignment and non-temporal tores (#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
* Mark TensorPrimitives as unsafe (#93412)
* Use the improved vectorization algorithm for binary and ternary TensorPrimitives operations (#93409)
* Update InvokeSpanSpanIntoSpan<TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarIntoSpan<TTransformOperator, TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanScalarIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Improve codegen slightly by using case 0, rather than default
* Adjust the canAlign check to be latter, to reduce branch count for data under the threshold
* Add a comment explaining the NonTemporalByteThreshold
* Make sure xTransformOp.CanVectorize is checked on .NET Standard
* Use the improved vectorization algorithm for aggregate TensorPrimitives operations (#93695)
* Improve the handling of the IAggregationOperator implementations
* Update Aggregate<TTransformOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Update Aggregate<TBinaryOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Respond to PR feedback
* [wasm] Remove more active issues for #92885 (#93596)
* adding patch from pr 93556
* Vectorizes IndexOfMin/Max/Magnitude (#93469)
* resolved merge conflicts
* net core full done
* minor code cleanup
* NetStandard and PR fixes.
* minor pr changes
* Fix IndexOfMaxMagnitudeOperator
* Fix IndexOfMaxMagnitudeOperator on netcore
* updates from PR comments
* netcore fixed
* net standard updated
* add reference assembly exclusions
* made naive approach better
* resolved PR comments
* minor comment changes
* minor formatting fixes
* added inlining
* fixes from PR comments
* comments from pr
* fixed spacing
---------
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Tanner Gooding <tagoo@outlook.com>
Co-authored-by: Ankit Jain <radical@gmail.com>
Co-authored-by: Radek Doulik <radek.doulik@gmail.com>
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@EgorBo@lewing@stephentoub@gfoidl
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Improve a vector implementation to support alignment and non-temporal tores - #93296

Merged
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align
Oct 12, 2023
Merged

Improve a vector implementation to support alignment and non-temporal tores#93296
tannergooding merged 5 commits into
dotnet:mainfrom
tannergooding:vectorize-align

Conversation

@tannergooding

Copy link
Copy Markdown
Member

No description provided.

@ghost

Copy link
Copy Markdown

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

Issue Details

null

Author:tannergooding
Assignees:tannergooding
Labels:

area-System.Numerics

Milestone:-

@EgorBo

Copy link
Copy Markdown
Member

If a function is idempotent (which most of them are) it's enough a single load to align data (like I did in https://github.com/dotnet/runtime/pull/93214/files) without any unrolled loops, etc - presumably, you can expose a bool IsIdempotent {get;} property in these abstraction and only align those for simplicity

@tannergooding
tannergooding marked this pull request as ready for review October 10, 2023 23:21
@tannergooding

tannergooding commented Oct 11, 2023

Copy link
Copy Markdown
MemberAuthor

Here are the before/after results for Abs, where Abs1 is the previous implementation and Abs2 is the implementation from this PR. We are faster across the board (except for 16 elements) and more than 2x faster for large inputs, with the gains really showing past 256 elements (1024 bytes).

MethodTensorLengthMeanErrorStdDev
Abs112.609 ns0.0611 ns0.0572 ns
Abs212.562 ns0.0518 ns0.0485 ns
Abs123.142 ns0.0487 ns0.0456 ns
Abs222.563 ns0.0121 ns0.0101 ns
Abs133.309 ns0.0294 ns0.0229 ns
Abs232.742 ns0.0171 ns0.0133 ns
Abs143.090 ns0.0496 ns0.0464 ns
Abs242.572 ns0.0187 ns0.0166 ns
Abs155.198 ns0.0190 ns0.0148 ns
Abs253.133 ns0.0788 ns0.0774 ns
Abs165.275 ns0.0790 ns0.0739 ns
Abs263.098 ns0.0782 ns0.0732 ns
Abs175.322 ns0.0916 ns0.0857 ns
Abs273.164 ns0.0544 ns0.0509 ns
Abs182.936 ns0.0553 ns0.0517 ns
Abs282.573 ns0.0442 ns0.0414 ns
Abs195.454 ns0.0910 ns0.0852 ns
Abs293.165 ns0.0536 ns0.0501 ns
Abs1105.431 ns0.0675 ns0.0631 ns
Abs2103.230 ns0.0308 ns0.0288 ns
Abs1115.369 ns0.0447 ns0.0418 ns
Abs2113.199 ns0.0443 ns0.0393 ns
Abs1125.571 ns0.0593 ns0.0555 ns
Abs2123.211 ns0.0320 ns0.0299 ns
Abs1135.585 ns0.0491 ns0.0459 ns
Abs2133.262 ns0.0295 ns0.0276 ns
Abs1145.766 ns0.0550 ns0.0514 ns
Abs2143.264 ns0.0292 ns0.0273 ns
Abs1155.865 ns0.0454 ns0.0354 ns
Abs2153.254 ns0.0451 ns0.0377 ns
Abs1162.790 ns0.0207 ns0.0193 ns
Abs2164.984 ns0.0479 ns0.0448 ns
Abs1326.515 ns0.0886 ns0.0785 ns
Abs2326.012 ns0.1203 ns0.1067 ns
Abs16417.126 ns0.1647 ns0.1540 ns
Abs26415.343 ns0.3172 ns0.3115 ns
Abs112838.650 ns0.7464 ns0.6982 ns
Abs212837.150 ns0.4134 ns0.3665 ns
Abs125682.113 ns0.5631 ns0.4702 ns
Abs225651.379 ns0.2897 ns0.2710 ns
Abs1512169.559 ns0.7197 ns0.6732 ns
Abs251278.668 ns0.4391 ns0.4107 ns
Abs11024336.791 ns1.9587 ns1.8321 ns
Abs21024133.500 ns0.7219 ns0.6400 ns
Abs12048698.482 ns1.3343 ns1.1142 ns
Abs22048244.729 ns1.6655 ns1.5579 ns
Abs140961,411.587 ns5.8918 ns5.2229 ns
Abs24096471.249 ns1.2273 ns1.0880 ns
Abs16553622,871.118 ns97.4602 ns91.1643 ns
Abs2655367,306.615 ns32.3811 ns30.2893 ns
Abs113107245,773.050 ns244.2154 ns228.4393 ns
Abs213107218,461.823 ns105.6972 ns93.6978 ns

@tannergooding

Copy link
Copy Markdown
MemberAuthor

Similar results for Exp

MethodTensorLengthMeanErrorStdDev
Exp115.224 ns0.1122 ns0.1050 ns
Exp214.863 ns0.0823 ns0.0770 ns
Exp127.359 ns0.1578 ns0.1689 ns
Exp226.872 ns0.0975 ns0.0912 ns
Exp139.315 ns0.1091 ns0.0852 ns
Exp239.018 ns0.1643 ns0.1537 ns
Exp144.820 ns0.1094 ns0.1075 ns
Exp244.863 ns0.1028 ns0.0962 ns
Exp159.103 ns0.0672 ns0.0629 ns
Exp256.563 ns0.1169 ns0.1036 ns
Exp169.280 ns0.2022 ns0.2076 ns
Exp266.685 ns0.1498 ns0.1538 ns
Exp179.111 ns0.0848 ns0.0793 ns
Exp276.660 ns0.1413 ns0.1321 ns
Exp185.006 ns0.0963 ns0.0901 ns
Exp284.848 ns0.1006 ns0.0941 ns
Exp1910.272 ns0.1189 ns0.1112 ns
Exp297.593 ns0.0876 ns0.0820 ns
Exp11010.188 ns0.0713 ns0.0596 ns
Exp2107.581 ns0.0935 ns0.0875 ns
Exp11110.274 ns0.1070 ns0.0949 ns
Exp2117.631 ns0.1412 ns0.1320 ns
Exp11210.246 ns0.0755 ns0.0670 ns
Exp2127.555 ns0.0742 ns0.0658 ns
Exp11310.270 ns0.1266 ns0.1184 ns
Exp2137.651 ns0.0916 ns0.0857 ns
Exp11410.252 ns0.1509 ns0.1411 ns
Exp2147.585 ns0.1017 ns0.0951 ns
Exp11510.428 ns0.0935 ns0.0875 ns
Exp2157.795 ns0.0801 ns0.0749 ns
Exp1167.271 ns0.0845 ns0.0749 ns
Exp21619.323 ns0.3291 ns0.3078 ns
Exp13218.716 ns0.2705 ns0.2530 ns
Exp23225.631 ns0.3860 ns0.3610 ns
Exp16448.860 ns0.4013 ns0.3753 ns
Exp26450.285 ns0.9766 ns0.9135 ns
Exp1128109.969 ns1.6557 ns1.5487 ns
Exp2128110.952 ns1.4802 ns1.3846 ns
Exp1256230.547 ns2.4931 ns2.0819 ns
Exp2256193.106 ns0.8509 ns0.7105 ns
Exp1512477.460 ns5.6110 ns5.2486 ns
Exp2512317.518 ns5.5631 ns5.2037 ns
Exp11024977.141 ns13.0740 ns12.2295 ns
Exp21024573.851 ns7.7362 ns7.2364 ns
Exp120481,931.447 ns12.4202 ns10.3714 ns
Exp220481,017.003 ns19.3625 ns20.7177 ns
Exp140963,892.225 ns27.1610 ns22.6807 ns
Exp240961,975.208 ns26.4692 ns23.4642 ns
Exp16553661,938.616 ns354.4827 ns296.0091 ns
Exp26553632,390.331 ns590.1723 ns552.0475 ns
Exp1131072125,798.377 ns2,260.7971 ns2,114.7510 ns
Exp213107261,126.610 ns1,091.3727 ns1,020.8707 ns

{
public static partial class TensorPrimitives
{
private const nuint NonTemporalByteThreshold = 256 * 1024;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a comment about what this is and how the value was chosen?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Suggested change
privateconstnuintNonTemporalByteThreshold=256*1024;
/// <summary>Defines the threshold, in bytes, at which non-temporal stores will be used.</summary>
/// <remarks>
/// A non-temporal store is one that allows the CPU to bypass the cache when writing to memory.
///
/// This can be beneficial when working with large amounts of memory where the writes would otherwise
/// cause large amounts of repeated updates and evictions. The hardware optimization manuals recommend
/// the threshold to be roughly half the size of the last level of on-die cache -- that is, if you have approximately
/// 4MB of L3 cache per core, you'd want this to be approx. 1-2MB, depending on if hyperthreading was enabled.
///
/// However, actually computing the amount of L3 cache per core can be tricky or error prone. Native memcpy
/// algorithms use a constant threshold that is typically around 256KB and we match that here for simplicity. This
/// threshold accounts for most processors in the last 10-15 years that had approx. 1MB L3 per core and support
/// hyperthreading, giving a per core last level cache of approx. 512KB.
/// </remarks>
privateconstnuintNonTemporalByteThreshold=256*1024;

Comment on lines +1331 to +1354
if (canAlign)
{
// Compute by how many elements we're misaligned and adjust the pointers accordingly
//
// Noting that we are only actually aligning dPtr. THis is because unaligned stores
// are more expensive than unaligned loads and aligning both is significantly more
// complex.

nuint misalignment = ((uint)(sizeof(Vector128<float>)) - ((nuint)(dPtr) % (uint)(sizeof(Vector128<float>)))) / sizeof(float);

xPtr += misalignment;
dPtr += misalignment;

Debug.Assert(((nuint)(dPtr) % (uint)(sizeof(Vector128<float>))) == 0);

remainder -= misalignment;
}

Vector128<float> vector1;
Vector128<float> vector2;
Vector128<float> vector3;
Vector128<float> vector4;

if (canAlign && (remainder > (NonTemporalByteThreshold / sizeof(float))))

@stephentoubstephentoubOct 12, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have two checks on canAlign here. Would it help to eliminate a branch if this were instead structured as:

if(canAlign){
...// do alignmentif(remainder>(NonTemporalByteThreshold/sizeof(float))){
...// handle non-temporal pathgoto AdjustingRefs;}}
...// what's currently in the else blockAdjustingRefs:
...// stuff currently after the else block

?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I don't think the additional complexity is worth it here and it can cause subtle issues with control flow analysis that are probably undesirable.

If we were concerned, I'd prefer switching the order so its if ((remainder > (NonTemporalByteThreshold / sizeof(float)) && canAlign) instead.


public static IEnumerable<object[]> TensorLengths =>
from length in Enumerable.Range(1, 128)
from length in Enumerable.Range(1, 256)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is good, but it's also going to double the number of test cases we're running. Not a big deal except that on netfx it seems to struggle with the theory count. We should probably subsequently change some of those theories to be loops, e.g. instead of:

[Theory][MemberData(nameof(TensorLengths))]publicvoidFoo(inttensorLength){
...}

do:

[Fact]publicvoidFoo(){foreach(inttensorLengthinTensorLengths){
...}}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I'll handle this in the immediately following PR covering binary/ternary.

@lewing

Copy link
Copy Markdown
Member

This appears to have broken the outerloop tests

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@lewing is there an issue or a link that can be shared so it can be investigated?

@lewing

lewing commented Oct 12, 2023

Copy link
Copy Markdown
Member

@tannergooding

Copy link
Copy Markdown
MemberAuthor

#93412 fixes the issue.

Another PR went in that removed unsafe and it didn't conflict, so the nothing caught the issue.

michaelgsharp pushed a commit to michaelgsharp/runtime that referenced this pull request Oct 20, 2023
… tores (dotnet#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
carlossanlop pushed a commit that referenced this pull request Oct 20, 2023
* Use FMA in TensorPrimitives (#92205)
* Simplify TensorPrimitive's AbsoluteOperator (#92577)
Vector{128/256/512} all provide Abs; no need to do this manually.
* Reduce some boilerplate in TensorPrimitive's IBinaryOperator (#92576)
Change a few of the static abstract interface methods to be virtual, as most implementations throw from these methods; we can consolidate that throwing to the base.
* Minor code cleanup in TensorPrimitives tests (#92575)
* Normalize some test naming
* Alphabetize tests
* Improve mistmatched length tests with all positions of the shorter tensor
* Alphabetize methods in TensorPrimitives.cs
* Vectorize TensorPrimitives.Min/Max{Magnitude} (#92618)
* Vectorize TensorPrimitives.Min/Max{Magnitude}
* Use AdvSimd.Max/Min
* Rename some parameters/locals for consistency
* Improve HorizontalAggregate
* Move a few helpers
* Avoid scalar path for returning found NaN
* Update TensorPrimitives aggregations to vectorize handling of remaining elements (#92672)
* Update TensorPrimitives.CosineSimilarity to vectorize handling of remaining elements
* Vectorize remainder handling for Aggregate helpers
* Flesh out TensorPrimitives XML docs (#92749)
* Flesh out TensorPrimitives XML docs
* Address PR feedback
- Remove use of FusedMultiplyAdd from all but CosineSimilarity
- Remove comments about platform/OS-specific behavior from Add/AddMultiply/Subtract/Multiply/MultiplyAdd/Divide/Negate
- Loosen comments about NaN and which exact one is returned
* Address PR feedback
* Vectorize TensorPrimitives.ConvertToHalf (#92715)
* Enable TensorPrimitives to perform in-place operations (#92820)
Some operations would produce incorrect results if the same span was passed as both an input and an output. When vectorization was employed but the span's length wasn't a perfect multiple of a vector, we'd do the standard trick of performing one last operation on the last vector's worth of data; however, that relies on the operation being idempotent, and if a previous operation has overwritten input with a new value due to the same memory being used for input and output, some operations won't be idempotent. This fixes that by masking off the already processed elements. It adds tests to validate in-place use works, and it updates the docs to carve out this valid overlapping.
* Vectorize TensorPrimitives.ConvertToSingle (#92779)
* Vectorize TensorPrimitives.ConvertToSingle
* Address PR feedback
* Throw exception in TensorPrimitives for unsupported span overlaps (#92838)
* This vectorizes TensorPrimitives.Log2 (#92897)
* Add a way to support operations that can't be vectorized on netstandard
* Updating TensorPrimitives.Log2 to be vectorized on .NET Core
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Ensure we do an arithmetic right shift in the Log2 vectorization
* Ensure the code can compile on .NET 7
* Ensure that edge cases are properly handled and don't resolve to `x`
* Ensure that Log2 special results are explicitly handled.
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
* Adding Log2 tests covering some special values (#92946)
* [wasm] Disable `TensorPrimitivesTests.ConvertToHalf_SpecialValues` (#92953)
Failing test: `System.Numerics.Tensors.Tests.TensorPrimitivesTests.ConvertToHalf_SpecialValues`
Issue: #92885
* Adding a vectorized implementation of TensorPrimitives.Log (#92960)
* Adding a vectorized implementation of TensorPrimitives.Log
* Make sure to hit Ctrl+S
* Consolidate some TensorPrimitivesTests logic around special values (#92982)
* Vectorize TensorPrimitives.Exp (#93018)
* Vectorize TensorPrimitives.Exp
* Update src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/TensorPrimitives.netstandard.cs
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax (#93029)
* Vectorize TensorPrimitives.Sigmoid and TensorPrimitives.SoftMax
- Adds a SigmoidOperator that just wraps the ExpOperator
- Vectorizes both passes of SoftMax, on top of ExpOperator. Simplest way to do this was to augment the existing InvokeSpanScalarIntoSpan to take a transform operator.
- In doing so, found some naming inconsistencies I'd previously introduced, so I did some automatic renaming to make things more consistent.
- Added XML comments to all the internal/private surface area.
- Fleshes out some tests (and test values).
* Disable tests on mono
* Address PR feedback
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh (#93093)
* Vectorize TensorPrimitives.Tanh/Cosh/Sinh
Tanh and Cosh are based on AOCL-LibM.
AOCL-LibM doesn't appear to have a sinh implementation, so this Sinh is just based on the sinh formula based on exp(x).
I also augmented the tests further, including:
- Added more tests for sinh/cosh/tanh
- Add an equality routine that supports comparing larger values with a tolerance
- Tightened the tolerance for most functions
- Changed some tests to be theories to be consistent with style elsewhere in the tests
- Fixed some use of Math to be MathF
* Remove unnecessary special-handling path from cosh
* Remove unnecessary special-handling path from tanh
* Redo sinh based on cosh
* Address PR feedback
* Replace confusing new T[] { ... }
* Remove a few unnecessary `unsafe` keyword uses in TensorPrimitives (#93219)
* Consolidate a few exception throws in TensorPrimitives (#93168)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value (#93169)
* Fix TensorPrimitives.IndexOfXx corner-case when first element is seed value
Found as part of adding more tests for Min/Max{Magnitude} to validate they match their IndexOfXx variants.
* Address PR feedback
* Improve a vector implementation to support alignment and non-temporal tores (#93296)
* Improve a vector implementation to support alignment and non-temporal stores
* Fix a build error and mark a couple methods as AggressiveInlining
* Fix the remaining block count computation
* Ensure overlapping for small data on the V256/512 is handled
* Ensure we only go down the vectorized path when supported for netstandard
* Mark TensorPrimitives as unsafe (#93412)
* Use the improved vectorization algorithm for binary and ternary TensorPrimitives operations (#93409)
* Update InvokeSpanSpanIntoSpan<TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarIntoSpan<TTransformOperator, TBinaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanSpanScalarIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Update InvokeSpanScalarSpanIntoSpan<TTernaryOperator> for TensorPrimitives to use the better SIMD algorithm
* Improve codegen slightly by using case 0, rather than default
* Adjust the canAlign check to be latter, to reduce branch count for data under the threshold
* Add a comment explaining the NonTemporalByteThreshold
* Make sure xTransformOp.CanVectorize is checked on .NET Standard
* Use the improved vectorization algorithm for aggregate TensorPrimitives operations (#93695)
* Improve the handling of the IAggregationOperator implementations
* Update Aggregate<TTransformOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Update Aggregate<TBinaryOperator, TAggregationOperator> for TensorPrimitives to use the better SIMD algorithm
* Respond to PR feedback
* [wasm] Remove more active issues for #92885 (#93596)
* adding patch from pr 93556
* Vectorizes IndexOfMin/Max/Magnitude (#93469)
* resolved merge conflicts
* net core full done
* minor code cleanup
* NetStandard and PR fixes.
* minor pr changes
* Fix IndexOfMaxMagnitudeOperator
* Fix IndexOfMaxMagnitudeOperator on netcore
* updates from PR comments
* netcore fixed
* net standard updated
* add reference assembly exclusions
* made naive approach better
* resolved PR comments
* minor comment changes
* minor formatting fixes
* added inlining
* fixes from PR comments
* comments from pr
* fixed spacing
---------
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
---------
Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Tanner Gooding <tagoo@outlook.com>
Co-authored-by: Ankit Jain <radical@gmail.com>
Co-authored-by: Radek Doulik <radek.doulik@gmail.com>
Co-authored-by: Eric StJohn <ericstj@microsoft.com>
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@EgorBo@lewing@stephentoub@gfoidl