Mojo 布局(Layout)系统实战:从行主序到分块(Tiled)布局的完整示例解析
2026/9/12 3:28:23 网站建设 项目流程

Mojo 布局(Layout)系统实战:从行主序到分块(Tiled)布局的完整示例解析

【免费下载链接】mojoThe Modular Platform (includes MAX & Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo

导读

本文基于 Modular MAX 仓库中的 max/examples/layouts 示例目录,系统讲解 Mojo 语言中Layout类型(内存布局抽象)的核心概念与实战用法。你将掌握如何用Layout描述张量的内存访问模式、如何在逻辑坐标与线性内存索引之间互相转换、以及如何通过分块(tiling)与布局代数构建 GPU/CPU 友好的缓存高效访问模式。文末还给出了 Pixi 与 Bazel 两种运行示例的方式,并深入源码 layout.mojo 剖析底层实现原理。

一、示例概览:这个目录讲什么

max/examples/layouts目录是官方 "Introduction to layouts" 教程的配套代码,包含两个可直接运行的 Mojo 程序:

文件主题演示的核心能力
basic_layouts.mojo基础布局行主序/列主序、坐标与索引互转、嵌套(层次化)布局
tiled_layouts.mojo分块布局布局构造函数、tile_to_shapeblocked_productmake_ordered_layoutmake_layoutzipped_divide

目录中同时提供了运行环境声明 pixi.toml(依赖max包)和 Bazel 构建文件 BUILD.bazel(将两个示例编译为mojo_binary并注册了modular_run_binary_test测试),说明这套示例既可用于本地学习,也被仓库 CI 作为二进制运行测试的一部分。

两个示例文件均以# DOC: max/layout/layouts.mdx注释标明它们与官方文档的对应关系,是教程内容最直接的可运行落点。

二、环境准备与运行方式

2.1 使用 Pixi 运行(推荐)

仓库在 pixi.toml 中预定义了运行任务。只要本机安装了 Pixi,进入该目录后执行:

pixi run mojo basic_layouts pixi run mojo tiled_layouts

pixi.toml中的任务定义如下:

[tasks] basic_layouts = "mojo run basic_layouts.mojo" tiled_layouts = "mojo run tiled_layouts.mojo"

环境方面,该 workspace 声明了conda-forge与 Modular MAX nightly 频道,支持osx-arm64linux-64linux-aarch64三个平台,唯一的运行依赖是max = "*"。首次运行 Pixi 会自动解析 pixi.lock 锁定文件并创建包含 Mojo 编译器的环境。

2.2 使用 Bazel 运行

仓库是 Bazel 管理的 monorepo,在仓库根目录可直接构建并运行示例:

./bazelw run //max/examples/layouts:basic_layouts ./bazelw run //max/examples/layouts:tiled_layouts

对应的 BUILD.bazel 展示了 Mojo 目标的组织方式:每个示例是一个mojo_binary,依赖//max:layout(MAX 的布局模块)与@mojo//:std(标准库),并配套一个modular_run_binary_test用于在 CI 中验证程序可正常执行。

2.3 直接使用 mojo CLI

如果不使用包管理器,也可直接调用 Mojo 编译器:

mojo run basic_layouts.mojo mojo run tiled_layouts.mojo

需要保证环境中已安装可用的 Mojo 编译器,并能解析from layout import ...的导入路径。

三、基础布局:row-major 与 column-major

3.1 两个核心工厂方法

basic_layouts.mojo 首先演示了两种最经典的线性存储顺序:

def row_and_column_major(): print("row major and column major") var l2x4row_major = Layout.row_major(2, 4) print_layout(l2x4row_major) print() var l6x6col_major = Layout.col_major(6, 6) print_layout(l6x6col_major) print()
  • Layout.row_major(2, 4):行主序,最后一个维度在内存中变化最快,得到 shape(2, 4)、stride(4, 1)
  • Layout.col_major(6, 6):列主序(Fortran/MATLAB 风格),第一个维度变化最快,得到 stride(1, 6)

3.2 源码层面的实现证据

在 MAX 布局模块源码 layout.mojo 中,row_major(shape: IntTuple)的实现是:

return Layout(shape, reverse(prefix_product(reverse(shape))))

即对 shape 从右向左做前缀积再反转,从而得到(4, 1)这类"最后一维 stride 为 1"的行主序步长。而col_major的默认构造路径(见 layout.mojo)在未显式提供 stride 时,直接使用prefix_product(self.shape)计算列主序步长——这正是文档注释里"empty stride 表示列主序"的约定。

细节提示:Layoutshapestride字段都是IntTuple类型(见 layout.mojo),stride表示沿每个维度移动一个单位时需要在内存中跳过的元素数。

四、坐标与索引的互相转换

4.1 正向映射:坐标 → 线性索引

Layout__call__方法(layout.mojo)实现坐标到线性索引的核心映射:

def coords_to_index(): print("coordinates to index") var l3x4row_major = Layout.row_major(3, 4) print_layout(l3x4row_major) var coords: IntTuple = [1, 1] var idx = l3x4row_major(coords) print("index at (1, 1): ", idx) print("coordinates at index 7:", l3x4row_major.idx2crd(7)) print()

(3, 4)行主序布局,(1, 1)的线性索引为1*4 + 1 = 5。底层通过crd2idx计算,核心公式即坐标与步长的内积(inner_product)。

4.2 反向映射:索引 → 坐标

idx2crd(layout.mojo)是__call__的逆运算,把线性索引还原为逻辑坐标。示例中idx2crd(7)(3, 4)行主序布局返回(1, 3)

这两个方法被标注为@always_inline("nodebug"),属于"零成本抽象"——在源码注释中明确写出其设计目标之一是建立 logical/physical index 之间的零开销映射(见 layout.mojo)。crd2idx的具体实现在 int_tuple.mojo,它对单元素坐标、未知值(UNKNOWN_VALUE)、自定义 stride 等场景做了专门的快速路径处理。

4.3 可视化工具print_layout

示例中大量使用的print_layout来自布局模块(layout.mojo),它会把 2D 布局打印成带内存索引的数字表格,方便直观看到每个逻辑坐标落在内存的哪个位置。注意其实现会abort非 2D 布局,因此只适合可视化二维场景。

五、嵌套(层次化)布局:Layout 的直接构造

5.1 显式 shape/stride 构造

布局不限于二维,Layout支持任意层次的嵌套 shape 与 stride。basic_layouts.mojo 演示了两种嵌套方式:

def nested_modes(): print("nested modes") var layout_a = Layout([4, 4], [4, 1]) print_layout(layout_a) print() var layout_b = Layout( [[2, 2], [2, 2]], [[1, 4], [2, 8]], ) print_layout(layout_b) print()
  • layout_a:shape 为([4], [4])、stride 为([4], [1]),即二维行主序布局;
  • layout_b:shape 为([[2, 2], [2, 2]])、stride 为([[1, 4], [2, 8]]),是一个两层嵌套的布局——外层 2×2,每个元素内部又是 2×2 的子块。这种层次化结构是分块矩阵运算的基础。

从源码看,Layout的构造器(layout.mojo)接受(shape, stride)两个IntTupleIntTuple的元素本身也可以是元组,从而天然支持嵌套维度。

六、分块(Tiled)布局的五种构建手法

tiled_layouts.mojo 通过五个函数系统演示了"布局代数"(layout algebra)的核心操作,这些操作共同服务于把大张量拆成 cache 友好的小块。

6.1 用构造函数直接创建分块布局

def use_layout_constructor(): print("layout constructor") var tiled_layout = Layout( [[3, 2], [2, 5]], # shape [[1, 6], [3, 12]], # strides ) print_layout(tiled_layout) print()

[[3, 2], [2, 5]]表示一个两层嵌套 shape:外层 2×2(每个元素是一个子块),内层子块 3×2 与 2×5。对应 stride[[1, 6], [3, 12]]定义了每个层级的内存步长。

6.2tile_to_shape:把 tile 平铺到目标 shape

def use_tile_to_shape(): print("tile to shape") var tts = tile_to_shape(Layout.col_major(3, 2), [6, 10]) print_layout(tts) print()

tile_to_shape(tile, target_shape)3×2的列主序 tile 重复平铺,填满6×10的目标形状(即 2 行 × 5 列的 tile 网格)。源码实现(layout.mojo)会逐维计算target_shape / tile_shape得到 tiler 的尺寸,并要求目标形状必须能被 tile 整除,否则直接abort报错;tiler 的存储顺序默认列主序,也可通过order参数指定。

6.3blocked_product:tile × tiler 的分块乘积

def use_blocked_product(): print("blocked product") # Define 2x3 tile var tile = Layout.col_major(3, 2) # Define a 2x5 tiler var tiler = Layout.col_major(2, 5) var blocked = blocked_product(tile.copy(), tiler.copy()) print("Tile:") print_layout(tile) print("\nTiler:") print_layout(tiler) print("\nTiled layout:") print(blocked) print()

blocked_product(layout_a, layout_b)内层 tilelayout_a)与外层 tilerlayout_b)组合成层次化分块布局:外层每个元素被替换为一块 tile。在 layout.mojo 的实现中,它先做logical_productzip_modes,把两组 mode 交织成(tile_mode, tiler_mode)的嵌套结构;coalesce_output参数可控制是否合并输出维度。源码文档给出了一个直观例子:blocked_product(row_major(2,2), row_major(2,3))生成 4×6 的 2×2 分块布局,其内存索引表清楚地展示了 2×2 块内地址连续、块间跳变的特征。

