系列主线反复出现一个主题:数据在哪里停留、谁决定它何时出片。这里聚焦片上 Scratchpad Memory(SRAM)如何在渲染与计算之间承担数据驻留,避免频繁的 DRAM 读写。
Apple 在 Metal 2 中引入 Tile Shading,利用 TBDR 架构下的片上 Tile 存储;Qualcomm 在 Vulkan 中通过扩展开放了类似机制。两个实现之外,延伸至主机平台(Xbox eSRAM)、数据中心平台(Nvidia DSM / L2 Persistent Cache)与开发工程实践。
一、Metal Tile Shading 核心机制与实践
Apple A 系列与 M 系列 GPU 均采用 TBDR 架构,具备统一内存模型与固定容量的 Tile SRAM。Metal 2 所引入的 Tile Shader 是该架构下的关键机制:开发者可在单个 Render Pass 内交替执行 draw 调用与 per-tile 的计算逻辑,直接复用片上内存完成数据交换,省去显存往返。
1.1 核心构件
Tile Shader 是运行在 Tile 局部内存上的 Compute Shader,通过 dispatchThreadsPerTile(...) 发起,处理单位通常是 32×32 像素的 Tile,需通过专门的 MTLTileRenderPipelineDescriptor 配置 pipeline。
Image Block 是二维像素网格结构,供 Tile Shader 与 Fragment Shader 共享访问。在 Metal Shader 中通过 threadgroup_imageblock<T> 声明,使用时需设置 imageblockSampleLength 指定每像素所占内存。卷积和抗锯齿可以用它做中间缓冲,MSAA resolve 同样适用。
Persistent Threadgroup Memory 以 threadgroup [[persistent]] 声明,存储每 Tile 的常驻共享数据,生命周期覆盖整个 Tile 执行过程。典型用途包括光源列表和统计直方图。
选择标准比较直接:Per Pixel Data 用 Image Block(如 Attachment),Per Tile Data 用 Persistent Threadgroup Memory(如 LightList)。
Apple 官方文档《Tailor your apps for Apple GPUs and tile-based deferred rendering》对此有明确划分:threadgroup memory 适合非结构化数据,imageblock 更适合图像数据;imageblock 能在同一 Render Pass 的 fragment 阶段与 tile 阶段之间传递数据,并在整个 tile 生命周期内跨 draw 与 dispatch 保持。Tech Talk 604 对 persistent threadgroup memory 的定位一致:tile shading 独有的存储,适合放在 tile 范围内恒定的数据,典型例子是 culling 之后的光源列表。
1.2 配置与流程
-
Render Pass 配置:需在 MTLRenderPassDescriptor 中预设 image block 与 persistent memory 的大小。但 Tile 尺寸由系统自动选择,Apple 推荐选择可容纳最大 image block 的 tile size。
rpDesc.imageblockSampleLength = 8; // 每像素 8 字节 rpDesc.threadgroupMemoryLength = 4*1024; // 每 Tile 分配 4KBTech Talk 604 给出的依据是:A11 上 imageblock 的容量为 32 KB,tile size 分三档,选能容纳 imageblock 的最大一档,可以摊薄 GPU 图元处理阶段的固定开销。
-
管线状态与执行控制:Metal 支持在同一 encoder 中动态切换 render pipeline。支持串行调用多个 Tile Shader,配合 draw → dispatch → draw 流程组织复杂 Pass。pipeline 切换后若 persistent 内存需求变动,应调用
setThreadgroupMemoryLength:重新配置。[encoder setRenderPipelineState: tilePipelineState_A]; [encoder dispatchThreadsPerTile: tileThreadgroupSize]; -
Tile 内通信机制:Tile Shader 与 Fragment Shader 可通过共享 image block 完成数据传递。只需保证命令提交顺序,即 draw 写 → dispatch 读,或 dispatch 写 → draw 读,Metal 将自动维护一致性。
1.3 同步与一致性
Metal Tile 内存不属于常规资源(MTLBuffer / MTLTexture),故不支持 memoryBarrier() / MTLFence 等同步接口。
- 隐式同步模型:
- 每 Tile 阶段切换处,GPU 内部 scheduler 会自动完成 drain & tag 处理,保证写后读顺序;
- 命令顺序保持性,由 driver 保证 encode 阶段命令顺序即为 GPU 执行顺序;
- draw ↔ dispatch 切换时,数据一致性自动维护;
- draw → draw 场景中无同步保障,若存在依赖,需插入一个空 dispatch 作为同步点或拆分 Render Pass。
Apple Tech Talk 604 对以上隐式模型有逐条对应的表述:tile dispatch 可以与 draw 任意交错,按 API 提交顺序执行;Apple 保证 dispatch 之前提交的 draw 结果在 dispatch 执行时可见,每个 dispatch 的结果对其后的 draw 或 dispatch 可见,但 draw 与 draw 之间明确没有这项保证。演讲原文 "No such guarantee is made between draws" 即第四条需要空 dispatch 做同步点的直接出处。
1.4 应用场景示例
以 Bloom 为例:大半径模糊传统上需要多次全屏 Pass、频繁读写纹理,Tile Shading 下有三条处理路径:
- Guard-Band Clamp(小模糊≤2px):首Pass将Tile边缘像素复制到内部,卷积采样时直接读取副本数据,零显存带宽消耗,SRAM存储少量边缘像素实现无缝效果。
- 分离卷积多Pass(大模糊≥5px):首Pass用Tile Shader完成小半径(5x5)模糊并写入纹理,次Pass全屏Compute叠加更大模糊。虽多一次带宽开销,但确保结果精确且实现简单(Bloom Demo采用此方案)。
- 邻域Strip预取(中模糊2-5px):首Pass预加载相邻Tile边缘条带到当前Tile,卷积时直接访问预取数据,带宽消耗与半径成正比,效果基本无缝。
这些策略都在解决同一个问题:Tile 边界处理。Metal 缺乏跨 Tile 读取机制,开发者必须自行扩展 Tile 范围或复制数据;Qualcomm 方案通过"tile apron"参数实现类似功能。
1.5 调试技巧与常见陷阱
Metal Tile Shading 存在几个常见配置错误:
- 若使用 imageblock 却未设置 imageblockSampleLength,会编译失败;
- Persistent memory 配置不足可能造成 GPU 崩溃;
- Tile pipeline 切换后未重设 threadgroup memory length 会造成脏数据干扰;
- Draw → Draw 之间共享 image block 失败需通过 Tile Shader 过渡;
- 使用 Xcode GPU Capture 工具可直观查看 Tile memory 分布与状态。
1.6 跨 Pass 与跨 GPU 的考虑
Tile Shading 生命周期限制在 Render Pass 内。若需跨 Pass 保留数据,需借助 .store / .load 语义与显存交换,Metal Tile 内存不具备跨 Pass 可见性。
非 Apple 架构(如 discrete GPU)上,Tile Shader 会降级为 Compute Shader 模式,自动转译为等价调度逻辑。尽管失去性能优势,但功能语义保持一致。
二、Adreno Tile Shading & Tile Memory Heap
在移动端架构演进过程中,Qualcomm 逐步将 GMEM(Graphics Memory)从固定用途的内部缓存转化为应用层可控的片上内存资源,并通过 Vulkan 扩展形式对外开放。其中 VK_QCOM_tile_shading 与 VK_QCOM_tile_memory_heap 两个扩展分别承担了调度与内存管理职能,为开发者提供了接近硬件层级的控制能力。
2.1 架构视角:Tile Memory 的外露化
传统 GMEM 仅由 driver 控制,用于 TBDR 模式中的 Tile 渲染缓存。扩展启用后,GMEM 可被视为一种受控的共享缓存区域,开发者可用于跨阶段存储渲染中间结果,从而减少 DRAM 往返。这种设计与 Xbox One 的 eSRAM 类似,也符合 Vulkan "可控性优先"的原则:GMEM 的容量和 Bank 结构直接暴露给应用层,分配与释放均由开发者驱动。
2.2 VK_QCOM_tile_shading
该扩展主要解决两个问题:在 Vulkan 中显式描述 Tile 级调度行为;为 Tile 计算引入更明确的生命周期控制机制。
-
启用配置:通过
VkRenderPassTileShadingCreateInfoQCOM在 Render Pass 创建阶段启用 Tile Shading 与 per-tile execution 模式,tileApronSize可设置横/纵向 apron 区域(如 {2,2}),用于滤波边界处理。 -
命令序列:声明一个 per-tile 区域,block 内所有指令均将在每个 Tile 上独立执行。此处的 Dispatch Tile 是根据 Tile 网格自动展开,Draw 指令也不会被可见性剔除,确保每个 Tile 至少执行一次。该机制适合用于初始化、预清除或调试覆盖等 Tile 局部操作。
vkCmdBeginPerTileExecutionQCOM(...); vkCmdDispatchTileQCOM(...); vkCmdDrawIndirect(...); // 可选 vkCmdEndPerTileExecutionQCOM(...);Khronos 规范对此的措辞明确:per-tile execution 内记录的 draw 会绕过 visibility pass 的剔除结果,保证对 render pass 中的每个 tile 都执行一次。可见性信息只服务于常规 draw 的跳过逻辑,per-tile 区块不受其影响。
-
Shader 功能增强:
-
扩展引入若干 SPIR-V 存储修饰符与内建变量,使 Shader 可以直接读写 tile memory:
layout(tile_memory):用于声明 tile-local attachment;TileOffsetQCOM:当前 Tile 起始坐标;TileDimensionQCOM:Tile 尺寸;TileApronSizeQCOM:硬件级 apron 大小。
规范对 apron 的语义边界也写得很死:apron 像素由 LoadOp 初始化、被 render pass 内的 draw 更新,但永远不会被 StoreOp 写回附件,shader 对其只能读不能写。另有一条值得记录的版本信息:该扩展 Revision 2(2025-08-13)把 tileShadingApron 从必选能力降级为可选 feature,原因是早期 Adreno 驱动曾声明支持扩展却未实现 apron,规范明确要求应用先查询 feature 再启用。
-
支持的功能包括:
- Fragment/Compute Shader 访问 tile-local image2D;
- 原子操作(若
tileShadingAtomicOps被支持); - 读取/写入 color/depth/stencil 附件的 Tile 局部内容;
- 在 Tile 内部实现 OIT、局部合成、定制 G-Buffer 布局等。
-
-
对比 Metal:与 Metal 的封装式 imageblock 不同,Qualcomm 扩展提供对附件纹理的直接访问。其粒度更粗、控制力更强,但同步需求更显式,且行为更贴近底层硬件。在 Metal 中,仅能通过 imageblock 间接缓存数据,且无法访问 color/depth/stencil 附件。
2.3 VK_QCOM_tile_memory_heap
此扩展将 GMEM 暴露为 Vulkan 设备的一个 Memory Heap 类型,开发者可通过标准的内存分配与绑定流程管理其使用周期。该机制将 GMEM 视作一段带地址管理的显式 L1.5 缓存,开发者需显式控制绑定区域与同步关系,负担虽重但可提升性能。
- 基本流程:首先通过
vkAllocateMemory分配带VK_MEMORY_HEAP_TILE_MEMORY_BIT_QCOM属性的设备内存,作为 tile memory heap 的物理空间。随后将该内存通过vkBindImageMemory/vkBindBufferMemory绑定到附件或缓冲对象。每次使用前调用vkCmdBindTileMemoryQCOM(...)显式激活,使其成为当前 Render Pass 的 GMEM 映射区域。调用vkCmdBindTileMemoryQCOM(NULL)可使当前绑定失效,Submit 结束后若未特殊支持 queueSubmitBoundary,GMEM 会被标记为无效。 - 生命周期约束:默认模式下 memory 有效期限定在同一 vkQueueSubmit 内。若设备支持 queueSubmitBoundary,可在下一 Submit 中继续使用。不可跨队列或跨帧共享,也不能绕过 driver 的映射控制。
Khronos 扩展提案文档对以上约束有原文对应:heap 内容只在同一次 vkQueueSubmit 的 command buffer submission batch 内保持有效,batch 执行结束即被丢弃,queueSubmitBoundary 为 VK_TRUE 时失效边界才扩展到整个 queue submit;tile memory 内容只在同一 Queue 内执行的 command buffer 之间可见。提案另有一条直接影响资源规划的约束:tile memory 相比其他 heap 很小,可能只放得下一两张 image,因此预期用法就是让多个对象 alias 同一段区间、按执行时序切片复用(5.4 节的 Alias 建议出处在此)。
2.4 同步与一致性
Vulkan 的同步哲学为"无隐式保证",即使是在 per-tile execution 中,所有写后读场景也必须手动插入 barrier。
- 典型同步场景:
- Tile Shader → Tile Draw:使用
vkCmdPipelineBarrier2明确标注 tile memory 的访问语义,需设置:VK_ACCESS_2_SHADER_TILE_ATTACHMENT_WRITE_BIT_QCOM;VK_ACCESS_2_SHADER_TILE_ATTACHMENT_READ_BIT_QCOM;VK_DEPENDENCY_BY_REGION_BIT。
- GMEM 写后读(跨 Pass):插入 render pass dependency 或 pipeline barrier,遵循通用 Vulkan 内存同步规则。
- 跨 Submit 访问:需结合 VkSemaphore 或 VkFence 保证调度次序,并验证设备支持跨 Submit 保留 GMEM 内容。
- 解绑行为:一旦
vkCmdBindTileMemoryQCOM(NULL)被调用,原 memory 区域即刻标记为失效,驱动可立即回收。
- Tile Shader → Tile Draw:使用
2.5 硬件支持与相关专利
Adreno GPU 提供如下底层机制支持:
- GMEM 容量:典型为 14–32MB,多 Bank 组织(公开报道口径:Adreno 845 约 12MB、850 约 18MB,随代际递增,区间上限对应更新的旗舰型号);
- Tile Scheduler:基于 Visibility Mask 分发 Tile 任务,可并行处理多个 Tile;
- 同步位图:GMEM 内维护读写 Bitmap,配合 API barrier 插入做 Tile 粒度同步;
- Dispatch Tile 机制:支持基于 Tile 网格的自动化工作组布局;
- 专利:
- US 9,489,710 B2(FlexRender):对应 Tile Shading 模式锁定;
- US 9,280,845 B2:描述 GMEM 复用与同步机制;
- US 10,192,280 B2:涉及 Tile 分辨率控制(VRS);
- US 9,842,376 B2:任务抢占与中断机制。
2.6 性能收益
根据 Qualcomm 提供的性能数据,以下方案在实际使用中效果明确:
- G-Buffer 缓存至 GMEM: 可减少 ≈45% DRAM 带宽;
- Tile 内光照累积:在 1080p 分辨率下可降低约 0.4ms 延迟;
- Barrier 插入:每次 tile attachment 写 → 读必须插入 barrier,建议封装简化逻辑;
- 调度粒度调整:根据 tileGranularity 控制调度策略,避免 bank 冲突与并行度下降;
- 生命周期规划:尽量避免 GMEM 跨 Submit 使用,若必须,需查询 queueSubmitBoundary 属性。
2.7 局限与展望
当前扩展的已知限制包括:
- 仅支持单队列可见,无法用于多线程/多队列渲染路径;
- 同步机制繁琐,需手动插入完整 pipeline barrier;
- Tile 尺寸不可配置,仅能读取 GPU 提供的 granularity 属性;
- 缺乏调试工具对 tile memory 问题做精确诊断;
- 与 Metal 相比,缺乏高层自动化封装,开发门槛高。
Khronos 正在推动如 VK_EXT_shared_tile_memory、vkCmdTileBarrierQCOM 等扩展,试图降低 barrier 管理负担并提升调度灵活性。这将有助于推动 tile shading 成为移动图形编程的通用实践。
三、Metal vs Adreno Tile Shading 差异对比与架构思考
Metal 与 Adreno Tile Shading 的并列分析揭示出两种体系在设计目标、抽象层级与开发负担方面的差异。API 行为的分歧背后是底层 GPU 架构特性与平台策略的分歧。
3.1 自动同步 vs. 手动同步
- Metal Tile Shading 完全采用 GPU 固件驱动的同步机制。按逻辑顺序提交 draw 与 dispatch 即可,系统在阶段边界自动插入 per-Tile write drain 和执行序列切换,无需手动介入同步逻辑。
- 相比之下,VK_QCOM_tile_shading 延续 Vulkan 显式控制哲学,所有 Tile Attachment 访问行为(尤其写后读)必须通过 vkCmdPipelineBarrier2 等手段显式声明依赖,开发者需掌握 Tile 生命周期、访问语义、资源布局、同步顺序等全部细节。
- 这种分歧反映了两者的 API 定位差异:
- Metal:高度集成、封装完善,更适合对抽象层友好型开发;
- Vulkan QCOM 扩展:控制力强、透明度高,面向具备硬件知识的图形引擎开发者。
以上是 API 层面的差异。以下从硬件侧分析同一分歧。
Metal 的自动同步与 Vulkan 的手动 barrier 保护的是同一个物理对象:同一块片上 SRAM 上的写后读冒险。Apple 侧由 scheduler 在阶段边界完成 per-Tile drain & tag,一致性判定权在硬件手里。Adreno 侧 GMEM 内部维护读写 Bitmap,硬件本来就具备 Tile 粒度的依赖追踪能力,vkCmdPipelineBarrier2 里那些 access bit 的作用是让开发者把硬件已经掌握的事实再声明一遍。同一个物理事件,一边由硬件自行判定,一边要求开发者显式声明。1.2 节那句"只需保证命令提交顺序,Metal 将自动维护一致性"能成立,前提是 encode 顺序即执行顺序、阶段边界即同步点,driver 与硬件调度器对命令流有确定性接管,这套保证不是软件层的推测。下文 3.5 节对比表里"同步机制:自动完成 / 手动 barrier"这一行,描述的正是这个判定权的归属问题。
手动同步在这里不是更底层的体现,而是旧屏障模型的残存形态。传统 Pipeline Barrier 的三组参数各自对应旧硬件上一个真实的物理清洗动作(Barrier 三参数的物理语义在 4.3 节展开)。当数据驻留在 GMEM 这种显式绑定的片上区域时,数据根本没有离开 SRAM,没有缓存可刷、没有 Layout 可换,barrier 的物理内容只剩下执行序。2.6 节要求每次 tile attachment 写转读都插入一次 barrier,这些申报在硬件侧对应的只是 GMEM Bitmap 的一次状态翻转,没有任何数据移动。Vulkan 仍要求开发者按旧模型逐项申报,这份繁琐是 API 对旧硬件行为的代偿,不是当前硬件的需求。5.3 节推荐封装 Tile Barrier 工具函数来规避遗漏,2.6 节也建议把 barrier 插入逻辑封装简化,这些工程建议的存在本身说明申报动作已经退化为机械劳动。
自动同步与手动同步因此不是两种并列的设计哲学,而是屏障语义物理异化过程的两个阶段:Metal 已把一致性判定权收归硬件,Vulkan 还把它留在开发者手里。1.3 节那个 draw → draw 需要插入空 dispatch 作为同步点的做法,是自动模型覆盖不到时由开发者补做的依赖声明。判定权最终归谁,取决于末级缓存的一致性域能覆盖多大范围。
3.2 内存模型:封装抽象 vs. 直接暴露
- Metal 提供
threadgroup_imageblock和persistent threadgroup memory两个接口用于描述 Tile 内存空间,但不允许直接访问 color / depth / stencil 附件的 tile 局部内容,必须通过专设缓存结构间接读写。这保证了数据隔离性,但牺牲了灵活性。 - 而在 Adreno 的实现中,tile memory heap 可以显式分配与绑定,Shader 甚至可通过
layout(tile_memory)映射附件本体,在 compute 或 fragment 阶段以 imageLoad/imageStore 方式访问。开发者由此可精确控制 GMEM 中每字节数据的读写路径。特性 Metal Adreno Vulkan 内存类型 抽象的 imageblock / threadgroup 显式绑定的 tile memory 资源绑定方式 RenderPass 描述符 VkDeviceMemory + vkCmdBindTileMemoryQCOM 可访问内容 中间缓存(非 attachment) attachment 本身(color/depth/stencil) 跨 Pass 生命周期 不支持 限定范围支持(单 Submit 内)
3.3 执行粒度与灵活性
- Metal 支持在 Tile 层级插入计算调度(dispatchThreadsPerTile),并通过多个 Tile Pipeline State 组织复杂逻辑。但 Tile 层不支持强制执行 draw,draw 是否触发仍受图元覆盖控制。
- VK_QCOM_tile_shading 在 per-Tile execution 区间内允许执行 draw 指令,无论该 Tile 是否被 geometry 覆盖,GPU 都会强制执行片元阶段。这一能力非常适合在 Tile 级别完成清除、辅助绘制、图层遮罩等操作。
- VK_QCOM_tile_shading 的 Dispatch Tile 基于 shading rate 自动调度,线程组数由硬件决定;Metal 中则由开发者通过 threadgroup size 控制。
3.4 GPU 架构差异影响
- Apple GPU 是典型 TBDR 架构,Tile 内存采用 per-Tile 分配,容量通常为几十 KB。每个 Tile 的 imageblock 与 persistent memory 都在 Tile 开始时分配,结束时释放。
- Adreno GMEM 为共享式 Tile 内存池,容量通常在 14~32MB 之间,按 tile scheduler 划分使用,并支持多 Tile 并发写入。其架构支持 Tile 粒度任务中断、抢占与复用,适合动态内容较多的大型管线调度。
架构特性 Apple GPU (Metal) Adreno GPU (Vulkan) Tile 内存类型 独立 per-Tile SRAM 全局共享 GMEM 内存规模 每 Tile 数十 KB 全局 14–32MB 分配模型 RenderPass 内隐式分配 显式分配 + 显式绑定 适用场景 多 Tile 小数据并发 单 Tile 大数据驻留 / 跨 Pass 数据传递 执行粒度 Draw / Dispatch 切换为主 支持每 Tile Draw + Dispatch 强制执行
3.5 开发生态与工程体验
- Metal Tile Shading 背靠 Apple 高度统一的软硬件生态,Xcode GPU Frame Capture 对 Tile 内存布局和阶段交错均有详细可视化支持,memory 使用率也可直接观测。
- Adreno 扩展功能仍处于普及早期,开发者需自行理解规范、测试同步机制、构建封装框架,并使用 RenderDoc 或 Adreno Profiler 手动分析 GMEM 使用情况。扩展文档详实,但高门槛限制了其在轻量型引擎或跨平台中间件中的普及。
项目 Metal Adreno Vulkan API 抽象级别 高 低 同步机制 自动完成 手动 barrier 附件访问 仅间接访问(imageblock) 直接访问 attachment 内容 Tool 支持 完善(Xcode / Capture) 中等(RenderDoc / Adreno Profiler) 开发复杂度 低(更适合初学者) 高(适合资深图形开发者)
四、扩展与趋势
Tile Shading 的思想是以片上高速缓存替代频繁的外部显存访问,在 GPU 局部完成更高密度的数据加工。主机、数据中心乃至 HPC 场景中也演化出不同形态的可编程缓存机制,服务于更高负载密度与更广泛的数据复用需求。
4.1 主机平台:Xbox One 的 eSRAM 实践
为弥补 DDR3 带宽瓶颈,Xbox One 在 GPU 内部集成了 32MB 高速 eSRAM,用于显式缓存关键渲染资源。其设计目标与移动端 GMEM 相似,但其控制模式、缓存层级、生命周期均更为宏观。
- 设计目标与行为特点:eSRAM 作为 L3 等级的可控缓存,由开发者显式绑定 RenderTarget 和 Depth Buffer 等资源。其理论带宽高达 204 GB/s,足以完全容纳 1080p 分辨率的多目标 G-Buffer,与 GPU pipeline 紧耦合后可大幅减少主存访问。
204 GB/s 这个数字出自微软在 Hot Chips 25 上公布的 SoC 规格。后藤弘茂在 PC Watch 的架构分析里给出了对应的物理结构:eSRAM 按 8MB 一组组织为四个 block,每组配独立的 256-bit 读写总线,合计 1024-bit 位宽,单向峰值 109 GB/s。作为对照,DDR3 主存的带宽只有约 68 GB/s,eSRAM 峰值接近其三倍,1080p 多目标 G-Buffer 能整个驻留片上,依赖的正是这个量级的带宽差。
- 对比分析:
- 生命周期:eSRAM 支持跨 Pass、跨 Frame 数据驻留,而 GMEM/Tile Memory 多数限定在单次 Submit 或 Render Pass;
- 控制方式:eSRAM 更类似一种高性能显存分区,由引擎负责分配管理,而 Tile Shader 的片上 memory 更偏向 shader 级别显式可编程区域;
- 应用模式:eSRAM 主要用于减少高频 G-Buffer 操作的带宽压力,而 Tile Shading 更多关注中间阶段数据复用、计算交错与临时局部缓冲的消除。
4.2 数据中心:NVIDIA DSM 与可编程 L2 缓存
HPC 与 AI 推理/训练场景下,GPU 对片上可编程缓存的依赖不亚于图形渲染。NVIDIA 架构中提供了 Distributed Shared Memory(DSM)与持久化 L2 缓存窗口(Persisting L2 Window)两个可编程缓存机制,支持跨线程块、跨 SM 的数据共享。
- DSM(Distributed Shared Memory):
- 每个 SM 内含 64-128KB 的 Shared Memory,支持 CUDA kernel 显式编程;
- 与 Metal 的 threadgroup memory 相似,但支持 SM 间 cluster 通信(如通过 Cooperative Group)。
- 典型用法包括 WMMA 临时数据缓存、Reduction、PrefixSum 等内核中频繁访问数据的局部共享。
- 可编程 L2 Cache(Access Policy Window):
- 开发者可声明某段 GPU global memory 为"Persisting L2",提示 driver 在指定窗口期间保留其 L2 residency。
- 提高在 kernel 间或 large-scale 工作负载中对关键数据的共享命中率。
- 在 AI 推理中常用于模型权重、embedding tables 等高复用结构。
- 与图形 Tile Shading 的对比:
项目 NVIDIA DSM & L2 Metal/Adreno Tile Memory 粒度 Thread Block / SM / Global Per-Tile 生命周期 Kernel 内 / Kernel 间 Render Pass / Submit 内 控制方式 CUDA 显式接口 + 属性配置 RenderPass 配置 + Shader 指令 主要应用 计算、AI、模拟 图形渲染与屏幕空间滤波
NVIDIA 官方博客在 CUDA 11 发布时提供了一组可对照的数字:A100 的 L2 为 40 MB,接近 V100 的 7 倍,CUDA 11 随之提供了从这 40 MB 中划出 set-aside 区域给持久化访问的 API,开发者通过 accessPolicyWindow 声明命中按 persisting、未命中按 streaming 处理;同一篇文章引用 GV100 的数据,shared memory 带宽是 global memory 的 17 倍、L2 的 3 倍。片上 SRAM 与片外存储之间的带宽差就是这个量级,同一趋势在 NVIDIA 产品线里同样成立。
4.3 统一分析与趋势展望
- 下表总结了不同平台的可编程缓存机制在规模、粒度、生命周期、开发模型等方面的异同:
特性/平台 Apple Metal Adreno Vulkan Xbox One eSRAM Nvidia DSM & L2 层级位置 Tile SRAM (L1.5) GMEM (L1.5-L2) eSRAM (L3) SM Shared + L2 Cache 缓存容量 数十 KB / Tile 14–32MB / 全局共享 32MB / 全局 KB–MB 级可编程共享区 生命周期 Render Pass Submit / Pass 内 多 Pass / 多 Frame Kernel 级 控制方式 RenderPass 编程 Memory+Dispatch 控制 Resource Mapping CUDA API / 属性注解 应用模式 局部协同渲染 延迟合成 / 后处理 全局资源缓存 通用计算 / AI - 图形领域以 Tile 为中心展开计算-渲染融合,强调 intra-tile 并行与缓存消除;
- HPC / AI 领域更关注 thread block 间 / SM 间共享效率,侧重 kernel 协同;
上表未列入的另一个共同点是数据驻留位置的决定权,这一因素直接决定同步语义还剩多少物理内容。
Tile SRAM、GMEM、eSRAM、DSM、Persisting L2 成为可编程对象之后,数据驻留在哪里由硬件拓扑决定:Tile 尺寸、GMEM Bank 组织、SM 数量、L2 分区方式。API 侧的 Layout 状态不再回答这个问题。片上内存的外露化本身就是证据:硬件把一块 SRAM 的容量、生命周期与 Bank 结构直接交给开发者管理,资源当前处于哪种 Layout 随之退化为一个纯逻辑标签。eSRAM 的跨 Pass 驻留与 DSM 的跨 SM 共享进一步说明,驻留语义一旦由拓扑决定,API 能做的只是声明意图,而不是搬动数据。5.4 节列出的生命周期边界同样由硬件划定:Tile 内存的作用域是 Render Pass,GMEM 的有效期是单个 Submit,与资源状态机无关。
传统 Pipeline Barrier 的三组参数,各自对应旧硬件上一个真实的物理动作。src/dstStage 对应管线级的停顿与排空;src/dstAccess 对应缓存所有权的转移,ROP Cache 刷回显存、Texture Cache 失效都在这一组;old/newLayout 对应解压缩元数据落盘与数据重排。屏障在旧硬件上是一个高代价的物理清洗动作,参数的繁琐是物理代价在 API 层的直接体现。参数越细,说明硬件需要开发者代管的物理细节越多。2.4 节列出的 VK_ACCESS_2_SHADER_TILE_ATTACHMENT_WRITE_BIT_QCOM 这类标志位,每一个都是这套物理动作在 API 字段上残留的命名。
这个前提正在消失。现代 GPU 的末级缓存是全片共享、高度一致的,NVIDIA 的巨型 L2 与 Apple SoC 级的 System Level Cache 都把一致性域扩大到整颗芯片。配合端到端保持的通用无损压缩机制(Apple UTC、AMD 增强 DCC),跨编排边界的数据流转不再需要经历物理上的解压、回写与失效。数据在逻辑上根本没有移动,Layout 转换也就彻底失去物理意义。片上内存进一步强化了这一趋势:Metal 的 Tile 内存不属于 MTLBuffer / MTLTexture 这类常规资源,在 API 层面连一个可翻转的 Layout 状态都没有。压缩失效税的量化数字见系列 Vol.6 的 UTC 一节;该节给出的外露化证据与上述分析指向相同结论。
屏障因此可以被大幅精简。Metal 4 已经把 Barrier 退化为纯粹的流水线执行序依赖(Execution Dependency),只剩 after(前置阶段)、before(后置阶段)、access(访问范围)三个逻辑参数。这三个参数里没有 Layout,也没有缓存所有权,因为硬件上已经没有对应的物理动作可声明。剩下的唯一问题是执行序:谁先写、谁后读、影响范围到哪,这正是屏障在物理上仅剩的内容。2.4 节那套 QCOM access bit 与 VK_DEPENDENCY_BY_REGION_BIT,是退化完成之前开发者仍需按旧模型支付的申报成本。2.7 节提到 Khronos 正在推动 vkCmdTileBarrierQCOM 这类扩展来降低 barrier 管理负担,说明这份申报成本已经被规范制定者感知为纯粹的负担。
内存子系统的收敛与同步语义的物理异化互为因果:一致性域越大,屏障的物理内容越少,直到只剩执行序。自动同步与手动同步是这条退化曲线上的两个采样点。
五、最佳实践
5.1 资源布局策略
Tile 内存容量有限,应优先为频繁读写、生命周期短、访问模式规则的数据分配空间。
- Metal: 仔细估算
imageblock和persistent threadgroup memory的用量。imageblock方面,根据每个像素需要暂存的数据大小(颜色、法线、材质ID 等)精确设置imageblockSampleLength。persistent memory方面,根据每 tile 所需存储的列表大小(如光源索引)设置threadgroupMemoryLength,避免过度分配。 - Adreno Vulkan: 利用
vkCmdBindTileMemoryQCOM的灵活性,为G-Buffer等大型中间渲染目标在GMEM中规划分区。预先设计好哪些pass的哪些附件需要驻留GMEM,并管理好它们的绑定与解绑。 - Xbox One / Nvidia: 将全局性、高频读写的大型资源(如延迟渲染的G-Buffer、阴影贴图、可跨帧复用的数据)优先分配到eSRAM或可编程L2中。
分配先看访问频率:用热力图工具(Xcode GPU Report、Adreno Profiler、Nsight)把高频读写对象标出来,再确定分配优先级;imageblock 与 GMEM 的区域粒度按对象粒度划分,低复用数据不占这层空间。
5.2 多阶段调度策略:编排高效的 Draw ↔ Dispatch 流水线
Tile Shading的优势在于将计算(Dispatch)无缝插入渲染(Draw)流程中。合理安排 dispatch 与 draw 的交错顺序是提升 Tile 内数据复用效率的关键。
- Apple Metal:
- 使用
dispatchThreadsPerTile插入计算阶段; - 在 Tile Shader 之间或与 Draw 之间共享 image block。
- 使用
- Adreno Vulkan:
- 利用
vkCmdBeginPerTileExecutionQCOM区块组织 Dispatch + Draw; - 可强制每 Tile 执行某些 Draw,用于初始化、覆盖、Debug 可视化。
- 利用
链路成立的前提是中间产物不越过生命周期边界:G-Buffer → Tile 光照 → 全屏合成对应 Draw → Dispatch → Draw,Metal 里整条链要留在同一个 Render Pass(跨 Pass 就得 store/load,见 1.6),Adreno 里不能跨 Submit(见 2.3)。跨多个计算 Pass 的操作拆成多段 Dispatch Pipeline,段间衔接仍按各平台自己的同步模型走。per-Tile 计算量不均衡时,阶段耗时会被个别 Tile 拖长。
5.3 同步与一致性控制:理解隐式与显式的边界
同步是并行编程中的常见难点。可编程缓存场景下,同步遗漏会导致 tile attachment 脏读,产生帧间闪烁或像素级渲染错误,且难以在离线截帧中复现。
- Apple Metal:
- 同步规则见 1.3 节(隐式模型:阶段切换即同步点,draw→draw 除外)。
- Adreno Vulkan:
- 同步规则见 2.4 节(显式 barrier,QCOM access flag)。
显式模型里没有驱动兜底的默认路径(见 2.4),写后读全部靠声明;检查手段是验证层与 RenderDoc / Adreno Frame Analyzer 读 tile attachment 访问依赖。
5.4 跨Pass或多帧数据持久化策略
不同的可编程缓存具有不同的生命周期,理解这一点是设计复杂渲染管线的基础。
- Apple Metal:
- Tile 内存作用域为 Render Pass;
- 跨 Pass 必须通过
.store写出显存,并通过.load恢复。
- Adreno Vulkan:
- GMEM 内容默认有效期为单 Submit;
- 需要保持连续 vkQueueSubmit,或检查
queueSubmitBoundary能力。
生命周期边界直接换算成编排规则:一组互相依赖的 Pass 要放进同一次 vkQueueSubmit(设备支持 queueSubmitBoundary 才能跨 Submit,见 2.3),tile memory 按 logical phase 分段分配。容量不够时用 Alias:生命周期不重叠的对象共享同一段 tile memory、按执行时序切片复用(GMEM 只放得下一两张 image,见 2.3 节提案引文)。
5.5 Tile边界处理策略
对于屏幕空间的图像滤波等算法,如何处理Tile边界是一个普遍存在的问题。
- Apple Metal:
- 边界处理机制见 1.4 节(Guard-Band Clamp / Strip Prefetch / 分离卷积)。
- Adreno Vulkan:
- 通过
tileApronSize配置硬件级 apron(机制见 2.2 节)。
- 通过
apron 尺寸的下限由滤波半径决定,配大了只是多算一圈边缘像素;Metal 侧没有 apron(机制见 1.4),边界数据要自己复制或预取。两条路径对滤波代码来说是两份边界处理,用一层分支封装遮住有/无 apron 的差异。
5.6 性能平衡策略
使用可编程缓存并非没有代价,开发者需要在 Tile 容量、并发数量与 Bank 冲突概率之间找到平衡点。
- 问题来源:
- Tile 越大,数据局部性好,切换频率低,但并发 Tile 数量下降;
- Tile 过小,提升并行度但增加 scheduler 压力与切换开销;
- GMEM / eSRAM Bank 分布可能造成访问冲突,影响带宽利用率。
参数调整方向取决于硬件是否开放配置:Tile 尺寸在 Adreno 上只读(见 2.7),dispatch 策略只能拿 tileGranularity 当输入;shading rate 与 workgroup size 超出 SM 资源上限会绑定失败,按硬件约束设定。高并发路径把负载拆到多个 shader 调度阶段,每个阶段只占一部分 tile usage。
验证分两层:静态侧先估算 tile 内内存、shader 执行时间与 threadgroup 分布,构建占用率模型;实测侧对不同 tile 配置做 A/B,记录 Wave occupancy、Tile overlap 与 SRAM bank hit rate。
六、结语
Tile Shading 及其衍生的可编程缓存机制改变了 GPU 内部的数据流路径。前文记录的四个平台给出了各自的实现:Apple 把一致性判定权交给硬件 scheduler,Adreno 将 GMEM 的 Bank 结构直接暴露给应用层,eSRAM 以 204 GB/s 的片上带宽兜住了 DDR3 的瓶颈,NVIDIA 则把 40 MB 的 L2 划出持久化窗口供 kernel 间复用。
- Apple Metal 的 Tile Shader 在单个 Render Pass 内完成 Forward+ 光照、透明度合成和屏幕空间滤波,省去显存往返。自动同步模型免除了 barrier 申报,开发负担集中在 imageblock 容量规划上。
- Qualcomm 的 Vulkan 扩展开放了对 GMEM 的完整编程访问:Tile Shader 执行模型、tile memory 绑定与解绑、生命周期管理均由开发者控制。G-Buffer 可完全驻留片上,代价是每次 tile attachment 写转读都需要一次显式 barrier。
- eSRAM 和 NVIDIA DSM / Persisting L2 把相同的思路推到主机和数据中心:热点数据留在片上,全局内存访问降到最低。
这些实现共享的物理前提是片上 SRAM 的带宽优势(shared memory 带宽为 global memory 的 17 倍)。这个倍数设定了所有片上驻留策略的收益上限,也是各平台不断扩大一致性域、压缩屏障物理内容的底层驱动力。
参考文献
- Apple. Metal 2 on A11 - Tile Shading. Apple Developer Tech Talks. https://developer.apple.com/videos/play/tech-talks/604/
- Apple. Tailor your apps for Apple GPUs and tile-based deferred rendering. Apple Developer Documentation. https://developer.apple.com/documentation/metal/tailor-your-apps-for-apple-gpus-and-tile-based-deferred-rendering
- Apple. Understanding the Metal 4 core API. Apple Developer Documentation. https://developer.apple.com/documentation/metal/understanding-the-metal-4-core-api
- Apple. Synchronizing stages within a pass. Apple Developer Documentation. https://developer.apple.com/documentation/metal/synchronizing-stages-within-a-pass
- Khronos. VK_QCOM_tile_shading. Vulkan Feature Proposal. https://github.khronos.org/Vulkan-Site/features/latest/features/proposals/VK_QCOM_tile_shading.html
- Khronos. VK_QCOM_tile_shading. Vulkan Extension Specification. https://registry.khronos.org/vulkan/specs/latest/man/html/VK_QCOM_tile_shading.html
- Khronos. VK_QCOM_tile_memory_heap. Vulkan Extension Specification. https://docs.vulkan.org/features/latest/features/proposals/VK_QCOM_tile_memory_heap.html
- NVIDIA. CUDA 11 Features Revealed. NVIDIA Developer Blog. https://developer.nvidia.com/blog/cuda-11-features-revealed/#:~:text=CUDA%2011%20provides%20new%20specialized,of%20the%20Volta%20V100%20GPU.
- Lei Mao. CUDA L2 Persistent Cache. https://leimao.github.io/blog/CUDA-L2-Persistent-Cache/?
- arXiv. Benchmarking and Dissecting the Nvidia Hopper GPU Architecture. https://arxiv.org/html/2402.13499v1?
- NVIDIA Developer Forums. What is the inter-SM linkage of DSM(cluster). https://forums.developer.nvidia.com/t/what-is-the-inter-sm-linkage-of-dsm-cluster/300533?utm_source=chatgpt.com
- NVIDIA Developer Forums. Performance of diagonal access to distributed shared memory. https://forums.developer.nvidia.com/t/performance-of-diagonal-access-to-distributed-shared-memory/283152?
- 后藤弘茂. 「Xbox One」を汎用からゲーム路線へと変化させたMicrosoftの背景. PC Watch. https://pc.watch.impress.co.jp/docs/column/kaigai/653026.html
- GameSpot(引述 Digital Foundry / Eurogamer 报告). Xbox's Project Scorpio Full Tech Specs Explained In-Depth. https://www.gamespot.com/articles/xboxs-project-scorpio-full-tech-specs-explained-in/1100-6449215/
