CUTLASS Python DSL 任务调度框架的统一内存分配器:task_scheduling.memory 模块深度解析
2026/9/16 21:02:39 网站建设 项目流程

CUTLASS Python DSL 任务调度框架的统一内存分配器:task_scheduling.memory 模块深度解析

【免费下载链接】cutlassCUDA Templates and Python DSLs for High-Performance Linear Algebra项目地址: https://gitcode.com/GitHub_Trending/cu/cutlass

导读

本文围绕 CUTLASS Python DSL 中cutlass.experimental.task_scheduling.memory模块展开,该模块是 Task Scheduling(TS)框架的统一内存分配器,为 SMEM(共享内存)与 TMEM(张量内存)提供声明式布局机制:资源以"分配对象"的形式声明需求,分配器负责计算扁平化的物理布局,并支持基于流水线阶段的别名复用(phase-based aliasing)。读完本文,你将掌握SmemAllocation/TmemAllocation声明模型、SmemAllocator/TmemAllocator的布局算法、别名分组与 mbarrier 统一管理机制,以及它们与TaskManager、资源钩子的完整调用链,可直接在自己的 TS 内核中落地使用。

说明:本文对应的 RST 文档 media/docs/pythonDSL/ts_api/memory.rst 通过 Sphinxautomodule指令自动生成 API 页面,其真实内容即模块源码中的 docstring 与实现。所有示例与结论均基于仓库源码 python/CuTeDSL/cutlass/experimental/task_scheduling/memory.py。

模块定位:TS 框架中的统一内存布局层

TS(Task Scheduling)框架用于以"资源—任务"图的形式描述持久化 kernel 的数据流与调度:每个资源(如SmemAbTmemCGmemAb)声明自己需要的内存,任务(Task)在资源之间搬运、生产与消费数据。memory模块解决其中的核心问题——当多个资源共存于同一物理存储(SMEM 的字节空间或 TMEM 的列空间)时,如何确定每个资源区域的偏移量

从架构上看,该模块的角色可以概括为:

  • 声明SmemAllocation(SMEM 字节区域)与TmemAllocation(TMEM 列区域)是纯数据类,描述"我需要多大的区域、对齐要求是什么、可选的数据类型是什么";
  • 布局_LayoutAllocator基类提供通用的 bump allocation 与别名分组算法,SmemAllocator(按字节、带对齐)与TmemAllocator(按列、无对齐)是其具体实现;
  • 物化SmemAllocator.allocate()在 DSL trace 阶段发射单个cutlass.Array(..., space=cutlass.AddressSpace.smem)统一分配,并可通过assign_barrier_ptrs()在统一块内预分配流水线 mbarrier 存储;
  • 上下文传递ResourceContext(冻结 dataclass)被嵌入StageInfo,向各资源携带smem_basetmem_ptr_i32

模块头部 docstring 给出了典型用法(见 memory.py):

allocator = SmemAllocator() # Resources expose their allocations for alloc in resource.get_smem_requirements(): allocator.add(alloc) # Optional: alias allocations whose lifetimes don't overlap. # Each inner list is a "phase" — allocations within a phase coexist # and get sequential offsets. Different phases reuse the same # physical region. allocator.add_alias_group([ [smem_ab._alloc_a, smem_ab._alloc_b], # phase 1: coexist [epilogue._alloc_scratch], # phase 2: reuses ]) allocator.compute_layout() # pure Python — sets .offset on each alloc # allocator.allocate() called later at DSL-trace time to emit the # cutlass.Array(..., space=cutlass.AddressSpace.smem) op.

这一流程的关键设计是布局计算与分配发射分离compute_layout()是纯 Python 运算,只修改各分配对象的.offset字段,便于在宿主端调试与验证;allocate()才在 DSL trace 时(@cute.kernel/@cute.jit内)真正发射分配指令。

声明模型:SmemAllocation 与 TmemAllocation

SmemAllocation:命名 SMEM 区域

SmemAllocation是一个@dataclass,字段如下(见 memory.py):

