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.