PoC 13G — Wave Cortex-A55 versus Wave NPU
Date: 2026-07-29
Final result: PASS in the separately sealed R2 experiment
Performance classification: cpu-faster
Question
Section titled “Question”The earlier PoC 13F soak measured graph->Run() on the Wave NPU, but it did
not measure the same operation on the Wave CPU. Host-Mac timings are not a
valid CPU/NPU comparison. PoC 13G therefore asks one narrow question:
For the exact quantized Erf transform used by the learned mask partition, how fast is one
UINT8[481]frame on the Wave Cortex-A55 compared with the same frame on the Wave NPU?
This is not a peak-throughput benchmark and not a claim about arbitrary models. It measures one tiny operation whose CPU implementation is a 256-byte lookup table.
Fixed workload
Section titled “Fixed workload”Both paths consumed the same deterministic xorshift32 frame before the next frame was generated:
input: UINT8[481]seed: 0x5733130fCPU operation: embedded 256-byte quantized-Erf LUTNPU operation: sealed Erf NBGcold frames: 1warm-up frames: 100measured frames: 10,000total per path: 10,101measured ordering: even CPU->NPU, odd NPU->CPUThe CPU table SHA-256 is
c9525963e022d4a4aea0814845119b37488f3727ec8d96684f52e1e2ad776296.
The NBG SHA-256 is
96447d6afd63a7c349bbd80f0af99150c89ef24ed7f1ef66ab285075362e6c94.
One graph was created and compiled before the cold run. The NPU total
interval surrounds input copy, graph->Run(), and output copy. CPU wall time
covers exactly 481 indexed output writes plus the two thread-clock calls
inside the wall-clock bracket.
Timing and validation
Section titled “Timing and validation”Wall durations use CLOCK_MONOTONIC_RAW. Thread and complete-process CPU
durations use CLOCK_THREAD_CPUTIME_ID and
CLOCK_PROCESS_CPUTIME_ID. Before graph execution the runner sampled
100,000 back-to-back wall-clock pairs:
mean pair: 85.15458 nsminimum pair: 41 nsThe 10,000 records are serialized little-endian into one fixed 640,036-byte file. The host validates every sequence number, alternating-order tag, mandatory positive duration, byte count, remote/local SHA-256, native/host FNV-1a64, and nearest-rank percentile.
Each output buffer has 32-byte leading and trailing canaries. Every CPU byte was compared with the NPU byte before advancing.
R1 clean negative
Section titled “R1 clean negative”The first sealed implementation at commit 7b08f61 was run exactly once
under /tmp/wave-npu-poc13g. It produced a valid cold result and then
failed closed at paired frame 4,833 with exit 7.
failure frame: 4,833normal debug markers: 4,833 after the cold runtiming file: absentpartial timing file: absenthealth differences: nonekernel alerts: noneholder leak: noneretry: noneFrame 4,833 was NPU-first and the runtime emitted its normal run marker without an error. The supported diagnosis was a resolution-limited zero tick around the sub-microsecond CPU thread-time interval. R1 combined its clock/copy/run checks, so this remains an inference rather than a directly identified failing call.
R1 also used VSI_NN_LOG_LEVEL=5, which printed one debug line per
inference and therefore was unsuitable for a timing result. Its evidence is
retained at
artifacts/npu/runs/poc13g-erf-wave-cpu-vs-npu-20260729; its remote root
was not deleted or replayed.
R2 correction
Section titled “R2 correction”R2 is a separately built binary, manifest, run directory, and remote root:
remote root: /tmp/wave-npu-poc13g-r2runtime log level: 2 (warnings/errors)binary SHA-256: cc9154961833124c138553e15e572442033bd3c40b26ca302cd32aed40715799source SHA-256: 07feee816c2c5958b38f4965a076d061ae7bca9fb8d381f5d1010fe109c8be91git commit: ef5af98582c9db8d851d397d1e6b1af851c257f5R2 records equal CPU thread timestamps as resolution-limited zero samples. CPU wall time and every NPU duration must remain positive. The native and host zero counts must agree. R2 also identifies every clock, copy, run, and duration stage separately on failure.
The full host gate before R2 was:
ONNX-hybrid: 187 passedNPU suite: 346 passed, 302 subtests passedRuff: check and format PASSbuild: two byte-identical offline AArch64 binariespolicy: source and ELF PASSNumerical result
Section titled “Numerical result”R2 passed on its only execution:
CPU frames: 10,101NPU graph runs: 10,101bytes compared/path: 4,858,581mismatched bytes: 0canaries: PASSinput FNV-1a64: 0x367c7eaad959d707CPU output FNV-1a64: 0x742e3a62d58acd0dNPU output FNV-1a64: 0x742e3a62d58acd0dfinal PRNG state: 0x6ae16fe0health differences: nonekernel alerts: noneThe raw timing file SHA-256 is
7998b0df5000c0140b5c3229b244dbed8d158bbfc42a025fb741ff478dc0eb32.
Its FNV-1a64, independently reproduced by the host, is
0x3966c5381aecace1.
Performance result
Section titled “Performance result”All values below are uncorrected nanoseconds over 10,000 steady-state frames. Percentiles use nearest rank.
| Boundary | Mean | p50 | p95 | p99 | p99.9 | Max |
|---|---|---|---|---|---|---|
| CPU wall | 3,303 | 3,333 | 3,458 | 3,500 | 3,958 | 18,417 |
| CPU thread | 2,372 | 2,416 | 2,500 | 2,542 | 2,667 | 5,500 |
| NPU copy in | 7,653 | 7,583 | 7,875 | 8,584 | 23,083 | 31,375 |
NPU Run() |
68,330 | 65,750 | 74,458 | 86,083 | 293,542 | 344,667 |
| NPU copy out | 7,188 | 7,084 | 7,625 | 9,084 | 23,375 | 27,416 |
| NPU total | 86,338 | 83,625 | 93,000 | 106,959 | 314,292 | 366,083 |
| NPU thread | 76,281 | 74,083 | 83,208 | 87,125 | 300,792 | 313,541 |
One of 10,000 CPU thread measurements was zero at the available clock resolution. Its CPU wall duration was valid, and the native/host zero count matched.
Ratio of means:
NPU Run / CPU wall: 20.6858xNPU total / CPU wall: 26.1375xHere, a ratio above one means the CPU is faster. The paired-frame ratio distribution confirms that the conclusion is not an artifact of comparing separate percentile positions:
| Paired ratio | Mean | p50 | p95 | p99 |
|---|---|---|---|---|
NPU Run() / CPU wall |
20.7713× | 20.4255× | 23.3003× | 26.4313× |
| NPU total / CPU wall | 26.2450× | 25.9507× | 29.1020× | 32.6010× |
Alternation also preserved the conclusion:
CPU-first NPU-total/CPU ratio: 27.1138xNPU-first NPU-total/CPU ratio: 25.2261xCold time was 5.042 us on CPU, 1.505 ms for NPU Run(), and 1.582 ms for
the NPU total. Graph construction and compilation are excluded.
Mean isolated throughput and 10 ms hop consumption:
| Path | Frames/s | Fraction of 10 ms hop |
|---|---|---|
| CPU wall | 302,734 | 0.0330% |
NPU Run() |
14,635 | 0.6833% |
| NPU total | 11,582 | 0.8634% |
The complete paired loop consumed 923.272 ms of process CPU time. The host command duration was 1.130 seconds.
System context
Section titled “System context”All four Cortex-A55 CPUs (CPU part 0xd05) were online. The sole cpufreq
policy covered CPUs 0–3 and reported:
governor: schedutilobserved current: 1,500,000 kHz before and afterminimum: 100,000 kHzmaximum: 2,016,000 kHzSoC temperature: 38.5 C before, 38.8 C afterNo affinity, priority, governor, frequency, NPU clock, or thermal setting was changed.
Interpretation for noise cancellation
Section titled “Interpretation for noise cancellation”The Wave Cortex-A55 is decisively faster for this tiny lookup. Offloading Erf alone is therefore an acceleration loss even though both paths fit comfortably inside a 10 ms audio hop.
This does not imply that the Wave NPU is generally slower. The fixed graph submission and tensor copies dominate a 481-byte operation. A useful affine or convolutional partition has more arithmetic per submission and may amortize that overhead. PoC 14 must measure each larger sealed partition rather than extrapolate from this result.
For the current learned mask, NPU execution remains a functional proof and a valid hybrid boundary, but the CPU LUT is the correct performance choice if Erf is the only offloaded operation.
Limits
Section titled “Limits”- Natural scheduler behavior was measured; the benchmark did not isolate a core or pin frequency.
- CPU wall time includes timing calls, making the raw LUT itself faster than the reported boundary.
- The NPU numbers include TIM-VX/OpenVX submission behavior for this exact NBG and runtime only.
- Cold time is first execution after compile, not process startup or graph construction.
- These values cannot be used for Conv, MatMul, recurrent, or arbitrary ONNX performance claims.
The machine-readable result is
erf-wave-cpu-vs-npu.json.