Source browser
examples/benchmark.cpp
Read the complete file below or download its original bytes. Line numbers provide stable links to a declaration or processing step.
File fingerprint
1/** @file benchmark.cpp
2 * Seeded CPU/GPU comparisons for the example compute pipelines. Warmup excludes
3 * compilation and allocation. Steady-state timings include hybrid backend transfers.
4 * Final host readback and correctness comparisons occur after timed execution.
5 */
6#include "runner.hpp" // Include runner.hpp declarations.
7#include "evt3_support.hpp" // Include evt3_support.hpp declarations.
8#include "evt3_flow_checks.hpp" // Include evt3_flow_checks.hpp declarations.
9#include "neural_support.hpp" // Include neural_support.hpp declarations.
10#include "transformer_support.hpp" // Include transformer_support.hpp declarations.
11#include "fft_support.hpp" // Include fft_support.hpp declarations.
12#include "signal_filters.hpp" // Include signal_filters.hpp declarations.
13#include "lenet_support.hpp" // Build the complete seven-stage image classifier.
14#include <chrono> // Include chrono declarations.
15#include <cstdlib> // Include cstdlib declarations.
16#include <functional> // Include functional declarations.
17#include <future> // Include future declarations.
18#include <iomanip> // Include iomanip declarations.
19#include <sstream> // Include sstream declarations.
20#include <vector> // Include vector declarations.
21#ifdef SHDR_PLUGIN_PATH // Enable this branch when its feature macro is defined.
22#include <dlfcn.h> // Include dlfcn.h declarations.
23#endif // Finish this conditional compilation branch.
24
25namespace { // Group these helpers within their namespace.
26using namespace shdr::glsl; // Allow concise GLSL type and builtin names in this scope.
27const unsigned random_seed = 12345u; // Set random seed from this expression.
28
29unsigned setting(const char* name, unsigned fallback, unsigned maximum) { // Implement setting for the operations below.
30 const char* value = std::getenv(name); // Set value from this expression.
31 if (!value) // Check this condition before entering the following branch.
32 return fallback; // Return fallback.
33 char* end = 0; // Set end from this expression.
34 const unsigned long number = std::strtoul(value, &end, 10); // Set number from this expression.
35 if (!*value || *end || !number || number > maximum) // Check this condition before entering the following branch.
36 throw std::invalid_argument(std::string(name) + " must be between 1 and " + // Report must be between 1 and .
37 std::to_string(maximum)); // Apply to string to these arguments.
38 return unsigned(number); // Return unsigned(number).
39} // End this block.
40
41shdr::extent groups(const shdr::kernel& kernel, unsigned x, unsigned y = 1, unsigned z = 1) { // Set y from this expression.
42 const shdr::extent local = kernel.local_size(); // Set local from this expression.
43 return shdr::extent( // Return shdr::extent(.
44 (x + local.x - 1) / local.x, (y + local.y - 1) / local.y, (z + local.z - 1) / local.z); // Continue the expression using x, local, x.
45} // End this block.
46
47void same(float actual, float expected, double absolute, double relative) { // Compare CPU and GPU values using the selected tolerance.
48 const double difference = std::fabs(double(actual) - double(expected)); // Set difference from this expression.
49 if (!std::isfinite(actual) || !std::isfinite(expected) || // Check this condition before entering the following branch.
50 difference > // Continue the expression using difference.
51 absolute + relative * std::max(std::fabs(double(actual)), std::fabs(double(expected)))) // Apply max to these arguments.
52 throw std::runtime_error("CPU/GPU float mismatch: " + std::to_string(actual) + " versus " + // Report CPU/GPU float mismatch: .
53 std::to_string(expected)); // Apply to string to these arguments.
54} // End this block.
55void same(unsigned actual, unsigned expected, double, double) { // Compare CPU and GPU values using the selected tolerance.
56 if (actual != expected) // Check this condition before entering the following branch.
57 throw std::runtime_error("CPU/GPU uint mismatch"); // Report CPU/GPU uint mismatch.
58} // End this block.
59template <class T, std::size_t N> // Accept the value types or dimensions used by this template.
60void same(const shdr::glsl::vec<T, N>& actual, // Compare CPU and GPU values using the selected tolerance.
61 const shdr::glsl::vec<T, N>& expected, // Declare expected for the following calculation.
62 double absolute, // Use this floating-point value or resource in the following calculation.
63 double relative) { // Use this floating-point value or resource in the following calculation.
64 for (std::size_t c = 0; c < N; ++c) // Iterate over c for this calculation.
65 same(actual[c], expected[c], absolute, relative); // Compare CPU and GPU values using the selected tolerance.
66} // End this block.
67
68struct comparison { // Declare comparison.
69 virtual ~comparison() { // Apply comparison to these arguments.
70 } // End this block.
71 virtual void capture() = 0; // Construct capture with these initial values.
72 virtual void verify() = 0; // Construct verify with these initial values.
73}; // Finish this declaration.
74template <class T> class output_check : public comparison { // Accept the value types or dimensions used by this template.
75 const shdr::buffer<T>& resource_; // Declare resource for the following calculation.
76 std::vector<T> expected_; // Declare expected for the following calculation.
77 double absolute_, relative_; // Use this floating-point value or resource in the following calculation.
78
79 public: // Set public access for the following class members.
80 explicit output_check(const shdr::buffer<T>& resource, // Declare output check with the parameters continued below.
81 double absolute = 1e-4, // Use this floating-point value or resource in the following calculation.
82 double relative = 1e-4) // Use this floating-point value or resource in the following calculation.
83 : resource_(resource), absolute_(absolute), relative_(relative) { // Implement resource for the operations below.
84 } // End this block.
85 void capture() override { // Retain a host copy of this CPU output after its timed execution.
86 const T* data = resource_.data(); // Set data from this expression.
87 expected_.assign(data, data + resource_.size()); // Copy the complete CPU reference while the resource remains unmodified.
88 } // End this block.
89 void verify() override { // Synchronize this GPU output and compare each element outside the timed section.
90 const T* data = resource_.data(); // Set data from this expression.
91 for (std::size_t i = 0; i < expected_.size(); ++i) { // Iterate over i for this calculation.
92 try { // Run the operation while retaining the error handling below.
93 same(data[i], expected_[i], absolute_, relative_); // Compare CPU and GPU values using the selected tolerance.
94 } catch (const std::exception& error) { // Report the exception from the preceding operation.
95 throw std::runtime_error(std::string(error.what()) + " at element " + // Report at element .
96 std::to_string(i)); // Apply to string to these arguments.
97 } // End this block.
98 } // End this block.
99 } // End this block.
100}; // Finish this declaration.
101
102struct timing { // Declare timing.
103 std::string name, size; // Declare name for the following calculation.
104 double cpu, gpu; // Use this floating-point value or resource in the following calculation.
105}; // Finish this declaration.
106class benchmark { // Declare benchmark.
107 shdr::device* device_; // Declare device for the following calculation.
108 unsigned iterations_; // Declare iterations for the following calculation.
109 std::vector<timing> timings_; // Declare timings for the following calculation.
110 std::vector<std::string> metadata_; // Declare metadata for the following calculation.
111
112 public: // Set public access for the following class members.
113 benchmark(shdr::device* device, unsigned iterations) // Apply benchmark to these arguments.
114 : device_(device), iterations_(iterations) { // Implement device for the operations below.
115 } // End this block.
116 void note(const std::string& text) { // Implement note for the operations below.
117 metadata_.push_back(text); // Apply push back to these arguments.
118 } // End this block.
119 void measure(const std::string& name, // Measure one reusable pipeline after both backends have been warmed up.
120 const std::string& size, // Declare size for the following calculation.
121 const shdr::pipeline& pipeline, // Declare pipeline for the following calculation.
122 std::initializer_list<comparison*> checks) { // Declare checks for the following calculation.
123 measure_operations( // Apply measure operations to these arguments.
124 name, // Continue the expression using name.
125 size, // Continue the expression using size.
126 [&pipeline](bool) { // Continue the expression using pipeline, bool.
127 pipeline.run(); // Execute the configured pipeline on the requested backend.
128 }, // Finish this declaration.
129 [&pipeline, this](bool) { // Continue the expression using pipeline, this, bool.
130 pipeline.run(*device_); // Execute the configured pipeline on the requested backend.
131 }, // Finish this declaration.
132 checks); // Continue the expression using checks.
133 } // End this block.
134 void measure_operations(const std::string& name, // Measure completed resident operations with reset and validation outside the timed loop.
135 const std::string& size, // Declare size for the following calculation.
136 const std::function<void(bool)>& cpu_operation, // Declare cpu operation for the following calculation.
137 const std::function<void(bool)>& gpu_operation, // Declare gpu operation for the following calculation.
138 std::initializer_list<comparison*> checks) { // Declare checks for the following calculation.
139 typedef std::chrono::steady_clock clock; // Measure elapsed time with the monotonic steady-clock counter.
140 cpu_operation(true); // Warm up CPU execution and reset stateful operations to their documented initial state.
141 clock::time_point begin = clock::now(); // Begin timing only after the CPU warmup has finished.
142 for (unsigned i = 0; i < iterations_; ++i) // Iterate over i for this calculation.
143 cpu_operation(false); // Run the next CPU operation with the state produced by its predecessor.
144 const double cpu = // Calculate the mean completed CPU operation time in milliseconds.
145 std::chrono::duration<double, std::milli>(clock::now() - begin).count() / iterations_; // Apply now to these arguments.
146 for (comparison* check : checks) // Visit each check required by this loop.
147 check->capture(); // Save the CPU outputs after timing so host comparison work is excluded.
148 double gpu = 0.0; // Initialize the GPU timing field for the optional device run.
149 if (device_) { // Check this condition before entering the following branch.
150 gpu_operation(true); // Compile, upload and warm up GPU resources before the timer starts.
151 device_->synchronize(); // Finish all warmup GPU commands before starting the timer.
152 begin = clock::now(); // Begin timing only after all warmup GPU commands have completed.
153 for (unsigned i = 0; i < iterations_; ++i) // Iterate over i for this calculation.
154 gpu_operation(false); // Run the next resident GPU operation before collecting final results.
155 device_->synchronize(); // Wait for the timed GPU commands to complete before stopping the timer.
156 gpu = std::chrono::duration<double, std::milli>(clock::now() - begin).count() / // Convert completed GPU elapsed time into milliseconds.
157 iterations_; // Divide total GPU time by the number of complete operations.
158 for (comparison* check : checks) { // Visit each check required by this loop.
159 try { // Run the operation while retaining the error handling below.
160 check->verify(); // Read outputs after timing and compare them with the retained CPU reference.
161 } catch (const std::exception& error) { // Report the exception from the preceding operation.
162 throw std::runtime_error(name + ": " + error.what()); // Report : .
163 } // End this block.
164 } // End this block.
165 } // End this block.
166 timings_.push_back(timing{name, size, cpu, gpu}); // Retain the workload and both completed-operation timings for the results table.
167 } // End this block.
168 void print(unsigned scale) const { // Write the reproducible input settings, renderer and completed-operation timing table.
169 std::cout << "Seed: " << random_seed << ", iterations: " << iterations_ // Write this part of the example result or diagnostic.
170 << ", scale: " << scale << ".\n"; // Write this part of the example result or diagnostic.
171 if (device_) // Check this condition before entering the following branch.
172 std::cout << "GPU renderer: " << device_->renderer() << ".\n"; // Write this part of the example result or diagnostic.
173 for (const std::string& text : metadata_) // Visit each std required by this loop.
174 std::cout << text << "\n"; // Write this part of the example result or diagnostic.
175 std::cout // Write this part of the example result or diagnostic.
176 << "Steady-state resident timings, compilation, first uploads and final readback excluded.\n" // Write this part of the example result or diagnostic.
177 << "Hybrid timings include transfers between CPU and GPU stages.\n\n" // Write this part of the example result or diagnostic.
178 << "| Pipeline | Workload | CPU ms | GPU ms | CPU/GPU |\n" // Write this part of the example result or diagnostic.
179 << "| --- | --- | ---: | ---: | ---: |\n" // Write this part of the example result or diagnostic.
180 << std::fixed << std::setprecision(3); // Write this part of the example result or diagnostic.
181 for (const timing& row : timings_) { // Visit each row required by this loop.
182 std::cout << "| " << row.name << " | " << row.size << " | " << row.cpu << " | "; // Write this part of the example result or diagnostic.
183 if (device_) // Check this condition before entering the following branch.
184 std::cout << row.gpu << " | " << row.cpu / row.gpu; // Write this part of the example result or diagnostic.
185 else // Print placeholders for the CPU execution mode.
186 std::cout << "- | -"; // Write this part of the example result or diagnostic.
187 std::cout << " |\n"; // Write this part of the example result or diagnostic.
188 } // End this block.
189 } // End this block.
190}; // Finish this declaration.
191
192void random_complex(shdr::buffer<vec2>& values, unsigned seed) { // Implement random complex for the operations below.
193 std::mt19937 generator(seed); // Construct generator with these initial values.
194 std::uniform_real_distribution<float> distribution(-1.0f, 1.0f); // Construct distribution with these initial values.
195 vec2* data = values.data(); // Set data from this expression.
196 for (std::size_t i = 0; i < values.size(); ++i) // Iterate over i for this calculation.
197 data[i] = vec2(distribution(generator), distribution(generator)); // Set data[i] from this expression.
198} // End this block.
199std::string dimensions(unsigned x, unsigned y, unsigned z = 0) { // Set z from this expression.
200 return std::to_string(x) + " x " + std::to_string(y) + (z ? " x " + std::to_string(z) : ""); // Return the result of this operation.
201} // End this block.
202
203void buffer_pipeline(benchmark& bench, unsigned count, bool hybrid, shdr::kernel* external = 0) { // Set external from this expression.
204 shdr::buffer<float> input(count), scaled(count), clipped(count), result(1); // Construct input with these initial values.
205 shdr_examples::randomize(input, random_seed, -4.0f, 12.0f); // Fill the input with reproducible random values.
206 auto owned_affine = // Set owned affine from this expression.
207 external ? std::unique_ptr<shdr::kernel>() : shdr::make_kernel("affine_kernel"); // Create the linked affine kernel instance.
208 shdr::kernel& affine = external ? *external : *owned_affine; // Set affine from this expression.
209 affine.bind("input_values", input) // Connect this shader port to its application resource.
210 .bind("output_values", scaled) // Connect this shader port to its application resource.
211 .set<unsigned>("count", count) // Supply this shader uniform with its typed application value.
212 .set<float>("scale", 2.0f) // Supply this shader uniform with its typed application value.
213 .set<float>("offset", 1.0f); // Supply this shader uniform with its typed application value.
214 auto clamp = shdr::make_kernel("clamp_kernel"); // Create the linked clamp kernel instance.
215 clamp->bind("input_values", scaled) // Connect this shader port to its application resource.
216 .bind("output_values", clipped) // Connect this shader port to its application resource.
217 .set<unsigned>("count", count) // Supply this shader uniform with its typed application value.
218 .set<float>("lower", 0.0f) // Supply this shader uniform with its typed application value.
219 .set<float>("upper", 8.0f); // Supply this shader uniform with its typed application value.
220 auto sum = shdr::make_kernel("sum_kernel"); // Create the linked sum kernel instance.
221 sum->bind("input_values", clipped).bind("output_values", result).set<unsigned>("count", count); // Connect this shader port to its application resource.
222 shdr::pipeline pipeline = // Set pipeline from this expression.
223 shdr::dispatch(affine, groups(affine, count)) >> // Add a shader dispatch with the required workgroup counts.
224 shdr::dispatch(*clamp, // Add a shader dispatch with the required workgroup counts.
225 groups(*clamp, count), // Apply groups to these arguments.
226 hybrid ? shdr::backend::cpu : shdr::backend::automatic) >> // Continue the expression using hybrid, shdr, backend.
227 shdr::dispatch(*sum, groups(*sum, 1)); // Add a shader dispatch with the required workgroup counts.
228 output_check<float> check_result(result), check_clipped(clipped); // Construct check result with these initial values.
229 bench.measure(hybrid ? "Hybrid affine/clamp/sum" // Apply measure to these arguments.
230 : external ? "Dynamic library stages" // Declare external for the following calculation.
231 : "Buffer affine/clamp/sum", // Provide this text argument for the operation above.
232 std::to_string(count) + " floats", // Apply to string to these arguments.
233 pipeline, // Continue the expression using pipeline.
234 {&check_result, &check_clipped}); // Continue the expression using check result, check clipped.
235} // End this block.
236
237void image_pipeline(benchmark& bench, // Declare image pipeline with the parameters continued below.
238 unsigned side, // Declare side for the following calculation.
239 shdr::device* device = 0, // Set device from this expression.
240 bool borrow = false) { // Set borrow from this expression.
241 shdr::buffer<float> input(side * side), output(side * side); // Construct input with these initial values.
242 shdr_examples::randomize(input, random_seed, 0.0f, 1.0f); // Fill the input with reproducible random values.
243 // The application retains ownership of a texture temporarily borrowed by shdr.
244 shdr::image2d<float, 1> application_image(side, side), filtered(side, side); // Construct application image with these initial values.
245 shdr::image2d<float, 1> original = // Set original from this expression.
246 borrow && device ? shdr::image2d<float, 1>::from_gl( // Apply from gl to these arguments.
247 *device, application_image.gl_handle(*device), side, side) // Apply gl handle to these arguments.
248 : application_image; // Declare application image for the following calculation.
249 auto into = shdr::make_kernel("buffer_to_image_kernel"); // Create the linked buffer to image kernel instance.
250 into->bind("input_values", input).bind("output_image", original); // Connect this shader port to its application resource.
251 auto blur = shdr::make_kernel("blur_kernel"); // Create the linked blur kernel instance.
252 blur->bind("input_image", original).bind("output_image", filtered); // Connect this shader port to its application resource.
253 auto out = shdr::make_kernel("image_to_buffer_kernel"); // Create the linked image to buffer kernel instance.
254 out->bind("input_image", filtered).bind("output_values", output); // Connect this shader port to its application resource.
255 shdr::pipeline pipeline = shdr::dispatch(*into, groups(*into, side, side)) >> // Add a shader dispatch with the required workgroup counts.
256 shdr::dispatch(*blur, groups(*blur, side, side)) >> // Add a shader dispatch with the required workgroup counts.
257 shdr::dispatch(*out, groups(*out, side, side)); // Add a shader dispatch with the required workgroup counts.
258 output_check<float> check(output); // Construct check with these initial values.
259 bench.measure(borrow ? "Borrowed image blur" : "Buffer/image blur", // Apply measure to these arguments.
260 dimensions(side, side), // Apply dimensions to these arguments.
261 pipeline, // Continue the expression using pipeline.
262 {&check}); // Continue the expression using check.
263} // End this block.
264
265void bayer_pipeline(benchmark& bench, unsigned side) { // Implement bayer pipeline for the operations below.
266 const unsigned count = side * side, words = (count / 4u * 5u + 3u) / 4u; // Set count from this expression.
267 shdr::buffer<unsigned> packed(words), samples(count); // Construct packed with these initial values.
268 shdr::buffer<vec4> colors(count); // Construct colors with these initial values.
269 shdr::buffer<float> gray(count); // Construct gray with these initial values.
270 std::mt19937 generator(random_seed); // Construct generator with these initial values.
271 for (unsigned i = 0; i < words; ++i) // Iterate over i for this calculation.
272 packed.data()[i] = generator(); // Apply data to these arguments.
273 auto unpack = shdr::make_kernel("bayer_unpack_kernel"); // Create the linked bayer unpack kernel instance.
274 unpack->bind("packed_words", packed) // Connect this shader port to its application resource.
275 .bind("samples", samples) // Connect this shader port to its application resource.
276 .set<unsigned>("count", count) // Supply this shader uniform with its typed application value.
277 .set<unsigned>("packing", 2u) // Supply this shader uniform with its typed application value.
278 .set<unsigned>("bit_depth", 10u); // Supply this shader uniform with its typed application value.
279 auto debayer = shdr::make_kernel("debayer_kernel"); // Create the linked debayer kernel instance.
280 debayer->bind("samples", samples) // Connect this shader port to its application resource.
281 .bind("colors", colors) // Connect this shader port to its application resource.
282 .set<unsigned>("width", side) // Supply this shader uniform with its typed application value.
283 .set<unsigned>("height", side) // Supply this shader uniform with its typed application value.
284 .set<unsigned>("pattern", 0u) // Supply this shader uniform with its typed application value.
285 .set<unsigned>("bit_depth", 10u); // Supply this shader uniform with its typed application value.
286 auto luminance = shdr::make_kernel("luminance_kernel"); // Create the linked luminance kernel instance.
287 luminance->bind("colors", colors).bind("gray", gray).set<unsigned>("count", count); // Connect this shader port to its application resource.
288 shdr::pipeline pipeline = shdr::dispatch(*unpack, groups(*unpack, count)) >> // Add a shader dispatch with the required workgroup counts.
289 shdr::dispatch(*debayer, groups(*debayer, side, side)) >> // Add a shader dispatch with the required workgroup counts.
290 shdr::dispatch(*luminance, groups(*luminance, count)); // Add a shader dispatch with the required workgroup counts.
291 output_check<unsigned> sample_check(samples); // Construct sample check with these initial values.
292 output_check<vec4> color_check(colors); // Construct color check with these initial values.
293 output_check<float> gray_check(gray); // Construct gray check with these initial values.
294 bench.measure("RAW10/debayer/luminance", // Apply measure to these arguments.
295 dimensions(side, side), // Apply dimensions to these arguments.
296 pipeline, // Continue the expression using pipeline.
297 {&sample_check, &color_check, &gray_check}); // Continue the expression using sample check, color check, gray check.
298} // End this block.
299
300void evt3_pipeline(benchmark& bench, unsigned word_count) { // Implement evt3 pipeline for the operations below.
301 shdr_examples::evt3_decoder decoder(word_count); // Construct decoder with these initial values.
302 decoder.load(shdr_examples::evt3_random_words(word_count, random_seed)); // Apply load to these arguments.
303 output_check<uvec4> event_check(decoder.events), state_check(decoder.state); // Construct event check with these initial values.
304 output_check<unsigned> offset_check(decoder.offsets); // Construct offset check with these initial values.
305 bench.measure("EVT3 event decoding", // Apply measure to these arguments.
306 std::to_string(word_count) + " packed words", // Apply to string to these arguments.
307 decoder.reset_pipeline(), // Apply reset pipeline to these arguments.
308 {&event_check, &state_check, &offset_check}); // Continue the expression using event check, state check, offset check.
309 bench.note("EVT3 compact output: " + std::to_string(decoder.event_count()) + " events from " + // Apply note to these arguments.
310 std::to_string(word_count) + " words, five stages include state reset."); // Apply to string to these arguments.
311} // End this block.
312
313void evt3_optical_flow(benchmark& bench) { // Implement evt3 optical flow for the operations below.
314 const std::vector<std::uint16_t> plane = shdr_examples::evt3_planar_words(32, 24); // Set plane from this expression.
315 std::vector<std::uint16_t> words; // Declare words for the following calculation.
316 std::mt19937 random(random_seed); // Construct random with these initial values.
317 for (std::size_t i = 0; i < plane.size(); i += 4) { // Iterate over i for this calculation.
318 // Seeded spatial sampling varies neighborhoods while preserving the known temporal plane.
319 if (random() % 5u == 0u) // Check this condition before entering the following branch.
320 continue; // Skip the remaining work for this loop iteration.
321 words.insert(words.end(), plane.begin() + i, plane.begin() + i + 4); // Apply insert to these arguments.
322 } // End this block.
323 shdr_examples::evt3_decoder decoder(static_cast<unsigned>(words.size())); // Construct decoder with these initial values.
324 decoder.load(words); // Apply load to these arguments.
325 shdr::buffer<vec4> flow(decoder.events.size()); // Construct flow with these initial values.
326 auto shader = shdr_examples::evt3_flow_kernel(decoder, flow, 64u); // Set shader from this expression.
327 shdr::pipeline pipeline = decoder.reset_pipeline(); // Set pipeline from this expression.
328 pipeline += shdr::dispatch(*shader, groups(*shader, static_cast<unsigned>(flow.size()))); // Add a shader dispatch with the required workgroup counts.
329 output_check<uvec4> event_check(decoder.events); // Construct event check with these initial values.
330 output_check<unsigned> offset_check(decoder.offsets); // Construct offset check with these initial values.
331 output_check<vec4> flow_check(flow, 1e-3, 1e-3); // Construct flow check with these initial values.
332 bench.measure("EVT3 + optical flow", // Apply measure to these arguments.
333 "32 x 24 sampled plane, history 64", // Provide this text argument for the operation above.
334 pipeline, // Continue the expression using pipeline.
335 {&event_check, &offset_check, &flow_check}); // Continue the expression using event check, offset check, flow check.
336 bench.note("Optical flow input: " + std::to_string(decoder.event_count()) + // Apply note to these arguments.
337 " seeded samples on a known temporal plane, six linked stages."); // Provide this text argument for the operation above.
338} // End this block.
339
340void binary_neural_pipeline(benchmark& bench, shdr::device* device, bool convolutional) { // Implement binary neural pipeline for the operations below.
341 shdr_examples::binary_network model(convolutional, 32); // Construct model with these initial values.
342 const std::string name = convolutional ? "CNN" : "ANN"; // Set name from this expression.
343 const std::string size = std::to_string(model.batch_count()) + " samples, " + // Set size from this expression.
344 std::to_string(model.parameter_count()) + " parameters"; // Apply to string to these arguments.
345 output_check<float> parameter_check(model.parameters), gradient_check(model.gradients); // Construct parameter check with these initial values.
346 bench.measure_operations( // Apply measure operations to these arguments.
347 name + " training", // Provide this text argument for the operation above.
348 size, // Continue the expression using size.
349 [&](bool warmup) { // Continue the expression using bool, warmup.
350 if (warmup) // Check this condition before entering the following branch.
351 model.reset_parameters(); // Restore the same initial parameter vector before this backend's warmup update.
352 model.training_pipeline().run(); // Compute all sample gradients and apply one averaged SGD parameter update.
353 }, // Finish this declaration.
354 [&](bool warmup) { // Continue the expression using bool, warmup.
355 if (warmup) // Check this condition before entering the following branch.
356 model.reset_parameters(); // Restore the same initial parameter vector before this backend's warmup update.
357 model.training_pipeline().run(*device); // Compute all sample gradients and apply one averaged SGD parameter update.
358 }, // Finish this declaration.
359 {¶meter_check, &gradient_check}); // Continue the expression using parameter check, gradient check.
360 output_check<float> prediction_check(model.predictions); // Construct prediction check with these initial values.
361 bench.measure(name + " inference", size, model.prediction_pipeline(), {&prediction_check}); // Apply measure to these arguments.
362} // End this block.
363
364void transformer_pipeline(benchmark& bench, shdr::device* device) { // Implement transformer pipeline for the operations below.
365 shdr_examples::transformer_model model(8); // Construct model with these initial values.
366 model.reset_inputs(random_seed); // Apply reset inputs to these arguments.
367 const std::string size = "8 sequences, 4 tokens x 4, 134 parameters"; // Set size from this expression.
368 output_check<float> parameter_check(model.parameters, 1e-4, 1e-3), // Declare parameter check with the parameters continued below.
369 gradient_check(model.gradients, 1e-4, 1e-3); // Apply gradient check to these arguments.
370 bench.measure_operations( // Apply measure operations to these arguments.
371 "Transformer training", // Provide this text argument for the operation above.
372 size, // Continue the expression using size.
373 [&](bool warmup) { // Continue the expression using bool, warmup.
374 if (warmup) // Check this condition before entering the following branch.
375 model.reset_parameters(random_seed); // Restore the same initial parameter vector before this backend's warmup update.
376 model.training_pipeline().run(); // Compute all sample gradients and apply one averaged SGD parameter update.
377 }, // Finish this declaration.
378 [&](bool warmup) { // Continue the expression using bool, warmup.
379 if (warmup) // Check this condition before entering the following branch.
380 model.reset_parameters(random_seed); // Restore the same initial parameter vector before this backend's warmup update.
381 model.training_pipeline().run(*device); // Compute all sample gradients and apply one averaged SGD parameter update.
382 }, // Finish this declaration.
383 {¶meter_check, &gradient_check}); // Continue the expression using parameter check, gradient check.
384 output_check<vec4> prediction_check(model.predictions, 1e-4, 1e-3); // Construct prediction check with these initial values.
385 bench.measure("Transformer inference", size, model.prediction_pipeline(), {&prediction_check}); // Apply measure to these arguments.
386 bench.note( // Apply note to these arguments.
387 "Neural training timings measure one mini-batch gradient/SGD update, inference measures the complete forward pipeline."); // Provide this text argument for the operation above.
388} // End this block.
389
390void fft_round_trip(benchmark& bench) { // Implement fft round trip for the operations below.
391 const unsigned length = 1024, channels = 8; // Set length from this expression.
392 shdr::buffer<vec2> input(length * channels), spectrum(length * channels), // Declare input with the parameters continued below.
393 output(length * channels); // Apply output to these arguments.
394 random_complex(input, random_seed); // Apply random complex to these arguments.
395 shdr_examples::fft1d forward(input, spectrum, length, channels); // Construct forward with these initial values.
396 shdr_examples::fft1d inverse(spectrum, output, length, channels, true); // Construct inverse with these initial values.
397 shdr::pipeline pipeline = forward.processing_pipeline(); // Set pipeline from this expression.
398 inverse.append_to(pipeline); // Apply append to to these arguments.
399 output_check<vec2> spectrum_check(spectrum, 1e-4, 1e-3), output_comparison(output, 1e-4, 1e-3); // Construct spectrum check with these initial values.
400 bench.measure( // Apply measure to these arguments.
401 "FFT forward/inverse", "8 x 1024 complex", pipeline, {&spectrum_check, &output_comparison}); // Provide this text argument for the operation above.
402 const shdr::buffer<vec2>& read_input = input; // Use a constant buffer view so GPU writes synchronize before host reads.
403 const shdr::buffer<vec2>& read_output = output; // Use a constant buffer view so GPU writes synchronize before host reads.
404 for (unsigned i = 0; i < length * channels; ++i) // Iterate over i for this calculation.
405 same(read_output.data()[i], read_input.data()[i], 1e-4, 1e-3); // Compare CPU and GPU values using the selected tolerance.
406} // End this block.
407
408void fft_band_pass(benchmark& bench) { // Implement fft band pass for the operations below.
409 const unsigned length = 1024, channels = 8; // Set length from this expression.
410 shdr::buffer<vec2> input(length * channels), output(length * channels); // Construct input with these initial values.
411 random_complex(input, random_seed); // Apply random complex to these arguments.
412 shdr_examples::fft_filter filter(input, // Declare filter with the parameters continued below.
413 output, // Continue the expression using output.
414 length, // Continue the expression using length.
415 channels, // Continue the expression using channels.
416 48000.0f, // Complete this expression or declaration.
417 shdr_examples::frequency_filter::band_pass, // Continue the expression using shdr examples, frequency filter, band pass.
418 4000.0f, // Complete this expression or declaration.
419 10000.0f); // Complete this expression or declaration.
420 output_check<vec2> check(output, 1e-4, 1e-3); // Construct check with these initial values.
421 bench.measure( // Apply measure to these arguments.
422 "FFT band-pass", "8 x 1024 complex, 4-10 kHz", filter.processing_pipeline(), {&check}); // Apply processing pipeline to these arguments.
423} // End this block.
424
425void fir_low_pass(benchmark& bench) { // Implement fir low pass for the operations below.
426 const unsigned count = 2048, channels = 8; // Set count from this expression.
427 shdr::buffer<float> input(count * channels), output(count * channels); // Construct input with these initial values.
428 shdr_examples::randomize(input, random_seed, -1.0f, 1.0f); // Fill the input with reproducible random values.
429 shdr_examples::fir_pipeline filter(input, // Declare filter with the parameters continued below.
430 output, // Continue the expression using output.
431 count, // Continue the expression using count.
432 channels, // Continue the expression using channels.
433 shdr_examples::filter_mode::low_pass, // Continue the expression using shdr examples, filter mode, low pass.
434 48000.0, // Complete this expression or declaration.
435 4000.0, // Complete this expression or declaration.
436 0.0, // Complete this expression or declaration.
437 65); // Complete this expression or declaration.
438 output_check<float> check(output); // Construct check with these initial values.
439 bench.measure( // Apply measure to these arguments.
440 "FIR low-pass", "8 x 2048 floats, 65 taps", filter.processing_pipeline(), {&check}); // Apply processing pipeline to these arguments.
441} // End this block.
442
443void biquad_low_pass(benchmark& bench, shdr::device* device) { // Implement biquad low pass for the operations below.
444 const unsigned count = 2048, channels = 8; // Set count from this expression.
445 shdr::buffer<float> input(count * channels), output(count * channels); // Construct input with these initial values.
446 shdr_examples::randomize(input, random_seed, -1.0f, 1.0f); // Fill the input with reproducible random values.
447 shdr_examples::biquad_pipeline filter( // Declare filter with the parameters continued below.
448 input, output, count, channels, shdr_examples::filter_mode::low_pass, 48000.0, 4000.0); // Continue the expression using input, output, count.
449 output_check<float> output_comparison(output); // Construct output comparison with these initial values.
450 output_check<vec2> state_comparison(filter.state); // Construct state comparison with these initial values.
451 bench.measure_operations( // Apply measure operations to these arguments.
452 "Biquad low-pass", // Provide this text argument for the operation above.
453 "8 x 2048 floats, persistent state", // Provide this text argument for the operation above.
454 [&](bool warmup) { // Continue the expression using bool, warmup.
455 if (warmup) // Check this condition before entering the following branch.
456 filter.reset_state(); // Restore identical zero-delay state before this backend processes its first signal block.
457 filter.processing_pipeline().run(); // Execute the configured pipeline on the requested backend.
458 }, // Finish this declaration.
459 [&](bool warmup) { // Continue the expression using bool, warmup.
460 if (warmup) // Check this condition before entering the following branch.
461 filter.reset_state(); // Restore identical zero-delay state before this backend processes its first signal block.
462 filter.processing_pipeline().run(*device); // Execute the configured pipeline on the requested backend.
463 }, // Finish this declaration.
464 {&output_comparison, &state_comparison}); // Continue the expression using output comparison, state comparison.
465} // End this block.
466
467/// Compare completed asynchronous batches, with readback confined to warmup validation.
468void async_pipeline(benchmark& bench, shdr::device* device) { // Implement async pipeline for the operations below.
469 const unsigned count = 257, frame_count = 32, slot_count = 3; // Set count from this expression.
470 struct slot { // Declare slot.
471 shdr::buffer<float> input, output; // Declare input for the following calculation.
472 std::future<void> cpu_done; // Declare cpu done for the following calculation.
473 shdr::submission gpu_done; // Declare gpu done for the following calculation.
474 bool occupied; // Declare occupied for the following calculation.
475 unsigned frame; // Declare frame for the following calculation.
476 slot() : input(count), output(count), occupied(false), frame(0) { // Apply slot to these arguments.
477 } // End this block.
478 }; // Finish this declaration.
479 std::vector<slot> slots(slot_count); // Allocate three disjoint input and output slots before measuring the ring.
480 for (unsigned i = 0; i < slot_count; ++i) // Iterate over i for this calculation.
481 shdr_examples::randomize(slots[i].input, random_seed + i, -1.0f, 1.0f); // Fill the input with reproducible random values.
482 auto shader = shdr::make_kernel("affine_kernel"); // Create the linked affine kernel instance.
483 shader->set<unsigned>("count", count); // Supply this shader uniform with its typed application value.
484 shdr::pipeline pipeline = shdr::dispatch(*shader, groups(*shader, count)); // Add a shader dispatch with the required workgroup counts.
485 shdr::executor workers(2, slot_count); // Use two CPU workers with enough bounded queue capacity for the three live slots.
486 if (device) { // Check this condition before entering the following branch.
487 for (slot& resource : slots) { // Visit each resource required by this loop.
488 shader->bind("input_values", resource.input).bind("output_values", resource.output); // Connect this shader port to its application resource.
489 pipeline.prepare(*device); // Prepare all slot resources and compile the GPU program before ring timing.
490 } // End this block.
491 } // End this block.
492 const auto execute = [&](bool gpu, bool validate) { // Set execute from this expression.
493 unsigned produced = 0, completed = 0; // Set produced from this expression.
494 while (completed < frame_count) { // Pump the three resource slots until the complete frame batch finishes.
495 bool progress = false; // Set progress from this expression.
496 for (unsigned index = 0; index < slot_count; ++index) { // Iterate over index for this calculation.
497 slot& resource = slots[index]; // Set resource from this expression.
498 if (resource.occupied) { // Check this condition before entering the following branch.
499 const bool ready = // Set ready from this expression.
500 gpu ? resource.gpu_done.ready() // Apply ready to these arguments.
501 : resource.cpu_done.wait_for(std::chrono::nanoseconds(0)) == // Declare resource for the following calculation.
502 std::future_status::ready; // Continue the expression using std, future status, ready.
503 if (!ready) // Keep this slot occupied until its preceding operation actually finishes.
504 continue; // Skip the remaining work for this loop iteration.
505 if (!gpu) // Check this condition before entering the following branch.
506 resource.cpu_done.get(); // Observe CPU worker exceptions before considering this slot reusable.
507 if (validate) { // Check every completed warmup frame while keeping readback outside the timed ring.
508 const shdr::buffer<float>& input = resource.input; // Use a constant buffer view so GPU writes synchronize before host reads.
509 const shdr::buffer<float>& output = resource.output; // Use a constant buffer view so GPU writes synchronize before host reads.
510 for (unsigned i = 0; i < count; ++i) // Iterate over i for this calculation.
511 same(output.data()[i], // Compare CPU and GPU values using the selected tolerance.
512 input.data()[i] * float(resource.frame + 1) - // Apply data to these arguments.
513 float(resource.frame), // Apply float to these arguments.
514 1e-4, // Complete this expression or declaration.
515 1e-4); // Complete this expression or declaration.
516 } // End this block.
517 resource.occupied = false; // Release this slot only after its prior frame has completed.
518 ++completed; // Advance the completed-frame counter after observing its fence or future.
519 progress = true; // Set progress from this expression.
520 } // End this block.
521 if (produced < frame_count && produced % slot_count == index) { // Assign frames round-robin so both backends receive the same slot inputs.
522 resource.frame = produced++; // Record the frame identity before changing its shader uniforms.
523 shader->bind("input_values", resource.input) // Connect this shader port to its application resource.
524 .bind("output_values", resource.output) // Connect this shader port to its application resource.
525 .set<float>("scale", float(resource.frame + 1)) // Supply this shader uniform with its typed application value.
526 .set<float>("offset", -float(resource.frame)); // Supply this shader uniform with its typed application value.
527 if (gpu) // Check this condition before entering the following branch.
528 resource.gpu_done = pipeline.submit(*device); // Queue the frame on GPU and retain its zero-timeout completion fence.
529 else // Queue the frame snapshot on the CPU worker pool.
530 resource.cpu_done = workers.submit(pipeline); // Queue a snapshot of the frame bindings for the bounded CPU executor.
531 resource.occupied = true; // Protect this slot from reuse until its submission or future is ready.
532 progress = true; // Set progress from this expression.
533 } // End this block.
534 } // End this block.
535 if (gpu) // Check this condition before entering the following branch.
536 device->collect_garbage(); // Poll and retire completed GPU fence records on the producer thread.
537 if (!progress) // Check this condition before entering the following branch.
538 std::this_thread::yield(); // Yield the CPU when all live slots are still busy.
539 } // End this block.
540 }; // Finish this declaration.
541 output_check<float> a(slots[0].output), b(slots[1].output), c(slots[2].output); // Construct a with these initial values.
542 bench.measure_operations( // Apply measure operations to these arguments.
543 "Async ring", // Provide this text argument for the operation above.
544 "32 x 257 floats, 3 slots", // Provide this text argument for the operation above.
545 [&](bool validate) { // Continue the expression using bool, validate.
546 execute(false, validate); // Apply execute to these arguments.
547 }, // Finish this declaration.
548 [&](bool validate) { // Continue the expression using bool, validate.
549 execute(true, validate); // Apply execute to these arguments.
550 }, // Finish this declaration.
551 {&a, &b, &c}); // Continue the expression using a, b, c.
552 workers.shutdown(); // Apply shutdown to these arguments.
553} // End this block.
554
555void convolution_2d(benchmark& bench, unsigned side) { // Implement convolution 2d for the operations below.
556 shdr::buffer<vec2> input(side * side), output(side * side); // Construct input with these initial values.
557 shdr::buffer<float> coefficients(25); // Construct coefficients with these initial values.
558 random_complex(input, random_seed); // Apply random complex to these arguments.
559 shdr_examples::randomize(coefficients, random_seed + 1, -0.04f, 0.04f); // Fill the input with reproducible random values.
560 auto convolution = shdr::make_kernel("convolution2d_kernel"); // Create the linked convolution2d kernel instance.
561 convolution->bind("input_values", input) // Connect this shader port to its application resource.
562 .bind("output_values", output) // Connect this shader port to its application resource.
563 .bind("coefficients", coefficients) // Connect this shader port to its application resource.
564 .set<uvec2>("dimensions", uvec2(side, side)) // Supply this shader uniform with its typed application value.
565 .set<uvec2>("kernel_size", uvec2(5, 5)); // Supply this shader uniform with its typed application value.
566 shdr::pipeline pipeline; // Declare pipeline for the following calculation.
567 pipeline += shdr::dispatch(*convolution, groups(*convolution, side, side)); // Add a shader dispatch with the required workgroup counts.
568 output_check<vec2> check(output); // Construct check with these initial values.
569 bench.measure("2D convolution 5 x 5", dimensions(side, side) + " vec2", pipeline, {&check}); // Apply measure to these arguments.
570} // End this block.
571
572void convolution_3d(benchmark& bench, unsigned side) { // Implement convolution 3d for the operations below.
573 shdr::buffer<float> input(side * side * side), output(side * side * side), coefficients(27); // Construct input with these initial values.
574 shdr_examples::randomize(input, random_seed, -1.0f, 1.0f); // Fill the input with reproducible random values.
575 shdr_examples::randomize(coefficients, random_seed + 1, -1.0f / 27, 1.0f / 27); // Fill the input with reproducible random values.
576 auto convolution = shdr::make_kernel("convolution3d_kernel"); // Create the linked convolution3d kernel instance.
577 convolution->bind("input_values", input) // Connect this shader port to its application resource.
578 .bind("output_values", output) // Connect this shader port to its application resource.
579 .bind("coefficients", coefficients) // Connect this shader port to its application resource.
580 .set<uvec3>("dimensions", uvec3(side, side, side)) // Supply this shader uniform with its typed application value.
581 .set<uvec3>("kernel_size", uvec3(3, 3, 3)); // Supply this shader uniform with its typed application value.
582 shdr::pipeline pipeline; // Declare pipeline for the following calculation.
583 pipeline += shdr::dispatch(*convolution, groups(*convolution, side, side, side)); // Add a shader dispatch with the required workgroup counts.
584 output_check<float> check(output); // Construct check with these initial values.
585 bench.measure("3D convolution 3 x 3 x 3", dimensions(side, side, side), pipeline, {&check}); // Apply measure to these arguments.
586} // End this block.
587
588void fourier_pipeline(benchmark& bench) { // Implement fourier pipeline for the operations below.
589 const unsigned side = 32, // Set side from this expression.
590 count = side * side; // Direct DFT remains small even when other workloads scale.
591 shdr::buffer<vec2> input(count), rows(count), spectrum(count), inverse_rows(count), // Declare input with the parameters continued below.
592 restored(count); // Apply restored to these arguments.
593 random_complex(input, random_seed); // Apply random complex to these arguments.
594 auto forward_rows = shdr::make_kernel("dft_rows_kernel"); // Create the linked dft rows kernel instance.
595 auto forward_columns = shdr::make_kernel("dft_columns_kernel"); // Create the linked dft columns kernel instance.
596 auto reverse_rows = shdr::make_kernel("dft_rows_kernel"); // Create the linked dft rows kernel instance.
597 auto reverse_columns = shdr::make_kernel("dft_columns_kernel"); // Create the linked dft columns kernel instance.
598 shdr::kernel* kernels[] = { // Declare kernels for the following calculation.
599 forward_rows.get(), forward_columns.get(), reverse_rows.get(), reverse_columns.get()}; // Apply get to these arguments.
600 shdr::buffer<vec2>* inputs[] = {&input, &rows, &spectrum, &inverse_rows}; // Declare inputs for the following calculation.
601 shdr::buffer<vec2>* outputs[] = {&rows, &spectrum, &inverse_rows, &restored}; // Declare outputs for the following calculation.
602 shdr::pipeline pipeline; // Declare pipeline for the following calculation.
603 for (unsigned i = 0; i < 4; ++i) { // Iterate over i for this calculation.
604 kernels[i] // Continue the expression using kernels, i.
605 ->bind("input_values", *inputs[i]) // Connect this shader port to its application resource.
606 .bind("output_values", *outputs[i]) // Connect this shader port to its application resource.
607 .set<uvec2>("dimensions", uvec2(side, side)) // Supply this shader uniform with its typed application value.
608 .set<float>("direction", i < 2 ? -1.0f : 1.0f) // Supply this shader uniform with its typed application value.
609 .set<float>("normalization", i < 2 ? 1.0f : 1.0f / side); // Supply this shader uniform with its typed application value.
610 pipeline += shdr::dispatch(*kernels[i], groups(*kernels[i], side, side)); // Add a shader dispatch with the required workgroup counts.
611 } // End this block.
612 output_check<vec2> spectral_check(spectrum, 1e-3, 1e-3), // Declare spectral check with the parameters continued below.
613 reconstruction_check(restored, 1e-3, 1e-3); // Apply reconstruction check to these arguments.
614 bench.measure("Forward/inverse direct DFT", // Apply measure to these arguments.
615 dimensions(side, side) + " complex", // Apply dimensions to these arguments.
616 pipeline, // Continue the expression using pipeline.
617 {&spectral_check, &reconstruction_check}); // Continue the expression using spectral check, reconstruction check.
618 const shdr::buffer<vec2>& read_input = input; // Use a constant buffer view so GPU writes synchronize before host reads.
619 const shdr::buffer<vec2>& read_restored = restored; // Use a constant buffer view so GPU writes synchronize before host reads.
620 for (unsigned i = 0; i < count; ++i) // Iterate over i for this calculation.
621 same(read_restored.data()[i], read_input.data()[i], 1e-3, 1e-3); // Compare CPU and GPU values using the selected tolerance.
622} // End this block.
623
624void vision_pipeline(benchmark& bench, unsigned side) { // Implement vision pipeline for the operations below.
625 const unsigned count = side * side; // Set count from this expression.
626 shdr::buffer<float> input(count), horizontal(count), smooth(count), response(count); // Construct input with these initial values.
627 shdr::buffer<vec2> gradient(count); // Construct gradient with these initial values.
628 shdr::buffer<vec4> tensor(count); // Construct tensor with these initial values.
629 shdr::buffer<unsigned> peaks(count); // Construct peaks with these initial values.
630 shdr_examples::randomize(input, random_seed, 0.0f, 1.0f); // Fill the input with reproducible random values.
631 auto h = shdr::make_kernel("gaussian_horizontal_kernel"), // Create the linked gaussian horizontal kernel instance.
632 v = shdr::make_kernel("gaussian_vertical_kernel"); // Create the linked gaussian vertical kernel instance.
633 auto sobel = shdr::make_kernel("sobel_kernel"), // Create the linked sobel kernel instance.
634 outer = shdr::make_kernel("harris_tensor_kernel"); // Create the linked harris tensor kernel instance.
635 auto harris = shdr::make_kernel("harris_response_kernel"), // Create the linked harris response kernel instance.
636 nonmax = shdr::make_kernel("nonmax_kernel"); // Create the linked nonmax kernel instance.
637 h->bind("input_values", input).bind("output_values", horizontal); // Connect this shader port to its application resource.
638 v->bind("input_values", horizontal).bind("output_values", smooth); // Connect this shader port to its application resource.
639 sobel->bind("input_values", smooth).bind("output_values", gradient); // Connect this shader port to its application resource.
640 outer->bind("input_values", gradient) // Connect this shader port to its application resource.
641 .bind("output_values", tensor) // Connect this shader port to its application resource.
642 .set<unsigned>("count", count); // Supply this shader uniform with its typed application value.
643 harris->bind("input_values", tensor) // Connect this shader port to its application resource.
644 .bind("output_values", response) // Connect this shader port to its application resource.
645 .set<float>("harris_k", 0.04f); // Supply this shader uniform with its typed application value.
646 nonmax->bind("input_values", response) // Connect this shader port to its application resource.
647 .bind("output_values", peaks) // Connect this shader port to its application resource.
648 .set<float>("threshold", 1e-8f); // Supply this shader uniform with its typed application value.
649 shdr::kernel* kernels[] = { // Declare kernels for the following calculation.
650 h.get(), v.get(), sobel.get(), outer.get(), harris.get(), nonmax.get()}; // Apply get to these arguments.
651 shdr::pipeline pipeline; // Declare pipeline for the following calculation.
652 for (unsigned i = 0; i < 6; ++i) { // Iterate over i for this calculation.
653 if (i != 3) // Check this condition before entering the following branch.
654 kernels[i]->set<unsigned>("width", side).set<unsigned>("height", side); // Supply this shader uniform with its typed application value.
655 pipeline += shdr::dispatch(*kernels[i], groups(*kernels[i], count)); // Add a shader dispatch with the required workgroup counts.
656 } // End this block.
657 output_check<float> smooth_check(smooth), response_check(response); // Construct smooth check with these initial values.
658 output_check<vec2> gradient_check(gradient); // Construct gradient check with these initial values.
659 output_check<unsigned> peak_check(peaks); // Construct peak check with these initial values.
660 bench.measure("Gaussian/Sobel/Harris/NMS", // Apply measure to these arguments.
661 dimensions(side, side), // Apply dimensions to these arguments.
662 pipeline, // Continue the expression using pipeline.
663 {&smooth_check, &gradient_check, &response_check, &peak_check}); // Continue the expression using smooth check, gradient check, response check.
664} // End this block.
665
666void lenet_pipeline(benchmark& bench) { // Compare all seven layers of the named image classifier.
667 shdr_examples::lenet_classifier model(32); // Allocate a resident batch of thirty-two grayscale images.
668 shdr_examples::randomize(model.inputs, random_seed, 0.0f, 1.0f); // Use normalized seeded image values.
669 shdr_examples::randomize(model.parameters, random_seed + 1u, -0.025f, 0.025f); // Keep randomized learned parameters small enough for stable comparisons.
670 output_check<float> convolution_check(model.conv2, 1e-4, 1e-3); // Check the second convolution features after each backend completes.
671 output_check<float> hidden_check(model.hidden2, 1e-4, 1e-3); // Check the final hidden representation as well as the head.
672 output_check<float> logits_check(model.logits, 1e-4, 1e-3); // Retain the CPU reference for every class score.
673 bench.measure("LeNet classifier", // Identify this full convolutional model in the timing table.
674 "32 x 28 x 28, 61,706 parameters", // State the complete resident workload size.
675 model.inference_pipeline(), // Execute two convolution and pool pairs followed by three dense stages.
676 {&convolution_check, &hidden_check, &logits_check}); // Verify internal features and final scores outside timing.
677} // Finish the complete classifier comparison.
678
679void compression_pipeline(benchmark& bench, unsigned count) { // Implement compression pipeline for the operations below.
680 const unsigned block_size = 256, blocks = (count + block_size - 1) / block_size; // Set block size from this expression.
681 shdr::buffer<unsigned> input(count), sizes(blocks), offsets(blocks + 1), encoded(count * 2), // Declare input with the parameters continued below.
682 restored(count); // Apply restored to these arguments.
683 std::mt19937 generator(random_seed); // Construct generator with these initial values.
684 std::uniform_int_distribution<unsigned> run_length(4, 16), value(0, 4095); // Construct run length with these initial values.
685 unsigned position = 0; // Set position from this expression.
686 while (position < count) { // Fill the input with seeded runs until the requested element count is reached.
687 const unsigned run = run_length(generator), sample = value(generator); // Set run from this expression.
688 for (unsigned i = 0; i < run && position < count; ++i) // Iterate over i for this calculation.
689 input.data()[position++] = sample; // Apply data to these arguments.
690 } // End this block.
691 auto tally = shdr::make_kernel("rle_count_kernel"), // Create the linked rle count kernel instance.
692 prefix = shdr::make_kernel("rle_offsets_kernel"); // Create the linked rle offsets kernel instance.
693 auto encode = shdr::make_kernel("rle_encode_kernel"), // Create the linked rle encode kernel instance.
694 decode = shdr::make_kernel("rle_decode_kernel"); // Create the linked rle decode kernel instance.
695 tally->bind("values", input) // Connect this shader port to its application resource.
696 .bind("sizes", sizes) // Connect this shader port to its application resource.
697 .set<unsigned>("count", count) // Supply this shader uniform with its typed application value.
698 .set<unsigned>("block_size", block_size); // Supply this shader uniform with its typed application value.
699 prefix->bind("sizes", sizes).bind("offsets", offsets).set<unsigned>("blocks", blocks); // Connect this shader port to its application resource.
700 encode->bind("values", input) // Connect this shader port to its application resource.
701 .bind("offsets", offsets) // Connect this shader port to its application resource.
702 .bind("encoded", encoded) // Connect this shader port to its application resource.
703 .set<unsigned>("count", count) // Supply this shader uniform with its typed application value.
704 .set<unsigned>("block_size", block_size); // Supply this shader uniform with its typed application value.
705 decode->bind("encoded", encoded) // Connect this shader port to its application resource.
706 .bind("offsets", offsets) // Connect this shader port to its application resource.
707 .bind("restored", restored) // Connect this shader port to its application resource.
708 .set<unsigned>("count", count) // Supply this shader uniform with its typed application value.
709 .set<unsigned>("block_size", block_size); // Supply this shader uniform with its typed application value.
710 shdr::pipeline pipeline = shdr::dispatch(*tally, groups(*tally, blocks)) >> // Add a shader dispatch with the required workgroup counts.
711 shdr::dispatch(*prefix, groups(*prefix, 1)) >> // Add a shader dispatch with the required workgroup counts.
712 shdr::dispatch(*encode, groups(*encode, blocks)) >> // Add a shader dispatch with the required workgroup counts.
713 shdr::dispatch(*decode, groups(*decode, blocks)); // Add a shader dispatch with the required workgroup counts.
714 output_check<unsigned> restored_check(restored), offsets_check(offsets), encoded_check(encoded); // Construct restored check with these initial values.
715 bench.measure("Lossless tiled RLE round trip", // Apply measure to these arguments.
716 std::to_string(count) + " uints", // Apply to string to these arguments.
717 pipeline, // Continue the expression using pipeline.
718 {&restored_check, &offsets_check, &encoded_check}); // Continue the expression using restored check, offsets check, encoded check.
719 const shdr::buffer<unsigned>& read_input = input; // Use a constant buffer view so GPU writes synchronize before host reads.
720 const shdr::buffer<unsigned>& read_restored = restored; // Use a constant buffer view so GPU writes synchronize before host reads.
721 for (unsigned i = 0; i < count; ++i) // Iterate over i for this calculation.
722 same(read_restored.data()[i], read_input.data()[i], 0, 0); // Compare CPU and GPU values using the selected tolerance.
723 const shdr::buffer<unsigned>& read_offsets = offsets; // Use a constant buffer view so GPU writes synchronize before host reads.
724 const unsigned stream_words = read_offsets.data()[blocks]; // Set stream words from this expression.
725 std::ostringstream metadata; // Declare metadata for the following calculation.
726 metadata << "Lossless RLE: " << stream_words << " payload words + " << blocks + 1 // Provide this text argument for the operation above.
727 << " offset words, " << std::fixed << std::setprecision(2) // Write this part of the example result or diagnostic.
728 << double(count) / double(stream_words + blocks + 1) << "x size reduction."; // Write this part of the example result or diagnostic.
729 bench.note(metadata.str()); // Apply note to these arguments.
730} // End this block.
731
732#ifdef SHDR_PLUGIN_PATH // Enable this branch when its feature macro is defined.
733void dynamic_pipeline(benchmark& bench, unsigned count) { // Implement dynamic pipeline for the operations below.
734 void* library = dlopen(SHDR_PLUGIN_PATH, RTLD_NOW | RTLD_LOCAL); // Set library from this expression.
735 if (!library) // Check this condition before entering the following branch.
736 throw std::runtime_error(dlerror()); // Report this failed operation.
737 typedef shdr::kernel* (*create_function)(); // Declare shdr for the following calculation.
738 typedef void (*destroy_function)(shdr::kernel*); // Construct void with these initial values.
739 create_function create = // Set create from this expression.
740 reinterpret_cast<create_function>(dlsym(library, "shdr_create_kernel")); // Apply dlsym to these arguments.
741 destroy_function destroy = // Set destroy from this expression.
742 reinterpret_cast<destroy_function>(dlsym(library, "shdr_destroy_kernel")); // Apply dlsym to these arguments.
743 if (!create || !destroy) { // Check this condition before entering the following branch.
744 dlclose(library); // Apply dlclose to these arguments.
745 throw std::runtime_error("incomplete benchmark plugin"); // Report incomplete benchmark plugin.
746 } // End this block.
747 shdr::kernel* shader = 0; // Set shader from this expression.
748 try { // Run the operation while retaining the error handling below.
749 shader = create(); // Set shader from this expression.
750 if (!shader) // Check this condition before entering the following branch.
751 throw std::runtime_error("benchmark plugin returned no shader"); // Report an empty result from the plugin factory.
752 buffer_pipeline(bench, count, false, shader); // Apply buffer pipeline to these arguments.
753 destroy(shader); // Apply destroy to these arguments.
754 shader = 0; // Set shader from this expression.
755 dlclose(library); // Apply dlclose to these arguments.
756 } catch (...) { // Report the exception from the preceding operation.
757 if (shader) // Check this condition before entering the following branch.
758 destroy(shader); // Apply destroy to these arguments.
759 dlclose(library); // Apply dlclose to these arguments.
760 throw; // Report this failed operation.
761 } // End this block.
762} // End this block.
763#endif // Finish this conditional compilation branch.
764} // namespace
765
766int main(int argc, char** argv) { // Run this example and validate its results.
767 try { // Run the operation while retaining the error handling below.
768 shdr_examples::runner run(argc, argv); // Construct run with these initial values.
769 const unsigned iterations = setting("SHDR_BENCH_ITERATIONS", 3, 1000); // Set iterations from this expression.
770 const unsigned scale = setting("SHDR_BENCH_SCALE", 1, 4); // Set scale from this expression.
771 const unsigned side = 128 * scale; // Set side from this expression.
772 benchmark bench(run.device(), iterations); // Construct bench with these initial values.
773 buffer_pipeline(bench, side * side, false); // Apply buffer pipeline to these arguments.
774 image_pipeline(bench, side); // Apply image pipeline to these arguments.
775 image_pipeline(bench, side, run.device(), true); // Apply image pipeline to these arguments.
776 buffer_pipeline(bench, side * side, true); // Apply buffer pipeline to these arguments.
777#ifdef SHDR_PLUGIN_PATH // Enable this branch when its feature macro is defined.
778 dynamic_pipeline(bench, side * side); // Apply dynamic pipeline to these arguments.
779#endif // Finish this conditional compilation branch.
780 bayer_pipeline(bench, side); // Apply bayer pipeline to these arguments.
781 convolution_2d(bench, side); // Apply convolution 2d to these arguments.
782 convolution_3d(bench, 24 * scale); // Apply convolution 3d to these arguments.
783 fourier_pipeline(bench); // Apply fourier pipeline to these arguments.
784 vision_pipeline(bench, side); // Apply vision pipeline to these arguments.
785 compression_pipeline(bench, 65536 * scale); // Apply compression pipeline to these arguments.
786 evt3_pipeline(bench, 65536 * scale); // Apply evt3 pipeline to these arguments.
787 async_pipeline(bench, run.device()); // Apply async pipeline to these arguments.
788 evt3_optical_flow(bench); // Apply evt3 optical flow to these arguments.
789 binary_neural_pipeline(bench, run.device(), false); // Apply binary neural pipeline to these arguments.
790 binary_neural_pipeline(bench, run.device(), true); // Apply binary neural pipeline to these arguments.
791 transformer_pipeline(bench, run.device()); // Apply transformer pipeline to these arguments.
792 fft_round_trip(bench); // Apply fft round trip to these arguments.
793 fir_low_pass(bench); // Apply fir low pass to these arguments.
794 biquad_low_pass(bench, run.device()); // Apply biquad low pass to these arguments.
795 fft_band_pass(bench); // Apply fft band pass to these arguments.
796 lenet_pipeline(bench); // Compare the named image classifier on both backends.
797 bench.print(scale); // Apply print to these arguments.
798 return 0; // Report successful completion.
799#ifdef SHDR_HAVE_EGL // Enable this branch when its feature macro is defined.
800 } catch (const shdr_examples::context_unavailable& error) { // Report the exception from the preceding operation.
801 std::cerr << "SKIP: " << error.what() << "\n"; // Write this part of the example result or diagnostic.
802 return 77; // Report an unavailable optional GPU backend.
803#endif // Finish this conditional compilation branch.
804 } catch (const std::exception& error) { // Report the exception from the preceding operation.
805 std::cerr << error.what() << "\n"; // Write this part of the example result or diagnostic.
806 return 1; // Report a failed check or operation.
807 } // End this block.
808} // End this block.