- 计算机视觉
- 人工智能
- 深度学习
- 图像处理
【免费下载链接】kornia
🐍 Geometric Computer Vision Library for Spatial AI
本文基于 benchmarks/feature/graf_benchmark.md 记录的一次正式 A/B 基准评测,系统讲解 Kornia(0.9.0rc1)三条本地特征提取管线——SIFT、SIFT–AffNet–HardNet、KeyNet–HardNet——在优化提交(e8e4ec0f等)相对基线提交(601b5a4a等)下的端到端性能差异:包括 RTX 4090 / Intel i7-14700K / Apple M1 三种硬件的速度与质量数据、torch.compile 选择性编译的收益边界,以及方向直方图累积(scatter_add_)、亚像素精化批处理、channels-last 激活等优化的源码级实现。读完本文,你将掌握如何解读这类"加速但保质量"的基准报告、如何用仓库内的基准脚本在 CPU/CUDA/MPS 上复现,以及哪些优化路径在哪种硬件上真正生效。
基准概览:工作量、被比较提交与评测口径
该基准使用Oxford graf序列(6 张 640×800 原始分辨率图像及其 5 个H1toKp单应矩阵真值),输入为灰度 float32、batch 1、请求4096个特征。第一、二小节(GPU/CPU)采用 eager 模式,不开启 autocast 也不做 torch.compile;两版实现的金字塔层数、尺度、迭代预算、描述子、权重、匹配阈值与 RANSAC 设置完全一致,因此速度差异只来自实现层面而非配置漂移。
| 小节 | 基线提交 | 优化提交 |
|---|---|---|
| GPU / CPU eager | 601b5a4a | e8e4ec0f |
| 选择性编译(CUDA) | 601b5a4a | fb66de1d(同一优化库实现 + 编译模式) |
| MPS | 1eea5835 | f61ea9e4 |
计时口径由基准脚本 benchmarks/feature/local_features.py 定义:包含两张图像的特征提取与 SNN 匹配,排除图像 I/O 与 RANSAC;每格取五对图像(1–2 … 1–6)各自 median 的均值,原始中位数与 IQR 保留在 JSON 中。质量指标为平均 L1 角点重投影误差(像素),即用 RANSAC 估计的单应与真值单应分别把图像四个角点投影后的平均距离,实现见 benchmarks/feature/scale_space_detector.py。
GPU 端到端速度与质量(RTX 4090)
环境:RTX 4090、PyTorch 2.14.0+cu130、Python 3.11.14、Kornia 0.9.0rc1、WSL2 Linux。单位 ms/pair:
| 管线 | 基线 ms/pair | 优化 ms/pair | 加速比 | 平均角点误差 基线 → 优化 |
|---|---|---|---|---|
| SIFT | 215.9 | 114.5 | 1.89× | 223.640 → 223.640 |
| SIFT–AffNet–HardNet | 269.4 | 174.1 | 1.55× | 2.183 → 2.183 |
| KeyNet–HardNet | 131.5 | 126.9 | 1.04× | 5.638 → 5.638 |
几个需要谨慎解读的结论:
- KeyNet–HardNet 的约 4% 提升小于观测到的计时波动,不足以构成端到端 GPU 加速的证据;SIFT 与 SIFT–AffNet–HardNet 的提升则远大于波动。
- 两版 GPU 运行在每一对图像、每一条管线上的匹配数、RANSAC 内点数与角点误差完全相同——加速没有以牺牲匹配质量为代价。
逐对 GPU 角点误差(px):
| 图像对 | SIFT 误差 | SIFT–AffNet–HardNet 误差 | KeyNet–HardNet 误差 |
|---|---|---|---|
| 1–2 | 1.381 | 1.325 | 2.029 |
| 1–3 | 1.506 | 1.216 | 1.428 |
| 1–4 | 2.777 | 0.860 | 2.531 |
| 1–5 | 507.132 | 2.624 | 3.700 |
| 1–6 | 605.405 | 4.891 | 18.500 |
表中揭示一个重要事实:纯 SIFT 在两个最大视角变化(1–5、1–6)上本已严重失败(数百像素误差),优化前后失败模式完全一致——大均值误差不是"只平均成功对"造成的假象,优化也没有掩盖失败。
GPU 峰值额外显存(MiB,指一次提取+匹配调用中,超出已驻留图像/模型/输出的最大新增张量分配,不含 RANSAC 与分配器预留):
| 管线 | 基线峰值额外 CUDA MiB | 优化峰值额外 CUDA MiB |
|---|---|---|
| SIFT | 576.0 | 665.8 |
| SIFT–AffNet–HardNet | 1058.3 | 1058.6 |
| KeyNet–HardNet | 1057.7 | 1057.7 |
SIFT 的提速大约以90 MiB 额外峰值显存为代价,换来更少的精化(refinement)启动次数。
从源码看计时实现:脚本在 benchmarks/feature/local_features.py 中对 CUDA 使用torch.cuda.reset_peak_memory_stats记录峰值分配,并通过 benchmarks/common.py 的time_us(内部封装torch.utils.benchmark.Timer.blocked_autorange)得到带预热、多次重复的 median/IQR 墙钟时间。
CPU 速度与质量(Intel Core i7-14700K)
环境为单 CPU 线程。代表性 1–2 对被反复计时,其余四对仍全部评估质量(JSON 中这些对的 timing 字段为null,表示有意不计时)。每格为 median ± IQR,单位为秒/pair:
| 管线 | 基线 s/pair | 优化 s/pair | 加速比 |
|---|---|---|---|
| SIFT | 3.861 ± 0.022 | 2.856 ± 0.051 | 1.35× |
| SIFT–AffNet–HardNet | 16.343 ± 0.145 | 12.714 ± 0.080 | 1.29× |
| KeyNet–HardNet | 16.368 ± 0.031 | 12.887 ± 0.113 | 1.27× |
CPU 上的匹配数、内点数与重投影误差同样在每对图像上完全一致。注意CPU 与 GPU 的结果不可直接互比:两者的自适应精化后端与数值内核不同,正确的做法是各自与同设备的基线对比。
逐对 CPU 角点误差(px):
| 图像对 | SIFT 误差 | SIFT–AffNet–HardNet 误差 | KeyNet–HardNet 误差 |
|---|---|---|---|
| 1–2 | 1.247 | 1.343 | 1.263 |
| 1–3 | 1.435 | 1.227 | 1.679 |
| 1–4 | 2.611 | 1.548 | 1.833 |
| 1–5 | 507.037 | 1.792 | 3.658 |
| 1–6 | 605.427 | 11.658 | 8.828 |
Apple Silicon(MPS)速度与质量
环境:Apple M1(8 GB)、macOS 26.5.1、PyTorch 2.14.0、Python 3.11.14、Kornia 0.9.0rc1,与 CPU/CUDA 运行使用相同输入(input_sha256一致)。每对图像都计时,每格同样为五对 median 的均值:
| 管线 | 基线 ms/pair | 优化 ms/pair | 加速比 | 平均角点误差 基线 → 优化 |
|---|---|---|---|---|
| SIFT | 1787.8 | 1068.8 | 1.67× | 223.546 → 223.546 |
| SIFT–AffNet–HardNet | 3066.7 | 2342.2 | 1.31× | 3.741 → 3.741 |
| KeyNet–HardNet | 2109.5 | 2118.6 | 1.00× | 3.452 → 3.452 |
MPS 评测有两个关键工程细节(见 benchmarks/feature/local_features.py):harness 在计时区域内显式调用torch.mps.synchronize;而 RANSAC 由于其批量化 SVD 在 MPS 上会使进程崩溃(issue #4201/#4204),被放到 CPU 上评估,且 RANSAC 位于计时区域之外,因此计时不受影响。MPS 上peak_extra_cuda_bytes为null。
逐对 MPS 计时(median ± IQR,ms):
| 图像对 | SIFT ms 基线 → 优化 | SIFT–AffNet–HardNet ms 基线 → 优化 | KeyNet–HardNet ms 基线 → 优化 |
|---|---|---|---|
| 1–2 | 1770 ± 23 → 1059 ± 10 | 3115 ± 43 → 2353 ± 31 | 2118 ± 33 → 2120 ± 17 |
| 1–3 | 1789 ± 10 → 1062 ± 18 | 3069 ± 20 → 2344 ± 23 | 2107 ± 6 → 2122 ± 24 |
| 1–4 | 1778 ± 6 → 1068 ± 6 | 3067 ± 4 → 2338 ± 4 | 2107 ± 6 → 2121 ± 10 |
| 1–5 | 1791 ± 12 → 1078 ± 2 | 3046 ± 4 → 2343 ± 2 | 2107 ± 17 → 2110 ± 9 |
| 1–6 | 1812 ± 48 → 1076 ± 10 | 3037 ± 16 → 2334 ± 10 | 2108 ± 18 → 2120 ± 9 |
逐对 MPS 角点误差(px):
| 图像对 | SIFT 误差 | SIFT–AffNet–HardNet 误差 | KeyNet–HardNet 误差 |
|---|---|---|---|
| 1–2 | 1.247 | 1.927 | 1.263 |
| 1–3 | 1.435 | 1.365 | 1.679 |
| 1–4 | 2.586 | 1.961 | 1.833 |
| 1–5 | 507.037 | 1.792 | 3.657 |
| 1–6 | 605.427 | 11.658 | 8.828 |
MPS 上所有对的匹配数、内点数与角点误差在两次运行间也完全一致。KeyNet–HardNet 在 MPS 上完全不变(噪声范围内):因为 channels-last 转换只在 CPU/CUDA 生效,且 KeyNet 使用 OriNet 而非梯度直方图定向,MPS 上这条管线没有走到任何优化路径——这正是源码里 kornia/feature/keynet.py 的x.device.type in ("cpu", "cuda")条件门控的直接结果。
选择性编译(CUDA):torch.compile 只编译关键热区
第三组实验对比基线601b5a4a与fb66de1d(同一优化库实现 + 编译基准模式)。注意:这不是全管线编译——现有工厂只编译尺度空间金字塔、响应与亚像素模块(或 KeyNet 的响应/NMS),使用dynamic=True与默认 Inductor 设置;描述子、定向、仿射适应、匹配与 RANSAC 保持 eager。
| 管线 | 基线编译 ms/pair | 优化编译 ms/pair | 加速比 | 平均角点误差 基线 → 优化 |
|---|---|---|---|---|
| SIFT | 94.64 | 71.23 | 1.33× | 223.654 → 223.654 |
| SIFT–AffNet–HardNet | 158.27 | 132.87 | 1.19× | 2.381 → 2.381 |
| KeyNet–HardNet | 106.01 | 105.08 | 1.01× | 5.638 → 5.638 |
解读要点:
- KeyNet 的差异仍是计时噪声;两个编译版本间所有对的匹配数、内点数、特征数与角点误差完全一致。
- 编译本身会改变部分尺度空间结果,因此 eager 与编译的质量必须分开比较,例如 SIFT–AffNet–HardNet 编译后均值误差 2.381 px,eager 为 2.183 px。
- 每个版本在独立进程中用全新的
TORCHINDUCTOR_CACHE_DIR启动;首次调用被排除在预热与稳态计时之外,但记录在 JSON 中:SIFT 首次提取+匹配调用基线 43.42 s / 优化 43.38 s;KeyNet 15.25 s / 16.84 s。SIFT–AffNet–HardNet 在 SIFT 之后运行,复用了已编译的检测器图(0.233/0.209 s),因此其首次调用不是独立的冷编译测量;首次延迟包含实际执行与初始化,不只是编译时间。 - 编译模式峰值额外 CUDA 分配:SIFT 566.9 → 690.1 MiB;SIFT–AffNet–HardNet 1058.6 → 1058.6 MiB;KeyNet–HardNet 1057.7 → 1057.7 MiB。五对各自的 median/IQR 与首次调用延迟全部保留在原始 JSON 中。
编译模式命令(源码见 benchmarks/feature/local_features.py 的--compile参数):
.venv/bin/python -m benchmarks.feature.local_features --seq /data/graf --device cuda --compile --json graf-compiled.json从 benchmarks/feature/scale_space_detector.py 可以看到编译范围:scalespace 方法通过compile_modules=["subpix", "resp", "scale_pyr"]编译三个模块;KeyNet 方法则在 KeyNetExtractor 中执行det.model = torch.compile(det.model, dynamic=True)与det.nms = torch.compile(det.nms, dynamic=True)——注释说明 model 与 nms 在 6 种不同图像尺寸上运行,dynamic=True可避免重复编译;而 aff/ori/descriptor 不编译,因为extract_patches_from_pyramid若被追踪,每个 (特征数, patch 尺寸) 组合都会特化出新图,导致预热尖峰。
本次未运行编译模式的 CPU 对比。更早的 SIFT 运行时图表(benchmarks/feature/sift_runtime.md)覆盖公开尺度空间 SIFT 预设在 batch 1 的 CPU eager/编译,以及 CUDA batch 1/4/8 的运行时,与本次 graf 配对基准是不同的工作量(单图提取 vs 双图提取+匹配),不要互比绝对数值:
优化实现细节(源码级佐证)
原文档"Implementation"一节列出了四项核心优化,均可直接对应到当前仓库源码:
1. 用scatter_add_累积方向直方图。每个梯度像素只向相邻的两个方向 bin 投票,替代原来对整块 patch 的 36 次完整扫描。实现在 kornia/feature/orientation.py 的PatchDominantGradientOrientation.forward:先计算 36-bin 角度与加权幅度,然后两次ang_bins.scatter_add_分别累加左右两个 bin,半精度输入提升到 float32 累积后再除回 patch 面积。文档注释同时提醒:CUDA 上scatter_add_使用原子操作,两次相同输入可能产生 ulp 级差异,极端接近的 bin 可能返回相邻峰,torch.use_deterministic_algorithms(True)可恢复确定性。
2. 内建亚像素模块的正/负响应精化批处理。正负响应精化合并为批量调用,减少 kernel 启动次数(这也是 GPU 显存表中"以约 90 MiB 换更少精化启动"的由来);NMS 邻域保持分离,自定义模块与精化器保留独立调用并带候选数上限,且精化迭代次数不变。
3. 合并前只 gather 被选中的坐标。在合并正/负结果前先按选中坐标 gather,避免在整个 octave 上做密集的三坐标合并。
4. KeyNet / HardNet 的 channels-last float32 激活。KeyNet 窄卷积块在 CPU/CUDA 上于块边界转换一次 channels-last(kornia/feature/keynet.py),HardNet 在 CPU 上同样处理(kornia/feature/hardnet.py)。参数布局与 checkpoint 键保持不变,因此预训练权重契约不受影响——这也是 MPS 上 KeyNet 管线完全看不到收益的原因。
复现指南
数据准备。使用 Oxford affine graf 数据集(Oxford affine 评测数据集中的 graf 序列),归档 SHA-256 为999871b945ee968a00a0d5f9af957d1382fb9dae1511cdee9553366817b53b5b;每张输入图像与单应矩阵的哈希同样记录在 JSON 元数据的input_sha256字段中(见 benchmarks/feature/graf_results/optimized-cuda.json),可逐字节校验输入一致性。原版 Oxford PPM 优先,仅当 PPM 不存在时才用 PNG(转换副本可能解码出不同像素)。
运行命令(在仓库根目录、使用.venv解释器):
# CUDA:五对全部计时 .venv/bin/python -m benchmarks.feature.local_features --seq /data/graf --device cuda --json graf-cuda.json # CPU:--timing-pairs 2 只计时代表性 1–2 对,但仍评估全部五对质量 .venv/bin/python -m benchmarks.feature.local_features --seq /data/graf --device cpu --timing-pairs 2 --json graf-cpu.json # MPS:五对全部计时,RANSAC 自动落到 CPU .venv/bin/python -m benchmarks.feature.local_features --seq /data/graf --device mps --json graf-mps.json基线对比。在基线 worktree 根目录用主检出(primary checkout)的显式解释器通过runpy.run_path运行同一 harness,并确认打印的kornia.__file__指向基线 worktree 而非 editable 主检出。最终基准进程顺序执行、无并发测试干扰。
管线配置(与文档一致,全部在 benchmarks/feature/scale_space_detector.py 中可查):
- 尺度空间管线:
ScalePyramid(3, 1.6, 32, double_image=True)(3 层 + 3 个额外层)、DoG 极小/极大值、AdaptiveQuadInterp3d默认参数、32 像素梯度方向 patch。 - SIFT 使用
SIFTDescriptor(32, rootsift=True)(RootSIFT);SIFT–AffNet–HardNet 在定向前加预训练 AffNet、定向后加预训练 HardNet;KeyNet–HardNet 使用公开预设与预训练 OriNet。 - SNN 匹配 ratio 0.85;单应 RANSAC 参数:threshold 2.0、max_iter 10、batch_size 8196、confidence 0.9999、seed 3407(见 benchmarks/feature/local_features.py);模型构建使用 PyTorch seed 0。
方法论约束(详见 benchmarks/common.py 与 benchmarks/README.md 的 methodology contract):计时预算至少扩展到 5 次预热调用时长,避免慢 CPU 调用退化为单一样本;CPU 线程数为 1,与共享 Timer 一致;matmul TF32 被禁用,cuDNN 保持默认 TF32 并记录在元数据中;结果 JSON 为严格合法 JSON(NaN 转为 null),形状为{"metadata": ..., "results": [...]}。
原始结果文件
以下 JSON 保留每对的 median/IQR、首次调用延迟、特征数、匹配数、内点数、峰值显存与完整元数据(benchmarks/feature/graf_results/):
- 基线 CUDA
- 优化 CUDA
- 基线 CPU
- 优化 CPU
- 基线选择性编译 CUDA
- 优化选择性编译 CUDA
- 基线 MPS
- 优化 MPS
需要强调的是:这些是本地基准结果而非发布级的性能承诺。它们是 benchmarks/README.md 意义上的对比工件(comparison artefacts),由benchmarks/results_schema.py的results_schema.validate_artefact在 CI 中校验;测量发生在 harness 记录聚合load快照之前,且各次运行顺序执行、无并发负载。引用任何数字时,请同时引用 Kornia 版本与git_commit,因为一个<kornia-version>目录可能横跨多个提交。
- 计算机视觉
- 人工智能
- 深度学习
- 图像处理
【免费下载链接】kornia
🐍 Geometric Computer Vision Library for Spatial AI
相关推荐
Kornia SIFT 尺度空间基准全解析:patch 与 pyramid 双后端在 Oxford graf 上的性能与质量对比
Kornia SIFT 尺度空间基准全解析:patch 与 pyramid 双后端在 Oxford graf 上的性能与质量对比 SIFTFeatureScal
计算机视觉深度学习人工智能图像处理OpenMed CPU INT8 分类头快速路径:本地端 token 分类的量化推理、SIMD 分派与基准验证
OpenMed CPU INT8 分类头快速路径:本地端 token 分类的量化推理、SIMD 分派与基准验证 导读 OpenMed 的 CPU INT8 Cl
人工智能NLP医疗健康数据脱敏本地部署大模型AI 应用MCP 服务联邦学习50万行Excel不崩溃?Apache Fesod高性能处理实战
50万行Excel不崩溃?Apache Fesod高性能处理实战 Apache Fesod(孵化中)是一款面向 Java 1.8+ 的 Excel 处理库,它的
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考