<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Blosc Home Page  (Posts about blosc array-api reductions computation)</title><link>https://blosc.org/</link><description></description><atom:link href="https://blosc.org/categories/blosc-array-api-reductions-computation.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2026 &lt;a href="mailto:blosc@blosc.org"&gt;The Blosc Developers&lt;/a&gt; </copyright><lastBuildDate>Sun, 19 Jul 2026 11:09:12 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title> Cumulative reductions in Blosc2</title><link>https://blosc.org/posts/cumsum/</link><dc:creator>Luke Shaw</dc:creator><description>&lt;p&gt;As mentioned in previous blog posts (see &lt;a class="reference external" href="https://ironarray.io/blog/array-api"&gt;this blog&lt;/a&gt;) the maintainers of &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;python-blosc2&lt;/span&gt;&lt;/code&gt; are going all-in on Array API integration. This means adding new functions to bring the library up to the standard. Of course, integrating a given function may be more or less difficult for a given library which aspires to compatibility, depending on legacy code, design principles, and the overarching philosophy of the package. Since &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;python-blosc2&lt;/span&gt;&lt;/code&gt; uses chunked arrays, handling reductions and mapping between local chunk- and global array-indexing can be tricky. We had some help from Yang Kang Chua at UConn with this functionality - many thanks to him!&lt;/p&gt;
&lt;section id="cumulative-reductions"&gt;
&lt;h2&gt;Cumulative reductions&lt;/h2&gt;
&lt;p&gt;Consider an array &lt;code class="docutils literal"&gt;a&lt;/code&gt; of shape &lt;code class="docutils literal"&gt;(1000, 2000, 3000)&lt;/code&gt; and data type &lt;code class="docutils literal"&gt;float64&lt;/code&gt; (more on numerical precision later). The result of &lt;code class="docutils literal"&gt;sum(a, axis=0)&lt;/code&gt; would be &lt;code class="docutils literal"&gt;(20, 30)&lt;/code&gt; and &lt;code class="docutils literal"&gt;sum(a, axis=1)&lt;/code&gt; would be &lt;code class="docutils literal"&gt;(1000, 3000)&lt;/code&gt;. In general we can say that reductions &lt;em&gt;reduce&lt;/em&gt; the sizes of arrays. On the other hand, cumulative reductions store the intermediate reduction results along the reduction axis, so that the shape of the result is always the same as that of the input array: &lt;code class="docutils literal"&gt;cumulative_sum(a, axis=ax)&lt;/code&gt; is always &lt;code class="docutils literal"&gt;(1000, 2000, 3000)&lt;/code&gt; for any (valid) value of &lt;code class="docutils literal"&gt;ax&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This has a couple of consequences. One is that memory consumption may be rather important: the array &lt;code class="docutils literal"&gt;a&lt;/code&gt; will occupy &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;math.prod((1000,&lt;/span&gt; 2000, &lt;span class="pre"&gt;3000))*8/(1024**3)&lt;/span&gt; = 44.7GB&lt;/code&gt;, but its sum along the first axis only &lt;code class="docutils literal"&gt;.0447GB&lt;/code&gt;. Thus we can easily store the final result in memory. Not so for the result of &lt;code class="docutils literal"&gt;cumulative_sum&lt;/code&gt; which also occupies &lt;code class="docutils literal"&gt;44.7GB&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;The second consequence, for chunked array libraries, is that the order in which one loads chunks and calculates the result matters. Consider the following diagram, where we have a 1D array of three elements. To calculate the final sum, we may load the chunks in any order and do not require access to any previous value except the running total - loading the first, third and finally second chunks, we obtain the correct sum of 4. However, for the cumulative sum, each element of the result depends on the previous element (and from there the sum of all prior elements of the array). Consequently, we must ensure we load the chunks according to their order in memory - if not, we will end up with an incorrect final result. A minimal criterion is that the final element of the cumulative sum should be the same as the sum, which is not the case here!&lt;/p&gt;
&lt;img alt="/images/cumulative_sumprod/ordermatters.png" class="align-center" src="https://blosc.org/images/cumulative_sumprod/ordermatters.png" style="width: 50%;"&gt;
&lt;/section&gt;
&lt;section id="consequences-for-numerical-precision"&gt;
&lt;h2&gt;Consequences for numerical precision&lt;/h2&gt;
&lt;p&gt;When calculating reductions, numerical precision is a common hiccup. For products, one can quickly overflow the data type - the product of &lt;code class="docutils literal"&gt;arange(1, 14)&lt;/code&gt; already overflows the maximum value of &lt;code class="docutils literal"&gt;int32&lt;/code&gt;. For sums, rounding errors incurred due to adding elements of a small size to the running total of a large size can quickly become significant. For this reason, Numpy will try to use pairwise summation to calculate &lt;code class="docutils literal"&gt;sum(a)&lt;/code&gt; - this involves breaking the array into small parts, calculating the sum on each small part (i.e. simply successively adding elements to a running total), and then recursively summing pairs of sums until the final result is reached. Each recursive sum operation thus involves the sum of two numbers of similar size, thus reducing the rounding errors incurred when summing disparate numbers. This algorithm also only has a minimal additional overhead compared to the naive approach and is eminently parallelisable. And it has a natural recursive implementation, something which computer scientists always find appealing even if only for aesthetic reasons!&lt;/p&gt;
&lt;img alt="/images/cumulative_sumprod/pairwise_sum.png" class="align-center" src="https://blosc.org/images/cumulative_sumprod/pairwise_sum.png" style="width: 50%;"&gt;
&lt;p&gt;Unfortunately, such an approach is not possible for cumulative sums since, as discussed above, order matters! One possibility is to use Kahan summation (the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Kahan_summation_algorithm"&gt;Wikipedia article is excellent&lt;/a&gt;), which does have additional costs (both in terms of FLOPS and memory consumption) although these are not prohibitive. One essentially keeps track of the rounding errors incurred with an auxiliary running total and uses this to correct the sum:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-1" name="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-1" href="https://blosc.org/posts/cumsum/#rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Kahan summation algorithm&lt;/span&gt;
&lt;a id="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-2" name="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-2" href="https://blosc.org/posts/cumsum/#rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;tot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;a id="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-3" name="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-3" href="https://blosc.org/posts/cumsum/#rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;tracker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;a id="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-4" name="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-4" href="https://blosc.org/posts/cumsum/#rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-4"&gt;&lt;/a&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-5" name="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-5" href="https://blosc.org/posts/cumsum/#rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-5"&gt;&lt;/a&gt;    &lt;span class="n"&gt;corrected_el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="c1"&gt;# nudge el with accumulated lost digits&lt;/span&gt;
&lt;a id="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-6" name="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-6" href="https://blosc.org/posts/cumsum/#rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-6"&gt;&lt;/a&gt;    &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tot&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;corrected_el&lt;/span&gt; &lt;span class="c1"&gt;# lose last few digits of el&lt;/span&gt;
&lt;a id="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-7" name="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-7" href="https://blosc.org/posts/cumsum/#rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-7"&gt;&lt;/a&gt;    &lt;span class="n"&gt;tracker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;corrected_el&lt;/span&gt;  &lt;span class="c1"&gt;# store the lost digits of el&lt;/span&gt;
&lt;a id="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-8" name="rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-8" href="https://blosc.org/posts/cumsum/#rest_code_fec1cc5aff714af7973a4f9ba8e2cc0c-8"&gt;&lt;/a&gt;    &lt;span class="n"&gt;tot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In implementation, we calculate the cumulative sum on a decompressed chunk in order and then carry forward the last element of the cumulative sum (i.e. the sum of the whole chunk) to the next chunk, incrementing the result of the cumulative sum by this carried-over value to give the &lt;em&gt;global&lt;/em&gt; cumulative sum. Thus, we can use Kahan summation between the small(er) values of the local chunk cumulative sum and the large(r) carried-forward running total to try and conserve precision.&lt;/p&gt;
&lt;p&gt;Unfortunately, we still observe discrepancies with respect to the Numpy implementation (which sums element-by-element essentially) of cumulative sum - but this also differs from the results of &lt;code class="docutils literal"&gt;np.sum&lt;/code&gt; due to the latter's use of pairwise summation! Finite arithmetic imposes an insuperable barrier: three different algorithms cannot guarantee agreement in every possible case. Since the Kahan sum approach has a slight overhead, we decided to junk it, as it did not improve precision sufficiently to justify its use.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="experiments"&gt;
&lt;h2&gt;Experiments&lt;/h2&gt;
&lt;p&gt;We performed some experiments comparing the new &lt;code class="docutils literal"&gt;blosc2.cumulative_sum&lt;/code&gt; function to Numpy's version for some large arrays of (of size &lt;code class="docutils literal"&gt;(N, N, N)&lt;/code&gt; for various values of &lt;code class="docutils literal"&gt;N&lt;/code&gt;). Since the working set is double the size of the input array (input + output), we expect to see significant benefits from Blosc2 compression and exploitation of caching. Indeed, once the working set size starts to approach the available RAM (32 GB), NumPy begins to slow down rapidly and when the working set exceeds memory and swap must be used NumPy becomes vastly slower.&lt;/p&gt;
&lt;img alt="/images/cumulative_sumprod/cumsumbench.png" class="align-center" src="https://blosc.org/images/cumulative_sumprod/cumsumbench.png" style="width: 50%;"&gt;
&lt;p&gt;The plot shows the average computation time for &lt;code class="docutils literal"&gt;cumulative_sum&lt;/code&gt; over the three different axes of the input array. The benchmark code may be found &lt;a class="reference external" href="https://github.com/Blosc/python-blosc2/blob/main/bench/ndarray/cumsum_bench.py"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Blosc2 achieves superior compression and enables computation on larger datasets by tightly integrating compression and computation and interleaving I/O and computation. The returns on such an approach are clear in an era of &lt;a class="reference external" href="https://arstechnica.com/gadgets/2025/11/spiking-memory-prices-mean-that-it-is-once-again-a-horrible-time-to-build-a-pc/"&gt;increasingly expensive RAM&lt;/a&gt; and thus increasingly desirable memory efficiency. As an array library catering in a unique way to this growing need, bringing Blosc2 into greater alignment with the interlibrary array API standard is of utmost importance to ease its integration into users' workflows and applications. We are thus especially pleased that the performance of the freshly-implemented cumulative reduction operations mandated by the Array API standard only underline the validity of chunkwise operations.&lt;/p&gt;
&lt;p&gt;The Blosc team isn't resting on our laurels either, as we continue to optimise the existing framework to accelerate computations further. The recent introduction of the &lt;code class="docutils literal"&gt;miniexpr&lt;/code&gt; library into the backend is the capstone to these efforts, and has made the compression/computation integration truly seamless, &lt;a class="reference external" href="https://ironarray.io/blog/miniexpr-powered-blosc2"&gt;bringing incredible speedups for memory-bound computations&lt;/a&gt;, justifying Blosc2's compression-first, cache-aware philosophy. This all allows Blosc2 to handle significantly larger working sets than other solutions, delivering high performance for both in-memory and on-disk datasets, even exceeding available RAM.&lt;/p&gt;
&lt;p&gt;If you find our work useful and valuable, we would be grateful if you could support us by &lt;a class="reference external" href="https://www.blosc.org/pages/donate/"&gt;making a donation&lt;/a&gt;. Your contribution will help us continue to develop and improve Blosc packages, making them more accessible and useful for everyone.  Our team is committed to creating high-quality and efficient software, and your support will help us to achieve this goal.&lt;/p&gt;
&lt;/section&gt;</description><category>blosc array-api reductions computation</category><guid>https://blosc.org/posts/cumsum/</guid><pubDate>Mon, 16 Feb 2026 10:32:20 GMT</pubDate></item></channel></rss>