#set document(title: "8.2 TileTensor layouts", author: "Modular Inc. / XYZ Homework") #set page(width: 8.5in, height: auto, margin: 1in) #import "@preview/cetz:0.5.2" #set text(font: ("STIX Two Text", "Libertinus Serif", "New Computer Modern"), size: 10.5pt, lang: "en") #show math.equation: set text(font: ("STIX Two Math", "New Computer Modern Math")) #set par(justify: true, leading: 0.62em, spacing: 0.9em) #set enum(spacing: 1.1em) // room between list items so tall inline fractions don't collide #set list(spacing: 1.1em) #set table(stroke: 0.5pt + rgb("#c7ccd3")) #let BLUE = rgb("#183B6F") // brand navy — section bars + example/solution labels (white on navy 11.09:1) #let ORANGE = rgb("#A94509") // brand primary-700 — AA-safe deep orange for TEXT (5.93:1 on white; raw brand #F37021 is 2.94:1 and must never carry text) #let RED = rgb("#DC2626") // brand error-600 #let GREEN = rgb("#059669") // brand success-600 (decoration only; small green text uses green-text #007942) #show heading.where(level: 1): it => block(width: 100%, above: 0pt, below: 16pt, fill: gradient.linear(BLUE, rgb("#2C5AA0")), inset: (x: 14pt, y: 12pt), radius: 3pt, text(fill: white, weight: "bold", size: 19pt, it.body)) #show heading.where(level: 2): it => block(width: 100%, above: 18pt, below: 10pt, fill: BLUE, inset: (x: 10pt, y: 6pt), radius: 2pt, text(fill: white, weight: "bold", size: 12pt, it.body)) #show heading.where(level: 3): it => text(fill: ORANGE, weight: "bold", size: 12.5pt, it.body) #show heading.where(level: 4): it => text(fill: BLUE, weight: "bold", size: 10.5pt, it.body) #let examplebox(label, title, body) = block(width: 100%, breakable: true, fill: rgb("#EFF1F5"), stroke: 0.5pt + rgb("#CFDDF0"), radius: 4pt, inset: 10pt, above: 12pt, below: 12pt)[ #block(below: 6pt)[#box(fill: BLUE, inset: (x: 6pt, y: 2pt), radius: 2pt, text(fill: white, weight: "bold", size: 8.5pt, label)) #h(0.4em) #strong[#title]] #body] // rail = decorative left rule (raw brand token); labelcolor = AA-safe label text shade #let notebox(label, rail, labelcolor, tint, body) = block(width: 100%, breakable: true, fill: tint, stroke: (left: 3pt + rail), inset: (left: 10pt, rest: 8pt), radius: (right: 4pt), above: 11pt, below: 11pt)[ #text(fill: labelcolor, weight: "bold", size: 7.5pt, tracking: 0.5pt)[#upper(label)] #linebreak() #body] #let solutionbox(body) = block(above: 4pt, below: 8pt)[ #text(fill: BLUE, weight: "bold", size: 8.5pt)[Solution] #linebreak() #body] #let figph(msg) = block(width: 100%, height: 60pt, fill: rgb("#f6f7f9"), stroke: (paint: rgb("#c7ccd3"), dash: "dashed"), radius: 4pt, inset: 10pt)[ #align(center + horizon, text(fill: rgb("#889"), style: "italic", size: 9pt, msg))] // Standardize inlined figure sizes: measure the natural CeTZ canvas, then scale to a // consistent envelope (aspect-aware; see build_typst.py FIG_* constants). Unlike the // print preamble, dimensions are FLOORED: in an editor a user can trim a figure to a // degenerate 1-D shape (a bare line), and w/h or tw/w would then divide by zero. #let _STD_W = 3.5 #let _WIDE_W = 5.6 #let _MAX_H = 3.4 #let _ASPECT_WIDE = 2.2 #let _UPSCALE_MAX = 1.15 #let stdfig(body) = context { let m = measure(body) let w = calc.max(m.width / 1in, 0.01) let h = calc.max(m.height / 1in, 0.01) let tw = if w / h > _ASPECT_WIDE { _WIDE_W } else { _STD_W } let s = calc.min(tw / w, _MAX_H / h, _UPSCALE_MAX) align(center, box(scale(x: s * 100%, y: s * 100%, reflow: true, body))) } #show figure: set block(breakable: false) #set figure(gap: 8pt) #show figure.caption: set text(size: 8.5pt, fill: rgb("#555")) == 8.2#h(0.6em)TileTensor layouts The TileTensor type is a flexible abstraction for working with multidimensional data. Every TileTensor has an associated #emph[layout] that describes how logical tensor coordinates are mapped to memory locations. This section describes how to create and work with layouts for TileTensor. In particular, it discusses the following types: - The #link("https://mojolang.org/docs/layout/tile_layout/Layout/")[tile\_layout.Layout] struct describes an arrangement of data in memory. A #emph[layout] is a function that maps a set of logical coordinates (like (#emph[x], #emph[y]) in a two-dimensional array) to a linear index value. Layouts can also describe more complex organizations, like a matrix subdivided into tiles. - #link("https://mojolang.org/docs/std/utils/coord/Coord/")[Coord] is a tuple-like container for storing integer coordinates that supports both compile-time and run-time values. It's used for defining logical coordinates and layout shapes, among other things. === Example code and imports You can find most of the code examples on this page in the #link("https://github.com/modular/modular/tree/mojo/v1.0.0b2/mojo/docs/code/manual/tile-tensor/layouts/")[public GitHub repo]. Some of the concepts presented here can be a little hard to grasp from static examples, so we recommend downloading the example code and experimenting. For brevity, the code examples on this page omit imports. Many of them are also short snippets, which need to be placed inside a function to be valid Mojo code. The following imports include all of the types and functions used on this page. from layout import Coord, coord, Idx, print\_layout from layout.tile\_layout import ( Layout, blocked\_product, col\_major, col\_major\_nested, row\_major, row\_major\_nested, zipped\_divide, ) === What's a Layout? A layout is a function that maps a set of logical coordinates to a single linear index value. Mojo's layout system is modeled on the layout abstraction in #link("https://github.com/NVIDIA/cutlass/blob/main/media/docs/cpp/cute/01_layout.md")[CuTe], part of NVIDIA's CUTLASS library. CuTe defines a small set of fundamental operations—composition, complement, product, and division—that combine algebraically into higher-level tiling operations. Mojo's Layout provides several of those higher-level operations directly, such as blocked\_product() and zipped\_divide(). For example, a layout could describe a 2x4 row-major matrix, or a 6x6 column-major matrix. var row\_major2x4 = row\_major\[2, 4\]() var col\_major6x6 = col\_major\[6, 6\]()Layouts are made up of two sets of numbers: shape and stride, where shape describes the logical coordinate space and the stride determines the mapping to the linear index value. A simple layout can be written as (#emph[shape]:#emph[stride]). For example, a contiguous vector of length 4 can be represented as (4:1): #figure(figph[Diagram: images layout 1d layout with strides], alt: "Diagram: images layout 1d layout with strides", caption: none) A 3x4 row-major layout can be represented as ((3, 4):(4, 1)). That is, the #emph[shape] is 3x4 and the #emph[strides] are 4 and 1. You can break this down into two sub-layouts or #emph[modes]: a row mode and a column mode: 3 rows with a stride of 4 (3:4, the first numbers from each tuple) and 4 columns with a stride of 1 (4:1, the second numbers from each tuple). The #link("https://mojolang.org/docs/layout/layout/print_layout/")[print\_layout()] function generates an ASCII diagram of any 2D layout, showing the coordinates on the outside and the corresponding index values in the grid. var row\_major3x4 = row\_major\[3, 4\]() print\_layout(row\_major3x4.to\_layout())Output: ((3, 4):(4, 1)) 0 1 2 3 +----+----+----+----+ 0 | 0 | 1 | 2 | 3 | +----+----+----+----+ 1 | 4 | 5 | 6 | 7 | +----+----+----+----+ 2 | 8 | 9 | 10 | 11 | +----+----+----+----+The coordinate to index mapping is performed by calculating the dot product of the logical coordinates and the corresponding strides. For example, given the coordinates (#emph[i, j]) and the layout shown above, the index value is \$i#emph[4 + j]1\$. So coordinate (1, 1) maps to 5, as shown in the diagram. The following example shows how to use a Layout to convert between coordinates and index values. var coords = coord\[1, 1\]() var idx = row\_major3x4(coords) print("index at (1, 1): ", idx) print("coordinates at index 7:", row\_major3x4.idx2crd(7))Output: index at coordinates (1, 1): 5 coordinates at index 7: (1, 3)As this example shows, the layout is a function that takes a set of integer coordinates and returns a single integer (the linear index). The Layout struct also provides an #link("https://mojolang.org/docs/layout/tile_layout/Layout/#idx2crd")[idx2crd()] method that transforms a linear index into a set of logical coordinates. When you use a TileTensor, you'll use the layout to define how the data is organized in memory, and then access data through the TileTensor interface. You won't usually need to convert coordinates to indexes yourself. But it's helpful to understand how it works. #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ #emph[Printing layouts] You can use print\_layout() to print a diagram of any 2D layout. This function currently only supports the legacy Layout type, so you'll need to call .to\_layout() to convert a TileTensor-style layout to a legacy layout: print\_layout(my\_layout.to\_layout())You can pass #emph[any] layout to the built-in print() function to print a string representation of the layout in the form of a (#emph[shape]:#emph[stride]) pair. ] ==== Coord and Idx: representing multidimensional coordinates Logical coordinates—and a layout's shape and size—are represented using the #link("https://mojolang.org/docs/std/utils/coord/Coord/")[Coord] type, a tuple-like structure that can hold both compile-time and run-time integer values. Each element of a Coord is either an integer value or a nested tuple. You can create a simple Coord with all compile-time values or all run-time values using the #link("https://mojolang.org/docs/std/utils/coord/coord/")[coord()] function: var comptime\_coords = coord\[4, 4\]() var runtime\_coords = coord\[DType.int32\]((a, b, c))You can mix compile-time and run-time values: use a plain Int (or any CoordLike) for run-time values, and #link("https://mojolang.org/docs/std/utils/coord/Idx/")[Idx\[N\]] for compile-time integers. comptime comptime\_int = Idx\[columns\] \# Compile-time int from parameter value comptime comptime\_int2 = Idx\[4\] \# Compile-time int from Int literal var runtime\_int = rows \# Run-time int from dynamic value var mixed\_shape = Coord((rows, Idx\[columns\])) var mixed\_layout = row\_major((rows, Idx\[columns\]))You can create nested Coord values by passing nested tuples to the constructor—either tuples of values constructed with Idx, or tuples of Coord values: var shape1 = Coord((Idx\[6\], Idx\[8\])) var shape2 = Coord((coord\[2, 2\](), coord\[3, 4\]())) var shape3 = Coord((shape1, shape2)) print(shape3)((6, 8), ((2, 2), (3, 4)))The #link("https://mojolang.org/docs/std/utils/coord/")[coord] module provides a number of functions for working with Coords. ==== Modes A layout has one or more #emph[modes], where a mode is a shape:stride pair. For example, the 1D vector layout (8:1) has a single mode: 8 elements with a stride of 1: #figure(figph[Diagram: images layout 1d layout], alt: "Diagram: images layout 1d layout", caption: none) The 2D row-major matrix layout ((2, 4):(4, 1)) has two modes, 2:4 (the first numbers from each tuple) and 4:1 (the second numbers from each tuple). Taking them right to left, the second mode describes 4 columns with a stride of one. The first mode specifies that there are two of these groups with a stride of 4: #figure(figph[Diagram: images layout 2d layout with strides], alt: "Diagram: images layout 2d layout with strides", caption: none) In a column-major layout, the row number varies the fastest, so a column-major 2x4 matrix has the layout ((2, 4):(1, 2)) and looks like this: #figure(figph[Diagram: images layout 2d col major layout with strides], alt: "Diagram: images layout 2d col major layout with strides", caption: none) A layout's #emph[rank] is the number of dimensions in its shape, which is equivalent to the number of top-level modes in the layout. A rank-1 (or 1D) layout describes a vector. A rank-2 layout describes a 2D matrix, and so on. A layout's #emph[size] is defined as the product of all of the modes in the layout's shape. To put it another way, it's the number of elements that the layout addresses: that is, the #emph[domain] of the layout function. Modes can also be nested to represent more complicated strides along a dimension. For example, the layout (8:1) represents a 1D vector of 8 elements. #figure(figph[Diagram: images layout 1d layout], alt: "Diagram: images layout 1d layout", caption: none) The layout (((4, 2):(1, 4))) is #emph[also] a 1D vector of 8 elements. The extra set of parentheses indicates a nested or hierarchical mode. Instead of being represented by a single mode like 8:1, this layout's single dimension is represented by the multi-mode (4, 2):(1, 4): #figure(figph[Diagram: images layout 1d multi modal layout], alt: "Diagram: images layout 1d multi modal layout", caption: none) Note that in the nested modes, there's no notion of row and column. You can think of the first mode as the "inner" mode (defining a group) and the next mode as an "outer" mode (defining a repeat of the group) as shown above. A set of nested modes (a #emph[multi-mode]) counts as a single mode when considering the parent layout's rank. For example, the layouts (8:1) and (((4, 2):(1, 4))) are both 1D, or rank-1 layouts. A layout's #emph[flat rank] counts the #strong[total] number of modes in the layout, so (8:1) has a flat rank of 1, while (((4, 2):(1, 4))) has a flat rank of 2. This gets more interesting when we move to two dimensions. Consider the following 2D layouts: #figure(figph[Diagram: images layout multi modal layout], alt: "Diagram: images layout multi modal layout", caption: none) Layouts A and B are both 2D matrix layouts with the same overall 2D shape, but with the elements in a different order. Layout B is #emph[tiled], so instead of being in row-major or column-major order, four consecutive indices are grouped into each 2x2 tile. This is sometimes called #emph[tile-major order]. We can break this tiled layout into two modes, one for the rows and one for the columns: - Layout B has a row mode of (2, 2):(1, 4). We can further break this into two sub-modes: the inner mode, 2:1, defines a group of two rows with a stride of one. The outer mode, 2:4, specifies that the group occurs twice with a stride of 4. - The column has the mode (2, 2):(2, 8). Once again we can break this into two sub-modes: (2:2) defines a group of two columns with a stride of two, and the group occurs twice with a stride of 8 (2:8). If all of those modes are swimming before your eyes, take a moment to study the figure and trace out the strides yourself. === Making layouts There are multiple ways to create layouts. The #link("https://mojolang.org/docs/layout/tile_layout/row_major/")[row\_major()] and #link("https://mojolang.org/docs/layout/tile_layout/col_major/")[col\_major()] functions are probably the simplest ways to create a layout. The row\_major() function creates a generalized row-major layout: that is, the rightmost coordinate varies the fastest. The col\_major() function creates a generalized column-major layout, where the leftmost coordinate varies the fastest. There are two ways to call these functions. You can either pass in a set of compile-time dimensions as variadic parameter values: comptime row\_major3d = row\_major\[4, 4, 4\]() comptime col\_major3d = col\_major\[4, 4, 4\]()Or you can pass in a Coord defining the shape of the layout. var from\_coords = row\_major(coord\[6, 8\]())Defining a layout using Coord values lets you define both compile-time and run-time dimensions, as discussed in the next section. ==== Run-time layouts Layouts with compile-time known dimensions are more efficient, especially on GPU. However, sometimes you don't know the shape of the data—or you know the width of the data but not the number of rows. As described in the section on Coord and Idx, you can define a Coord using the Coord constructor or the coord() convenience function. Using Coord, you can define layouts with all compile-time dimensions, all run-time dimensions, or a mixture of the two. \# Layout with compile-time dimensions comptime row\_major\_comptime = row\_major(coord\[16, 8\]()) \# Layout with run-time dimensions var a, b = 4, 8 var row\_major\_runtime = row\_major(coord\[DType.int32\]((a, b))) \# Mixed layout with one run-time dimension and one compile-time dimension var row\_major\_mixed = row\_major((rows, Idx\[columns\])) ==== Tiled layouts Sometimes you need a more complicated memory layout. For example, to improve the efficiency of memory accesses, you may want to load your data into memory using a tile-major layout. "tile-major" describes a layout where a the overall layout is divided into rectangular "tiles" and elements inside a tile are laid out consecutively in memory. The following is an example of a tile-major layout: (((3, 2), (2, 5)):((1, 6), (3, 12))) 0 1 2 3 4 5 6 7 8 9 +----+----+----+----+----+----+----+----+----+----+ 0 | 0 | 3 | 12 | 15 | 24 | 27 | 36 | 39 | 48 | 51 | +----+----+----+----+----+----+----+----+----+----+ 1 | 1 | 4 | 13 | 16 | 25 | 28 | 37 | 40 | 49 | 52 | +----+----+----+----+----+----+----+----+----+----+ 2 | 2 | 5 | 14 | 17 | 26 | 29 | 38 | 41 | 50 | 53 | +----+----+----+----+----+----+----+----+----+----+ 3 | 6 | 9 | 18 | 21 | 30 | 33 | 42 | 45 | 54 | 57 | +----+----+----+----+----+----+----+----+----+----+ 4 | 7 | 10 | 19 | 22 | 31 | 34 | 43 | 46 | 55 | 58 | +----+----+----+----+----+----+----+----+----+----+ 5 | 8 | 11 | 20 | 23 | 32 | 35 | 44 | 47 | 56 | 59 | +----+----+----+----+----+----+----+----+----+----+This 6x10 tile-major is indexed vertically in 2 groups of 3 rows (3, 2) : (1, 6) and horizontally in 5 groups of 2 columns (2, 5):(3, 12). If you know a layout's shape and strides in advance, you can create the layout using the Layout constructor. But calculating the strides for a complicated layout is far from intuitive. An easier way to generate this type of tiled layout is the #link("https://mojolang.org/docs/layout/tile_layout/blocked_product/")[blocked\_product()] function. blocked\_product() takes two layouts: a #emph[tile] layout and a #emph[tiler] layout: essentially, every element in the tiler layout is replaced by a tile. The following example generates the same tiled layout using blocked\_product(). It also prints out the two input layouts. def use\_blocked\_product(): print("blocked product") \# Define 3x2 tile var tile = col\_major\[3, 2\]() \# Define a 2x5 tiler var tiler = col\_major\[2, 5\]() var blocked = blocked\_product(tile, tiler) print("Tile:") print\_layout(tile.to\_layout()) print("\\nTiler:") print\_layout(tiler.to\_layout()) print("\\nTiled layout:") print\_layout(blocked.to\_layout()) print()Output: Tile: ((3, 2):(1, 3)) 0 1 +---+---+ 0 | 0 | 3 | +---+---+ 1 | 1 | 4 | +---+---+ 2 | 2 | 5 | +---+---+ Tiler: ((2, 5):(1, 2)) 0 1 2 3 4 +----+----+----+----+----+ 0 | 0 | 2 | 4 | 6 | 8 | +----+----+----+----+----+ 1 | 1 | 3 | 5 | 7 | 9 | +----+----+----+----+----+ Tiled layout: (((3, 2), (2, 5)):((1, 6), (3, 12))) 0 1 2 3 4 5 6 7 8 9 +----+----+----+----+----+----+----+----+----+----+ 0 | 0 | 3 | 12 | 15 | 24 | 27 | 36 | 39 | 48 | 51 | +----+----+----+----+----+----+----+----+----+----+ 1 | 1 | 4 | 13 | 16 | 25 | 28 | 37 | 40 | 49 | 52 | +----+----+----+----+----+----+----+----+----+----+ 2 | 2 | 5 | 14 | 17 | 26 | 29 | 38 | 41 | 50 | 53 | +----+----+----+----+----+----+----+----+----+----+ 3 | 6 | 9 | 18 | 21 | 30 | 33 | 42 | 45 | 54 | 57 | +----+----+----+----+----+----+----+----+----+----+ 4 | 7 | 10 | 19 | 22 | 31 | 34 | 43 | 46 | 55 | 58 | +----+----+----+----+----+----+----+----+----+----+ 5 | 8 | 11 | 20 | 23 | 32 | 35 | 44 | 47 | 56 | 59 | +----+----+----+----+----+----+----+----+----+----+As you can see, blocked\_product() combines two simple layouts to generate a more complex one. The layout produced by blocked\_product() places items in the same tile consecutively in memory. But addressing individual tiles is inconvenient. The #link("https://mojolang.org/docs/layout/tile_layout/zipped_divide/")[zipped\_divide()] function, by contrast, tiles a 2D tensor such that each column represents a tile worth of data. var base = row\_major\[6, 4\]() var result = zipped\_divide\[coord\[2, 2\]()\](base) print\_layout(base.to\_layout()) print\_layout(result.to\_layout())((6, 4):(4, 1)) 0 1 2 3 +----+----+----+----+ 0 | 0 | 1 | 2 | 3 | +----+----+----+----+ 1 | 4 | 5 | 6 | 7 | +----+----+----+----+ 2 | 8 | 9 | 10 | 11 | +----+----+----+----+ 3 | 12 | 13 | 14 | 15 | +----+----+----+----+ 4 | 16 | 17 | 18 | 19 | +----+----+----+----+ 5 | 20 | 21 | 22 | 23 | +----+----+----+----+ (((2, 2), (3, 2)):((4, 1), (8, 2))) 0 1 2 3 4 5 +----+----+----+----+----+----+ 0 | 0 | 8 | 16 | 2 | 10 | 18 | +----+----+----+----+----+----+ 1 | 4 | 12 | 20 | 6 | 14 | 22 | +----+----+----+----+----+----+ 2 | 1 | 9 | 17 | 3 | 11 | 19 | +----+----+----+----+----+----+ 3 | 5 | 13 | 21 | 7 | 15 | 23 | +----+----+----+----+----+----+Similar to blocked\_product(), the first tile of the new layout consists of the values from the top-left corner of the base layout (0, 4, 1, 5). However, in this case, the tiled layout is reshaped so that each column holds a single tile worth of data. This layout makes it very easy to address individual tiles of data. === Nested layouts The tiled layouts shown above derive a nested layout from simpler ones using blocked\_product() and zipped\_divide(). You can also construct a nested layout directly from a nested shape using #link("https://mojolang.org/docs/layout/tile_layout/row_major_nested/")[row\_major\_nested()] and #link("https://mojolang.org/docs/layout/tile_layout/col_major_nested/")[col\_major\_nested()]. These constructors compute row-major (or column-major) strides over the #emph[flattened] shape, then re-nest the strides to match the input shape. Nested layouts are useful when a tensor's logical structure is itself a grid of fragments—for example, the per-thread register tiles consumed by GPU matrix-multiply instructions (NVIDIA's MMA, #emph[matrix multiply-accumulate], and AMD's MFMA, #emph[matrix fused multiply-add]), or the outputs of blocked\_product(). The outer sub of each mode indexes the grid of fragments, and the inner sub indexes within a fragment. For a nested shape ((a, b), (c, d)): - row\_major\_nested() produces strides ((b\*c\*d, c\*d), (d, 1)). - col\_major\_nested() produces strides ((1, a), (a\*b, a\*b\*c)). The following example builds a 2x2 outer grid of 3x4 inner fragments in both orderings: \# A re-nested row-major layout: a 2x2 outer grid of 3x4 inner \# fragments. Shape ((2, 3), (2, 4)) flattens to (2, 3, 2, 4), \# producing row-major strides (24, 8, 4, 1), re-nested as \# ((24, 8), (4, 1)). var nested = row\_major\_nested( Coord(Coord(Idx\[2\], Idx\[3\]), Coord(Idx\[2\], Idx\[4\])) ) print\_layout(nested.to\_layout()) \# The column-major variant re-nests col-major strides over the same \# flattened shape: (1, 2, 6, 12), re-nested as ((1, 2), (6, 12)). var nested\_col = col\_major\_nested( Coord(Coord(Idx\[2\], Idx\[3\]), Coord(Idx\[2\], Idx\[4\])) ) print\_layout(nested\_col.to\_layout())Output: (((2, 3), (2, 4)):((24, 8), (4, 1))) 0 1 2 3 4 5 6 7 +----+----+----+----+----+----+----+----+ 0 | 0 | 4 | 1 | 5 | 2 | 6 | 3 | 7 | +----+----+----+----+----+----+----+----+ 1 | 24 | 28 | 25 | 29 | 26 | 30 | 27 | 31 | +----+----+----+----+----+----+----+----+ 2 | 8 | 12 | 9 | 13 | 10 | 14 | 11 | 15 | +----+----+----+----+----+----+----+----+ 3 | 32 | 36 | 33 | 37 | 34 | 38 | 35 | 39 | +----+----+----+----+----+----+----+----+ 4 | 16 | 20 | 17 | 21 | 18 | 22 | 19 | 23 | +----+----+----+----+----+----+----+----+ 5 | 40 | 44 | 41 | 45 | 42 | 46 | 43 | 47 | +----+----+----+----+----+----+----+----+ (((2, 3), (2, 4)):((1, 2), (6, 12))) 0 1 2 3 4 5 6 7 +----+----+----+----+----+----+----+----+ 0 | 0 | 6 | 12 | 18 | 24 | 30 | 36 | 42 | +----+----+----+----+----+----+----+----+ 1 | 1 | 7 | 13 | 19 | 25 | 31 | 37 | 43 | +----+----+----+----+----+----+----+----+ 2 | 2 | 8 | 14 | 20 | 26 | 32 | 38 | 44 | +----+----+----+----+----+----+----+----+ 3 | 3 | 9 | 15 | 21 | 27 | 33 | 39 | 45 | +----+----+----+----+----+----+----+----+ 4 | 4 | 10 | 16 | 22 | 28 | 34 | 40 | 46 | +----+----+----+----+----+----+----+----+ 5 | 5 | 11 | 17 | 23 | 29 | 35 | 41 | 47 | +----+----+----+----+----+----+----+----+The result types are the RowMajorNestedLayout and ColMajorNestedLayout aliases. These constructors are currently restricted to all-static (compile-time) leaf dimensions and a single level of nesting. For flat shapes, use #link("https://mojolang.org/docs/layout/tile_layout/row_major/")[row\_major()] or #link("https://mojolang.org/docs/layout/tile_layout/col_major/")[col\_major()] instead. Once you have a nested layout, you can build a TileTensor from it and use tile() to extract individual fragments. See Extracting a tile in the tensors guide. === Non-contiguous layouts All of the examples so far have been dense layouts, where all of the elements are contiguous in memory. However, layouts can also describe sparse logical arrays. For example, a (4:2) layout is a sparse 1D array: #figure(figph[Diagram: images layout 1d sparse layout], alt: "Diagram: images layout 1d sparse layout", caption: none) A layout's #emph[cosize] is the size of the layout's codomain, which you can think of as the size of the smallest contiguous array that can contain all of the layout's elements. The cosize is the largest linear index value generated by the layout plus 1. So in the example in Figure 8, the layout has a size of 4, but a cosize of 7. === Alternate coordinates Coordinates for layouts can be written in the same format as the shape Coord. For example, given a layout with the shape ((2, 2), (2, 2)) shown earlier: #figure(figph[Diagram: images layout multi modal layout coords], alt: "Diagram: images layout multi modal layout coords", caption: none) The coordinates for the layout in Figure 9 above can be written ((#emph[i, j]), (#emph[k, l])). However, this layout can also be addressed as a logical 2D matrix. So ((0, 1), (0, 1)) and (2, 2) are both valid coordinates that map to the same index. In fact, this is true for any layout: the layout can be addressed with 1D or 2D coordinates as well as its "natural" coordinates. When mapping coordinates, the dimensions are traversed in #emph[colexicographical] order (that is, a generalized column-major order, where the leftmost coordinate varies fastest). Table 1 shows how different 1D and 2D coordinates map to the "natural" coordinates of the ((2, 2), (2, 2)) shape shown above: #figure(table( columns: 3, align: left, inset: 6pt, table.header([1D], [2D], [Natural]), [0], [(0, 0)], [((0, 0), (0, 0))], [1], [(1, 0)], [((1, 0), (0, 0))], [2], [(2, 0)], [((0, 1), (0, 0))], [3], [(3, 0)], [((1, 1), (0, 0))], [4], [(0, 1)], [((0, 0), (1, 0))], [5], [(1, 1)], [((1, 0), (1, 0))], [6], [(2, 1)], [((0, 1), (1, 0))], [7], [(3, 1)], [((1, 1), (1, 0))], [8], [(0, 2)], [((0, 0), (0, 1))], [...], [...], [...], [15], [(3, 3)], [((1, 1), (1, 1))], )) The layout function takes any of these coordinates and returns the linear index value. The layout's idx2crd() method takes a linear index and returns the natural coordinates for that index.