Skip to content

Model

DS-CNN architecture

The backbone is a depthwise-separable convolutional neural network (DS-CNN) with 4 stages, inspired by MobileNetV1. All variants are built by the single build_dscnn_model() function in birdnet_stm32/models/dscnn.py.

Block structure

Each depthwise-separable block (default):

flowchart TD
    X["Input"] --> DW["Depthwise Conv2D 3×3"]
    DW --> BN1["BatchNorm"]
    BN1 --> R1["ReLU6"]
    R1 --> PW["Pointwise Conv2D 1×1"]
    PW --> BN2["BatchNorm"]
    BN2 --> R2["ReLU6"]
    X -. "residual\n(stride=1, same channels)" .-> ADD["Add"]
    R2 --> ADD
    ADD --> Y["Output"]

When stride=1 and input/output channels match, a residual skip connection is added.

Removed: inverted residual and squeeze-and-excite blocks

Earlier versions could build a MobileNetV2-style backbone with inverted residual blocks and squeeze-and-excite attention. Both were removed because that backbone could not reach the INT8 parity gates: per-channel PTQ landed far below the 0.95 minimum, and quantization-aware training did not recover the lower tail. The dominant residual errors sit at the signed linear projection and the Add boundaries inside an inverted-residual block, where the requantization step has to reconcile two differently scaled tensors.

Plain depthwise separable blocks have no such boundary and quantize cleanly, so they are the only path the builder offers. Removing the options also removed a trap: they were enabled by default, which made the quantization-hostile architecture the one you got unless you opted out.

Stage configuration

Stage Base output channels Stride Base repeats
Stem 16 × alpha (1, 2) 1
1 32 × alpha (2, 2) 2 × depth_multiplier
2 64 × alpha (2, 2) 3 × depth_multiplier
3 128 × alpha (2, 2) 4 × depth_multiplier
4 256 × alpha (2, 2) 2 × depth_multiplier

All channel counts are rounded to the nearest multiple of 8 via _make_divisible(channels, 8) (defined in birdnet_stm32/models/blocks.py).

The base widths (32, 64, 128, 256) can be replaced with stage_widths. When the last stage's width equals embeddings_size, there is no separate 1 × 1 embedding convolution.

Depthwise kernels are 3 × 3. dw_kernel_size=5 widens them in stages 2–4; stage 1 carries the largest feature map and stays 3 × 3.

After the final stage:

  1. Pooling (head_pooling):
    • gap (default): global average pooling over frequency and time.
    • freq_mean_time_maxmean: mean over frequency, then the sum of max and mean over time, flattened. No trainable weights. The frequency mean is a frozen depthwise convolution with a constant 1/F kernel, because the N6 runs an average that collapses frequency but keeps time (AveragePool or MEAN) on the Cortex-M55; the convolution stays on the NPU.
  2. Dropout (0.5)
  3. Dense with sigmoid activation → [B, num_classes]

Building blocks

All reusable building blocks live in birdnet_stm32/models/blocks.py:

  • _make_divisible(v, divisor) — round channel counts to multiples of divisor

Scaling knobs

alpha (width multiplier)

Scales channel counts across all stages. Default 1.0.

alpha Stage 1 Stage 2 Stage 3 Stage 4 Relative params
0.25 8 16 32 64 ~6%
0.5 16 32 64 128 ~25%
1.0 32 64 128 256 100%
1.5 48 96 192 384 ~225%

depth_multiplier (block repeats)

Repeats each DS block within a stage. Default 1. Only the first block in each stage uses stride 2; subsequent blocks use stride 1 with residual connections.

Model profiler

Use birdnet_stm32/models/profiler.py to analyze a model before deployment:

  • profile_model(model) — per-layer MACs, params, and activation memory
  • check_n6_compatibility(model) — flags layers using ops outside the N6 NPU supported set

N6 NPU constraints

N6 compatibility is the absolute priority

Every model architecture change must be validated against the STM32N6 NPU operator set. Always run stedgeai analyze before committing model changes.

Key constraints:

  • Channel alignment: all channel counts must be multiples of 8 for NPU vectorization.
  • Supported ops: Conv2D, DepthwiseConv2D, BatchNormalization, ReLU6, GlobalAveragePooling2D, Dense, Add, Multiply (SE), Reshape, Sigmoid. Verify exotic ops with stedgeai analyze.
  • Activation memory: intermediate activations must fit in NPU SRAM. Large spatial dimensions or channel counts may exceed limits.
  • QAT is safe: the activation-aware QAT graph (--qat) checkpoints a synchronized clean deployment model and uses final conversion's exact calibration manifest, so the resulting .keras file has no FakeQuant ops and remains compatible with standard PTQ conversion.