如何在 Vulkan 中跨多个计算队列执行并行计算着色器?
How to execute parallel compute shaders across multiple compute queues in Vulkan?
更新:已解决,您可以在此处找到更多详细信息:
A similar question was asked previously,但该问题最初集中在使用多个命令缓冲区,并触发跨不同线程的提交以实现着色器的并行执行。大多数答案表明解决方案是改用多个队列。使用多个队列似乎也是各种博客文章和 Khronos 论坛答案的共识。我尝试了这些建议 运行ning 跨多个队列执行着色器,但无法看到并行执行,所以我想问问我可能做错了什么。正如所建议的那样,这个问题包括 运行 多个计算着色器的可用代码被提交到多个队列,希望这对其他希望做同样事情的人有用(一旦解决)。
当前的实现是 in this pull request / branch,但我将介绍 Vulkan 的主要具体要点,以确保只需要 Vulkan 知识即可回答此问题。还值得一提的是,当前的用例专门用于计算队列和计算着色器,而不是图形或传输队列(尽管 insights/experience 实现并行性仍然非常有用,并且很可能也会导致答案).
更具体地说,我有以下内容:
- Multiple queues first are "fetched" - 我的设备是 NVIDIA 1650,在队列系列索引 0 中支持 16 个图形+计算队列,在队列系列索引 2 中支持 8 个计算队列
- evalAsync performs the submission (which contains recorded shader commands) - 你应该注意到创建了一个我们可以使用的栅栏。此外,提交没有任何 waitStageMasks (PipelineStageFlags)。
- evalAwait allows us to wait for the fence - 当调用 evalAwait 时,我们可以通过创建的栅栏等待提交完成
以上示例中没有显示但很重要的几点:
- 同一应用程序、实例和设备上的所有 evalAsync 运行
- 每个 evalAsync 使用自己独立的 commandBuffer 和缓冲区执行,并在单独的队列中执行
- 如果您想知道内存屏障是否有作用,我们已尝试完全删除所有内存屏障(this on for example,即 运行 在着色器执行之前),但这并没有产生任何影响性能差异
基准测试中使用的测试 can be found here,但是唯一需要了解的关键事项是:
- This is the shader 我们用于测试,如您所见,我们只是添加了一堆 atomicAdd 步骤来增加处理时间
- 目前测试有 small buffer size and high number of shader loop iterations,但我们还测试了较大的缓冲区大小(即 100,000 而不是 10)和较小的迭代(1,000 而不是 100,000,000)。
当运行进行测试时,我们首先运行在同一个队列上执行一组“同步”着色器(数量是可变的,但我们已经用 6-16 进行了测试,后者是队列的最大数量)。然后我们以异步方式 运行 这些,我们 运行 所有这些和 evalAwait 直到它们完成。比较两种方法的结果时间时,尽管它们 运行 跨不同的计算队列,但它们花费的时间相同。
我的问题是:
- 我目前在获取队列时是否遗漏了什么?
- vulkan 设置中是否还有其他参数需要配置以确保异步执行?
- 是否有任何我可能不知道的关于潜在操作系统进程只能以同步方式向 GPU 提交 GPU 工作负载的限制?
- 在处理多个队列提交时,是否需要多线程才能使并行执行正常工作?
此外,我在各种 reddit 帖子和 Khronos Group 论坛中找到了一些有用的在线资源,这些资源提供了关于该主题的非常深入的概念和理论概述,但我还没有遇到显示并行的端到端代码示例着色器的执行。如果您可以分享任何实际示例,这些示例可以并行执行着色器,那将非常有帮助。
如果有更多详细信息或问题可以帮助提供更多背景信息,请告诉我,很乐意回答他们and/or提供更多详细信息。
为了完整起见,我的测试使用了:
- Vulkan SDK 1.2
- Windows 10
- 英伟达 1650
已在类似帖子中分享的其他相关链接:
- Similar discussion with suggested link to example but which seems to have disappeared...
- Post on Leveraging asynchronous queues for concurrent execution(遗憾的是没有示例代码)
- (比较老 - 5 岁)Post that suggests nvidia cards can't do parallel execution of shaders,但似乎没有决定性的答案
- Nvidia 演示 on Vulkan Multithreading with multiple queue execution(因此我在上面的线程问题)
您正在“异步执行”。你只是不期望它的行为方式。
在 CPU 上,如果您有一个线程处于活动状态,那么您正在使用一个 CPU 核心(或 hyper-thread)。该核心的所有执行和计算能力都单独提供给您的线程(忽略 pre-emption)。但与此同时,如果还有其他核心,你的一个线程就无法使用这些核心的任何计算资源。除非您创建另一个线程。
GPU 不是这样工作的。队列 不像 CPU 线程那样 。它并不具体涉及特定数量的计算资源。队列只是执行命令的接口。底层硬件决定如何将命令外包给 GPU 作为一个整体提供的各种计算资源。
执行命令时通常会发生的情况是,硬件会尝试使用您的命令完全饱和可用的着色器执行单元。如果可用的着色器单元多于您的操作所需的调用次数,则一些资源可立即用于下一个命令。但如果不是,那么整个 GPU 的计算资源将专门用于执行第一个操作;第二个必须等待资源可用才能启动。
将工作推入多少个计算队列并不重要;他们都将尝试使用尽可能多的计算资源。所以他们将主要按照某种特定顺序执行。
存在队列优先级系统,但这些系统主要帮助确定命令的执行顺序。也就是说,如果 high-priority 队列有一些命令需要执行,那么它们将在下次计算资源可用于新命令时优先执行。
所以在 3 个单独的队列上提交 3 个分派批次不会比在一个包含 3 个分派操作的队列上提交 1 个批次更快地完成。
存在多个队列(属于同一系列)的主要原因是能够从多个线程提交工作而无需它们进行 inter-thread 同步(并提供一些可能的提交优先级)。
我已经能够使用 this suggestion 解决。为了提供进一步的上下文,我试图将命令提交到同一个系列中的多个队列,但是在建议链接中指出,NVIDIA(和其他 GPU 供应商)在并行处理命令时具有不同的功能提交。
在我的特殊情况下,我测试的 NVIDIA 1650 卡仅支持在不同 queueFamilies 中提交工作负载时的并发处理 - 更具体地说,它只能支持跨一个图形队列和一个图形队列的一个并发命令提交计算家庭队列。
我 re-implemented 允许为特定命令分配系列队列的代码,并且我能够实现并行处理(通过跨两个队列系列提交,速度提高了 2 倍)。
这里是关于实现的更多细节https://kompute.cc/overview/async-parallel.html
更新:已解决,您可以在此处找到更多详细信息:
A similar question was asked previously,但该问题最初集中在使用多个命令缓冲区,并触发跨不同线程的提交以实现着色器的并行执行。大多数答案表明解决方案是改用多个队列。使用多个队列似乎也是各种博客文章和 Khronos 论坛答案的共识。我尝试了这些建议 运行ning 跨多个队列执行着色器,但无法看到并行执行,所以我想问问我可能做错了什么。正如所建议的那样,这个问题包括 运行 多个计算着色器的可用代码被提交到多个队列,希望这对其他希望做同样事情的人有用(一旦解决)。
当前的实现是 in this pull request / branch,但我将介绍 Vulkan 的主要具体要点,以确保只需要 Vulkan 知识即可回答此问题。还值得一提的是,当前的用例专门用于计算队列和计算着色器,而不是图形或传输队列(尽管 insights/experience 实现并行性仍然非常有用,并且很可能也会导致答案).
更具体地说,我有以下内容:
- Multiple queues first are "fetched" - 我的设备是 NVIDIA 1650,在队列系列索引 0 中支持 16 个图形+计算队列,在队列系列索引 2 中支持 8 个计算队列
- evalAsync performs the submission (which contains recorded shader commands) - 你应该注意到创建了一个我们可以使用的栅栏。此外,提交没有任何 waitStageMasks (PipelineStageFlags)。
- evalAwait allows us to wait for the fence - 当调用 evalAwait 时,我们可以通过创建的栅栏等待提交完成
以上示例中没有显示但很重要的几点:
- 同一应用程序、实例和设备上的所有 evalAsync 运行
- 每个 evalAsync 使用自己独立的 commandBuffer 和缓冲区执行,并在单独的队列中执行
- 如果您想知道内存屏障是否有作用,我们已尝试完全删除所有内存屏障(this on for example,即 运行 在着色器执行之前),但这并没有产生任何影响性能差异
基准测试中使用的测试 can be found here,但是唯一需要了解的关键事项是:
- This is the shader 我们用于测试,如您所见,我们只是添加了一堆 atomicAdd 步骤来增加处理时间
- 目前测试有 small buffer size and high number of shader loop iterations,但我们还测试了较大的缓冲区大小(即 100,000 而不是 10)和较小的迭代(1,000 而不是 100,000,000)。
当运行进行测试时,我们首先运行在同一个队列上执行一组“同步”着色器(数量是可变的,但我们已经用 6-16 进行了测试,后者是队列的最大数量)。然后我们以异步方式 运行 这些,我们 运行 所有这些和 evalAwait 直到它们完成。比较两种方法的结果时间时,尽管它们 运行 跨不同的计算队列,但它们花费的时间相同。
我的问题是:
- 我目前在获取队列时是否遗漏了什么?
- vulkan 设置中是否还有其他参数需要配置以确保异步执行?
- 是否有任何我可能不知道的关于潜在操作系统进程只能以同步方式向 GPU 提交 GPU 工作负载的限制?
- 在处理多个队列提交时,是否需要多线程才能使并行执行正常工作?
此外,我在各种 reddit 帖子和 Khronos Group 论坛中找到了一些有用的在线资源,这些资源提供了关于该主题的非常深入的概念和理论概述,但我还没有遇到显示并行的端到端代码示例着色器的执行。如果您可以分享任何实际示例,这些示例可以并行执行着色器,那将非常有帮助。
如果有更多详细信息或问题可以帮助提供更多背景信息,请告诉我,很乐意回答他们and/or提供更多详细信息。
为了完整起见,我的测试使用了:
- Vulkan SDK 1.2
- Windows 10
- 英伟达 1650
已在类似帖子中分享的其他相关链接:
- Similar discussion with suggested link to example but which seems to have disappeared...
- Post on Leveraging asynchronous queues for concurrent execution(遗憾的是没有示例代码)
- (比较老 - 5 岁)Post that suggests nvidia cards can't do parallel execution of shaders,但似乎没有决定性的答案
- Nvidia 演示 on Vulkan Multithreading with multiple queue execution(因此我在上面的线程问题)
您正在“异步执行”。你只是不期望它的行为方式。
在 CPU 上,如果您有一个线程处于活动状态,那么您正在使用一个 CPU 核心(或 hyper-thread)。该核心的所有执行和计算能力都单独提供给您的线程(忽略 pre-emption)。但与此同时,如果还有其他核心,你的一个线程就无法使用这些核心的任何计算资源。除非您创建另一个线程。
GPU 不是这样工作的。队列 不像 CPU 线程那样 。它并不具体涉及特定数量的计算资源。队列只是执行命令的接口。底层硬件决定如何将命令外包给 GPU 作为一个整体提供的各种计算资源。
执行命令时通常会发生的情况是,硬件会尝试使用您的命令完全饱和可用的着色器执行单元。如果可用的着色器单元多于您的操作所需的调用次数,则一些资源可立即用于下一个命令。但如果不是,那么整个 GPU 的计算资源将专门用于执行第一个操作;第二个必须等待资源可用才能启动。
将工作推入多少个计算队列并不重要;他们都将尝试使用尽可能多的计算资源。所以他们将主要按照某种特定顺序执行。
存在队列优先级系统,但这些系统主要帮助确定命令的执行顺序。也就是说,如果 high-priority 队列有一些命令需要执行,那么它们将在下次计算资源可用于新命令时优先执行。
所以在 3 个单独的队列上提交 3 个分派批次不会比在一个包含 3 个分派操作的队列上提交 1 个批次更快地完成。
存在多个队列(属于同一系列)的主要原因是能够从多个线程提交工作而无需它们进行 inter-thread 同步(并提供一些可能的提交优先级)。
我已经能够使用 this suggestion 解决。为了提供进一步的上下文,我试图将命令提交到同一个系列中的多个队列,但是在建议链接中指出,NVIDIA(和其他 GPU 供应商)在并行处理命令时具有不同的功能提交。
在我的特殊情况下,我测试的 NVIDIA 1650 卡仅支持在不同 queueFamilies 中提交工作负载时的并发处理 - 更具体地说,它只能支持跨一个图形队列和一个图形队列的一个并发命令提交计算家庭队列。
我 re-implemented 允许为特定命令分配系列队列的代码,并且我能够实现并行处理(通过跨两个队列系列提交,速度提高了 2 倍)。
这里是关于实现的更多细节https://kompute.cc/overview/async-parallel.html