You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Given that there never were Macs with AMD processors, or (AFAIK) with AVX-512, you could reduce the size of your library build by adding DYNAMIC_LIST="CORE2 NEHALEM SANDYBRIDGE HASWELL", removing the dedicated BLAS kernels for 10 irrelevant cpus (if you don't already do this). Probably no longer worth it for you, but I guess I should add this as the default for x86_64 builds with OSNAME=Darwin...
It would be worth summarizing what we are actually building in this repo I think. It looks like we are using DYNAMIC_ARCH=1 TARGET=PRESCOTT, but it's not exactly clear to me from the OpenBLAS README what that does. I am guessing "all architectures from PRESCOTT up" - but if so, that seems a little excessive?
Other questions I'd have:
What is the binary size impact of including/excluding a single architecture?
What happens on non-included architectures? Does it use the next-older target? If so, what performance is left on the table on average?
TARGET=PRESCOTT in combination with DYNAMIC_ARCH is "use compiler options for Prescott when compiling the common code (thread setup, interfaces, LAPACK)", DYNAMIC _ARCH on x86_64 is a list of about 15 Intel and AMD cpus unless you specify your own subset via DYNAMIC_LIST. Can't give an exact answer for the per-model overhead, but it is something like 50 BLAS kernel objects plus parameters and function pointer table setup. Any non-included target gets supported by the next best available - again no exact figures but I'd guess at most 10 percent performance loss unless the fallback incurs restrictions like going from avx to sse.
We discussed this a bit in the NumPy optimization team meeting yesterday. It seemed reasonable to everyone to build for fewer target architectures. When selecting the ones to ship, the expectation was that Haswell (first AVX2) and SkylakeX (first AVX512) would be important.
With target PRESCOTT (current default build flags on Linux):
Note: see Makefile.system for details on DYNAMIC_CORE/DYNAMIC_LIST to see how architectures are selected.
So it's about 1.5 MB shared library size extra per architecture that is built for. The compression factor for the shared library is about 3.5x. Meaning that the current contribution of libopenblas.so to x86-64 numpy/scipy wheels is currently ~9.5 MB, and if we'd go from 15 to say 5 architectures, we'd reduce wheel sizes by about 4 MB.
Given the current traffic for numpy/scipy on PyPI, such a 4 MB reduction would saves about 17 PB/year (petabytes - how often do you get to use those:)) of download volume. I think we should make a selection of architectures based on what we know, then do some performance testing, and ship the reduced-size wheels unless we find a serious performance concern.
Makes sense to me. We should keep the PRESCOTT target for low-end processors, and add a few others based on some measure of middle-end and high-end processors. The aarch64 wheels, with only a few kernels shipped, are much smaller than the x86_64 ones.
Reacted by czgdp1807, Rohit Goswami and Agriya Khetarpal
Something I only recently learned about is psABI level (https://gitlab.com/x86-psABIs/x86-64-ABI). This is what Linux distros have been using recently to select and deploy different optimization levels. The levels are:
For NumPy, SSE3 has been part of the baseline for quite a while now, so we're kinda halfway to x86-64-v2. v2 is still "very old machines", v3 probably roughly lines up with Haswell, and v4 with SkylakeX.
For NumPy we pick different levels per target, making the performance/size tradeoff per set of functions based on benchmarking or knowledge of what instructions are used:
Generating multi-targets for "argfunc.dispatch.h"
Enabled targets: AVX512_SKX, AVX2, SSE42, baseline
Generating multi-targets for "x86_simd_argsort.dispatch.h"
Enabled targets: AVX512_SKX, AVX2
Generating multi-targets for "x86_simd_qsort.dispatch.h"
Enabled targets: AVX512_SKX, AVX2
Generating multi-targets for "x86_simd_qsort_16bit.dispatch.h"
Enabled targets: AVX512_SPR, AVX512_ICL
Generating multi-targets for "highway_qsort.dispatch.h"
Enabled targets:
Generating multi-targets for "highway_qsort_16bit.dispatch.h"
Enabled targets:
Generating multi-targets for "loops_arithm_fp.dispatch.h"
Enabled targets: FMA3__AVX2, baseline
Generating multi-targets for "loops_arithmetic.dispatch.h"
Enabled targets: AVX512_SKX, AVX512F, AVX2, SSE41, baseline
...
Generating multi-targets for "_simd.dispatch.h"
Enabled targets: SSE42, AVX2, FMA3, FMA3__AVX2, AVX512F, AVX512_SKX, baseline
Baseline is SSE3 as the highest level, which matches Prescott.
We should hopefully have some more benchmarking results to decide on this soon.
I spent some time looking through the wikipedia page about avx-512. It seems there are many more instruction variations than actual chips that support them. AMD Zen 4 (ryzen 7000) has AVX-512f, but it seems Intel has stopped shipping consumer chips with AVX-512 variations. They are available for server (XEON) level processors.
There is also some talk about use of AVX-512 actually harming overall performance: due to thermal and power considerations. Although the vectored operations are faster, they come at a cost.
From my (very) limited understanding of Apple products, the only relevant "flavor" of AVX512 support in terms of OpenBLAS' TARGET names would be SKYLAKEX for the 2017 iMac Pro, 2019 Mac Pro and 2020 MacBook Pro.
Does that make sense? Are there other benchmarks we should explore?
Next steps: If we are agreed these are the 5 groups, I should try to make a scipy-openblas wheel using DYNAMIC_LIST="PRESCOTT NEHALEM SANDYBRIDGE HASWELL SAPPHIRERAPIDS". @martin-frbg is that the correct way to specify these groupings? I mean, if I specify SAPPHIRERAPIDS will a COOPERLAKE or SKYLAKEX cpu hit those kernels? Once the wheel is available, we can check size reduction and re-run the benchmarks to see that the performance is similar.
The main difference between SkylakeX and the newer AVX512 targets should be availability of BFLOAT16 instructions - if numpy/scipy does not benefit from them, you should stick to SKYLAKEX. Currently Cooperlake and SkylakeX would both fall back "downwards" to Haswell if no SkylakeX support is available (as Sapphire Rapids is the one with the most advanced instruction set, which may not be supported on the older models).
Ahh, so the correct invocation would be DYNAMIC_LIST="PRESCOTT NEHALEM SANDYBRIDGE HASWELL SKYLAKEX" (substitute SKYLAKEX for SAPPHIRERAPIDS).
NumPy (and so SciPy) does not yet have a native BFLOAT16, but there are extension modules on PyPI that extend NumPy like this, however it does not percolate into linalg calls to OpenBLAS.
The question is how much difference between the absolute timings in these two files are acceptable. I use 10% but can be increased 20%, 30%. If we are willing to accept 38% difference in the absolute timings then we are good to go. This is what I get when I set threshold to 0.38. This means all the absolute timings in reduced_kernel branch are within 38% range of main branch.
(scipy-dev) 15:54:04:~/Quansight/numpy-benchmarks-openblas % python CompareAndParseMarkdownResults.py --file1=../benchmarks.output.txt --file2=../benchmarks.output_reduced_kernel.txt --threshold=0.38
Machine info for file ../benchmarks.output.txt is {'arch': 'x86_64', 'cpu': 'Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz', 'machine': 'asv machine', 'num_cpu': '2', 'os': 'Linux 6.8.0-1009-aws', 'ram': '8008632', 'python': '3.12', 'Cython': '', 'build': '', 'packaging': ''}
Machine info for file ../benchmarks.output_reduced_kernel.txt is {'arch': 'x86_64', 'cpu': 'Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz', 'machine': 'ip-172-31-25-34', 'num_cpu': '2', 'os': 'Linux 6.8.0-1009-aws', 'ram': '8008640', 'python': '3.12', 'Cython': '', 'build': '', 'packaging': ''}
I can't say I understand the results yet. Two questions:
Why are there two numbers under perf_ratio? This is hard to interpret for me.
Why isn't the ratio 1.0 for the architectures that were kept in this list: DYNAMIC_LIST="PRESCOTT NEHALEM SANDYBRIDGE HASWELL SKYLAKEX"? Aren't the identical kernels supposed to be executed for those?
So here is the explanation behind the results in this gist.
Consider the following snippet from the above gist. First explaining what each tuple containing 2 floating point number means here. The number in each tuple on the left comes from main branch's results/benchmarks.output.txt and the numbers on the right comes from reduced_kernel branch's results/benchmarks.output.txt. For example (0.00169936, 0.00198691) under mean for HASWELL means 0.00169936 is mean from main and 0.00198691 is mean from reduced_kernel. mean is nothing but absolute timing. Similarly for spread and perf_ratios.
Now if you calculate percentage change from main to reduced_kernel it will come out to be abs(0.00198691 - 0.00169936)/0.00169936 comes out to be 0.169 or 16.9%. I hope this is making sense. The threshold is of 10%. So any absolute timing change of greater than 10% is included in my gist. The percentage is calculated using just mean.
The results in my gist basically mean that for each benchmark (in the following snippet its bench_linalg.Eindot.time_dot_d_dot_b_c) some kernels' absolute timing deviate by more than a certain threshold. The left one coming from main and the right one coming from reduced_kernel.
Okay, so then I get mean, but perf_ratios is something different, because it doesn't show that 16.9%. That's still a "difference in performance between the current and the best architecture" - where "best" is still arbitrary based on absolute timings.
For that gist, if the mean column is absolute timing and the average of the mean of the first run (first number in the tuples) and the average of the second run are different like this:
that doesn't mean that performance of the first run is better - it's just variation due to the machine being more loaded or something like that. If so, averaging a few runs should fix that.
We had something like this already a few months ago (plus the changes after than to order the architecture names on the x-axis always in the same order, and the buggy point for P2 dropped):
That's what we can draw conclusions from. When a second set of points would be shown for the same benchmark (with the openblas build with 5 kernels), then the data points for the kernels that are still included (e.g., Haswell) should overlap (modulo noise - and if not, something is wrong with the builds), and the ones that were dropped should be slightly slower. I don't think we can from the current data.
I think we can either decide to ignore the absolute timing data, because it's not meaningful for a single run, and just deploy the new builds (unless someone can explain why the code paths taken aren't identical). Or we can redo the benchmarks and generate the desired plots.
For that gist, if the mean column is absolute timing and the average of the mean of the first run (first number in the tuples) and the average of the second run are different like this:
So there is no difference between main and reduced_kernel? I thought that the timings coming from main are the ones where we are using all 15 kernels but in reduced_kernel we are just using 5 kernels.
I thought that the timings coming from main are the ones where we are using all 15 kernels but in reduced_kernel we are just using 5 kernels.
Yes indeed, but if both the 15-kernel and 5-kernel builds contain HASWELL, then the "mean" result for HASWELL should be identical. Variation in absolute timing is due to something external influencing the absolute timings. It's a cloud environment and the benchmarks for 15 were run far apart from those for 5, so perhaps there was something else going on on the machine. 10% variation isn't strange, but it doesn't mean anything.
Ah! I see. Exactly. 100% correct. Well then I think absolute timings shouldn't be compared. What should be compare is perf_ratios (they reflect relative timings I guess). I can do it by a minor change.
Only NEHALEM is present whose perf_ratio exceeds 10% deviation. May be because of some noise should be fine I think. Rest of the 4 kernels in the selected grouping are within 10%.
OK, I spent a while going over the two results. It seems to me everything is as expected. There were 5 clear groupings in the main results, and these 5 clear groupings remain in the reduced results. The same core type stayed in the same group. This indicates to me that the way we chose the 5 core types is correct, and we should move forward with the change in openblas-libs #166
This repo builds the scipyopenblas wheels, I don't think the work is reused by conda in its openblas recipe. So you will only see impact if you use pip install scipy numpy.
Activity
mattip commentedon Mar 6, 2024
I wonder what else we could do to tweak the dynamic kernels we ship. Fro instance, can we boost the minimum x86_64 on linux from PRESCOTT?
rgommers commentedon Mar 7, 2024
It would be worth summarizing what we are actually building in this repo I think. It looks like we are using
DYNAMIC_ARCH=1 TARGET=PRESCOTT, but it's not exactly clear to me from the OpenBLAS README what that does. I am guessing "all architectures from PRESCOTT up" - but if so, that seems a little excessive?Other questions I'd have:
martin-frbg commentedon Mar 7, 2024
TARGET=PRESCOTT in combination with DYNAMIC_ARCH is "use compiler options for Prescott when compiling the common code (thread setup, interfaces, LAPACK)", DYNAMIC _ARCH on x86_64 is a list of about 15 Intel and AMD cpus unless you specify your own subset via DYNAMIC_LIST. Can't give an exact answer for the per-model overhead, but it is something like 50 BLAS kernel objects plus parameters and function pointer table setup. Any non-included target gets supported by the next best available - again no exact figures but I'd guess at most 10 percent performance loss unless the fallback incurs restrictions like going from avx to sse.
rgommers commentedon Apr 16, 2024
We discussed this a bit in the NumPy optimization team meeting yesterday. It seemed reasonable to everyone to build for fewer target architectures. When selecting the ones to ship, the expectation was that Haswell (first AVX2) and SkylakeX (first AVX512) would be important.
With target
PRESCOTT(current default build flags on Linux):With
DYNAMIC_ARCHdisabled:With a custom selection (Prescott baseline, plus 3 newer architectures):
Note: see
Makefile.systemfor details onDYNAMIC_CORE/DYNAMIC_LISTto see how architectures are selected.So it's about 1.5 MB shared library size extra per architecture that is built for. The compression factor for the shared library is about 3.5x. Meaning that the current contribution of
libopenblas.soto x86-64 numpy/scipy wheels is currently ~9.5 MB, and if we'd go from 15 to say 5 architectures, we'd reduce wheel sizes by about 4 MB.rgommers commentedon Apr 16, 2024
Given the current traffic for numpy/scipy on PyPI, such a 4 MB reduction would saves about 17 PB/year (petabytes - how often do you get to use those:)) of download volume. I think we should make a selection of architectures based on what we know, then do some performance testing, and ship the reduced-size wheels unless we find a serious performance concern.
mattip commentedon Apr 16, 2024
Makes sense to me. We should keep the PRESCOTT target for low-end processors, and add a few others based on some measure of middle-end and high-end processors. The aarch64 wheels, with only a few kernels shipped, are much smaller than the x86_64 ones.
rgommers commentedon May 21, 2024
Something I only recently learned about is psABI level (https://gitlab.com/x86-psABIs/x86-64-ABI). This is what Linux distros have been using recently to select and deploy different optimization levels. The levels are:
For NumPy, SSE3 has been part of the baseline for quite a while now, so we're kinda halfway to
x86-64-v2.v2is still "very old machines",v3probably roughly lines up with Haswell, andv4with SkylakeX.rgommers commentedon May 21, 2024
For NumPy we pick different levels per target, making the performance/size tradeoff per set of functions based on benchmarking or knowledge of what instructions are used:
Baseline is SSE3 as the highest level, which matches Prescott.
We should hopefully have some more benchmarking results to decide on this soon.
mattip commentedon Jul 22, 2024
Is there any resolution of a recommended set of
DYNAMIC_LIST=???that we can add forx86_64?mattip commentedon Jul 23, 2024
I spent some time looking through the wikipedia page about avx-512. It seems there are many more instruction variations than actual chips that support them. AMD Zen 4 (ryzen 7000) has AVX-512f, but it seems Intel has stopped shipping consumer chips with AVX-512 variations. They are available for server (XEON) level processors.
There is also some talk about use of AVX-512 actually harming overall performance: due to thermal and power considerations. Although the vectored operations are faster, they come at a cost.
martin-frbg commentedon Jul 23, 2024
From my (very) limited understanding of Apple products, the only relevant "flavor" of AVX512 support in terms of OpenBLAS' TARGET names would be SKYLAKEX for the 2017 iMac Pro, 2019 Mac Pro and 2020 MacBook Pro.
mattip commentedon Jul 25, 2024
@ev-br ran the numpy linalg benchmarks against all the possible values of
DYNAMIC_CORE(fo linux x86_64) on a AWS m5 machine, using a script written by @czgdp1807. Here are the results as text.From what I can tell, we can see 5 different groupings with similar performance on these benchmarks:
COOPERLAKE,SKYLAKEX,SAPPHIRERAPIDS(corresponding to AVX512)HASWELL(AVX2)SANDYBRIDGE(SSE4.2, AVX, VT-x, VT-d)NEHALEM,ATOM(SSE4.2, VT-x, VT-d)PENRYN,CORE2,DUNNINGTON,PRESCOTT,KATMAI,NORTHWOOD,COPPERMINE,BANIAS(SSE3)Does that make sense? Are there other benchmarks we should explore?
Next steps: If we are agreed these are the 5 groups, I should try to make a scipy-openblas wheel using
DYNAMIC_LIST="PRESCOTT NEHALEM SANDYBRIDGE HASWELL SAPPHIRERAPIDS". @martin-frbg is that the correct way to specify these groupings? I mean, if I specifySAPPHIRERAPIDSwill aCOOPERLAKEorSKYLAKEXcpu hit those kernels? Once the wheel is available, we can check size reduction and re-run the benchmarks to see that the performance is similar.martin-frbg commentedon Jul 25, 2024
The main difference between SkylakeX and the newer AVX512 targets should be availability of BFLOAT16 instructions - if numpy/scipy does not benefit from them, you should stick to SKYLAKEX. Currently Cooperlake and SkylakeX would both fall back "downwards" to Haswell if no SkylakeX support is available (as Sapphire Rapids is the one with the most advanced instruction set, which may not be supported on the older models).
mattip commentedon Jul 25, 2024
Ahh, so the correct invocation would be
DYNAMIC_LIST="PRESCOTT NEHALEM SANDYBRIDGE HASWELL SKYLAKEX"(substituteSKYLAKEXforSAPPHIRERAPIDS).NumPy (and so SciPy) does not yet have a native
BFLOAT16, but there are extension modules on PyPI that extend NumPy like this, however it does not percolate intolinalgcalls to OpenBLAS.ev-br commentedon Jul 25, 2024
For those who prefer figures to text: https://github.com/ev-br/numpy-benchmarks-openblas/blob/main/viz_perf_arch.svg, generated by https://github.com/ev-br/numpy-benchmarks-openblas/blob/main/viz.benchmark.outputs.ipynb
(basically, the figure is just what Matti said)
12 remaining items
czgdp1807 commentedon Aug 8, 2024
So I added this script to parse and compare the benchmarking results produced by my repository - https://github.com/czgdp1807/numpy-benchmarks-openblas.
The results in this gist.
The question is how much difference between the absolute timings in these two files are acceptable. I use 10% but can be increased 20%, 30%. If we are willing to accept 38% difference in the absolute timings then we are good to go. This is what I get when I set threshold to
0.38. This means all the absolute timings inreduced_kernelbranch are within38%range ofmainbranch.rgommers commentedon Aug 8, 2024
I can't say I understand the results yet. Two questions:
perf_ratio? This is hard to interpret for me.DYNAMIC_LIST="PRESCOTT NEHALEM SANDYBRIDGE HASWELL SKYLAKEX"? Aren't the identical kernels supposed to be executed for those?czgdp1807 commentedon Aug 8, 2024
So here is the explanation behind the results in this gist.
Consider the following snippet from the above gist. First explaining what each tuple containing 2 floating point number means here. The number in each tuple on the left comes from main branch's results/benchmarks.output.txt and the numbers on the right comes from reduced_kernel branch's results/benchmarks.output.txt. For example
(0.00169936, 0.00198691)undermeanforHASWELLmeans0.00169936ismeanfrommainand0.00198691ismeanfromreduced_kernel.meanis nothing but absolute timing. Similarly forspreadandperf_ratios.Now if you calculate percentage change from
maintoreduced_kernelit will come out to beabs(0.00198691 - 0.00169936)/0.00169936comes out to be0.169or16.9%. I hope this is making sense. The threshold is of10%. So any absolute timing change of greater than10%is included in mygist. The percentage is calculated using justmean.The results in my
gistbasically mean that for each benchmark (in the following snippet itsbench_linalg.Eindot.time_dot_d_dot_b_c) some kernels' absolute timing deviate by more than a certain threshold. The left one coming frommainand the right one coming fromreduced_kernel.bench_linalg.Eindot.time_dot_d_dot_b_c
rgommers commentedon Aug 8, 2024
Okay, so then I get
mean, butperf_ratiosis something different, because it doesn't show that 16.9%. That's still a "difference in performance between the current and the best architecture" - where "best" is still arbitrary based on absolute timings.For that gist, if the
meancolumn is absolute timing and the average of the mean of the first run (first number in the tuples) and the average of the second run are different like this:that doesn't mean that performance of the first run is better - it's just variation due to the machine being more loaded or something like that. If so, averaging a few runs should fix that.
We had something like this already a few months ago (plus the changes after than to order the architecture names on the x-axis always in the same order, and the buggy point for
P2dropped):That's what we can draw conclusions from. When a second set of points would be shown for the same benchmark (with the openblas build with 5 kernels), then the data points for the kernels that are still included (e.g., Haswell) should overlap (modulo noise - and if not, something is wrong with the builds), and the ones that were dropped should be slightly slower. I don't think we can from the current data.
I think we can either decide to ignore the absolute timing data, because it's not meaningful for a single run, and just deploy the new builds (unless someone can explain why the code paths taken aren't identical). Or we can redo the benchmarks and generate the desired plots.
czgdp1807 commentedon Aug 8, 2024
So there is no difference between
mainandreduced_kernel? I thought that the timings coming frommainare the ones where we are using all 15 kernels but inreduced_kernelwe are just using 5 kernels.rgommers commentedon Aug 8, 2024
Yes indeed, but if both the 15-kernel and 5-kernel builds contain HASWELL, then the "mean" result for HASWELL should be identical. Variation in absolute timing is due to something external influencing the absolute timings. It's a cloud environment and the benchmarks for 15 were run far apart from those for 5, so perhaps there was something else going on on the machine. 10% variation isn't strange, but it doesn't mean anything.
czgdp1807 commentedon Aug 8, 2024
Ah! I see. Exactly. 100% correct. Well then I think absolute timings shouldn't be compared. What should be compare is
perf_ratios(they reflect relative timings I guess). I can do it by a minor change.In fact, if I compare
perf_ratios(see https://github.com/czgdp1807/numpy-benchmarks-openblas/commit/de9c56abc03c4efbf6067fa4222c423928a0a4f7) then the deviation of 10% is as follows,Click to see `perf_ratio` comparison between `main` and `reduced_kernel`
bench_linalg.Eindot.time_matmul_d_matmul_b_c
('bench_linalg.Einsum.time_einsum_noncon_outer',"<class'numpy.float32'>")
('bench_linalg.Einsum.time_einsum_noncon_outer',"<class'numpy.float64'>")
('bench_linalg.Linalg.time_det',"'complex128'")
('bench_linalg.Linalg.time_det',"'int16'")
('bench_linalg.Linalg.time_det',"'int32'")
('bench_linalg.Linalg.time_pinv',"'complex128'")
('bench_linalg.Linalg.time_pinv',"'complex64'")
('bench_linalg.Linalg.time_svd',"'complex128'")
('bench_linalg.Linalg.time_svd',"'complex64'")
('bench_linalg.LinalgNorm.time_norm',"'complex128'")
bench_linalg.LinalgSmallArrays.time_det_small_array
czgdp1807 commentedon Aug 8, 2024
Only
NEHALEMis present whoseperf_ratioexceeds 10% deviation. May be because of some noise should be fine I think. Rest of the 4 kernels in the selected grouping are within 10%.mattip commentedon Aug 8, 2024
OK, I spent a while going over the two results. It seems to me everything is as expected. There were 5 clear groupings in the
mainresults, and these 5 clear groupings remain in thereducedresults. The same core type stayed in the same group. This indicates to me that the way we chose the 5 core types is correct, and we should move forward with the change in openblas-libs #166[-]Shrink macos x86_64 blas library size[/-][+]Shrink x86_64 blas library size[/+]ilayn commentedon Mar 27, 2026
Thanks for informing me about this @rgommers I did not know this. When I look at my fresh conda installation now, I see
I only did
conda install numpy scipyI think this is the one that conda pulls. Am I doing something wrong that I do not see 10MB version?I am on AMD though in case it matters
mattip commentedon Mar 27, 2026
This repo builds the scipyopenblas wheels, I don't think the work is reused by conda in its openblas recipe. So you will only see impact if you use
pip install scipy numpy.rgommers commentedon Mar 27, 2026
Indeed. Look on PyPI for
scipy-openblas32for wheel size, andpip/uv-install it to see the unpacked size.ilayn commentedon Mar 28, 2026
Makes sense thanks @mattip