开源 OCR 引擎 SimdPaddleOCR 发布了 2.0。这个版本第一次动了大版本号,原因只有一个:补上了 GPU 后端。Windows、Linux、安卓走 Vulkan,macOS 走 Metal,从算子内核调度到系统互操作全部是 C#,仓库里没有一行 C/C++。
实测数据先摆出来:RTX 3080 Ti 上 medium 模型端到端从 546 ms 降到 36 ms,15 倍;CPU 路径的识别结果和 1.4.2 版本逐行相同,精度零退化。

不熟悉这个项目的先补一句背景:它是 PP-OCRv6 的纯 C# 实现,不依赖 Paddle Inference、ONNX Runtime,也不依赖 OpenCV。1.x 一直在打磨 CPU 上的 SIMD 指令优化,2.0 把战场扩到了 GPU。
先看数据,再谈原理
测试机配置:Ryzen 7 5800X + RTX 3080 Ti(驱动 581.80),64 GB 内存,.NET 10.0.11。测试集是仓库自带 100 张合成图,4 并发、预热 1 次,取去掉首张后的中位数:
| 模型 | 1.4.2 CPU | 2.0 CPU | 2.0 Vulkan | Vulkan vs 1.4.2 |
|---|---|---|---|---|
| tiny | 55.1 | 49.6 | 22.1 | 2.5× |
| small | 191.1 | 166.8 | 27.4 | 7.0× |
| medium | 545.8 | 523.2 | 36.4 | 15.0× |
单位 ms/图。medium 的吞吐从每秒 1.8 张涨到每秒 25 张。
换 Intel Arc B580 再测:medium 从 591.5 降到 57.1 ms,10.4 倍。N 卡 A 卡都有可观收益,说明 Vulkan 路线不是只对某一家硬件调的优。
15 倍不是一次写出来的
版本背后有三个值得单独拎出来的坑。
GEMM 起步只有 3.5 TFLOPS。第一版协作矩阵内核里最大的 GEMM 只跑到约 3.5 TFLOPS,端到端要 125 ms。重写后引入共享内存双缓冲、向量化读写、按输出通道数选窄 tile,最大的几个 GEMM 到了 25-28 TFLOPS,端到端压到 40 ms 上下。
显存一度涨到 24 GB。早期版本跑完 100 张图,进程工作集最高冲到 24 GB。靠 graph 共享 arena、LRU 缓存、中间张量复用修掉之后,3080 Ti 上 medium 峰值 984 MB——比纯 CPU 路径的 1170 MB 还低。GPU 版反而更省内存,这算少见案例。
REC 模型最初整图留在 CPU。SVTR 结构里有些特殊算子(batch 维 5-D Transpose、MaxPool batch 之类)一开始没有 GPU 实现,检测和分类在 GPU 上、识别退回 CPU,来回搬运把收益吃掉大半。逐个补齐算子后,检测、方向分类、识别三个模型才真正整图跑在 GPU 上。
上手:升级几乎零改动
NuGet 升到 2.0.0,Run 方法签名和支持的像素格式(BGR/RGB/BGRA/RGBA)都没变,默认 Auto 模式自动挑最优后端:
using var ocr = PaddleOcrAll.Load(ChineseV6SmallModels.Default);
PaddleOcrResult result = ocr.Run(bgr, width, height, stride);
想固定后端,或者三个模型分开走(比如检测用 GPU、识别留 CPU),分别设置即可:
using var ocr = PaddleOcrAll.Load(ChineseV6SmallModels.Default, new PaddleOcrOptions
{
Detector = new PaddleOcrDetectorOptions { Backend = OcrBackend.Vulkan },
Recognizer = new PaddleOcrRecognizerOptions { Backend = OcrBackend.Vulkan },
Classifier = new PaddleOcrClassifierOptions { Backend = OcrBackend.Vulkan },
});
2.0 还新增了 returnCtcAlignment 参数。传 true 后每行文本会带 CTC 时间戳,映射回原图能拿到逐字符的四边形坐标:
PaddleOcrResult result = ocr.Run(bgr, width, height, returnCtcAlignment: true);
foreach (PaddleOcrLine line in result.Lines)
foreach (PaddleOcrCharacterBox ch in line.EstimateCharacterBoxes())
Console.WriteLine($"{ch.Text} @ ({ch.X1:F0},{ch.Y1:F0})");
字符框是估算值,做敏感词涂黑、证件打码这类"按字符覆盖"的需求够用。参数默认关闭,用不上就别开,省一笔开销。
不是所有核显都值得开 GPU
笔记本核显的表现分化很有意思,正好说明"GPU 加速"不是无脑开。
AMD Radeon 880M 核显:针对 32KB 共享内存、无缓存等硬件特性加了轻量 GEMM 变体,medium 从 516.1 降到 118.1 ms,4.3 倍。
Intel UHD 770 核显:没有协作矩阵扩展,针对性优化后 GEMM 也只到 fp32 峰值的 57-67%,端到端 1073 ms,反而比同机 CPU 的 562 ms 慢。
UHD 770 这种情况,Auto 模式会自动回退到 CPU 后端。这个设计值得点个赞——检测到 GPU 不划算就别硬上,比很多上来就抢显卡的方案务实。
手机和浏览器也跑得动
骁龙 8 Gen 3 上通过 Vulkan 跑通了安卓原生的 GPU 加速,解决 Adreno GPU 特有的编译和内存限制后:small 从 811.7 降到 249.3 ms,约 3.3 倍;medium 从 3294.1 降到 1466.4 ms,约 2.2 倍,内存占用还更低。
浏览器 WebAssembly 路线也有结果:开 LLVM AOT 和多线程后,tiny 模型在 Edge headless 下中位数 103.3 ms、吞吐 9.7 img/s、字符错误率 2.30%,只比桌面原生慢 2.3 倍。前端纯离线 OCR 场景够用了。
macOS 侧是独立的 Metal 后端,与 Vulkan 共用同一套 GPU session 架构,互操作经 P/Invoke 调 Objective-C runtime,没有 Swift 或 C++ 胶水层。M4 虚拟机 medium 拿到 6.47 倍加速,CI 环境的 M1 虚拟机约 2.8 倍。
精度:CPU 逐行一致,GPU 有一个抖动点
CPU 路径输出和 1.4.2 逐行相同。GPU 路径用 fp16,在阈值附近有少量抖动:实测出现过一张临界图片,GPU 把两行竖排文本合并成了一行——单个像素点概率跨过阈值所致,属 fp16 计算的正常现象,总体字符错误率与 CPU 持平或略好。
上线前建议做一个固定验收动作:同一批样本分别跑 CPU 后端和 GPU 后端,比对识别文本与行切分是否一致;发现边界行合并的,要么接受 fp16 抖动,要么对该业务固定 CPU 后端。票据金额、证件号这类精度敏感场景,不建议默认上 GPU。
已知限制,升级前看一眼
- GPU 后端只在 net10.0 目标框架下可用,老项目要先升 .NET 版本;
- fp16 在低置信度下偶有结果翻转,彻底解决要上 fp32,暂未做;
- 每次 dispatch 约 55 微秒的固定开销,后续靠算子融合继续压;
- 检测和识别两个阶段之间还没有做双流重叠。
CPU 路径同步有更新:CTC ArgMax 并行化,多核端到端提升 4% 到 15%;ARM64 手写 AdvSIMD,Neoverse N2 上 tiny 拿到 1.25 倍;检测后处理的 flood-fill 重写,耗时从 5.4 ms 降到 2.2 ms。
跑分 JSON 和复现命令都在仓库 docs 目录,测试数据集以 Apache-2.0 协议开源,HuggingFace 和 ModelScope 搜 simdpaddleocr-dataset-v1 能找到。

最后一张设备速查表,选后端前先对号入座:
| 设备 | 后端 | medium 相对同机 CPU |
|---|---|---|
| RTX 3080 Ti | Vulkan(协作矩阵) | 14.4× |
| Intel Arc B580 | Vulkan(协作矩阵) | 9.6× |
| Apple M4(虚拟机) | Metal | 6.47× |
| AMD Radeon 880M | Vulkan(协作矩阵) | 4.3× |
| 骁龙 8 Gen 3 | Vulkan(无协作矩阵) | 约 2.2× |
| Intel UHD 770 | Vulkan | 慢于 CPU,Auto 走 CPU |
升级方式一句话:NuGet 包 Sdcb.SimdPaddleOCR 升到 2.0.0,模型包 1.0.0 不用动,协议仍是 Apache-2.0。GitHub 搜 SimdPaddleOCR 就是仓库主页。

评论0