字段类型默认值说明
namestr必填人类可读标签,用于调试与使用报告
size_bytesint0所需字节数;当为 0 且提供dtype时自动计算
alignmentint128字节对齐要求(默认 128,面向 TMA)
dtypeAnyNone可选元素类型;设置后SmemAllocator.get()可返回带类型的cutlass.Array
countint1元素个数;与dtype配合自动计算size_bytes,并作为get()的形状
offsetint0(init=False距 SMEM 基址的字节偏移,由compute_layout()写入

__post_init__实现了尺寸自动推导:当dtype非空且size_bytes == 0时,size_bytes = count * (dtype.width // 8)。这意味着声明时既可以显式给出字节数,也可以只给类型与个数让分配器推导。

TmemAllocation:命名 TMEM 列区域

TmemAllocation结构更简单(见 memory.py):

字段类型说明
namestr人类可读标签
num_columnsint所需 TMEM 列数
offsetint距 TMEM 基址的列偏移,由compute_layout()写入

TMEM 以"列"为最小单元且不涉及字节对齐,因此没有alignment/dtype字段。

ResourceContext:传递给资源的只读上下文

ResourceContext@dataclass(frozen=True),被嵌入StageInfo(见 memory.py):

  • smem_base:统一 SMEM 分配的cutlass.Array基址指针;未使用SmemAllocator时为None
  • tmem_ptr_i32:共享内存中的cutlass.Array[Int32, 1],由nvvm.tcgen05_alloc写入,资源通过tmem_ptr_i32.load()+ 偏移推导各自的 TMEM 地址;未使用 TMEM 时为None

资源在resources.py中通过initialize_runtime_state_internal(context=...)接收该上下文(见 resources.py),task.pyTask的多个run变体同样以context: Optional[ResourceContext]为参数传递(见 task.py)。

布局引擎:_LayoutAllocator 基类

_LayoutAllocator是所有分配器的公共基类(见 memory.py),提供三组能力。

声明 API

  • add(alloc):注册一个独立的(不参与别名的)分配对象,返回该对象;
  • add_resource(resource):调用子类钩子_get_requirements(resource)取回资源暴露的所有分配并逐个注册;
  • add_alias_group(phases):声明若干相位(phase)共享同一物理区域。每个相位是一个分配对象列表——同一相位内的分配共存(依次获得顺序偏移),不同相位复用同一基址区域,区域尺寸按max(phase_total)取所有相位中的最大值。要求至少 2 个相位,否则抛出ValueError("Alias group must contain at least 2 phases")。被别名化的分配会以id(a)记入_aliased_ids,在布局时排除出"独立块"。

布局算法(compute_layout)

compute_layout()一次性的分配算法(见 memory.py)按以下步骤执行:

  1. 将注册的分配划分为独立分配别名组两类;
  2. 每个别名组构成一个块:尺寸为max(phase_total),对齐为组内成员对齐的最大值;
  3. 所有块按对齐降序排序,实现自然的紧凑打包;
  4. 依次 bump-allocate:offset = align_up(cursor, alignment);别名组内每个相位都从同一基址开始布局。

其辅助函数_align_up要求对齐值必须是 2 的幂(见 memory.py),否则断言失败:

def _align_up(value: int, alignment: int) -> int: assert alignment > 0 and (alignment & (alignment - 1)) == 0, ( f"alignment must be a power of 2, got {alignment}" ) return (value + alignment - 1) & ~(alignment - 1)

compute_layout()只能调用一次,重复调用会抛出RuntimeError("compute_layout() already called")。布局完成后可通过total属性获取总占用单元数(SMEM 为字节、TMEM 为列),但必须在compute_layout()之后访问,否则抛RuntimeError("Call compute_layout() first")

子类只需实现三个钩子:_alloc_size(以分配器单位为单位的尺寸)、_alloc_alignment(默认 1,即无需对齐)、_get_requirements(从资源取分配列表)。

使用报告

print_usage_report()在布局后输出人类可读的分配表(见 memory.py),内容包括:

  • 每个分配的名称(别名化的名称带*标记)、尺寸、对齐(如存在)、偏移与结束地址;
  • 每个别名组的相位构成明细;
  • 汇总行:TotalAlias savings(即"不别名化时的总大小 − 实际总大小",直观展示别名复用节省了多少空间)。

子类通过_unit_label()"B""cols")、_report_tag()(如"smem-layout"/"tmem-layout")、_report_title()_report_extra_lines()定制报告内容。

SmemAllocator:对齐感知的 SMEM 布局与统一分配

SmemAllocator是 SMEM 的具体分配器(见 memory.py),它在_LayoutAllocator之上增加了三个职责。

1. 数据分配与获取

  • add_resource(resource)调用resource.get_smem_requirements()收集数据分配,同时累积流水线 barrier SMEM(见下文第 2 点);
  • get(alloc):在allocate()之后调用,依据alloc.dtype/alloc.count派生带类型的cutlass.Arrayaddrspace=3,即 SMEM 地址空间)。若声明时未提供dtype,抛出TypeError,提示改用get_as_type()
  • get_as_type(alloc, dtype, count=1):以自定义类型重新解释访问(reinterpret cast),适用于声明的 dtype 与实际访问类型不同、或分配仅以原始size_bytes声明的情形;
  • get_typed_ptr(alloc, dtype, count=1)已废弃的旧接口,内部直接转调get_as_type()

