Skip to content

Commit 32f2f57

Browse files
Add a one workgroup argmax benchmark (#49)
This PR is based on #47. I opened a new one because the old one got stale.
1 parent c9fe9ca commit 32f2f57

6 files changed

Lines changed: 414 additions & 0 deletions

File tree

benchmarks/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
add_subdirectory(argmax)
1516
add_subdirectory(compute)
1617
add_subdirectory(convolution)
1718
add_subdirectory(matmul)

benchmarks/argmax/CMakeLists.txt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Copyright 2024 Advanced Micro Devices Inc.
2+
# Copyright 2020-2024 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
uvkc_glsl_shader_instance(
17+
NAME
18+
one_workgroup_argmax_loop_shader
19+
SRC
20+
"one_workgroup_argmax_loop.glsl"
21+
GLSLC_ARGS
22+
"--target-env=vulkan1.1"
23+
)
24+
25+
uvkc_glsl_shader_instance(
26+
NAME
27+
one_workgroup_argmax_subgroup_shader
28+
SRC
29+
"one_workgroup_argmax_subgroup.glsl"
30+
GLSLC_ARGS
31+
"--target-env=vulkan1.1"
32+
)
33+
34+
uvkc_cc_binary(
35+
NAME
36+
one_workgrop_argmax
37+
SRCS
38+
"one_workgroup_argmax_main.cc"
39+
DEPS
40+
::one_workgroup_argmax_loop_shader
41+
::one_workgroup_argmax_subgroup_shader
42+
benchmark::benchmark
43+
uvkc::benchmark::core
44+
uvkc::benchmark::main
45+
)

benchmarks/argmax/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Argmax Benchmarks
2+
3+
This directory contains microbenchmarks for evaluating different strategy to
4+
implement argmax.
5+
6+
### `one_workgroup_argmax`
7+
8+
Performs argmax using just one workgroup. The workgroup just contains one
9+
subgroup. This approach does not use any synchronization mechanisms.
10+
11+
A subgroup uses either a single thread to loop over all elements or subgroup
12+
reduction operations involving all invocations.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# version 450 core
2+
#extension GL_EXT_control_flow_attributes : enable
3+
4+
layout(local_size_x = 32, local_size_y = 1, local_size_z = 1) in;
5+
6+
layout(set=0, binding=0) buffer InputBuffer { float data[]; } Input;
7+
layout(set=0, binding=1) buffer OutputBuffer { uint data; } Output;
8+
9+
layout(constant_id = 0) const uint totalCount = 1; // Total number of scalars
10+
11+
// Each workgroup contains just one subgroup.
12+
13+
void main() {
14+
uint laneID = gl_LocalInvocationID.x;
15+
16+
if (laneID != 0) return;
17+
18+
uint wgResult = 0;
19+
float wgMax = Input.data[0];
20+
21+
for (uint i = 1; i < totalCount; ++i) {
22+
float elem = Input.data[i];
23+
if (elem > wgMax) {
24+
wgResult = i;
25+
wgMax = elem;
26+
}
27+
}
28+
29+
Output.data = wgResult;
30+
}
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
// Copyright 2024 Advanced Micro Devices Inc.
2+
// Copyright 2020-2024 Google LLC
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
#include <chrono>
17+
#include <memory>
18+
#include <numeric>
19+
20+
#include "absl/strings/str_cat.h"
21+
#include "absl/types/span.h"
22+
#include "benchmark/benchmark.h"
23+
#include "uvkc/benchmark/data_type_util.h"
24+
#include "uvkc/benchmark/main.h"
25+
#include "uvkc/benchmark/status_util.h"
26+
#include "uvkc/benchmark/vulkan_buffer_util.h"
27+
#include "uvkc/benchmark/vulkan_context.h"
28+
#include "uvkc/vulkan/device.h"
29+
#include "uvkc/vulkan/pipeline.h"
30+
31+
using ::uvkc::benchmark::LatencyMeasureMode;
32+
using ::uvkc::vulkan::Pipeline;
33+
34+
static const char kBenchmarkName[] = "one_workgroup_argmax";
35+
36+
static const uint32_t kLoopShader[] = {
37+
#include "one_workgroup_argmax_loop_shader_spirv_instance.inc"
38+
};
39+
40+
static const uint32_t kSubgroupShader[] = {
41+
#include "one_workgroup_argmax_subgroup_shader_spirv_instance.inc"
42+
};
43+
44+
struct ShaderCode {
45+
const char *name; // Test case name
46+
const uint32_t *code; // SPIR-V code
47+
size_t code_num_bytes; // Number of bytes for SPIR-V code
48+
int workgroup_size; // Number of invocations per workgroup
49+
};
50+
51+
ShaderCode kShaders[] = {
52+
{"loop", kLoopShader, sizeof(kLoopShader), 32},
53+
{"subgroup", kSubgroupShader, sizeof(kSubgroupShader), 32},
54+
};
55+
56+
static void Argmax(::benchmark::State &state, ::uvkc::vulkan::Device *device,
57+
const ::uvkc::benchmark::LatencyMeasure *latency_measure,
58+
const uint32_t *code, size_t code_num_words,
59+
size_t total_elements, int workgroup_size) {
60+
//===-------------------------------------------------------------------===/
61+
// Create shader module, pipeline, and descriptor sets
62+
//===-------------------------------------------------------------------===/
63+
64+
BM_CHECK_OK_AND_ASSIGN(auto shader_module,
65+
device->CreateShaderModule(code, code_num_words));
66+
BM_CHECK_OK_AND_ASSIGN(auto descriptor_pool,
67+
device->CreateDescriptorPool(*shader_module));
68+
BM_CHECK_OK_AND_ASSIGN(auto layout_set_map,
69+
descriptor_pool->AllocateDescriptorSets(
70+
shader_module->descriptor_set_layouts()));
71+
72+
Pipeline::SpecConstant spec_constants[] = {
73+
{/*id=*/0, Pipeline::SpecConstant::Type::u32,
74+
static_cast<int32_t>(total_elements)},
75+
{/*id=*/1, Pipeline::SpecConstant::Type::u32, workgroup_size},
76+
};
77+
BM_CHECK_OK_AND_ASSIGN(
78+
auto pipeline, device->CreatePipeline(*shader_module, "main",
79+
absl::MakeSpan(spec_constants, 2)));
80+
81+
//===-------------------------------------------------------------------===/
82+
// Create buffers
83+
//===-------------------------------------------------------------------===/
84+
85+
const size_t src_buffer_size = total_elements * sizeof(float);
86+
const size_t dst_buffer_size = sizeof(int);
87+
88+
BM_CHECK_OK_AND_ASSIGN(
89+
auto src_buffer,
90+
device->CreateBuffer(
91+
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
92+
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, src_buffer_size));
93+
BM_CHECK_OK_AND_ASSIGN(
94+
auto dst_buffer,
95+
device->CreateBuffer(
96+
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
97+
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
98+
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, dst_buffer_size));
99+
100+
// Create a buffer for zeroing the destination buffer.
101+
BM_CHECK_OK_AND_ASSIGN(
102+
auto data_buffer,
103+
device->CreateBuffer(
104+
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
105+
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
106+
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, dst_buffer_size));
107+
108+
//===-------------------------------------------------------------------===/
109+
// Set source buffer data
110+
//===-------------------------------------------------------------------===/
111+
112+
auto generate_float_data = [](size_t i) {
113+
return i == 550 ? float(i) : 1.0f;
114+
};
115+
116+
BM_CHECK_OK(::uvkc::benchmark::SetDeviceBufferViaStagingBuffer(
117+
device, src_buffer.get(), src_buffer_size,
118+
[&](void *ptr, size_t num_bytes) {
119+
float *src_float_buffer = reinterpret_cast<float *>(ptr);
120+
for (size_t i = 0; i < num_bytes / sizeof(float); i++) {
121+
src_float_buffer[i] = generate_float_data(i);
122+
}
123+
}));
124+
125+
//===-------------------------------------------------------------------===/
126+
// Dispatch
127+
//===-------------------------------------------------------------------===/
128+
129+
std::vector<::uvkc::vulkan::Device::BoundBuffer> bound_buffers = {
130+
{src_buffer.get(), /*set=*/0, /*binding=*/0},
131+
{dst_buffer.get(), /*set=*/0, /*binding=*/1},
132+
};
133+
BM_CHECK_OK(device->AttachBufferToDescriptor(
134+
*shader_module, layout_set_map,
135+
{bound_buffers.data(), bound_buffers.size()}));
136+
137+
BM_CHECK_EQ(shader_module->descriptor_set_layouts().size(), 1)
138+
<< "unexpected number of descriptor sets";
139+
auto descriptor_set_layout = shader_module->descriptor_set_layouts().front();
140+
141+
std::vector<::uvkc::vulkan::CommandBuffer::BoundDescriptorSet>
142+
bound_descriptor_sets(1);
143+
bound_descriptor_sets[0].index = 0;
144+
bound_descriptor_sets[0].set = layout_set_map.at(descriptor_set_layout);
145+
BM_CHECK_OK_AND_ASSIGN(auto dispatch_cmdbuf, device->AllocateCommandBuffer());
146+
147+
BM_CHECK_OK(dispatch_cmdbuf->Begin());
148+
dispatch_cmdbuf->BindPipelineAndDescriptorSets(
149+
*pipeline, {bound_descriptor_sets.data(), bound_descriptor_sets.size()});
150+
dispatch_cmdbuf->Dispatch(1, 1, 1);
151+
BM_CHECK_OK(dispatch_cmdbuf->End());
152+
BM_CHECK_OK(device->QueueSubmitAndWait(*dispatch_cmdbuf));
153+
154+
//===-------------------------------------------------------------------===/
155+
// Verify destination buffer data
156+
//===-------------------------------------------------------------------===/
157+
158+
BM_CHECK_OK(::uvkc::benchmark::GetDeviceBufferViaStagingBuffer(
159+
device, dst_buffer.get(), dst_buffer_size,
160+
[&](void *ptr, size_t num_bytes) {
161+
int *dst_int_buffer = reinterpret_cast<int *>(ptr);
162+
float max = -10000.f;
163+
int idx = -1;
164+
for (size_t i = 0; i < total_elements; i++) {
165+
float data = generate_float_data(i);
166+
if (data > max) {
167+
idx = i;
168+
max = data;
169+
}
170+
};
171+
BM_CHECK_EQ(dst_int_buffer[0], idx)
172+
<< "destination buffer element #0 has incorrect value: "
173+
"expected to be "
174+
<< idx << " but found " << dst_int_buffer[0];
175+
}));
176+
177+
//===-------------------------------------------------------------------===/
178+
// Benchmarking
179+
//===-------------------------------------------------------------------===/
180+
181+
std::unique_ptr<::uvkc::vulkan::TimestampQueryPool> query_pool;
182+
bool use_timestamp =
183+
latency_measure->mode == LatencyMeasureMode::kGpuTimestamp;
184+
if (use_timestamp) {
185+
BM_CHECK_OK_AND_ASSIGN(query_pool, device->CreateTimestampQueryPool(2));
186+
}
187+
188+
BM_CHECK_OK_AND_ASSIGN(auto cmdbuf, device->AllocateCommandBuffer());
189+
for (auto _ : state) {
190+
BM_CHECK_OK(cmdbuf->Begin());
191+
if (use_timestamp) cmdbuf->ResetQueryPool(*query_pool);
192+
193+
cmdbuf->BindPipelineAndDescriptorSets(
194+
*pipeline,
195+
{bound_descriptor_sets.data(), bound_descriptor_sets.size()});
196+
197+
if (use_timestamp) {
198+
cmdbuf->WriteTimestamp(*query_pool, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0);
199+
}
200+
201+
cmdbuf->Dispatch(1, 1, 1);
202+
203+
if (use_timestamp) {
204+
cmdbuf->WriteTimestamp(*query_pool, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
205+
1);
206+
}
207+
208+
BM_CHECK_OK(cmdbuf->End());
209+
210+
auto start_time = std::chrono::high_resolution_clock::now();
211+
BM_CHECK_OK(device->QueueSubmitAndWait(*cmdbuf));
212+
auto end_time = std::chrono::high_resolution_clock::now();
213+
auto elapsed_seconds =
214+
std::chrono::duration_cast<std::chrono::duration<double>>(end_time -
215+
start_time);
216+
217+
switch (latency_measure->mode) {
218+
case LatencyMeasureMode::kSystemDispatch: {
219+
state.SetIterationTime(elapsed_seconds.count() -
220+
latency_measure->overhead_seconds);
221+
} break;
222+
case LatencyMeasureMode::kSystemSubmit: {
223+
state.SetIterationTime(elapsed_seconds.count());
224+
} break;
225+
case LatencyMeasureMode::kGpuTimestamp: {
226+
BM_CHECK_OK_AND_ASSIGN(
227+
double timestamp_seconds,
228+
query_pool->CalculateElapsedSecondsBetween(0, 1));
229+
state.SetIterationTime(timestamp_seconds);
230+
} break;
231+
}
232+
233+
BM_CHECK_OK(cmdbuf->Reset());
234+
}
235+
236+
state.SetBytesProcessed(state.iterations() * src_buffer_size);
237+
state.counters["FLOps"] =
238+
::benchmark::Counter(total_elements,
239+
::benchmark::Counter::kIsIterationInvariant |
240+
::benchmark::Counter::kIsRate,
241+
::benchmark::Counter::kIs1000);
242+
243+
// Reset the command pool to release all command buffers in the benchmarking
244+
// loop to avoid draining GPU resources.
245+
BM_CHECK_OK(device->ResetCommandPool());
246+
}
247+
248+
namespace uvkc {
249+
namespace benchmark {
250+
251+
absl::StatusOr<std::unique_ptr<VulkanContext>> CreateVulkanContext() {
252+
return CreateDefaultVulkanContext(kBenchmarkName);
253+
}
254+
255+
bool RegisterVulkanOverheadBenchmark(
256+
const vulkan::Driver::PhysicalDeviceInfo &physical_device,
257+
vulkan::Device *device, double *overhead_seconds) {
258+
return false;
259+
}
260+
261+
void RegisterVulkanBenchmarks(
262+
const vulkan::Driver::PhysicalDeviceInfo &physical_device,
263+
vulkan::Device *device, const LatencyMeasure *latency_measure) {
264+
const char *gpu_name = physical_device.v10_properties.deviceName;
265+
266+
for (const auto &shader : kShaders) {
267+
for (size_t total_elements : {1 << 10, 1 << 12, 1 << 14, 1 << 16}) {
268+
std::string test_name = absl::StrCat(
269+
gpu_name, "/#elements=", total_elements,
270+
"/workgroup_size=", shader.workgroup_size, "/", shader.name);
271+
::benchmark::RegisterBenchmark(test_name.c_str(), Argmax, device,
272+
latency_measure, shader.code,
273+
shader.code_num_bytes / sizeof(uint32_t),
274+
total_elements, shader.workgroup_size)
275+
->UseManualTime()
276+
->Unit(::benchmark::kMicrosecond);
277+
}
278+
}
279+
}
280+
281+
} // namespace benchmark
282+
} // namespace uvkc

0 commit comments

Comments
 (0)