KhronosGroup / KhronosGroup/Vulkan-ValidationLayers

Parallel vkCreateComputePipelines calls are extremely slow with shader instrumentation

Open
#10,852 1 comment 0 reactions 1 assignee Claimed by @spencer-lunarg View on GitHub
GPU-AV
Dominant language
C++
Stars
1k
Forks
504
Avg merge
11h 35m
Merged PRs (30d)
224

Description

**Environment:**
- GPU and driver version: NVIDIA RTX A4000 w/ 535.230
- OS: RHEL8
- build version: `270dbea039c2b485e33330be58a3e0ebcaef485f`
- Options enabled: `printf_enable` or `gpuav_enable`

**Describe the Issue**

The GPUAV validations cause immense performance overhead (factor > 20) on `vkCreateComputePipelines` when it is invoked in parallel.
Moreover, it is even sufficient for the `vkCreateComputePipelines` calls to run sequentially in some non-deterministic order.
This problem also occurs hen GPUAV is disabled, but debug printf is done via the `gpuav_shader_instrumentor.cpp`.

The cause for the slowdown is apparently that the compute pipelines created in this way do not benefit from the driver's shader cache (at `~/.cache/nvidia/GLCache` by default). The size of the cache increases continually across multiple runs of the same program, even if it always creates the same pipelines (in a different order).
This seems to point to some state that is held by the shader instrumentor, which results in different SPIR-V for different invocation orders.

**Expected behavior**

Enabling debug printf or gpu assisted validation does not circumvent the driver's shader cache mechanism for repeated runs. They should not unreasonably increase pipeline creation time.

**Additional context**

To reproduce the issue, you can apply this patch to the gpuav stress test:
```diff
diff --git a/tests/stress/gpu_av_stress.cpp b/tests/stress/gpu_av_stress.cpp
index fed19c0df..4f77265ba 100644
--- a/tests/stress/gpu_av_stress.cpp
+++ b/tests/stress/gpu_av_stress.cpp
@@ -21,6 +21,11 @@
#include "../framework/descriptor_helper.h"
#include "gpu_av_helper.h"

+#include
+#include
+#include
+#include
+
// If on Mesa, also suggest using MESA_SHADER_CACHE_DISABLE=1
class StressGpuAV : public VkLayerTest {
public:
@@ -109,7 +114,9 @@ TEST_F(StressGpuAV, DescriptorIndexing) {
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 2);
descriptor_set.UpdateDescriptorSets();

- const char *cs_source = R"glsl(
+ std::vector cs_sources;
+
+ const char *cs_source_prefix = R"glsl(
#version 450
#extension GL_EXT_nonuniform_qualifier : enable

@@ -124,7 +131,8 @@ TEST_F(StressGpuAV, DescriptorIndexing) {
}

vec4 bar(uint index) {
- vec4 result = vec4(1.0);
+ vec4 result = vec4()glsl";
+ const char *cs_source_suffix = R"glsl();
result -= texture(tex[index], vec2(0.1, 5.0));
result -= texture(tex[index], vec2(0.2, 5.0));
result -= texture(tex[index], vec2(0.3, 5.0));
@@ -175,20 +183,29 @@ TEST_F(StressGpuAV, DescriptorIndexing) {
result += bar(data.index + 2);
}
)glsl";
-
- CreateComputePipelineHelper pipe(*this);
- pipe.cs_ = VkShaderObj(this, cs_source, VK_SHADER_STAGE_COMPUTE_BIT, SPV_ENV_VULKAN_1_2);
- pipe.cp_ci_.layout = pipeline_layout;
- pipe.CreateComputePipeline();
-
- m_command_buffer.Begin();
- vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
- vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0, 1, &descriptor_set.set_, 0,
- nullptr);
- vk::CmdDispatch(m_command_buffer, 1, 1, 1);
- m_command_buffer.End();
-
- m_default_queue->SubmitAndWait(m_command_buffer);
+ const char *seed_str = std::getenv("STRESS_TEST_SEED");
+ unsigned long long seed;
+ if(seed_str) {
+ seed = std::strtoull(seed_str, NULL, 0);
+ } else {
+ seed = std::chrono::system_clock::now().time_since_epoch().count();
+ }
+ constexpr size_t N = 50;
+ for(size_t i = 0; i < N; i++) {
+ std::stringstream cs_source;
+ cs_source << cs_source_prefix << i << cs_source_suffix;
+ cs_sources.emplace_back(cs_source.str());
+ }
+ auto engine = std::default_random_engine{};
+ engine.seed(seed);
+ std::shuffle(cs_sources.begin(), cs_sources.end(), engine);
+
+ for(auto &cs_source : cs_sources){
+ CreateComputePipelineHelper pipe(*this);
+ pipe.cs_ = VkShaderObj(this, cs_source.c_str(), VK_SHADER_STAGE_COMPUTE_BIT, SPV_ENV_VULKAN_1_2);
+ pipe.cp_ci_.layout = pipeline_layout;
+ pipe.CreateComputePipeline();
+ }
}

TEST_F(StressGpuAV, DescriptorIndexing2) {

```

This test stitches together 50 slightly different shaders and shuffles them.
The result of repeatedly running this test as `ctest -R StressGpuAV.DescriptorIndexing$` are runtimes of 22.18s, 21.74s, 22.29s, etc. and an ever growing shader cache.
However, running it with a fixed seed as `STRESS_TEST_SEED=0 ctest -R StressGpuAV.DescriptorIndexing$` yields 21.27s, 0.79s, 0.77s, etc. and the shader cache does not grow after the first run.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.