Industrial anomaly detection on the edge
Two detectors that learn only from healthy equipment and flag anything else: one inspects products from photos, the other listens to motor-bearing vibration. Then I shrank both to run on a small CPU, and measured everything on a laptop limited to one thread as a stand-in for an edge device.
PatchCore on three categories of the public VisA dataset: a printed circuit board (pcb1), pharmaceutical capsules and cashews. It never sees a defect while learning. The colour shows which patches look unlike anything in the good parts.



The network is not the bottleneck. After int8 the ResNet18 backbone takes 38 ms; comparing its 1,024 patches with a 10,000-entry memory bank takes another ~115 ms. A greedy-coreset bank of 2,500 patches keeps most of the accuracy at close to half the time.
| Category | fp32, 10k bank | int8, 10k | int8, 2.5k | int8, 1k |
|---|---|---|---|---|
| pcb1 | 0.942 · 161 ms | 0.924 · 155 ms | 0.918 · 89 ms | 0.885 · 77 ms |
| capsules | 0.782 · 161 ms | 0.749 · 155 ms | 0.752 · 89 ms | 0.697 · 78 ms |
| cashew | 0.952 · 161 ms | 0.880 · 154 ms | 0.875 · 88 ms | 0.896 · 77 ms |
Image AUROC · median latency per image, one CPU thread. 100 defective and 100 good test images per category.
int8 is not free. It makes the model 4× smaller but costs 0.02–0.07 image AUROC, most on cashew. Calibration used good parts only, as it would on a real line.
A 4.5k-parameter autoencoder (11 KB as int8) on 0.17-second windows of bearing vibration from the CWRU dataset. Every model scores AUROC 1.0 here, even with heavy added noise, which says more about the dataset than the models, so I tested what actually breaks in production: a motor load the model never saw.
| Vibration monitor | Faults caught | False alarms (healthy) |
|---|---|---|
| Trained on all four motor loads | 100% | 6.3% |
| 3 HP load never seen | 100% | 100% |
| Same, threshold re-set from 2 s of healthy data | 100% | 2.8% |
The ranking stayed perfect, but a fixed alarm threshold flagged every healthy window at the new load. The fix is operational, not a bigger model: recalibrate when a machine changes operating point.
- Profile before you optimise. Quantizing the network made it 4× smaller but barely faster; the nearest-neighbour search was the real cost.
- Read the data sheet. CWRU's healthy recordings are 48 kHz, not 12 kHz like the fault files; their resonances only line up when read correctly. Read naively, every frequency feature is off by 4×.
- A perfect score can still be useless. AUROC 1.0 did not stop 100% false alarms at a new operating point. Thresholds need the same care as models.
- Say where it fails. Small defects barely light up at 256×256, and capsules (0.78 AUROC) is the weakest category. Both are listed in the repository, with what to try next.