pwl_split
birdnet_stm32.conversion.pwl_split
¶
Two-exposure PWL: a float-exact rewrite of the raw frontend's compressor for INT8 (2.2).
The raw frontend's band magnitude x = relu(bn(mix(smooth(|filterbank|)))) and
its PWL compressor y = k0 x + sum_i k_i relu(s_i x + b_i) are per-tensor INT8
tensors whose ranges are set by loud focal calls, while soundscape bins sit a
few INT8 steps above zero (2.2's N6-B0 on WABAD windows: band magnitude median
0.54 of a 0..16 range, PWL output median level 3 of 255). Their roundings
compound along the hinge chain. With every s_i > 0, per band b and a split
point xT_b where PWL_b reaches yt,
PWL(x) = PWL(min(x, xT)) + [PWL(x) - PWL(min(x, xT))]
exactly, and both terms run on the NPU with grids of their own:
- low: x_s = relu6(g * bn(mix(.))), g = 6 / xT, is g * min(x, xT) on a [0, 6] grid; y_low = PWL(x_s / g), the PWL with k0 / g and s_i / g. Every tensor of this path spans the quiet range only.
- high: u = relu(bn(mix(.)) - xT); y_high = k0 u + sum_i k_i relu(s_i u + c_i), c_i = b_i + s_i xT where the hinge t_i = -b_i / s_i >= xT, else 0 (that hinge is linear above xT). y_high is exactly 0 on quiet bins.
y = y_low + y_high feeds the stem as before. The mix convolution and the PWL are duplicated; the filterbank and the smoother are shared. On 2.2's N6-B0 over 61 WABAD sites, INT8 window AUPRC 0.4366 -> 0.4409 (float 0.4653) at 34.6 ms on the board (33.7 without), every op still on the NPU.
The rewrite only feeds the INT8 conversion: the float model, its checkpoint and the ONNX export are unchanged, and the converted model is validated against the float model as always.
can_split(model)
¶
Whether the model has the frontend this rewrite targets: raw, fused bank, compress mix, 3-hinge PWL, s_i > 0.
Source code in birdnet_stm32/conversion/pwl_split.py
split_points(k0, shifts, ks, yt)
¶
Per band, the first band magnitude where the PWL output reaches yt (_X_MAX if it never does).
Source code in birdnet_stm32/conversion/pwl_split.py
split_pwl(model, yt=SPLIT_YT)
¶
The model with its frontend's band magnitude and PWL split in two exposures (float-identical).
Layers outside the frontend are cloned with their weights, so the source
model's graph is left as it was (a reused layer would gain a second inbound
node, and split.split_model then sees a branched head). The frontend's
filterbank and smoother, internal to it, are shared.
Source code in birdnet_stm32/conversion/pwl_split.py
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | |