The Secrets Behind STM32 Image Classification in Embedded Machine Learning

The Secrets Behind STM32 Image Classification in Embedded Machine Learning

Where does the speed improvement of STM32 come from?

That’s right! The component that accelerates the image classification processing speed of STM32 is the Digital Signal Processor (DSP).

Currently, Arm‘s Cortex-M4, Cortex-M7, Cortex-M33, and Cortex-M35P microcontroller cores all come with DSP.

Compared to CPU (which excels at scheduling), DSP is very good at computations, handling complex operations in neural networks such as convolution and pooling, thus speeding up processing.

The Secrets Behind STM32 Image Classification in Embedded Machine Learning

By adding DSP extensions to the Thumb instruction set and optional Floating Point Unit (FPU), the performance of numerical calculations has been improved. This not only allows for direct execution of signal processing operations on Cortex-M4, Cortex-M7, Cortex-M33, and Cortex-M35P processors but also maintains the ease of use of the Cortex-M programming model.

Looking at the arm_convolve_HWC_q7_RGB.c file in the project source code, you will find that STM32 uses __SIMD32 and __SXTB16 DSP instructions to improve computational speed.

arm_status arm_convolve_HWC_q7_RGB(const q7_t * Im_in, const uint16_t dim_im_in, ... q7_t * Im_out, const uint16_t dim_im_out, q15_t * bufferA, q7_t * bufferB){ #if defined (ARM_MATH_DSP) /* Run the following code for Cortex-M4 and Cortex-M7 */ // Source code omitted... const q7_t *pPixel = Im_in + (i_ker_y * dim_im_in + i_ker_x) * 3; q31_t buf = *__SIMD32(pPixel); union arm_nnword top; union arm_nnword bottom; top.word = __SXTB16(buf); bottom.word = __SXTB16(__ROR(buf, 8)); // Source code omitted... #else /* Run the following code as reference implementation for Cortex-M0 and Cortex-M3 */ // Source code omitted... #endif /* ARM_MATH_DSP */ /* Return to application */ return (ARM_MATH_SUCCESS); }

You can also experience the execution speed of the image classification application with and without DSP in the Keil simulator.

The Secrets Behind STM32 Image Classification in Embedded Machine Learning

Selecting ARMCM3 for execution will not involve DSP in calculations.

Memory Constraints

The memory of STM32 is very limited, for example, the STM32F427AG has only 256KB of memory. In the project source file arm_nnexamples_cifar10.cpp, two buffers col_buffer and scratch_buffer are created and reused between the layers of the neural network to reduce memory usage.

  • col_buffer stores the output of the convolution layer

  • scratch_buffer stores the output of intermediate layers

// typedef int8_t q7_t; // typedef int16_t q15_t; q7_t col_buffer[2 * 5 * 5 * 32 * 2]; q7_t scratch_buffer[32 * 32 * 10 * 4]; // Cut the scratch buffer to two buffers. q7_t *img_buffer1 = scratch_buffer; q7_t *img_buffer2 = img_buffer1 + 32 * 32 * 32; // conv2 img_buffer2 -> img_buffer1 arm_convolve_HWC_q7_fast(img_buffer2, CONV2_IM_DIM, CONV2_IM_CH, conv2_wt, CONV2_OUT_CH, CONV2_KER_DIM, CONV2_PADDING, CONV2_STRIDE, conv2_bias, CONV2_BIAS_LSHIFT, CONV2_OUT_RSHIFT, img_buffer1, CONV2_OUT_DIM, (q15_t *) col_buffer, NULL); arm_relu_q7(img_buffer1, CONV2_OUT_DIM * CONV2_OUT_DIM * CONV2_OUT_CH); // pool2 img_buffer1 -> img_buffer2 arm_maxpool_q7_HWC(img_buffer1, CONV2_OUT_DIM, CONV2_OUT_CH, POOL2_KER_DIM, POOL2_PADDING, POOL2_STRIDE, POOL2_OUT_DIM, col_buffer, img_buffer2);
The arm_convolve_HWC_q7_fast function creates a convolution layer, in this case, the convolution layer uses img_buffer2 as input and img_buffer1 as output, using col_buffer as the data storage area during internal convolution operations.
Then, the activation function layer (the arm_relu_q7 function) directly performs ReLu operations on img_buffer1, and the processed data remains in img_buffer1.
After that, img_buffer1 will serve as input for the max pooling layer (the arm_maxpool_q7_HWC function), outputting the data to img_buffer2, and this process continues to use these two memory areas until the network computation is complete.
Due to the very limited memory on STM32, we cannot allocate large spaces for these two memory areas arbitrarily; we need to be very careful with allocations.
The size calculation method for col_buffer is as follows:
# 2*2*number of filters (convolution kernels)*kernel width*kernel height 2*2*(conv # of filters)*(kernel width)*(kernel height)

These parameters can be found in the Caffe model, as shown in the figure below:

The Secrets Behind STM32 Image Classification in Embedded Machine Learning

In this example, we have a total of three convolution layers, with one convolution layer (conv1) requiring the most memory space (2*2*32*5*5=3200 bytes), so the size of col_buffer is 3200 bytes. col_buffer will be used across all convolution layers.

scratch_buffer is divided into two parts: img_buffer1 and img_buffer2. For a specific layer, one serves as input while the other serves as output. Its size can be determined by traversing the usage across layers.

The Secrets Behind STM32 Image Classification in Embedded Machine Learning

As can be seen from the figure above, the size of scratch_buffer is:
max(img_buffer1)+max(img_buffer2) = 32*32*40

Selecting the Right Convolution Layer Function

In the CMSIS-NN library, there are multiple convolution layer functions:
  • arm_convolve_HWC_q7_basic

  • arm_convolve_HWC_q7_fast

  • arm_convolve_HWC_q7_RGB

  • arm_convolve_HWC_q7_fast_nonsquare

Each of them is optimized for speed and size to varying degrees, but they also have different focuses.

arm_ve_HWC_q7_basic function is the most basic version, designed for any second-order tensor and weights of any dimension.

arm_ve_HWC_q7_fast function, as its name suggests, runs faster than the previous function, but requires the input tensor channels to be a multiple of 4 and the output tensor channels (number of filters) to be a multiple of 2.

arm_ve_HWC_q7_rgb is specifically built for convolution with an input tensor channel count of 3, typically applied to the first convolution layer with RGB image data as input.

arm_convolve_HWC_q7_fast_nonsquare is similar to arm_convolve_HWC_q7_fast, but can take non-square input tensors.

For fully connected layers, the two most distinct options are:

  • arm_fully_connected_q7

  • arm_fully_connected_q7_opt

The first uses a regular weight matrix, while the one with the suffix “_opt” is optimized for speed, but the layer’s weight matrix must be pre-sorted in an interleaved manner.

Conclusion

In this issue, we explained three key points of STM32 image classification applications: DSP acceleration, memory usage, and the selection of convolution layer functions. We hope this helps with your CMSIS-NN application development.

Some students may not be familiar with machine learning concepts such as convolution, ReLu activation functions, and max pooling. No worries, we will focus on these topics in the next issue, and we welcome you to stay tuned! The Secrets Behind STM32 Image Classification in Embedded Machine Learning
RECOMMEND
Recommended Reading
The Secrets Behind STM32 Image Classification in Embedded Machine Learning

Embedded Machine Learning Series 00: STM32 Image Classification

The Secrets Behind STM32 Image Classification in Embedded Machine Learning

Stop Searching! There’s No Clearer Explanation About Convolutional Neural Networks (CNN)!

Edge Intelligence Lab

Edge Intelligence Lab

Focusing on Edge Intelligence!

Long press to recognize the QR code to follow

The Secrets Behind STM32 Image Classification in Embedded Machine Learning
The Secrets Behind STM32 Image Classification in Embedded Machine Learning
Your every like is taken seriously as a sign of appreciation.

Leave a Comment