<?xml version="1.0" ?><entry xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom" xmlns:planet="http://planet.intertwingly.net/"><id>http://blog.ezyang.com/?p=10519</id><link href="http://blog.ezyang.com/2025/08/you-could-have-invented-cute-hierarchical-layout-but-maybe-not-the-rest-of-it/" rel="alternate" type="text/html"/><link href="http://blog.ezyang.com/2025/08/you-could-have-invented-cute-hierarchical-layout-but-maybe-not-the-rest-of-it/#comments" rel="replies" type="text/html"/><link href="http://blog.ezyang.com/2025/08/you-could-have-invented-cute-hierarchical-layout-but-maybe-not-the-rest-of-it/feed/atom/" rel="replies" type="application/atom+xml"/><title xml:lang="en-US">You could have invented CuTe hierarchical layout (but maybe not the rest of it?)</title><summary xml:lang="en-US">CuTe is a C++ library that aims to make dealing with complicated indexing easier. A key part of how it does this is by defining a Layout type, which specifies how to map from logical coordinates to physical locations (CuTe likes to say layouts are &quot;functions from integers to integers.&quot;) In fact, CuTe layouts are […]</summary><content type="xhtml" xml:lang="en-US"><div xmlns="http://www.w3.org/1999/xhtml"><div class="document">



<p>CuTe is a C++ library that aims to make dealing with complicated indexing easier.  A key part of how it does this is by defining a <a class="reference external" href="https://docs.nvidia.com/cutlass/media/docs/cpp/cute/01_layout.html">Layout</a> type, which specifies how to map from logical coordinates to physical locations (CuTe likes to say layouts are &quot;functions from integers to integers.&quot;) In fact, CuTe layouts are a generalization of PyTorch strides, which say you always do this mapping by multiplying each coordinate with its respective stride and summing them together, e.g., <tt class="docutils literal">i0 * s0 + i1 * s1 + ...</tt>. Although NVIDIA's docs don't spell it out, the CuTe's generalization here is actually very natural, and in this blog post I'd like to explain how you could have invented it (on a good day).</p>
<p>First, a brief recap about strides. PyTorch views allow us to reinterpret the physical layout of a tensor in different ways, changing how we map logical coordinates into physical locations.  For example, consider this 2-D tensor:</p>
<pre class="literal-block">&gt;&gt;&gt; torch.arange(4).view(2, 2)
tensor([[0, 1],
        [2, 3]])
&gt;&gt;&gt; torch.arange(4).view(2, 2).stride()
(2, 1)
</pre>
<p>The physical memory reads <tt class="docutils literal">0, 1, 2, 3</tt>, and if I want to know what the value at coordinate (0, 1) is (row 0, col 1), I compute <tt class="docutils literal">0 * 2 + 1 * 1</tt>, which tells me I should read out the value at index 1 in physical memory.  If I change the strides, I can change the order I read out the physical locations.  For example, if I transpose I have:</p>
<pre class="literal-block">&gt;&gt;&gt; torch.arange(4).view(2, 2).T
tensor([[0, 2],
        [1, 3]])
&gt;&gt;&gt; torch.arange(4).view(2, 2).T.stride()
(1, 2)
</pre>
<p>The physical memory hasn't changed, but now when we read out coordinate (0, 1), we compute <tt class="docutils literal">0 * 1 + 1 * 2</tt>, which tells me I should read the value at index 2 (which is indeed what I see at this coordinate!)</p>
<p>PyTorch also allows us to &quot;flatten&quot; dimensions of a tensor, treating them as a 1D tensor.  Intuitively, a 2-D tensor flattened into a 1-D one involves just concatenating all the rows together into one line:</p>
<pre class="literal-block">&gt;&gt;&gt; torch.arange(4).view(2, 2).view(-1)
tensor([0, 1, 2, 3])
</pre>
<p>We should be able to do this for the transpose too, getting <tt class="docutils literal"><span class="pre">tensor([0,</span> 2, 1, 3])</tt>, but instead, this is what you get:</p>
<pre class="literal-block">&gt;&gt;&gt; torch.arange(4).view(2, 2).T.view(-1)
Traceback (most recent call last):
  File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt;
