Source browser
examples/real_data_support.hpp
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 real_data_support.hpp
2 * @brief Portable fixture readers and complete CPU/GPU output comparisons.
3 * Dataset paths contain data only. Shader sources remain embedded in the executable.
4 */
5#ifndef SHDR_EXAMPLES_REAL_DATA_SUPPORT_HPP // Include fixture support only once.
6#define SHDR_EXAMPLES_REAL_DATA_SUPPORT_HPP // Mark the support declarations as available.
7#include "runner.hpp" // Select the same execution backend as the other examples.
8#include <cstdint> // Assemble endian-defined dataset words explicitly.
9#include <cstdlib> // Allow a caller to choose a dataset directory through the environment.
10#include <cstring> // Copy float representations with std::memcpy.
11#include <fstream> // Read ordinary binary datasets from disk.
12#include <functional> // Restore mutable resources before comparing independent executions.
13#include <limits> // Check that float fixtures have the documented representation.
14
15namespace shdr_examples { // Keep fixture utilities outside the public library API.
16namespace real_data { // Group the real-data example's input and comparison helpers.
17inline std::string directory() { // Resolve the supplied fixture directory at runtime.
18 const char* override_path = std::getenv("SHDR_DATA_DIR"); // Check an optional caller override.
19 if (override_path && *override_path) { // Accept a nonempty explicit dataset location.
20 return override_path; // Use only this directory for the real-data fixture reads.
21 } // Ordinary builds use the CMake-provided source fixture directory.
22#ifdef SHDR_EXAMPLE_DATA_DIR // Select the dataset directory recorded by the example build.
23 return SHDR_EXAMPLE_DATA_DIR; // Return the ordinary example fixture path.
24#else // Keep a useful relative fallback for manual builds.
25 return "examples/data"; // Resolve fixtures relative to the caller's working directory.
26#endif // Finish the build-path selection.
27} // Use this selected directory to resolve prepared dataset filenames.
28inline std::vector<unsigned char> // Read one bounded fixture file.
29bytes(const std::string& name, // Read one bounded fixture file.
30 const std::string& base = directory()) { // Read one bounded fixture file.
31 const std::string path = base + "/" + name; // Join the fixed fixture filename to its directory.
32 std::ifstream input(path.c_str(), // Open at the end to measure size.
33 std::ios::binary | std::ios::ate); // Open at the end to measure size.
34 if (!input) { // Identify a missing prepared dataset immediately.
35 throw shdr::error("cannot open dataset " + path + // Report the failed format, numerical or resource precondition.
36 ", run scripts/prepare_datasets.py first"); // Explain preparation.
37 } // The dataset path is now open as an ordinary binary stream.
38 const std::streamoff count = input.tellg(); // Measure the exact byte count before allocation.
39 if (count <= 0 || // Reject empty or unexpectedly large example inputs.
40 count > 64 * 1024 * 1024) { // Reject empty or unexpectedly large example inputs.
41 throw shdr::error("invalid example dataset size: " + // Report the failed format, numerical or resource precondition.
42 name); // Keep fixture allocations bounded.
43 } // All documented public fixtures fit within this simple limit.
44 std::vector<unsigned char> result( // Allocate the measured payload.
45 static_cast<std::size_t>(count)); // Allocate the measured payload.
46 input.seekg(0); // Return to the first dataset byte.
47 input.read(reinterpret_cast<char*>(result.data()), count); // Read the complete fixture payload.
48 if (!input) { // Catch incomplete file reads before interpreting any value.
49 throw shdr::error("dataset read was incomplete: " + // Report the failed format, numerical or resource precondition.
50 name); // Report truncation or an I/O failure.
51 } // The caller receives a fully read binary fixture.
52 return result; // Transfer the owned byte array to its decoder.
53} // Preserve the file order of the raw byte values.
54inline std::vector<float> // Decode little-endian IEEE float32 fixtures.
55floats(const std::string& name) { // Decode little-endian IEEE float32 fixtures.
56 static_assert(sizeof(float) == 4 && std::numeric_limits<float>::is_iec559, // Check representation.
57 "float32 fixtures require IEEE floats"); // Check representation.
58 const std::vector<unsigned char> raw = bytes(name); // Read the complete named float fixture.
59 if (raw.size() % 4u) { // Require four bytes for every declared float32 value.
60 throw shdr::error("float dataset contains a partial value: " + // Report the failed format, numerical or resource precondition.
61 name); // Reject incomplete representations.
62 } // Whole values can now be assembled independently of endian order.
63 std::vector<float> result(raw.size() / 4u); // Allocate one native float per encoded value.
64 for (std::size_t i = 0; i < result.size(); ++i) { // Decode every payload element.
65 const std::uint32_t bits = // Assemble bytes in documented order.
66 std::uint32_t(raw[4u * i]) | (std::uint32_t(raw[4u * i + 1u]) << 8u) | // Assemble bytes in documented order.
67 (std::uint32_t(raw[4u * i + 2u]) << 16u) | // Assemble bytes in documented order.
68 (std::uint32_t(raw[4u * i + 3u]) << 24u); // Assemble bytes in documented order.
69 std::memcpy( // Copy the assembled float32 representation into its float value.
70 &result[i], &bits, sizeof(float)); // Copy the assembled float32 representation into its float value.
71 if (!std::isfinite(result[i])) { // Keep dataset corruption out of numerical comparisons.
72 throw shdr::error("nonfinite float in dataset: " + // Report the failed format, numerical or resource precondition.
73 name); // Reject invalid signal or model values.
74 } // Valid values retain their published float32 bit patterns.
75 } // The full fixture is now usable by either backend.
76 return result; // Supply the decoded scalar array.
77} // The example supports both little-endian and big-endian hosts.
78inline std::vector<std::uint16_t> // Decode EVT3 little-endian words.
79words(const std::string& name) { // Decode EVT3 little-endian words.
80 const std::vector<unsigned char> raw = // Read the public recording's prepared prefix.
81 bytes(name); // Read the public recording's prepared prefix.
82 if (raw.size() % 2u) { // Require complete sensor words.
83 throw shdr::error("EVT3 dataset contains a partial word"); // Report a truncated fixture.
84 } // Each pair of bytes forms one sensor word.
85 std::vector<std::uint16_t> result(raw.size() / 2u); // Allocate the word-oriented decoder input.
86 for (std::size_t i = 0; i < result.size(); ++i) { // Convert every complete word.
87 result[i] = static_cast<std::uint16_t>( // Decode low byte first.
88 unsigned(raw[2u * i]) | (unsigned(raw[2u * i + 1u]) << 8u)); // Decode low byte first.
89 } // Assemble each little-endian word from its two stored bytes.
90 return result; // Return the original sensor word sequence.
91} // The RAW text header is removed only by the documented preparation script.
92template <class T> // Apply the same helper to each compatible scalar or vector resource type.
93inline shdr::buffer<T> resource(const std::vector<T>& values) { // Upload ordinary prepared arrays.
94 shdr::buffer<T> result(values.size()); // Allocate a typed resource of the exact element count.
95 std::copy(values.begin(), // Copy the preserved source values into their input resource.
96 values.end(), // Copy the dataset into host resource storage.
97 result.data()); // Copy the dataset into host resource storage.
98 return result; // Share the resulting resource with pipeline stages.
99} // GPU upload remains deferred until a device actually uses the resource.
100inline double difference(float actual, // Measure a finite scalar output difference.
101 float expected) { // Measure a finite scalar output difference.
102 if (!std::isfinite(actual) || // Reject numerical failures on either backend.
103 !std::isfinite(expected)) { // Reject numerical failures on either backend.
104 throw shdr::error( // Report the failed format, numerical or resource precondition.
105 "real-data pipeline produced a nonfinite output"); // Name an invalid processing result.
106 } // Relative scaling makes the comparison meaningful for logits and signal amplitudes.
107 return std::fabs(double(actual) - expected) / // Combine absolute and relative error.
108 (1.0 + std::fabs(double(expected))); // Combine absolute and relative error.
109} // A tolerance of zero requires bit-identical numerical values.
110inline double difference(unsigned actual, unsigned expected) { // Compare discrete outputs exactly.
111 return actual == expected ? 0.0 : 1.0; // Keep event words, masks and lengths lossless.
112} // Require exact equality for integer outputs.
113template <class T, std::size_t N> // Apply the same helper to each compatible scalar or vector resource type.
114inline double difference(const glsl::vec<T, N>& actual, // Compare every vector component.
115 const glsl::vec<T, N>& expected) { // Compare every vector component.
116 double maximum = 0.0; // Track the worst normalized component difference.
117 for (std::size_t component = 0; component < N; // Include both coordinates and metadata components.
118 ++component) { // Include both coordinates and metadata components.
119 maximum = std::max(maximum, // Apply the matching scalar policy.
120 difference(actual[component], // Apply the matching scalar policy.
121 expected[component])); // Apply the matching scalar policy.
122 } // Vectors use their strictest component result.
123 return maximum; // Return the complete element's comparison error.
124} // Compare the typed vector components through the normalized scalar-error helper.
125class output_check { // Provide a common interface for several typed stage outputs.
126 public: // Expose only the capture and comparison operations needed by the evaluator.
127 virtual ~output_check() { // Destroy a concrete checker through this base type safely.
128 } // Destroy a concrete checker through this base type safely.
129 virtual void capture() = 0; // Save completed CPU output as the backend comparison reference.
130 virtual double verify() const = 0; // Compare the requested backend against that saved output.
131}; // A pipeline can validate several differently typed resources together.
132template <class T> // Apply the same helper to each compatible scalar or vector resource type.
133class checked_output : public output_check { // Keep an output resource and its CPU snapshot.
134 const shdr::buffer<T>& resource_; // Read completed results through a constant resource reference.
135 std::vector<T> reference_; // Preserve a complete CPU result across GPU execution.
136 double tolerance_; // Record the permitted normalized floating-point difference.
137 std::size_t // Compare only the meaningful output prefix when storage has spare capacity.
138 count_; // Compare only the meaningful output prefix when storage has spare capacity.
139 public: // Let each test choose the resource, error limit and optional compact count.
140 explicit checked_output(const shdr::buffer<T>& value, // Configure the output comparison.
141 double tolerance = 0.0001, // Configure the output comparison.
142 std::size_t count = 0) // Configure the output comparison.
143 : resource_(value), tolerance_(tolerance), // Configure the output comparison.
144 count_(count ? count : value.size()) { // Configure the output comparison.
145 if (count_ > // Prevent a checker from reading beyond allocated resource storage.
146 value.size()) { // Prevent a checker from reading beyond allocated resource storage.
147 throw shdr::error( // Report the failed format, numerical or resource precondition.
148 "real-data comparison count exceeds resource storage"); // Explain the invalid check.
149 } // The meaningful output range is now bounded.
150 } // The reference is populated only after CPU processing.
151 void capture() override { // Save a complete finite CPU output reference.
152 const T* values = resource_.data(); // Obtain completed host-readable resource values.
153 reference_.assign(values, values + count_); // Keep all meaningful output elements.
154 for (const T& value : // Validate CPU numerical results before using them as a reference.
155 reference_) { // Validate CPU numerical results before using them as a reference.
156 (void)difference(value, value); // Self-comparison detects nonfinite float components.
157 } // Discrete values remain exact in the snapshot.
158 } // Keep an independent CPU reference for later GPU comparisons.
159 double verify() const override { // Compare every completed output value with its CPU reference.
160 const T* values = // Read back GPU results only after the pipeline has been submitted.
161 resource_.data(); // Read back GPU results only after the pipeline has been submitted.
162 double maximum = 0.0; // Track a useful measured backend discrepancy.
163 for (std::size_t i = 0; i < reference_.size(); ++i) { // Check the full meaningful result.
164 maximum = std::max( // Compare the appropriate value type.
165 maximum, // Compare the appropriate value type.
166 difference(values[i], reference_[i])); // Compare the appropriate value type.
167 if (maximum > // Fail as soon as a result exceeds its explicit error limit.
168 tolerance_) { // Fail as soon as a result exceeds its explicit error limit.
169 throw shdr::error("real-data CPU/GPU output differs at element " + // Report the failed format, numerical or resource precondition.
170 std::to_string(i)); // Identify the bad location.
171 } // Successful outputs stay within the documented comparison tolerance.
172 } // All elements have now been compared.
173 return maximum; // Report the worst measured normalized difference.
174 } // Finish comparing completed results with the stored CPU reference.
175}; // Own the CPU reference vector and borrow the pipeline resource.
176inline void // Execute identical real inputs on both backends.
177evaluate(runner& run, // Execute identical real inputs on both backends.
178 const std::string& label, // Execute identical real inputs on both backends.
179 const shdr::pipeline& pipeline, // Execute identical real inputs on both backends.
180 std::initializer_list<output_check*> outputs, // Execute identical real inputs on both backends.
181 const std::function<void()>& reset = // Execute identical real inputs on both backends.
182 std::function<void()>()) { // Execute identical real inputs on both backends.
183 if (reset) { // Restore trainable parameters or streaming state when a case needs it.
184 reset(); // Establish the CPU run's documented starting state.
185 } // Stateless cases use their existing immutable input resources.
186 pipeline.run(); // Compute the complete CPU reference from real dataset values.
187 for (output_check* output : outputs) { // Capture every selected intermediate and final result.
188 output->capture(); // Preserve completed output before a device may overwrite it.
189 } // The comparison snapshots now describe this exact pipeline invocation.
190 if (run.gpu()) { // Run the requested profile only when the user selected a GPU backend.
191 if (reset) { // Give GPU execution the same initial mutable resources as CPU execution.
192 reset(); // Restore parameters or state before replaying the pipeline.
193 } // Input datasets remain identical across both runs.
194 run.run(pipeline); // Submit the linked stages through the selected library device.
195 } // CPU-only runs still validate finite and exact output checks below.
196 double maximum = 0.0; // Track the worst error across all selected stage resources.
197 for (const output_check* output : outputs) { // Validate the full result for each typed output.
198 maximum = // Include every recorded stage in the comparison.
199 std::max(maximum, output->verify()); // Include every recorded stage in the comparison.
200 } // Backend errors become ordinary test failures with useful element diagnostics.
201 std::cout << label << ": verified, normalized CPU/GPU error " << maximum // Report the completed real-data processing check.
202 << '\n'; // Report a measured functional check.
203} // Timed randomized benchmarks remain separate from these correctness runs.
204} // namespace real_data
205} // namespace shdr_examples
206#endif // Finish the fixture support include guard.