ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Slang 程序执行模型详解:从 Dispatch/Launch 到 Wave 级线程执行

Slang 程序执行模型详解:从 Dispatch/Launch 到 Wave 级线程执行 编译器图形学编程语言【免费下载链接】slangMaking it easier to work with shaders项目地址https://gitcode.com/GitHub_Trending/sl/slang点击查看免费下载Slang 作为一门面向 GPU 计算与图形渲染的着色语言其程序执行模型定义了工作负载如何被拆解为入口点调用、线程、线程组与 Wave并决定了groupshared内存、GroupMemoryBarrier()、波内投票/归约等机制的行为边界。本文以 docs/language-reference/basics-program-execution.md 为骨架结合仓库中的内存模型文档与一致性测试用例系统讲解 Slang 程序从dispatch/launch到线程、线程组、Wave 的完整执行层级并给出可落地的代码示例与性能建议。程序执行模型总览从宏观视角看Slang 程序的执行可以分为两个阶段工作负载被派发compute dispatch或启动graphics launch计算工作负载通过dispatch提交图形工作负载通过launch即绘制调用提交。派发/启动的工作负载被划分为入口点调用entry point invocations每个调用由独立的**线程thread**执行。单个入口点调用负责处理一个参数化输入点。输入参数与返回值的具体类型取决于入口点的类型典型的差异体现在片段着色器与计算核函数之间片段着色器fragment shader入口点每个被光栅化的片段调用一次。调用集合由光栅化器决定每次调用的输入来自光栅化器与顶点着色器阶段输出是逐片段的数值——例如面向 RGBA 渲染目标的红/绿/蓝/alpha 颜色分量向量。计算核函数compute kernel入口点每个用户定义的输入参数点调用一次输入即标识该调用的线程坐标thread coordinates。计算核函数没有固有输出而是把结果写入输出缓冲区output buffers。不同图形着色器与计算核函数的输入/输出细节参见 图形着色器与计算核函数其中枚举了 fragment、vertex、geometry、hull、domain、raygeneration、intersection、anyhit、closesthit、miss、callable、task、mesh 等图形/光线追踪阶段入口点以及 compute 计算入口点。图形启动由绘制调用draw calls与图形管线配置决定其如何被精确切分为着色器入口点调用取决于目标target。而计算派发是显式的具有由应用定义的细分结构这是理解 Slang 并行模型的关键。计算派发三维线程网格的细分结构对于计算派发应用将输入参数空间定义为一个线程组thread group网格细分过程如下网格维度grid一次计算派发是用户指定的三维整数点集合。用户传入三维网格维度向量grid_dim它规定了网格点g的范围0 ≤ g.x grid_dim.x、0 ≤ g.y grid_dim.y、0 ≤ g.z grid_dim.z。线程组thread group网格中的每个点都会实例化一个线程组。线程组大小同样由三维向量group_dim指定组内单个线程调用b满足0 ≤ b.x group_dim.x、0 ≤ b.y group_dim.y、0 ≤ b.z group_dim.z。 线程组维度通常以**属性attribute**形式写在计算入口点上例如[numthreads(4,1,1)]或以计算派发参数的形式指定。调用总数每个网格点与线程组点的组合都产生一个由独立线程执行的调用因此一次派发共有grid_dim.x * grid_dim.y * grid_dim.z * group_dim.x * group_dim.y * group_dim.z次调用。以 HLSL 风格的 Slang 代码为例线程组维度通过[numthreads(x, y, z)]属性声明RWStructuredBufferuint resultBuffer; [numthreads(4, 1, 1)] void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) { uint threadId dispatchThreadID.x; resultBuffer[threadId] threadId * 2; }其中SV_DispatchThreadID、SV_GroupID、SV_GroupThreadID等系统值分别对应全局线程坐标、网格点坐标与组内线程坐标。仓库的一致性测试 dispatch-thread-id-emission.slang 验证了这些系统值在计算入口点中的发射行为而 dispatch-invocation-count-functional.slang 则用numthreads(4,1,1)与单组派发验证了调用总数公式线程收到的SV_DispatchThreadID为 0..3正好落在[0, group_dim.x)区间内。关于线程组大小的实用建议在计算派发中Wave 是从线程组中按目标定义的方式细分出来的。通常一个 Wave 由相邻调用构成但应用一般不应假设 Wave 的具体形状。当计算线程组或图形启动与 Wave 大小不对齐时部分 Wave 可能只有部分填充partially filled。因此为了让硬件资源利用最大化线程组大小通常应取 Wave 大小的整数倍详见下文 Wave 一节。Wave硬件层面的调度单元无论在图形启动还是计算派发中单个调用都会进一步被分组为Wave。Wave 具有以下性质Wave 大小是 [4, 128] 区间内的 2 的幂具体值由目标target定义。在图形启动中Wave 由目标定义的机制从启动中形成同一 Wave 内的调用无需有更多共同点——它们只需要属于同一管线阶段并使用同一入口点。特别地片段阶段的一个 Wave 可以处理来自不同几何图元的片段。在计算派发中Wave 从线程组中按目标定义的方式细分通常由相邻调用组成但应用不应假设 Wave 形状。Wave 大小可以在着色器中通过WaveGetLaneCount()查询其签名与更多 Wave 内建函数见 Wave 内建函数。一致性测试 wave-lane-count-emission.slang 验证了该内建在 HLSL、SPIR-V、GLSL、Metal、WGSL 与 CUDA 目标上均能发射对应的 Wave 大小内建。值得注意的边界情况来自一致性测试清单 basics-program-execution/README.md 的未测声明在cpp 目标上Wave 内建如WaveGetLaneCount()会在编译期被拒绝诊断 E36107因为 CPU 线程模型中不存在对应的 Wave 原语——这也印证了Wave 语义由目标定义这一事实。线程组执行模型线程组执行模型的核心约束是线程组内的所有线程运行在同一组执行资源上。这带来一个关键能力——线程组可以共享通过groupshared属性分配的内存static groupshared float sharedData[256]; [numthreads(256, 1, 1)] void computeMain(uint3 groupThreadID : SV_GroupThreadID) { sharedData[groupThreadID.x] groupThreadID.x * 0.5f; // 等待组内所有线程完成写入 GroupMemoryBarrierWithGroupSync(); // 现在可以安全地读取其他线程写入的数据 float v sharedData[255 - groupThreadID.x]; }与线程组共享内存相关的**屏障barrier**包括GroupMemoryBarrier()对线程组内存访问施加顺序约束但不要求组内线程同步会合。GroupMemoryBarrierWithGroupSync()在屏障基础上强制组内线程执行同步。在地址空间模型见 地址空间中groupshared变量属于组共享Group-shared地址空间其实例作用域是线程组该文档同时提醒不同地址空间的指针一般不可互换特别是指向组共享内存的指针不能跨线程组互换。需要特别强调的是线程组执行模型仅适用于计算核函数。一致性测试 groupshared-memory-functional.slang 与 group-memory-barrier-emission.slang 分别验证了groupshared与两个屏障内建在各目标上的发射行为同时测试清单也指出groupshared/GroupMemoryBarrierWithGroupSync在 cpp 目标上不被支持编译期 E36107。Wave 执行模型SIMT 与非严格锁步Wave 中的所有线程以**单指令多线程SIMT**模型执行。但这里有一个重要的澄清尽管是 SIMT 模型Slang 并不要求 Wave 内的调用严格锁步执行除非满足两个条件同时成立这些调用处于互收敛mutually convergent的控制流路径上它们正在执行同步类函数如GroupMemoryBarrierWithWaveSync()。关于 SIMT 有两则值得记录的备注Remark 1实际执行硬件不一定用 SIMT 指令集实现。特别是CPU 目标通常不使用 SIMT 指令。Remark 2在 SPIR-V 术语中Wave 纠缠函数wave-tangled functions被称为具有subgroup 作用域的tangled instructions。Wave 纠缠函数波内高效协作Wave 中的线程可以通过 **Wave 纠缠函数wave-tangled functions**高效地同步与共享数据例如投票ballotWaveActiveBallot()等归约reductionWaveActiveSum()、WaveActiveMin()、WaveActiveMax()等洗牌shuffleWaveShuffle()、WaveReadLaneAt()等波作用域的控制流屏障GroupMemoryBarrierWithWaveSync()等。一个重要的性能收益在于原子访问合并同一 Wave 内多个线程对同一内存位置执行原子访问时常常可以被合并coalesced为一次操作、由单个线程执行。这能显著减少原子内存访问次数从而提升性能。这一建议在 内存一致性 文档中也有呼应当可行时应选举单个线程执行原子内存访问例如用WaveIsFirstLane()判断后由波首线程提交原子写并在归约场景中先用WaveActiveSum()在波内汇总、再由单个线程完成原子写。Wave 纠缠函数的输入/输出语义很特殊函数的输入是所有参与线程的输入输出则分布在所有参与线程上。通常参与线程指的是处于互收敛路径上的活动线程。线程的三种类别Wave 内的线程属于以下类别之一活动线程active thread参与产生结果的线程。非活动线程inactive thread不产生任何副作用的线程。一个线程可能因以下任一原因处于非活动状态该线程没有在执行当前路径即发生了控制流发散分配线程时 Wave 未能被完全利用部分填充的 Wave 中未被占用的槽位线程执行了discard语句仅片段着色器该语句会禁用线程。辅助线程helper thread用于计算导数derivatives的线程典型场景是片段四边形fragment quads。辅助线程不产生其他副作用并且除非另有说明不参与 Wave 纠缠函数。一致性测试 discard-inactive-thread-emission.slang 验证了discard语句在 HLSL、SPIR-V、GLSL、Metal 与 WGSL 目标上发射对应的片段丢弃指令而测试清单也记录了一个目标差异在 cuda/cpp 目标上片段着色器中使用discard会导致slangc崩溃exit 139——因为discard只在光栅化管线中有意义并不适用于计算/CPU 场景。发散路径上的 Wave 纠缠函数当 Wave 内执行已经发散时调用 Wave 纠缠函数需要特殊考虑详见 执行发散与重汇聚并非所有目标都支持在发散路径上调用 Wave 纠缠函数不支持时在发散路径上调用结果为未定义具体支持情况参见 目标平台。支持时Wave 纠缠函数默认只作用于互收敛的线程集合——即同步只发生在处于同一路径上的线程之间。发散路径示例来自发散/重汇聚文档[numthreads(128,1,1)] void computeMain(uint3 threadId : SV_DispatchThreadID) { uint minimumThreadId 0; // 触发发散 if ((threadId.x 1) 0) { // 取 then 分支的最小线程 id minimumThreadId WaveActiveMin(threadId.x); } else { // 取 else 分支的最小线程 id minimumThreadId WaveActiveMin(threadId.x); } // 重汇聚 }发散与重汇聚结构化控制流下的形态发散divergence发生在不同线程在条件分支上走向不同控制流路径时重汇聚reconvergence发生在分支汇合时。文档 执行发散与重汇聚 定义了三种作用域下的控制流均匀性线程组均匀路径thread-group-uniform path线程组内所有线程都处于均匀路径Wave 均匀路径wave-uniform pathWave 内所有线程都处于均匀路径互收敛mutually convergent集合Wave 内处于互均匀路径上的线程集合执行发散后这样的集合不止一个。在不同语句结构下的发散/重汇聚形态if语句部分线程进入then分支、其余进入else分支时发生发散线程退出 then/else 分支时重汇聚所有线程走同一分支时无发散。switch语句线程跳到不同 case 组时发生发散退出 switch 时重汇聚。此外相邻 case 标签组在 case 穿透fall-through处也会发生重汇聚。循环语句部分线程退出循环而其余继续时发生发散所有线程都退出循环时重汇聚。文档同时给出两条实用准则均匀性不等于同步性——即使线程处于均匀路径也不保证其执行进度均匀线程并不保证锁步执行而避免长时间的发散执行路径通常是提升性能的良好策略。与内存模型的关系屏障与原子线程组/波作用域屏障的语义在 内存一致性 文档中有更完整的定义内存屏障对其前后的内存访问施加重排约束屏障前的访问happens-before屏障后的访问等价于 acquire-release 内存序。屏障按地址空间有三种作用域All设备与线程组内存、Device所有线程实例作用域含存储缓冲与图像、Thread group线程组内存。Slang 标准库提供的屏障原语包括AllMemoryBarrier()、AllMemoryBarrierWithGroupSync()、AllMemoryBarrierWithWaveSync()、DeviceMemoryBarrier()、DeviceMemoryBarrierWithGroupSync()、GroupMemoryBarrier()、GroupMemoryBarrierWithGroupSync()与GroupMemoryBarrierWithWaveSync()等。其中AllMemoryBarrierWithWaveSync()将波内所有线程同步到同一屏障并对其前后的所有内存访问排序GroupMemoryBarrierWithWaveSync()则只对组共享内存访问排序。完整签名列表见 Wave 内建函数。一个将波内合并原子访问落到实处的完整示例源自内存一致性文档RWStructuredBufferuint outputBuffer; RWStructuredBufferAtomicuint syncBuffer; [numthreads(64,1,1)] void computeMain(uint3 dispatchThreadID: SV_DispatchThreadID) { // 写入一些输出... outputBuffer[dispatchThreadID.x] dispatchThreadID.x; // 同步波内所有线程并发出内存屏障。 // 对波内所有线程而言output 的写入 // 都 happens-before 发出完成信号的写入 AllMemoryBarrierWithWaveSync(); // 由波首线程发出完成信号 if (WaveIsFirstLane()) { syncBuffer[0].store(1, MemoryOrder.Relaxed); } }验证与测试执行模型的一致性测试仓库为本文所述的执行模型声明提供了系统化的一致性测试位于 docs/generated/tests/conformance/basics-program-execution/。测试清单将文档中的每条声明C1–C18映射到具体测试C3/C4/C5三维网格与调用总数dispatch-thread-id-emission.slang、numthreads-attribute-emission.slang、dispatch-invocation-count-functional.slang、dispatch-3d-group-coords-functional.slang——分别验证系统值发射、[numthreads]属性在各目标HLSL、SPIR-V、GLSL、Metal、WGSL、CUDA、CPP上的保留、调用总数公式与SV_GroupID/SV_GroupThreadID的范围约束。C6Wave 大小wave-lane-count-emission.slang。C10/C11线程组内存与屏障groupshared-memory-functional.slang、group-memory-barrier-emission.slang。C14Wave 纠缠函数wave-tangled-ballot-emission.slang、wave-tangled-reduction-emission.slang。C16非活动线程与discarddiscard-inactive-thread-emission.slang。该清单同时明确标注了未测试声明non-normativeWave 形状的不可假设性、部分填充 Wave、线程组大小对齐建议、SIMT 模型描述、锁步自由语义等属于调度与模型层面的陈述没有可观察的编译器接口因此不纳入测试而 cpp/cuda 目标对 Wave 内建与discard的限制则作为已知目标差异被记录。总结与实践要点回顾全文Slang 的执行模型可以浓缩为以下实践要点理解三层层级派发/启动dispatch/launch→ 入口点调用 → 线程 → 线程组 → Wave。计算派发是显式三维网格grid_dim × group_dim图形启动的细分方式由目标决定。线程组大小对齐 WaveWave 大小为 [4, 128] 的 2 的幂目标定义线程组大小一般取 Wave 大小的整数倍以充分利用硬件。groupshared只属于线程组线程组共享内存只能在线程组内可见配合GroupMemoryBarrier()/GroupMemoryBarrierWithGroupSync()使用该模型仅适用于计算核函数。Wave 纠缠函数优化原子访问利用投票、归约、洗牌与波内屏障把多个线程的原子访问合并为单次降低内存竞争。区分活动/非活动/辅助线程discard只在片段着色器中禁用线程辅助线程只服务导数计算不参与 Wave 纠缠函数。发散路径上谨慎调用 Wave 函数只有互收敛的线程集合参与部分目标不支持发散路径上的调用。不假设锁步SIMT 不意味着锁步执行需要同步时显式使用波作用域屏障如GroupMemoryBarrierWithWaveSync()。延伸阅读图形着色器与计算核函数各阶段入口点及其输入/输出。执行发散与重汇聚发散路径、互收敛集合与 Wave 纠缠函数约束。地址空间groupshared的组共享地址空间定位。内存一致性数据竞争、内存序、原子访问与屏障语义。Wave 内建函数全部标准与非标准 Wave 内建含WaveMask、WaveMulti系列与旋转内建。执行模型一致性测试C1–C18 声明与测试的完整映射。赞分享编译器图形学编程语言【免费下载链接】slangMaking it easier to work with shaders项目地址https://gitcode.com/GitHub_Trending/sl/slang点击查看免费下载相关推荐Slang 着色器执行模型术语表从 Dispatch/Launch 到 Wave 与收敛性Slang 着色器执行模型术语表从 Dispatch/Launch 到 Wave 与收敛性 本指南逐条解析 Slang 语言参考手册中《Glossary》定义编译器图形学编程语言Slang 程序执行模型解析从 Compute Dispatch 网格到 Wave 并发含 conformance 测试全览Slang 程序执行模型解析从 Compute Dispatch 网格到 Wave 并发含 conformance 测试全览 导读 本文以 Slang 语编译器图形学编程语言Slang 程序执行模型深度指南从分发调度到线程组与 Wave 语义Slang 程序执行模型深度指南从分发调度到线程组与 Wave 语义 导读 本文以 Slang 语言参考中 basics program execution.编译器图形学编程语言创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表