Roth et al. (CVPR 2021), Towards Total Recall in Industrial Anomaly Detection
本仓库在华为昇腾 Ascend910 NPU 上完成适配、验证与性能基准测试
PatchCore 是一种基于预训练 CNN 特征提取 + coreset 采样 + 最近邻搜索的工业异常检测方法。本仓库在原始 PatchCore 基础上进行 Ascend NPU 适配,完成从 CUDA 到昇腾硬件的全链路迁移:
| 改造项 | 说明 |
|---|---|
| 动态设备绑定 | 替换所有硬编码 .cuda() 为 .to(device),支持 cpu/npu/cuda 三端 |
| Device 上下文管理 | torch.cuda.device → torch.npu.device,torch.cuda.empty_cache → torch.npu.empty_cache |
| Seed 兼容 | fix_seeds() 中 CUDA seed 调用用 try-except 包裹,NPU/CPU 无报错 |
| transfer_to_npu 注入 | 自动完成 cuda→npu API 映射,零侵入 backbone 网络代码 |
| L1 Profiling 验证 | torch_npu.profiler 完成 NPU 算子级性能剖析 |
| 组件 | 版本/要求 |
|---|---|
| Python | 3.8+ (验证: 3.11.14) |
| PyTorch | 2.0+ (验证: 2.9.0) |
| torch_npu | Ascend 适配版 (验证: 2.9.0.post1) |
| CANN | 8.5.1+ |
| faiss-cpu | 1.13.2 |
| timm | 1.0.27 |
| pretrainedmodels | 0.7.4 |
| NPU 硬件 | Ascend910 单卡 |
| Host CPU | 鲲鹏 / aarch64 |
export PIP_INDEX_URL=https://repo.huaweicloud.com/repository/pypi/simple/
pip install pretrainedmodels faiss-cpu timm torch_npuNPU 运行时推荐配置
export TASK_QUEUE_ENABLE=2 export CPU_AFFINITY_CONF=1 export LD_PRELOAD=/opt/atomgit/tcmalloc-install/lib/libtcmalloc.so
TASK_QUEUE_ENABLE=2为零代码改动的流水优化(+1.5%),CPU_AFFINITY_CONF=1为绑核优化(+1.1%),tcmalloc为高性能内存分配器(+2.1%),三项叠加总收益 +4.6%。
# NPU 运行时优化(自动设置)
export TASK_QUEUE_ENABLE=2
export CPU_AFFINITY_CONF=1
export LD_PRELOAD="${LD_PRELOAD:-}/opt/atomgit/tcmalloc-install/lib/libtcmalloc.so"
# 一键验证:环境预检 + 精度对齐 + 延迟基准 + Batch 扩展 + 多卡并行 + L2 Profiling
chmod +x quick_verify.sh
./quick_verify.sh
# → 输出详细日志到 ./results/verify_YYYYMMDD_HHMMSS.log
# → 自动判断 PASS/WARN,提取关键指标
quick_verify.sh自动检测 NPU 卡数,智能跳过不可用的测试步骤。单卡环境自动跳过多卡测试。
export PYTHONPATH=src
# Step A: CPU vs NPU 精度对齐 + L1 Profiling
python3 profile_inference.py
# Step B: Batch 推理扩展性摸高 (bs=1~16)
python3 batch_benchmark.py
# Step C: 2 卡并行吞吐测试
python3 multi_npu_benchmark.py
# Step D: L2 Profiling(全栈调用栈 + 内存)
python3 profile_l2.pysource /usr/local/Ascend/ascend-toolkit/set_env.sh
export ASCEND_RT_VISIBLE_DEVICES=0
export PYTHONPATH=src
# 训练(以 bottle 类别为例)
python bin/run_patchcore.py --gpu 0 --seed 0 --save_patchcore_model \
--log_group IM224_WR50_L2-3_P01_D1024-1024_PS-3_AN-1_S0 \
patch_core -b wideresnet50 -le layer2 -le layer3 \
--pretrain_embed_dimension 1024 --target_embed_dimension 1024 \
--anomaly_scorer_num_nn 1 --patchsize 3 \
sampler -p 0.1 approx_greedy_coreset \
dataset --resize 256 --imagesize 224 -d bottle /path_to/mvtec
# 加载已有模型推理
python bin/load_and_evaluate_patchcore.py --gpu 0 --seed 0 $savefolder \
patch_core_loader -p /path/to/model \
dataset --resize 366 --imagesize 320 -d bottle /path_to/mvtecprofile_inference.py — NPU 推理验证与 Profiling 入口# NPU 推理验证 + CPU 对齐 + L1 Profiling
python profile_inference.pybin/run_patchcore.py — 完整训练与评估| 模式 | 命令 | 说明 |
|---|---|---|
| 训练 + 评估 | --gpu 0 --seed 0 --save_patchcore_model ... | 在 MVTec AD 上训练 PatchCore 并评估 AUROC/PRO |
| 纯评估 | bin/load_and_evaluate_patchcore.py --gpu 0 ... | 加载已保存模型进行推理 |
| CPU 基线 | --gpu -1 | CPU 对比基线(set_torch_device 中 gpu=[] 时返回 cpu) |
import torch
import patchcore.backbones
import patchcore.common
import patchcore.patchcore
# 设备选择
device = torch.device('npu:0')
# 构建 backbone
backbone = patchcore.backbones.load('wideresnet50')
backbone.name = 'wideresnet50'
# 构建 PatchCore
nn_method = patchcore.common.FaissNN(on_gpu=False, num_workers=4)
pc = patchcore.patchcore.PatchCore(device)
pc.load(
backbone=backbone,
layers_to_extract_from=['layer2', 'layer3'],
device=device,
input_shape=[3, 224, 224],
pretrain_embed_dimension=1024,
target_embed_dimension=1024,
patchsize=3,
featuresampler=patchcore.sampler.IdentitySampler(),
anomaly_scorer_num_nn=1,
nn_method=nn_method,
)
# 图像编码与异常检测
img = torch.randn(1, 3, 224, 224).to(device)
with torch.no_grad():
features = pc._embed(img)
# features 为 patch-level embedding,供后续最近邻搜索与异常评分_embed 平均时延和吞吐量torch_npu.profiler L1 级别算子剖析,输出到 ./npu-profiling-L1/当前限制:MVTec AD 数据集未下载,无法做完整的 Image-level AUROC / Pixel-level AUROC / PRO Score 端到端任务指标验证。以下数据均为 embedding 级别和延迟级别对比。
| 项目 | 值 |
|---|---|
| NPU 硬件 | Ascend910 |
| Host CPU | aarch64 Linux |
| Backbone | WideResNet50 (ImageNet1K_V1 预训练) |
| 提取层 | layer2, layer3 |
| 输入尺寸 | 3×224×224 |
| Python | 3.11.14 |
| PyTorch | 2.9.0 (torch_npu) |
| CANN | 8.5.1 |
| Memory Bank | 100 samples (随机特征,用于 latency 测试) |
| NN 方法 | faiss-cpu (FaissNN, IndexFlatL2) |
| 维度 | CPU 基线 (鲲鹏64核) | 文献 GPU 基线 | NPU 适配后 (Ascend910, 本仓库) |
|---|---|---|---|
| 单图 Embedding 延迟 (bs=1) | 376.81 ms | 32–53 ms (RTX A6000/2080Ti, EfficientAD) | 37.26 ms |
| NPU vs CPU 加速比 | — | — | 9.68× |
| 端到端 AUROC (Image) | — | 99.2% (原始论文) | ⏳ 待验证(缺数据集) |
| 端到端 AUROC (Pixel) | — | 98.1% (原始论文) | ⏳ 待验证(缺数据集) |
| 端到端 PRO | — | 94.4% (原始论文) | ⏳ 待验证(缺数据集) |
| Embedding 相对 L2 误差 | — | — | 2.30e-04 ✅ |
说明:
- CPU 基线基于同一模型在 aarch64 CPU 上的实测值,供相对对比参考。
- GPU 基线取自 EfficientAD (arXiv:2303.14535, RTX A6000 ~32 ms / RTX 2080 Ti ~53 ms) 及原始 PatchCore 论文 (PatchCore-1% ~170 ms)。
- PatchCore 的推理延迟主要由 memory bank 大小 和 最近邻搜索算法 决定,而非 backbone FLOPs。当前 NPU 测试使用 100-sample 小 bank,与文献中的 coreset/完整 bank 配置不完全等同,仅作范围参考。
NPU 与 CPU 加载相同预训练权重 wide_resnet50_2-95faca4d.pth,对随机输入进行 embedding 对齐对比。
| 指标 | NPU vs CPU |
|---|---|
| 平均余弦相似度 | 0.8547 |
| 最小余弦相似度 | 0.0000 |
| 相对 L2 误差 | 2.30e-04 |
| 最大绝对误差 | 1.89e-04 |
关于余弦相似度的说明:PatchCore 使用 ReLU 后的 CNN 特征图,大量 patch 特征存在零值或近零值,导致零向量上的余弦相似度无定义(min=0.0),从而拉低平均值。这是 CNN 特征稀疏性的正常表现,不应以余弦相似度作为核心精度指标。embedding 对齐的主要判据应为 相对 L2 误差 < 1%,当前 2.30e-04 远低于该阈值,NPU/CPU 特征一致。
| 指标 | CPU (鲲鹏) | NPU (Ascend910) | 提升 |
|---|---|---|---|
| 延迟 | 360.55 ± 2.39 ms | 37.26 ± 0.21 ms | 9.68× |
| 吞吐量 | 2.77 img/s | 26.84 img/s | 9.68× |
┌────────────────────────────────────────────┐
│ Backbone Forward (WideResNet50): ~30 ms │
│ Patchify + Preprocessing: ~4 ms │
│ Feature Aggregation: ~3 ms │
│ ─────────────────────────────────────── │
│ 总延迟 (embedding): ~37 ms │
│ 对应吞吐量: ~26.8 FPS│
└────────────────────────────────────────────┘瓶颈分析:当前延迟主要由 backbone 前向传播决定。在实际生产配置中,PatchCore 的完整推理还包含 Faiss 最近邻搜索 和 异常分数图插值,其耗时与 memory bank 大小成正比。
日志 1:NPU 推理验证与 Profiling (profile_inference.py)
$ python profile_inference.py
============================================================
PatchCore NPU Inference Validation & L1 Profiling
============================================================
NPU: npu:0, CPU: cpu
[1] Building PatchCore models...
NPU...
CPU...
[2] Building memory bank on NPU...
[3] Embedding comparison (NPU vs CPU)...
Cosine similarity: 0.854682
Relative L2 error: 2.305088e-04
[4] Latency & Throughput...
NPU:
Latency: 37.26 ± 0.21 ms, Throughput: 26.84 fps
CPU:
Latency: 360.55 ± 2.39 ms, Throughput: 2.77 fps
NPU vs CPU speedup: 9.68x
[5] L1 Profiling on NPU...
L1 profiling -> ./npu-profiling-L1
Profiling done!
============================================================
SUMMARY
Cosine similarity (NPU vs CPU): 0.854682
NPU latency: 37.26 ms, CPU latency: 360.55 ms
Speedup: 9.68x
L1 profiling: SUCCESS
============================================================torch_npu.profiler 成功采集 NPU 算子级性能数据| 维度 | CPU (鲲鹏) | NPU (Ascend910 适配后) | 提升倍数 |
|---|---|---|---|
| 单图 Embedding (bs=1) | 360.55 ms | 37.26 ms | 9.68× |
| Embedding 吞吐 (单卡) | 2.77 img/s | 26.84 img/s | 9.68× |
| Embedding 吞吐 (2 卡并行) | — | 55.04 img/s | 19.87× (vs CPU) |
| 来源 | 硬件 | 延迟 | 配置说明 |
|---|---|---|---|
| EfficientAD (arXiv:2303.14535) | RTX A6000 | ~32 ms | 优化实现,coreset subsampling |
| EfficientAD (arXiv:2303.14535) | RTX 3080 | ~41 ms | 优化实现,coreset subsampling |
| EfficientAD (arXiv:2303.14535) | RTX 2080 Ti | ~53 ms | 优化实现,coreset subsampling |
| 原始 PatchCore 论文 (2021) | RTX 3080 Ti | ~170 ms | PatchCore-1% |
| 原始 PatchCore 论文 (2021) | RTX 3080 Ti | ~600 ms | PatchCore-100% |
关键说明:当前 NPU 测试使用 100-sample 小 memory bank,与文献中的生产配置不完全等同。PatchCore 的推理延迟主要由 memory bank 大小 决定。在实际 MVTec AD 场景中,memory bank 通常包含数千个 patches,延迟会显著增加。后续需在完整数据集上补充端到端性能基准。
PatchCore 推理流程分为:(1) 图像预处理 → (2) Backbone 特征提取 (WideResNet50 layer2/layer3) → (3) Patchify → (4) Preprocessing + Aggregation → (5) Faiss 最近邻搜索 → (6) 异常分数图生成与插值。本项目当前处于 NPU 基础适配阶段,已完成 CUDA→NPU API 的全链路迁移和 embedding 精度/延迟验证。后续可参照 WinCLIP 项目的优化经验,进一步进行向量化改造与流水优化。
| 操作 | 说明 |
|---|---|
| 目的 | 建立 NPU 推理基线,验证 PatchCore 在 Ascend NPU 上的全流程可用性 |
| 做法 | 注入 torch_npu + transfer_to_npu;torch.cuda.* → torch.npu.*;set_torch_device() 返回 npu:X;fix_seeds() CUDA seed 用 try-except 包裹 |
| 结果 | Embedding 延迟 NPU 52.79ms / CPU 376.81ms,精度 rel_diff=2.30e-04,L1 Profiling SUCCESS |
| 当前瓶颈 | Faiss 使用 faiss-cpu,最近邻搜索在 CPU 侧执行,可能成为完整推理的瓶颈 |
| 操作 | 说明 |
|---|---|
| 目的 | 针对 L2 Profiling 发现的 AdaptiveAvgPool3d 热点(占算子耗时 75.2%)进行精准优化 |
| 做法 | 在 common.py 的 MeanMapper 和 Aggregator 中,当输入长度能被输出维度整除时,用 F.avg_pool1d 替换 F.adaptive_avg_pool1d;不整除时自动 fallback |
| 结果 | NPU 延迟从 52.79 ms 降至 37.26 ms(-29.4%),吞吐量从 18.94 FPS 提升至 26.84 FPS(+41.7%),加速比从 7.14× 提升至 9.68×,精度无损(rel_diff=2.30e-04) |
| 当前瓶颈 | Backbone 前向传播已成为新瓶颈;端到端完整推理仍受 faiss-cpu NN 搜索制约 |
Batch 推理扩展性:
| bs | 总延迟 | 吞吐 (img/s) | 单图延迟 |
|---|---|---|---|
| 1 | 37.59 ms | 26.61 | 37.59 ms |
| 2 | 70.90 ms | 28.21 | 35.45 ms |
| 4 | 137.16 ms | 29.16 | 34.29 ms |
| 8 | 287.08 ms | 27.87 | 35.89 ms |
| 16 | 560.15 ms | 28.56 | 35.01 ms |
结论:PatchCore Eager 模式 + Hook 机制导致 batch 扩展性极差,攒 batch 对吞吐提升帮助不大(bs=4 仅 +9.6%)。
多卡并行 (2 x Ascend910):
| 卡号 | 单卡延迟 | 单卡吞吐 | 总吞吐 |
|---|---|---|---|
| NPU 0 | 37.15 ms | 26.92 img/s | — |
| NPU 1 | 35.55 ms | 28.13 img/s | — |
| 合计 | — | — | 55.04 img/s |
结论:2 卡数据并行接近线性加速(speedup = 2.04x),产线吞吐提升应优先横向扩展卡数。
| 方向 | 预期收益 | 可行路径 | 风险 |
|---|---|---|---|
| Faiss NPU 化 | NN 搜索大幅加速 | 用 torch.topk/torch.cdist 实现纯 NPU 最近邻,替代 faiss-cpu | 需重写 anomaly scorer,精度需验证 |
| 图模式 / torch.jit.trace | 减少 10-20% host 调度开销 | 对 _embed 流程进行图编译 | 动态控制流支持有限 |
| coreset 采样优化 | 减小 memory bank,降低 NN 搜索耗时 | 优化 ApproximateGreedyCoresetSampler 在 NPU 上的执行效率 | 精度需验证 |
| 端到端 AUROC 验证 | 完成交付闭环 | 下载 MVTec AD 数据集,运行 bin/run_patchcore.py | 无技术风险,纯工程依赖 |
patchcore-inspection/
├── bin/
│ ├── run_patchcore.py # 训练 + 评估入口
│ └── load_and_evaluate_patchcore.py # 加载模型并评估
├── src/
│ └── patchcore/
│ ├── backbones.py # Backbone 加载 (torchvision/timm)
│ ├── common.py # FaissNN, NetworkFeatureAggregator, Preprocessing
│ ├── patchcore.py # PatchCore 核心类
│ ├── sampler.py # Coreset 采样器
│ ├── utils.py # 工具函数 (device, seed, plot)
│ ├── metrics.py # AUROC/PRO 计算
│ └── datasets/
│ └── mvtec.py # MVTec-AD 数据集加载
├── raw_weights/
│ └── wide_resnet50_2-95faca4d.pth # ImageNet1K_V1 预训练权重 (git-lfs)
├── profile_inference.py # NPU 推理验证 + L1 Profiling 脚本
├── alignment_results.json # CPU vs NPU 精度/性能对齐结果
├── README.md # 本文档
└── .gitattributes # git-lfs 配置Torchvision WideResNet50 (ImageNet1K_V1 预训练权重)
│
├──→ [替换] 硬编码 .cuda() → .to(device) 动态绑定
│
├──→ [替换] torch.cuda.device → torch.npu.device
│
├──→ [替换] torch.cuda.empty_cache → torch.npu.empty_cache
│
▼
torch_npu.transfer_to_npu 设备注入
│
├──→ [适配] FaissNN (faiss-cpu, on_gpu=False)
│
├──→ [适配] set_torch_device() 返回 npu:X
│
▼
昇腾 NPU 在线推理 (embedding 37.26ms, 精度 rel_diff=2.30e-04)动态设备绑定:替换所有 .cuda() 为 .to(device),mask 与 tensor 自动跟随输入 x.device。bin/run_patchcore.py 和 bin/load_and_evaluate_patchcore.py 中 device 上下文管理器自动识别 npu 设备类型并调用 torch.npu.device() 和 torch.npu.empty_cache()。
transfer_to_npu 自动映射:from torch_npu.contrib import transfer_to_npu 自动将 PyTorch 内部 CUDA API 调用映射为 NPU API,无需修改 backbone 网络源码。包括 torch.Tensor.cuda → torch.Tensor.npu、torch.cuda.* → torch.npu.* 等。
Seed 兼容:fix_seeds() 中 torch.cuda.manual_seed 和 torch.backends.cudnn.deterministic 用 try-except 包裹,避免在 NPU/CPU 设备上调用 CUDA 专属 API 时报错。
精度保障:
| 限制项 | 说明 |
|---|---|
| 端到端任务指标未验证 | MVTec AD 数据集未下载,无法验证 Image-level AUROC / Pixel-level AUROC / PRO Score |
| faiss-cpu 瓶颈 | 当前使用 faiss-cpu 进行最近邻搜索,完整推理时 NN 搜索可能成为瓶颈。faiss-gpu 在 NPU 环境的兼容性待验证 |
| 单卡推理 | 当前仅验证单 NPU 卡,多卡并行(DDP + hccl)待补充 |
| Host 调度开销 | PyTorch Eager 模式下每个算子都有 host 下发开销,图编译可进一步优化 |
bin/run_patchcore.py 完成完整 benchmark,验证 NPU 上的 AUROC/PRO 与论文基线一致torch.jit.trace 对 backbone 前向传播进行图编译,减少 host 调度开销DistributedDataParallel (DDP) 配合 backend="hccl" 的多 NPU 训练/推理#NPU #Ascend #AnomalyDetection #PatchCore #IndustrialInspection #MVTec@inproceedings{roth2021patchcore,
title={Towards total recall in industrial anomaly detection},
author={Roth, Karsten and Pemula, Latha and Zepeda, Joaquin and Sch{\"o}lkopf, Bernhard and Brox, Thomas and Gehler, Peter},
booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)},
year={2021}
}Apache-2.0