6.4make_ordered_layout:按指定顺序生成紧凑布局

def use_make_ordered_layout(): print("make ordered layout") var ordered = make_ordered_layout( [[3, 2], [2, 5]], # shape [[0, 2], [1, 3]], # order ) print(ordered)

make_ordered_layout(shape, order)根据遍历优先级order(数值越小优先级越高)生成**紧凑(bijective)**布局,即每个索引恰好对应一个坐标。源码实现(layout.mojo)调用compact_order(shape, order)计算 stride。例如文档示例中 shape(2,3,4,5)搭配 order(1,4,3,2)会得到 stride(1,40,10,2)——维度 0 变化最快(stride 1),维度 3 次之(stride 2),依此类推。

6.5make_layoutzipped_divide:拼接与除法

def use_make_layout(): print("make layout") var layout1 = Layout([2, 3], [3, 1]) var layout2 = Layout([4, 5], [5, 1]) var combined = make_layout(layout1, layout2) print_layout(combined)

make_layout将多个布局的 shape/stride首尾拼接成一个更高阶的布局(layout.mojo),是构建层次化布局的基础组装原语。

def use_zipped_divide(): print("zipped divide") # Create layouts var base = Layout.row_major(6, 8) var pattern = Layout([2, 2]) var result = zipped_divide(base, pattern) print_layout(result)

zipped_divide(layout_a, layout_b)hierarchical_unzip的别名(layout.mojo),把6×8的行主序布局按2×2的 pattern划分为 3×4 个分块的层次化布局——可以理解为blocked_product的逆操作。它同样支持传入LayoutList以应用多个分块模式(layout.mojo)。

七、这些布局能力用在何处

Layout在 MAX 生态中是张量访存的核心抽象。从 max/kernels/src/layout/init.mojo 的模块说明可以看到它的两种典型定位:

  • 张量坐标 → 线性内存索引的映射:用于把逻辑张量坐标映射到内存地址;
  • GPU 线程 → 数据块的映射:用于把 GPU 线程映射到数据的 tile 上。

分块相关函数(blocked_producttile_to_shapezipped_divide等)的源码注释均明确指向"矩阵乘法等张量运算中的 cache 高效利用"这一目标。在实际仓库中,Layout被广泛用于LayoutTensor(带显式内存布局的高性能张量类型,见 layout_tensor.mojo),并贯穿于max/kernels/src下各类 kernel 的实现中(例如 conv.mojo、tile_layout.mojo 等)。

如果你希望继续深入,官方还提供了更高阶的配套示例 max/examples/layout_tensor/layout_tensor_examples.mojo,演示LayoutTensor与 GPU 内核的结合使用。

八、小结

通过本目录的两个示例,你可以完整掌握 MojoLayout的核心使用链路:

  1. 基础存储顺序Layout.row_major/Layout.col_major覆盖行主序与列主序两种经典存储;
  2. 索引映射__call__(坐标→索引)与idx2crd(索引→坐标)构成双向转换,是零成本抽象的核心;
  3. 嵌套布局(shape, stride)直接构造支持任意层级嵌套,为分块做准备;
  4. 布局代数tile_to_shapeblocked_productmake_ordered_layoutmake_layoutzipped_divide分别对应平铺、组合、有序紧凑化、拼接与划分五种变换,是构建 cache 友好访存模式、支撑高性能张量运算的基础工具集。

动手实践时,推荐先pixi run mojo basic_layouts跑通基础示例,再用pixi run mojo tiled_layouts观察分块布局的内存索引表格;配合 layout.mojo 源码中每个函数自带的示例与输出,即可从"会调用"进阶到"懂原理"。

【免费下载链接】mojoThe Modular Platform (includes MAX & Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询