Source browser
examples/lenet_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 lenet_support.hpp
2 * @brief A complete modern LeNet-5-style classifier with seven native stages.
3 * This version uses full C3 connectivity, fixed average pooling, ReLU and a
4 * learned linear ten-class head. These are explicit modern adaptations of the
5 * historical network. Inputs are 28-by-28 grayscale values between zero and one.
6 * Model preparation may use a separate framework, but deployed inference uses
7 * only standard C++11 and the shdr single-file header.
8 */
9#ifndef SHDR_EXAMPLES_LENET_SUPPORT_HPP // Include this model helper only once.
10#define SHDR_EXAMPLES_LENET_SUPPORT_HPP // Remember that this model helper is available.
11#include "shdr.h" // Use portable resources and CPU or GPU dispatches.
12#include <algorithm> // Copy loaded model values into retained resources.
13#include <cmath> // Reject nonfinite loaded model values.
14#include <vector> // Accept a complete flattened parameter record.
15namespace shdr_examples { // Keep the classifier helper with the other reusable examples.
16/** @brief A fully connected C3, ReLU and average-pooling LeNet-5-style network.
17 * Layer shapes are 1x28x28 to 6x28x28 to 6x14x14 to 16x10x10 to 16x5x5,
18 * followed by feature vectors of 120, 84 and 10 values. First convolution uses
19 * two implicit zero pixels on each edge. The parameter order matches PyTorch
20 * convolution [out,in,ky,kx] and linear [out,in] tensors with adjacent biases.
21 */
22class lenet_classifier { // Keep model resources and their seven linked inference stages together.
23 static unsigned // Return a validated unsigned image count.
24 checked_batch(unsigned count) { // Validate dimensions before allocating intermediate features.
25 if (!count || count > 512u) // Reject empty batches and batches beyond this helper's dispatch limit.
26 throw shdr::error( // Keep the largest stage within minimum GLES work group limits.
27 "LeNet batch count must be between 1 and 512"); // Keep the largest stage within minimum GLES work group limits.
28 return count; // Return the validated number of independent images.
29 } // Finish batch validation.
30 unsigned batch_; // Remember the number of images sharing this classifier instance.
31 std::unique_ptr<shdr::kernel> conv1_, pool1_, conv2_, pool2_, dense1_, dense2_, // Own the seven compiled shader instances.
32 dense3_; // Own the seven compiled shader instances.
33 shdr::pipeline pipeline_; // Keep the ordered inference dispatches ready for repeated execution.
34 static shdr::extent // Return the dispatch size for a complete feature layer.
35 groups(unsigned values) { // Cover every output feature with 64-wide work groups.
36 return shdr::extent( // Build a one-dimensional work group count.
37 values / 64u + // Count all completely filled 64-invocation groups.
38 (values % 64u ? 1u : 0u)); // Include a final partial group when necessary.
39 } // Finish group-count calculation.
40 void convolution( // Configure one learned convolution stage.
41 shdr::kernel& shader, // Configure the selected native shader instance.
42 const shdr::buffer<float>& source, // Read features produced by the preceding layer.
43 const shdr::buffer<float>& output, // Write features consumed by the following layer.
44 unsigned width, // Specify the width of each input feature plane.
45 unsigned height, // Specify the height of each input feature plane.
46 unsigned input_channels, // Specify how many input feature planes each image has.
47 unsigned output_channels, // Specify how many learned feature planes to produce.
48 unsigned padding, // Specify the number of zero pixels around the input.
49 unsigned weight_offset, // Locate the first weight of this layer.
50 unsigned bias_offset) { // Connect one convolution stage to its exact model geometry.
51 shader.bind("input_values", source) // Connect the preceding features as this layer's input.
52 .bind("output_values", output) // Connect storage for this layer's output features.
53 .bind( // Attach the shared model parameter resource.
54 "parameters", // Select the native port for learned weights and biases.
55 parameters); // Share the preceding features, following features and model parameters.
56 shader.set<unsigned>("batch_count", batch_) // Process the configured number of images.
57 .set<unsigned>("input_width", width) // Pass the input feature-plane width.
58 .set<unsigned>("input_height", height); // Set the input geometry for every image.
59 shader.set<unsigned>("input_channels", input_channels) // Pass the number of source feature planes.
60 .set<unsigned>("output_channels", output_channels); // Set the two feature-plane counts.
61 shader.set<unsigned>("kernel_size", 5u) // Use five-by-five learned convolution kernels.
62 .set<unsigned>( // Set the following unsigned geometry option.
63 "padding", // Select the zero-padding geometry option.
64 padding); // Use five-by-five learned kernels with the requested zero padding.
65 shader.set<unsigned>("weight_offset", weight_offset) // Pass the first weight index for this layer.
66 .set<unsigned>("bias_offset", // Pass the first bias index for this layer.
67 bias_offset); // Locate this layer inside the shared flat model.
68 } // Finish convolution resource wiring.
69 void pooling(shdr::kernel& shader, // Configure one fixed average-pooling stage.
70 const shdr::buffer<float>& source, // Read features produced by the preceding layer.
71 const shdr::buffer<float>& output, // Write features consumed by the following layer.
72 unsigned width, // Specify the width of each input feature plane.
73 unsigned channels) { // Connect one fixed two-by-two average-pooling stage.
74 shader.bind("input_values", source) // Connect the preceding features as this layer's input.
75 .bind("output_values", output); // Keep pooling input and output in distinct resources.
76 shader.set<unsigned>("batch_count", batch_) // Process the configured number of images.
77 .set<unsigned>("channels", channels); // Process every image and channel independently.
78 shader.set<unsigned>("input_width", width) // Pass the square feature-plane width.
79 .set<unsigned>("input_height", width); // Set the square input plane used by this model.
80 } // Finish pooling resource wiring.
81 void dense(shdr::kernel& shader, // Configure one learned fully connected stage.
82 const shdr::buffer<float>& source, // Read features produced by the preceding layer.
83 const shdr::buffer<float>& output, // Write features consumed by the following layer.
84 unsigned input_features, // Specify the length of each input feature vector.
85 unsigned output_features, // Specify the length of each output feature vector.
86 unsigned weight_offset, // Locate the first weight of this layer.
87 unsigned bias_offset, // Locate the first bias of this layer.
88 bool relu) { // Connect a dense layer to its flattened feature and parameter ranges.
89 shader.bind("input_values", source) // Connect the preceding features as this layer's input.
90 .bind("output_values", output) // Connect storage for this layer's output features.
91 .bind("parameters", // Share the flat record of learned weights and biases.
92 parameters); // Share the input vector, output vector and learned model values.
93 shader.set<unsigned>("batch_count", batch_) // Process the configured number of images.
94 .set<unsigned>("input_features", input_features) // Pass the length of each source feature vector.
95 .set<unsigned>("output_features", // Pass the length of each result feature vector.
96 output_features); // Set each image's vector dimensions.
97 shader.set<unsigned>("weight_offset", weight_offset) // Pass the first weight index for this layer.
98 .set<unsigned>("bias_offset", // Pass the first bias index for this layer.
99 bias_offset); // Locate this layer's row-major weights and biases.
100 shader.set<unsigned>( // Pass the activation choice to the native stage.
101 "activation", // Select linear output or ReLU output.
102 relu ? 1u : 0u); // Keep the final logits linear while hidden layers use ReLU.
103 } // Finish dense resource wiring.
104 public: // Expose model resources and the complete inference pipeline.
105 enum { // Publish the fixed image and parameter dimensions.
106 parameter_count = 61706, // Count all weights and biases in this architecture.
107 input_features = 784, // Count the 28-by-28 grayscale values in one image.
108 class_count = 10 // Count the ten possible digit classes.
109 }; // Publish the exact model and image sizes.
110 shdr::buffer<float> inputs; // Store normalized 28-by-28 grayscale images consecutively.
111 shdr::buffer<float> parameters; // Store all learned weights and biases in the documented order.
112 shdr::buffer<float> conv1; // Retain the 6-by-28-by-28 first convolution features per image.
113 shdr::buffer<float> pool1; // Retain the 6-by-14-by-14 first pooled features per image.
114 shdr::buffer<float> conv2; // Retain the 16-by-10-by-10 second convolution features per image.
115 shdr::buffer<float> pool2; // Retain the 16-by-5-by-5 second pooled features per image.
116 shdr::buffer<float> hidden1; // Retain the 120 learned dense features per image.
117 shdr::buffer<float> hidden2; // Retain the 84 learned dense features per image.
118 shdr::buffer<float> logits; // Retain ten linear class scores per image.
119 /** @brief Allocate and connect a batch of complete seven-stage classifiers. */
120 explicit lenet_classifier( // Create one reusable complete classifier.
121 unsigned batch_count = 32) // Construct one reusable batch-sized model instance.
122 : batch_(checked_batch( // Validate the batch before any feature allocation.
123 batch_count)), // Check the batch before allocating model or feature storage.
124 conv1_(shdr::make_kernel("lenet_conv_kernel")), // Create the first learned convolution instance.
125 pool1_(shdr::make_kernel( // Create the first fixed average-pooling instance.
126 "lenet_pool_kernel")), // Create the first convolution and pooling stages.
127 conv2_(shdr::make_kernel("lenet_conv_kernel")), // Create the second learned convolution instance.
128 pool2_(shdr::make_kernel( // Create the second fixed average-pooling instance.
129 "lenet_pool_kernel")), // Create independent instances for the second feature block.
130 dense1_(shdr::make_kernel("lenet_dense_kernel")), // Create the 120-feature dense layer instance.
131 dense2_(shdr::make_kernel("lenet_dense_kernel")), // Create the 84-feature dense layer instance.
132 dense3_(shdr::make_kernel("lenet_dense_kernel")), // Create each learned dense layer.
133 inputs(batch_ * 784u), // Allocate all normalized input images.
134 parameters(parameter_count), // Allocate every image value and learned parameter.
135 conv1(batch_ * 4704u), pool1(batch_ * 1176u), conv2(batch_ * 1600u), // Allocate the first three spatial feature records.
136 pool2(batch_ * 400u), // Allocate the NCHW spatial intermediates.
137 hidden1(batch_ * 120u), hidden2(batch_ * 84u), // Allocate the two learned hidden feature records.
138 logits(batch_ * 10u) { // Allocate the dense intermediates and class logits.
139 convolution(*conv1_, // Connect the first learned convolution.
140 inputs, // Read the normalized grayscale input images.
141 conv1, // Write the first learned spatial features.
142 28u, // Use the 28-pixel input dimension.
143 28u, // Use the 28-pixel input dimension.
144 1u, // Read one grayscale source plane.
145 6u, // Use the six first-block feature planes.
146 2u, // Pad each edge with two implicit zero pixels.
147 0u, // Start the first convolution weight record.
148 150u); // Wire padded first convolution with its adjacent six biases.
149 pooling( // Connect the first average-pooling reduction.
150 *pool1_, conv1, pool1, 28u, 6u); // Reduce each first-layer plane from 28 to 14 pixels.
151 convolution(*conv2_, // Connect the second learned convolution.
152 pool1, // Read the first pooled spatial features.
153 conv2, // Use the second learned spatial feature record.
154 14u, // Use the 14-pixel first pooled dimension.
155 14u, // Use the 14-pixel first pooled dimension.
156 6u, // Use the six first-block feature planes.
157 16u, // Use the sixteen second-block feature planes.
158 0u, // Apply the second convolution over valid five-by-five input windows.
159 156u, // Start the second convolution weight record.
160 2556u); // Wire full C3 convolution over all six source planes.
161 pooling(*pool2_, // Connect the second average-pooling reduction.
162 conv2, // Use the second learned spatial feature record.
163 pool2, // Use the final pooled spatial feature record.
164 10u, // Use the ten-value dimension required by this layer.
165 16u); // Reduce each second-layer plane from 10 to five pixels.
166 dense(*dense1_, // Connect the first fully connected projection.
167 pool2, // Use the final pooled spatial feature record.
168 hidden1, // Use the 120-feature hidden representation.
169 400u, // Read all sixteen five-by-five pooled planes.
170 120u, // Use the 120-feature first dense representation.
171 2572u, // Start the 400-to-120 dense weight record.
172 50572u, // Start the 120 first dense biases.
173 true); // Apply the flattened full C5 projection and ReLU.
174 dense(*dense2_, // Connect the second fully connected projection.
175 hidden1, // Use the 120-feature hidden representation.
176 hidden2, // Use the 84-feature hidden representation.
177 120u, // Use the 120-feature first dense representation.
178 84u, // Use the 84-feature second dense representation.
179 50692u, // Start the 120-to-84 dense weight record.
180 60772u, // Start the 84 second dense biases.
181 true); // Apply the 84-feature learned projection and ReLU.
182 dense(*dense3_, // Connect the final linear class projection.
183 hidden2, // Use the 84-feature hidden representation.
184 logits, // Write the ten linear digit scores.
185 84u, // Use the 84-feature second dense representation.
186 10u, // Use the ten-value dimension required by this layer.
187 60856u, // Start the 84-to-10 classifier weight record.
188 61696u, // Start the ten final classifier biases.
189 false); // Produce ten unnormalized class logits.
190 pipeline_ = shdr::dispatch(*conv1_, groups(batch_ * 4704u)) >> // Run the first convolution before its pooled output is consumed.
191 shdr::dispatch(*pool1_, // Dispatch the first feature-plane reduction.
192 groups(batch_ * 1176u)); // Link the first spatial feature block.
193 pipeline_ += shdr::dispatch( // Append the next dependent native layer.
194 *conv2_, groups(batch_ * 1600u)); // Append the second learned convolution.
195 pipeline_ += shdr::dispatch( // Append the next dependent native layer.
196 *pool2_, groups(batch_ * 400u)); // Append the second average-pooling stage.
197 pipeline_ += // Append the next learned projection to the inference order.
198 shdr::dispatch(*dense1_, groups(batch_ * 120u)); // Append the 120-feature dense stage.
199 pipeline_ += // Append the next learned projection to the inference order.
200 shdr::dispatch(*dense2_, groups(batch_ * 84u)); // Append the 84-feature dense stage.
201 pipeline_ += shdr::dispatch( // Append the next dependent native layer.
202 *dense3_, groups(batch_ * 10u)); // Append the final ten-class linear head.
203 } // Finish complete model wiring.
204 /** @brief Return the number of independent images in this model instance. */
205 unsigned batch_count() const { // Expose the configured number of independent images.
206 return batch_; // Return the validated batch size.
207 } // Return the validated batch size from host metadata.
208 /** @brief Copy an entire finite model parameter record into shared storage. */
209 void load_parameters( // Load a complete finite flattened model.
210 const std::vector<float>& values) { // Accept only the documented complete model layout.
211 if (values.size() != parameter_count) // Require exactly the weights and biases of this architecture.
212 throw shdr::error( // Reject a truncated or mismatched trained model.
213 "LeNet parameters require exactly 61706 values"); // Reject a truncated or mismatched trained model.
214 for (float value : values) // Inspect every model value before copying the record.
215 if (!std::isfinite(value)) // Reject infinities and NaNs in loaded parameters.
216 throw shdr::error( // Prevent invalid loaded values from entering inference.
217 "LeNet parameters must be finite"); // Prevent invalid loaded values from entering inference.
218 std::copy(values.begin(), // Copy the complete validated parameter range.
219 values.end(), // Stop copying at the end of the model record.
220 parameters.data()); // Make the loaded model authoritative on the host.
221 } // Finish model parameter loading.
222 /** @brief Return all seven native inference stages in their data-flow order. */
223 const shdr::pipeline& inference_pipeline() const { // Expose all seven linked inference stages.
224 return pipeline_; // Return the reusable native CPU or GPU computation.
225 } // Expose the same complete computation to CPU and GPU execution.
226}; // Finish the reusable classifier helper.
227} // namespace shdr_examples
228#endif // Finish the classifier helper include guard.