2. Barrier SMEM 的统一管理

模块级常量定义了 barrier 的硬件约束(见 memory.py):

_MBARRIER_BYTES_PER_STAGE = 2 * 8 # 2 × Int64 per pipeline stage _MBARRIER_ALIGNMENT = 8 # alignment required by mbarrier hardware

即每个流水线阶段需要 2 个Int64(16 字节),mbarrier 硬件要求 8 字节对齐。SmemAllocator将 barrier 存储纳入统一块,避免create_pipeline()为每个资源单独分配:

  • 对单个资源:当resource.pipeline_config存在、barrier_ptr is None(即 barrier 存储尚未预分配)且资源不属于任何PipelineGroup时,按cfg.num_stages * 16字节累加_barrier_bytes
  • add_pipeline_group(group):注册一个PipelineGroup的 barrier 需求,按(N + 1) × S × 16字节计算(N 为成员数,S 为阶段数)——"多端"每个成员各一套 barrier-set,外加一套共享 barrier-set。注意:组内成员的数据SMEM 仍需通过add_resource()注册,但其per-resource barrier 记账会被自动跳过
  • barrier_smem_bytes属性报告 barrier 占用总字节数。

3. allocate() 与 assign_barrier_ptrs():DSL trace 阶段的物化

allocate()(见 memory.py)必须在内核 DSL trace 上下文(@cute.kernel/@cute.jit)中调用,其行为:

  • 未先compute_layout()则抛RuntimeError;重复调用直接返回已缓存的_smem_base
  • 若数据与 barrier 均为 0,返回None(不发射分配);
  • 统一块大小为align_up(total, 8) + barrier_bytes,块对齐取所有分配对齐的最大值(默认 128);
  • 发射单个cutlass.Array(cutlass.Uint8, unified_bytes, space=cutlass.AddressSpace.smem, alignment=block_align)作为基址,同时覆盖数据区与 barrier 区。

assign_barrier_ptrs()(见 memory.py)随后为每个需要 barrier 的资源在统一块内(数据区之后、按 8 字节对齐)通过cute.make_ptr(cutlass.Int64, ..., mem_space=cutlass.AddressSpace.smem)创建指针,并用dataclasses.replacePipelineConfig替换为设置了barrier_ptr的新实例,从而阻止create_pipeline()另行分配。PipelineGroup条目同样获得一个横跨(N + 1) × S个 barrier 条目的单一指针。

4. TMEM 指针基础设施

add_tmem_ptr(alloc)(见 memory.py)注册一个用于存放 32 位 TMEM 基址指针的 SMEM 分配。它像普通分配一样参与布局,但同时被记为_tmem_ptr_alloc,使TaskManager.setup_resources_and_tasks能自动推导带类型的cutlass.Array指针并填充ResourceContext.tmem_ptr_i32,调用方无需手工干预。

TmemAllocator:TMEM 列布局(不发射分配指令)

TmemAllocator是 TMEM 列的分配器(见 memory.py),与SmemAllocator的关键区别是:它只计算列偏移,不发射任何分配内在指令——因为 TMEM 没有硬件分配指令,内核必须手工调用nvvm.tcgen05_alloc,并传入总列数total_tmem_columns

模块 docstring 给出了标准用法:

tmem_alloc = TmemAllocator() tmem_alloc.add_resource(tmem_c_resource) tmem_alloc.compute_layout() num_cols = tmem_alloc.total_tmem_columns # pass to tcgen05_alloc

_alloc_size返回alloc.num_columns_get_requirements调用resource.get_tmem_requirements()total_tmem_columns属性在布局后给出总列数。TMEM 无对齐概念,因此_alloc_alignment沿用基类默认值 1。内核执行nvvm.tcgen05_alloc后会把基址指针写入 SMEM 中的tmem_ptr_i32,资源再通过tmem_ptr_i32.load()+ 各自.offset派生自己的 TMEM 地址。

