Skip to main content
Visitor
October 2, 2026
Question

ST Edge AI 4.0.1: generated STM32N6 network does not match its reference on the NPU (validate --mode target: cos 0.091, snr -5.87 dB)

Tool: ST Edge AI Core v4.0.1-20581 (7ed50de05), target stm32n6
Neural-ART compiler: 1.1.3-275
Runtime: ST Neural ART (LL_ATON api) v1.1.3, atonn-v1.1.3-275-g6770925d
Board: STM32N6570-DK, device 0x486, Cortex-M55
Host: Windows 11, STM32CubeProgrammer 2.23.0, gnu-tools-for-stm32 14.3.1


WHAT HAPPENS

A 23x60 -> 30x5 INT8 TCN, generated for stm32n6 with the Neural-ART compiler
and run on the NPU, returns an output that is not the INT8 graph's.

This is not an application bug. "stedgeai validate --mode target", run against
your own NPU_Validation project with your own random stimulus, reports the
board's output as essentially uncorrelated with the reference model the
network was generated from:

  acc=n.a. rmse=2.543027878 mae=1.976331592 l2r=1.100639820
  mean=-0.346161 std=2.520198 snr=-5.872663 nse=-2.889206 cos=0.091053

cos 0.091 is no correlation. snr -5.87 dB is noise above signal. nse -2.889 is
worse than answering with the mean of the reference. l2r 1.10 is an error
larger than the quantity it is an error in.

Two runs, with and without --full, gave these figures to nine decimals, so the
wrong output is deterministic rather than noisy.

Reference and board also differ in spread, not only in value:

                            min      max     mean      std
  reference (ONNX, host)  -3.922   +3.444   -0.105    1.289
  c-model (board, NPU)    -5.979   +6.218   +0.241    2.298


HOW IT WAS GENERATED

  stedgeai generate --model model_qdq_int8.onnx --target stm32n6 \
    --st-neural-art default@user_neuralart_STM32N6570-DK.json \
    --input-data-type int8 --output-data-type float32 \
    --inputs-ch-position chfirst

with stm32n6570_tcn.mpool as the memory pool (weights in xSPI2 at 0x74000000,
activations in AXISRAM3-6). The compiler ran with

  -O3 --cache-maintenance --Oauto-sched --native-float
  --enable-virtual-mem-pools --Omax-ca-pipe 4 --Ocache-opt --Os
  --enable-epoch-controller

and split the graph into 51 epochs: 2 pure software, 16 hybrid, 33 pure
hardware in 15 epoch-controller blobs. The hybrid epochs are SpaceToDepth /
DepthToSpace pairs around the dilated convolutions.


HOW TO REPRODUCE

1. stedgeai generate as above, with the attached model_qdq_int8.onnx and
   stm32n6570_tcn.mpool.
2. Load Projects/STM32N6570-DK/Applications/NPU_Validation (build conf N6-DK)
   with the generated network.c, using scripts/N6_scripts/n6_loader.py. The
   board must be in development boot (BOOT1 at 1-3).
3. stedgeai validate --model model_qdq_int8.onnx --target stm32n6
   --mode target --desc serial:<COM>:921600
   --val-json <out>/network_c_info.json

The attached st_target_validation_report.txt is what step 3 produced here.


WHAT WE RULED OUT BEFORE POSTING

Our application dumps one inference stage by stage over UDP and compares each
stage with the same graph under onnxruntime. Over four dumps on a running rig:

  the board's constants against the model's ... identical, bit for bit
  window -> int8 input tensor ............... 0 of 1380 elements differ
  int8 tensor -> network output ............. FAILS: 120-138 output levels
  network output -> dequantised values ...... max 2.06e-07

From the same dumps:

- The board's output lies on the same quantisation grid as the reference, step
  0.047830, to within 7e-4 of an integer multiple, so the output scale is
  right. But the board occupies grid levels -125..+120 where the correct
  answer occupies -18..+37.
- The error is 81.5% a fixed pattern (|mean error| RMS 2.23 against |error|
  RMS 2.47), smooth along the step axis, up to 111 output levels. It is not
  the head bias (correlation 0.117) and not the network's response to a
  constant input.
- Errors between dumps correlate +0.52..+0.90, highest for the dumps whose
  inputs are closest: the wrong function is deterministic.

Also checked on the board: the weight blob is CRC-32 verified against the
generated package at every start; its address is the one the mpool assigns;
the activation arena (AXISRAM3-6) is MPU non-cacheable, so it is never held in
the M55 D-cache; and the runtime reports the clocks the board is configured
for (mcu_freq=800MHz, noc_freq=400MHz, npu_freq=1000MHz, nic_freq=900MHz,
npu_cache=1).


QUESTIONS

1. Does this generation reproduce at ST with the same tool version, and does
   validate --mode target fail the same way?
2. Does any of --enable-epoch-controller, --Ocache-opt, --cache-maintenance or
   --Omax-ca-pipe 4 change the result? The hybrid SpaceToDepth/DepthToSpace
   epochs around the dilated convolutions are the part of this graph we cannot
   check from outside.
3. Is there a per-layer target validation that would name the first diverging
   epoch? The runtime reports PER_LAYER and PER_LAYER_WITH_DATA among its
   capabilities, and --full gave per-node timings but no per-node comparison.


ATTACHED

  model_qdq_int8.onnx .............. the input model (QDQ INT8, onnxruntime)
  stm32n6570_tcn.mpool ............. the memory pool used for generation
  network_generate_report.txt ...... generation report, incl. the epoch split
  network_c_info.json .............. for validate --val-json
  st_target_validation_report.txt .. the failing run of your own target validation
  modeldump_1.bin .................. one inference captured on the board

(Paths in the reports were replaced with C:\USER\... ; nothing else was
changed.)