Edge AI & Vision: Depthwise 2D Convolution
In modern convolutional neural networks designed for mobile and edge inference—such as MobileNet (V1, V2, V3), ConvNeXt, Xception, and EfficientNet—standard convolutions are replaced by Depthwise Separable Convolutions:
- Depthwise Convolution: Each input channel is filtered independently by its own spatial 2D kernel (typically \(3 \times 3\)).
- Pointwise Convolution: A \(1 \times 1\) convolution mixes channels linearly across the channel dimension (equivalent to standard GEMM).
While Pointwise convolution maps cleanly to BLAS/GEMM, the Depthwise Convolution is frequently the memory-bandwidth and efficiency bottleneck on CPU hardware.
Figure: Depthwise 2D Convolution Architecture — Legolas packs P=4 channels contiguously so a single SIMD FMA instruction computes 4 channel convolutions simultaneously in 1 clock cycle.
The Channel Layout Dilemma
- Standard
NCHWLayout: - The spatial pixels of a single channel are contiguous.
- Channels are separated by large strides (\(H \times W\)).
- Vectorizing across spatial width \(W\) suffers from small spatial dimensions, unaligned memory loads, and boundary padding penalties.
- Interleaved
NHWCLayout: - Channels are contiguous at each pixel \((h, w)\).
- Vectorization across channels works well only when the number of channels is large and a multiple of vector width.
- Industry Blocked Channel Formats (
nChw4c/nChw8c): - High-performance inference engines (Intel oneDNN, Apache TVM, ONNX Runtime) transform tensors into blocked layouts where \(P\) channels (4 on NEON, 8 on AVX2, 16 on AVX-512) are contiguous at every spatial pixel.
With Legolas++, this blocked layout is native:
// Legolas::Array<float, Rank, PackSize, PackDimension>
// C channels grouped by packets of P=4, across an HxW image
using Tensor = Legolas::Array<float, 2, 4, 2>;
2. Implementing the 3x3 Depthwise Stencil
The spatial stencil is written naturally with scalar-looking notation:
#include "Legolas/Array/Array.hxx"
#include "Legolas/Array/Map.hxx"
struct DepthwiseConv2D {
int H, W;
DepthwiseConv2D(int h, int w) : H(h), W(w) {}
template <class InA2D, class OutA2D>
void operator()(int begin, int end, InA2D input, OutA2D output) const {
using Scalar = typename InA2D::RealType;
// 3x3 Gaussian blur filter weights:
Scalar k00(1.0f/16.0f), k01(2.0f/16.0f), k02(1.0f/16.0f);
Scalar k10(2.0f/16.0f), k11(4.0f/16.0f), k12(2.0f/16.0f);
Scalar k20(1.0f/16.0f), k21(2.0f/16.0f), k22(1.0f/16.0f);
for (int c = begin; c < end; ++c) {
auto in_chan = input[c];
auto out_chan = output[c];
for (int r = 1; r < H - 1; ++r) {
const int row_prev = (r - 1) * W;
const int row_curr = r * W;
const int row_next = (r + 1) * W;
for (int col = 1; col < W - 1; ++col) {
// Stencil computes across P channels simultaneously!
Scalar sum = k00 * in_chan[row_prev + col - 1]
+ k01 * in_chan[row_prev + col]
+ k02 * in_chan[row_prev + col + 1]
+ k10 * in_chan[row_curr + col - 1]
+ k11 * in_chan[row_curr + col]
+ k12 * in_chan[row_curr + col + 1]
+ k20 * in_chan[row_next + col - 1]
+ k21 * in_chan[row_next + col]
+ k22 * in_chan[row_next + col + 1];
out_chan[row_curr + col] = sum;
}
}
}
}
};
3. Performance Results (Apple M1 Max)
Evaluating on a typical MobileNet feature layer: - \(C = 128\) feature channels - \(H \times W = 112 \times 112\) pixels (12,544 spatial elements per channel) - 27.88 MFLOPs per forward pass
| Configuration | Throughput | Forward Pass Time | Speedup |
|---|---|---|---|
| Scalar Baseline (\(P=1\)) | 36.4 GFlops | 0.77 ms | 1.00x |
| Legolas NEON SIMD (\(P=4\), 1 Core) | 45.2 GFlops | 0.62 ms | 1.24x |
| Legolas Multi-Core + NEON (\(P=4\), 8 Cores) | 212.3 GFlops | 0.13 ms | 5.83x |
Validation maximum absolute error: \(0.00\) (bit-for-bit mathematical equivalence with scalar reference).
🌿 Energy Efficiency & Mobile AI Autonomy
For battery-powered robotics, autonomous drones, and embedded smart cameras: * 7.08 GFlops per Watt: Sustained on general-purpose CPU cores without waking up power-hungry external neural accelerators. * Zero Transfer Latency: Eliminates PCIe memory copy overhead to discrete GPUs, which often consume 15–25 W just to initialize bus transfers. * 82.8% Energy Reduction: Directly maximizes battery life for autonomous robotic edge deployments.