RuntimeError: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.
</pre>
<p>The dreaded &quot;use reshape instead&quot; error!  The error is unavoidable under PyTorch striding: there is <em>no</em> stride we can select that will cause us to read the elements in this order (0, 2, 1, 3); after all, <tt class="docutils literal">i0 * s0</tt> is a pretty simple equation, we can't simultaneously have <tt class="docutils literal">1 * s0 == 2</tt> and <tt class="docutils literal">2 * s0 == 1</tt>.</p>
<p>Upon learning this, an understandable reaction is to just shrug, assume that this is impossible to fix, and move on with your life.  But today, you are especially annoyed by this problem, because you were only trying to flatten N batch dimensions into a single batch dimension so that you could pass it through a function that only works with one batch dimension, with the plan of unflattening it when you're done.  It doesn't matter that this particular layout is inexpressible with strides; you aren't going to rely on the layout in any nontrivial way, you just care that you can flatten and then unflatten back to the original layout.</p>
<p>Imagine we're dealing with a tensor of size <tt class="docutils literal">(2, 2, 2)</tt> where the strides for dim 0 and dim 1 were transposed as <tt class="docutils literal">(2, 4, 1)</tt>. It should be OK to flatten this into a tensor <tt class="docutils literal">(4, 2)</tt> and then unflatten it back to <tt class="docutils literal">(2, 2, 2)</tt>. Intuitively, I'd like to &quot;remember&quot; what the original sizes and strides are, so that I can go back to them. Here's an idea: let's just store the original size/stride as a <em>nested</em> entry in our size tuple. So instead of the size <tt class="docutils literal">(4, 2)</tt>, we have <tt class="docutils literal">((2, 2), 2)</tt>; and now analogously the stride can simply be <tt class="docutils literal">((2, 4), 1)</tt>. When I write <tt class="docutils literal">(2, 2)</tt> as the &quot;size&quot; of a dimension, I really just mean the product 4, but there is some <em>internal structure</em> that affects how I should index its inside, namely, the strides <tt class="docutils literal">(2, 4)</tt>. If I ask for the row at index 2, I first have to translate this 1D coordinate into a 2D coordinate (1, 0), and then apply the strides to it like before.</p>
<p>Well, it turns out, this is exactly how CuTe layouts work!  In CuTe, sizes/strides are hierarchical: a size is actually a tree of ints, where the hierarchy denotes internal structure of a dimension that you can address linearly (in fact, everything by default can be addressed in a 1-D linear way, even if its an N-D object.)  The <a class="reference external" href="https://docs.nvidia.com/cutlass/media/docs/cpp/cute/01_layout.html">documentation of Layout</a> does say this... but I actually suffered a lot extracting out the high level intuition of this blog post, because CuTe uses co-lexicographic ordering when linearizing (it iterates over coordinates (0,0), (1,0), (2,0), etc. rather than in the more normal lexicographic order (0,0), (0,1), (0,2)).  This leads to some truly deranged example code where they print a 2D matrix in conventional lexicographic ordering, and then turn around and say, &quot;But wait, if I have the layout take care of translating the 1D coordinate into an ND coordinate, it is colexicographic!!&quot;:</p>
<pre class="literal-block">&gt; print2D(s2xh4)
  0    2    1    3
  4    6    5    7
# sure, why not?

&gt; print1D(s2xh4)
  0    4    2    6    1    5    3    7
# wtf???
</pre>
<p>In any case, if you want to engage with the documentation, <tt class="docutils literal">s2xh4</tt> is the important example to pay attention to for understanding the nested semantics. However, note the example is smeared across like five sections and also you need to know about the co-lexicographic thing to understand why the examples print the way they do.</p>
</div></div></content><updated planet:format="August 22, 2025 06:48 AM">2025-08-22T06:48:48Z</updated><published planet:format="August 22, 2025 06:48 AM">2025-08-22T06:48:48Z</published><category scheme="http://blog.ezyang.com" term="PyTorch"/><author><name>Edward Z. Yang</name><uri>http://ezyang.com</uri></author><source><id>http://blog.ezyang.com/feed/atom/</id><link href="http://blog.ezyang.com" rel="alternate" type="text/html"/><link href="http://blog.ezyang.com/feed/atom/" rel="self" type="application/atom+xml"/><subtitle xml:lang="en-US">the arc of software bends towards understanding</subtitle><title xml:lang="en-US">ezyang’s blog</title><updated planet:format="December 20, 2025 10:55 PM">2025-12-20T22:55:05Z</updated><planet:format>atom10</planet:format><planet:bozo>false</planet:bozo><planet:css-id>edward-z-yang</planet:css-id><planet:items_per_page>60</planet:items_per_page><planet:encoding>utf-8</planet:encoding><planet:name>Edward Z. Yang</planet:name><planet:days_per_page>0</planet:days_per_page><planet:http_last_modified>Sat, 3 Jan 2026 23:01:37 GMT</planet:http_last_modified><planet:http_status>200</planet:http_status></source></entry>