与资源、TaskManager 的集成调用链

memory模块并非孤立组件,它与 TS 框架的其余部分形成了清晰的调用链。

资源侧:声明钩子

资源基类在 resources.py 中定义了四个需求声明钩子:

  • get_smem_requirements() -> list[SmemAllocation]:返回资源所需的数据 SMEM 分配,默认[];返回的分配对象应存为实例属性,供后续读取.offset
  • get_tmem_requirements() -> list[TmemAllocation]:返回资源所需的 TMEM 列分配,默认[]
  • get_producer_requirements()/get_consumer_requirements():返回 ProducerWork / ConsumerWork 阶段实际访问的分配子集(默认None表示全部),供穷举检查器构建生产者/消费者专属别名映射,以精确判断别名区域上的读写竞争。

资源类还通过getattr(resource, "pipeline_group", None)getattr(resource, "pipeline_config", None)暴露 barrier 记账所需的信息(见 memory.py)。PipelineGroup在 pipeline_group.py 中也会提示用户用SmemAllocator.add_pipeline_group()注册组级 barrier。

TaskManager 侧:统一入口

TaskManager.__init__接收smem_allocatortmem_allocatortmem_ptr_i32三个可选参数(见 task_manager.py):

  • 当传入SmemAllocator时,setup_resources_and_tasks()会调用smem_allocator.allocate(),并将得到的smem_base通过ResourceContext/StageInfo传递给所有资源;
  • 当传入TmemAllocator时,print_and_verify()阶段会打印其使用报告;
  • 传入的tmem_ptr_i32(由nvvm.tcgen05_alloc写入的 SMEMInt32标量)被放入ResourceContext,供资源推导 TMEM 地址。

TaskManager 还提供容量校验:smem_capacity_bytes(默认(228−1) × 1024 = 232448 B,对应 SM100/SM90)校验数据字节 + barrier 字节之和是否超出每 CTA 上限;tmem_capacity_columns(默认 512,SM100)校验总列数是否超出每 SM 上限(见 task_manager.py)。默认值对应具体架构,其他架构需显式覆盖。

工程要点与最佳实践

结合源码可以总结出以下使用要点:

  1. 先 compute_layout 后 allocate:布局计算是纯 Python 操作,可在 trace 前完成与调试;allocate()才发射cutlass.Array分配,必须在 DSL trace 上下文内调用。totalprint_usage_report()get()等接口都会在时机不对时抛出明确的RuntimeError/TypeError
  2. 别名组是内存节省的核心手段:把生命周期不重叠的分配放进不同相位,例如 epilogue 的 scratch 缓冲区复用主计算阶段的 A/B SMEM。print_usage_report()Alias savings行可直接量化收益。但要注意:别名化区域上的并发访问属于竞争,TS 框架通过穷举交错检查器(exhaustive_deadlock_race_check,默认开启)配合get_producer_requirements()/get_consumer_requirements()子集声明来检测这类别名竞争。
  3. dtype 声明带来类型安全:在SmemAllocation上声明dtypecount后,get()可免去重复指定类型;需要重新解释访问(或仅声明了原始字节)时用get_as_type()get_typed_ptr()已废弃。
  4. barrier 与数据统一分配:让SmemAllocator统一管理 mbarrier 存储(通过add_resource的自动记账、add_pipeline_groupallocateassign_barrier_ptrs),可避免create_pipeline()为每个资源分散分配 barrier SMEM;barrier_smem_bytestotal_smem_bytes分别报告两类占用。
  5. TMEM 分配不发射指令TmemAllocator只负责计算列偏移,tcgen05_alloc由内核手工调用,并把基址写入 SMEM 中的tmem_ptr_i32,资源通过load()+ 偏移寻址。

总结

task_scheduling.memory是 CUTLASS Python DSL 任务调度框架中连接"资源声明"与"物理布局"的枢纽:它以SmemAllocation/TmemAllocation描述需求,以_LayoutAllocator的相位别名布局算法实现 SMEM/TMEM 的紧凑复用,以SmemAllocator.allocate()发射统一分配并托管 pipeline mbarrier,以ResourceContextStageInfo中传递smem_basetmem_ptr_i32。理解这一模块,是掌握 TS 框架内存规划、别名复用与容量校验的关键一步。

【免费下载链接】cutlassCUDA Templates and Python DSLs for High-Performance Linear Algebra项目地址: https://gitcode.com/GitHub_Trending/cu/cutlass

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

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

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

立即咨询