Source browser
examples/lenet_checks.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 lenet_checks.hpp
2 * @brief Independent double-precision checks for the complete LeNet-style model.
3 * The reference uses ordinary host arrays and explicit layer calculations.
4 * It checks every intermediate feature and final logit, including edge padding,
5 * channel ordering, flattening, pooling and the two independent batch images.
6 */
7#ifndef SHDR_EXAMPLES_LENET_CHECKS_HPP // Include this independent model check only once.
8#define SHDR_EXAMPLES_LENET_CHECKS_HPP // Remember that the model check is available.
9#include "runner.hpp" // Select CPU or a supported graphics execution profile.
10#include "lenet_support.hpp" // Use the complete seven-stage native classifier.
11#include <random> // Build repeatable independent model and input fixtures.
12namespace shdr_examples { // Keep the model checks with the reusable examples.
13namespace lenet_detail { // Keep reference calculations separate from deployed shader code.
14struct reference_result { // Retain every independently calculated model layer.
15 std::vector<double> conv1, pool1, conv2, pool2, hidden1, hidden2, // Keep spatial features, dense features and final scores in double precision.
16 logits; // Keep spatial features, dense features and final scores in double precision.
17}; // Finish the reference feature record.
18inline std::vector<double> // Compute a padded learned convolution in ordinary double precision.
19convolution(const std::vector<double>& input, // Compute a padded learned convolution in ordinary double precision.
20 const std::vector<double>& weights, // Read the flattened model in double precision.
21 unsigned batch, // Process this many independent images.
22 unsigned width, // Specify the width of each input feature plane.
23 unsigned sources, // Read this many source channels or vector features.
24 unsigned outputs, // Produce this many channels or vector features.
25 unsigned padding, // Specify the number of zero pixels around the input.
26 unsigned weight_offset, // Locate the first weight of this layer.
27 unsigned bias_offset) { // Compute a padded learned convolution in ordinary double precision.
28 const unsigned side = // Find the output side for a five-by-five valid kernel.
29 width + 2u * padding - 4u; // Find the output side for a five-by-five valid kernel.
30 std::vector<double> result(batch * outputs * side * // Allocate each complete output channel plane.
31 side); // Allocate each complete output channel plane.
32 for (unsigned image = 0; image < batch; // Keep independent batch images in their original order.
33 ++image) { // Keep independent batch images in their original order.
34 for (unsigned target = 0; target < outputs; ++target) { // Visit each learned output filter.
35 for (unsigned y = 0; y < side; ++y) { // Visit each output row.
36 for (unsigned x = 0; x < side; ++x) { // Visit each output column.
37 double value = weights[bias_offset + // Start with the output filter's learned bias.
38 target]; // Start with the output filter's learned bias.
39 for (unsigned source = 0; source < sources; // Sum all source feature-plane contributions.
40 ++source) { // Sum all source feature-plane contributions.
41 for (unsigned ky = 0; ky < 5u; ++ky) { // Visit the five kernel rows.
42 for (unsigned kx = 0; kx < 5u; ++kx) { // Visit the five kernel columns.
43 const int sx = // Find the corresponding unpadded input column.
44 static_cast<int>(x + kx) - // Find the corresponding unpadded input column.
45 static_cast<int>( // Find the corresponding unpadded input column.
46 padding); // Find the corresponding unpadded input column.
47 const int sy = // Find the corresponding unpadded input row.
48 static_cast<int>(y + ky) - // Find the corresponding unpadded input row.
49 static_cast<int>( // Find the corresponding unpadded input row.
50 padding); // Find the corresponding unpadded input row.
51 if (sx < 0 || sy < 0 || sx >= static_cast<int>(width) || // Treat all pixels outside the image as zero.
52 sy >= static_cast<int>(width)) // Treat all pixels outside the image as zero.
53 continue; // Treat all pixels outside the image as zero.
54 const unsigned position = // Select the matching NCHW feature value.
55 (image * sources + source) * width * width + // Select the matching NCHW feature value.
56 unsigned(sy) * width + // Select the matching NCHW feature value.
57 unsigned(sx); // Select the matching NCHW feature value.
58 const unsigned coefficient = // Select the independently indexed learned coefficient.
59 weight_offset + ((target * sources + source) * 5u + ky) * 5u + // Select the independently indexed learned coefficient.
60 kx; // Select the independently indexed learned coefficient.
61 value += // Accumulate this contribution in double precision.
62 weights[coefficient] * // Accumulate this contribution in double precision.
63 input // Accumulate this contribution in double precision.
64 [position]; // Accumulate this contribution in double precision.
65 } // Finish this kernel row.
66 } // Finish this source kernel.
67 } // Finish the sum over source channels.
68 result[(image * outputs + target) * side * side + y * side + x] = // Apply ReLU after the complete convolution sum.
69 std::max(0.0, value); // Apply ReLU after the complete convolution sum.
70 } // Finish this output row.
71 } // Finish this output channel.
72 } // Finish this image's learned feature planes.
73 } // Finish all independent batch images.
74 return result; // Return the independently calculated convolution layer.
75} // Finish the convolution reference.
76inline std::vector<double> // Downsample channel planes with an explicit double average.
77pooling(const std::vector<double>& input, // Downsample channel planes with an explicit double average.
78 unsigned batch, // Process this many independent images.
79 unsigned channels, // Pool each feature channel independently.
80 unsigned width) { // Downsample channel planes with an explicit double average.
81 const unsigned side = width / 2u; // Halve the even spatial dimension.
82 std::vector<double> result(batch * channels * side * // Allocate each complete downsampled plane.
83 side); // Allocate each complete downsampled plane.
84 for (unsigned plane = 0; plane < batch * channels; // Keep batch and channel planes independent.
85 ++plane) { // Keep batch and channel planes independent.
86 for (unsigned y = 0; y < side; ++y) { // Visit each pooled output row.
87 for (unsigned x = 0; x < side; ++x) { // Visit each pooled output column.
88 double sum = 0.0; // Begin the four-pixel double accumulator at zero.
89 for (unsigned dy = 0; dy < 2u; ++dy) { // Visit the upper and lower source rows.
90 for (unsigned dx = 0; dx < 2u; // Visit the left and right source columns.
91 ++dx) { // Visit the left and right source columns.
92 sum += input[plane * width * width + (2u * y + dy) * width + 2u * x + // Add this source pixel to the explicit double sum.
93 dx]; // Add this source pixel to the explicit double sum.
94 } // Finish this source square row.
95 } // Finish this complete two-by-two source square.
96 result[plane * side * side + y * side + x] = // Store the average in the same plane ordering.
97 sum / 4.0; // Store the average in the same plane ordering.
98 } // Finish this pooled output row.
99 } // Finish this pooled output plane.
100 } // Finish the independent image and channel planes.
101 return result; // Return the independently downsampled feature layer.
102} // Finish the average-pooling reference.
103inline std::vector<double> // Compute an ordinary double matrix-vector product for every image.
104dense(const std::vector<double>& input, // Compute an ordinary double matrix-vector product for every image.
105 const std::vector<double>& weights, // Read the flattened model in double precision.
106 unsigned batch, // Process this many independent images.
107 unsigned sources, // Read this many source channels or vector features.
108 unsigned outputs, // Produce this many channels or vector features.
109 unsigned weight_offset, // Locate the first weight of this layer.
110 unsigned bias_offset, // Locate the first bias of this layer.
111 bool relu) { // Compute an ordinary double matrix-vector product for every image.
112 std::vector<double> result(batch * // Allocate a contiguous output vector for each image.
113 outputs); // Allocate a contiguous output vector for each image.
114 for (unsigned image = 0; image < batch; // Process the independent flattened input vectors.
115 ++image) { // Process the independent flattened input vectors.
116 for (unsigned target = 0; target < outputs; // Visit each learned output coordinate.
117 ++target) { // Visit each learned output coordinate.
118 double sum = // Start the dot product with the learned bias.
119 weights[bias_offset + target]; // Start the dot product with the learned bias.
120 for (unsigned source = 0; source < sources; // Visit every contributing input feature.
121 ++source) { // Visit every contributing input feature.
122 sum += // Accumulate the row-major learned projection.
123 weights[weight_offset + target * sources + source] * // Accumulate the row-major learned projection.
124 input[image * sources + source]; // Accumulate the row-major learned projection.
125 } // Finish the input-feature sum.
126 result[image * outputs + target] = // Keep logits linear and hidden features nonnegative.
127 relu ? std::max(0.0, sum) // Keep logits linear and hidden features nonnegative.
128 : sum; // Keep logits linear and hidden features nonnegative.
129 } // Finish the image's output vector.
130 } // Finish all independent image vectors.
131 return result; // Return the independently calculated dense layer.
132} // Finish the dense reference.
133inline void // Compare every native feature with its independent double result.
134compare(const shdr::buffer<float>& actual, // Compare every native feature with its independent double result.
135 const std::vector<double>& expected, // Use the independently computed double-precision features.
136 const char* layer) { // Compare every native feature with its independent double result.
137 if (actual.size() != expected.size()) // Require matching layer dimensions before comparing values.
138 throw shdr::error( // Reject an inconsistent model shape before reading output.
139 "LeNet reference layer size mismatch"); // Reject an inconsistent model shape before reading output.
140 const float* values = // Obtain the completed native output values.
141 actual.data(); // Synchronize the layer only for this untimed correctness check.
142 for (unsigned i = 0; i < expected.size(); // Verify every image, channel and feature coordinate.
143 ++i) { // Verify every image, channel and feature coordinate.
144 const float reference = static_cast<float>( // Convert the double oracle value for a float comparison.
145 expected[i]); // Convert the double oracle value for a float comparison.
146 require_close( // Check this value against the independent layer oracle.
147 values[i], // Use the native CPU or GPU feature value.
148 reference, // Use the oracle feature rounded to storage precision.
149 layer, // Identify the layer if this comparison fails.
150 0.00002f + // Allow a small absolute error around zero.
151 0.00003f * // Scale the tolerance with the expected feature magnitude.
152 std::fabs( // Measure the absolute expected magnitude.
153 reference)); // Allow float accumulation error while retaining a strict layer check.
154 } // Finish all feature comparisons.
155} // Finish the model-layer comparison.
156} // namespace lenet_detail
157/** @brief Calculate every model layer independently using a double host reference. */
158inline lenet_detail::reference_result lenet_reference( // Accept the same complete input and parameter layout as the native classifier.
159 const std::vector<float>& inputs, // Accept the complete batch of normalized input images.
160 const std::vector<float>& parameters, // Accept the documented flattened model record.
161 unsigned // Accept the number of independent images in this reference batch.
162 batch) { // Accept the same complete input and parameter layout as the native classifier.
163 if (!batch || batch > 512u || inputs.size() != batch * 784u || parameters.size() != 61706u) // Require the exact batch, image and model geometry.
164 throw shdr::error( // Require complete model and batch records.
165 "invalid LeNet reference geometry"); // Require complete model and batch records.
166 const std::vector<double> image(inputs.begin(), inputs.end()), // Convert each input value to the oracle's double precision.
167 weights(parameters.begin(), // Convert each stored parameter to double precision.
168 parameters.end()); // Convert the float parameters to the independent double reference.
169 lenet_detail::reference_result result; // Keep every independently computed intermediate layer.
170 result.conv1 = // Compute the independent first convolution features.
171 lenet_detail::convolution(image, // Use explicit double-precision convolution loops.
172 weights, // Read the independent double-precision model record.
173 batch, // Process every image in this oracle call.
174 28u, // Use the 28-pixel input dimension.
175 1u, // Read one grayscale source plane.
176 6u, // Use the six first-block feature planes.
177 2u, // Pad each edge with two implicit zero pixels.
178 0u, // Start the first convolution weight record.
179 150u); // Reproduce padded first convolution in double precision.
180 result.pool1 = // Compute the independent first pooled features.
181 lenet_detail::pooling(result.conv1, batch, 6u, 28u); // Reproduce first average pooling.
182 result.conv2 = lenet_detail::convolution( // Compute the full six-to-sixteen second convolution.
183 result.pool1, // Read the first independent pooled feature record.
184 weights, // Read the independent double-precision model record.
185 batch, // Process every image in this oracle call.
186 14u, // Use the 14-pixel first pooled dimension.
187 6u, // Use the six first-block feature planes.
188 16u, // Use the sixteen second-block feature planes.
189 0u, // Apply the second convolution over valid five-by-five input windows.
190 156u, // Start the second convolution weight record.
191 2556u); // Reproduce full C3 connectivity across six source planes.
192 result.pool2 = lenet_detail::pooling( // Compute the independent second average pooling.
193 result.conv2, batch, 16u, 10u); // Reproduce second average pooling in NCHW order.
194 result.hidden1 = lenet_detail::dense(result.pool2, // Project the independent pooled features to 120 values.
195 weights, // Read the independent double-precision model record.
196 batch, // Process every image in this oracle call.
197 400u, // Read all sixteen five-by-five pooled planes.
198 120u, // Use the 120-feature first dense representation.
199 2572u, // Start the 400-to-120 dense weight record.
200 50572u, // Start the 120 first dense biases.
201 true); // Reproduce the flattened full C5 projection.
202 result.hidden2 = lenet_detail::dense(result.hidden1, // Project the independent hidden features to 84 values.
203 weights, // Read the independent double-precision model record.
204 batch, // Process every image in this oracle call.
205 120u, // Use the 120-feature first dense representation.
206 84u, // Use the 84-feature second dense representation.
207 50692u, // Start the 120-to-84 dense weight record.
208 60772u, // Start the 84 second dense biases.
209 true); // Reproduce the F6 feature projection.
210 result.logits = lenet_detail::dense(result.hidden2, // Compute the ten independent linear class logits.
211 weights, // Read the independent double-precision model record.
212 batch, // Process every image in this oracle call.
213 84u, // Use the 84-feature second dense representation.
214 10u, // Use the ten-value dimension required by this layer.
215 60856u, // Start the 84-to-10 classifier weight record.
216 61696u, // Start the ten final classifier biases.
217 false); // Reproduce the learned linear ten-class output.
218 return result; // Return all reference layers for detailed validation.
219} // Finish complete independent model inference.
220/** @brief Verify all seven native layers on two distinct seeded and impulse images. */
221inline void check_lenet_classifier( // Verify the complete model on the chosen execution backend.
222 runner& run) { // Check geometry and every model layer against independent calculations.
223 lenet_classifier model(2); // Allocate two independent complete classifiers in one batch.
224 std::mt19937 generator(1978u); // Reproduce the same synthetic model on every backend.
225 std::uniform_real_distribution<float> weights(-0.08f, 0.08f), // Keep model values bounded and input pixels in the documented range.
226 pixels(0.0f, 1.0f); // Keep model values bounded and input pixels in the documented range.
227 std::vector<float> parameters( // Allocate one complete deterministic test model.
228 model.parameter_count); // Hold every learned value in its published layout.
229 for (float& value : parameters) // Initialize every learned model value.
230 value = weights( // Draw a reproducible small signed parameter.
231 generator); // Give all convolution and dense coefficients nontrivial seeded values.
232 for (unsigned i = 0; i < 6u; ++i) // Initialize each first convolution bias.
233 parameters[150u + i] = 0.01f + 0.003f * i; // Keep distinct first-layer biases visible.
234 for (unsigned i = 0; i < 16u; ++i) // Initialize each second convolution bias.
235 parameters[2556u + i] = // Keep second-block activations available for later checks.
236 0.02f + 0.005f * (i % 3u); // Keep second-layer channels distinguishable.
237 for (unsigned i = 0; i < 120u; ++i) // Initialize every first dense bias.
238 parameters[50572u + i] = // Keep first dense activations available for later checks.
239 0.02f + 0.002f * (i % 7u); // Exercise nonzero hidden features after flattening.
240 for (unsigned i = 0; i < 84u; ++i) // Initialize every second dense bias.
241 parameters[60772u + i] = // Keep second dense activations available for later checks.
242 0.01f + 0.003f * (i % 5u); // Exercise the learned 84-feature projection.
243 for (unsigned i = 0; i < 10u; ++i) // Initialize all final digit-score biases.
244 parameters[61696u + i] = // Give each class a distinct deterministic bias.
245 -0.02f + 0.004f * i; // Keep the final linear class scores distinct.
246 std::vector<float> inputs(2u * 784u, // Allocate two initially zero test images.
247 0.0f); // Keep the second image zero except for its explicit impulses.
248 for (unsigned i = 0; i < 784u; ++i) // Fill the first image with reproducible grayscale data.
249 inputs[i] = pixels(generator); // Use a seeded grayscale image in the first batch slot.
250 inputs[784u] = 1.0f; // Put an impulse at the second image's upper-left edge.
251 inputs[784u + 14u * 28u + 12u] = 0.75f; // Put a distinct interior impulse in the second image.
252 inputs[2u * 784u - 1u] = 0.4f; // Put a third impulse at the lower-right edge.
253 model.load_parameters( // Load the independent deterministic model into native storage.
254 parameters); // Copy the complete synthetic model into shared CPU and GPU storage.
255 std::copy( // Copy both test images into the model input resource.
256 inputs.begin(), // Start at the first normalized test image.
257 inputs.end(), // Finish at the end of the second test image.
258 model.inputs.data()); // Copy both independent input images in their original order.
259 const lenet_detail::reference_result expected = // Compute every layer with a separate double-precision oracle.
260 lenet_reference(inputs, // Run the independent complete classifier calculation.
261 parameters, // Use the same stored model values for the oracle.
262 2u); // Compute the full independent double oracle before native execution.
263 run.run( // Execute all seven linked native model stages.
264 model // Use the classifier's complete inference pipeline.
265 .inference_pipeline()); // Execute the same seven linked native stages on the selected backend.
266 lenet_detail::compare( // Compare every feature of the next native layer.
267 model.conv1, // Read the first native convolution feature record.
268 expected.conv1, // Use the independent first convolution features.
269 "LeNet padded first convolution"); // Check every padded border and first-layer feature.
270 lenet_detail::compare( // Compare every feature of the next native layer.
271 model.pool1, // Read the first native pooled feature record.
272 expected.pool1, // Use the independent first pooling features.
273 "LeNet first average pooling"); // Check the first downsampled feature planes.
274 lenet_detail::compare( // Compare every feature of the next native layer.
275 model.conv2, // Read the second native convolution feature record.
276 expected.conv2, // Use the independent second convolution features.
277 "LeNet full C3 convolution"); // Check every learned cross-channel contribution.
278 lenet_detail::compare( // Compare every feature of the next native layer.
279 model.pool2, // Read the second native pooled feature record.
280 expected.pool2, // Use the independent second pooling features.
281 "LeNet second average pooling"); // Check the NCHW features feeding the dense classifier.
282 lenet_detail::compare( // Compare every feature of the next native layer.
283 model.hidden1, // Read the native 120-feature hidden representation.
284 expected.hidden1, // Use the independent 120-feature hidden representation.
285 "LeNet full C5 projection"); // Check flattening and the 120-feature projection.
286 lenet_detail::compare( // Compare every feature of the next native layer.
287 model.hidden2, expected.hidden2, "LeNet F6 projection"); // Check the 84-feature ReLU stage.
288 lenet_detail::compare(model.logits, // Compare all native linear digit scores.
289 expected.logits, // Use the independently computed linear digit scores.
290 "LeNet ten-class logits"); // Check every final linear class score.
291 std::cout // Print a concise confirmation of complete model verification.
292 << "LeNet-style classifier: all seven layers and 61706 parameters verified against a double oracle\n"; // Report the complete independent inference check.
293} // Finish the native classifier validation.
294} // namespace shdr_examples
295#endif // Finish the model-check include guard.