测试环境:
- 宿主硬件:AMD Ryzen 7 5800X(8C/16T)、64 GB 内存,Windows 11
- 运行时框架:.NET 10.0 (
net10.0/browser-wasm),wasm-tools-net10负载(Emscripten 3.1.56) - 浏览器引擎:Microsoft Edge 155(Chromium 内核,Headless 模式)
- SIMD 特性:WASM SIMD128,
PackedSimd.IsSupported = True(Vector<float>.Count = 4) - 编译模式:LLVM AOT 静态编译 (
<RunAOTCompilation>true</RunAOTCompilation>) - 多线程机制:Web Workers +
SharedArrayBuffer(由本地服务端注入 COOP:same-origin与 COEP:require-corp安全响应头) - 并发参数:
--workers 4(EffectiveLineWorkerCount = 4) - 测试工程:
test/Sdcb.SimdPaddleOCR.WasmBench,评测集为dataset/全量 100 张标准图像(1 轮预热 + 99 轮实测,共 1,026 行标注)
CPU 内核派发顺序为 AVX-512 → AVX2 → AdvSIMD → PackedSimd → Vector<float> → 标量。浏览器中命中 PackedSimd(WebAssembly SIMD128)专用内核,实测如下:
| 模型 | 端到端平均耗时 (Mean ms) | 中位数 (Median ms) | P95 延迟 (ms) | 吞吐量 (img/s) |
|---|---|---|---|---|
| ChineseV6Tiny (tiny) | 113.3 ms | 113.1 ms | 141.3 ms | 8.83 img/s |
| ChineseV6Small (small) | 409.9 ms | 412.0 ms | 523.4 ms | 2.44 img/s |
| ChineseV6Medium (medium) | 1,360.4 ms | 1,371.0 ms | 1,732.6 ms | 0.74 img/s |
注:以上数据均为开启 LLVM AOT 静态编译的实测指标。若在未开启 AOT 的纯 JIT(Jiterpreter)模式下运行,性能会慢 8~30 倍,JIT 模式仅建议作为跳过编译等待的本地快速调试手段,生产环境必须使用 AOT。
在浏览器多线程环境下,tiny 4w 运行全量 100 张图平均耗时 113.3 ms(吞吐量 8.83 张/秒),同机桌面原生(--engine sharp --workers 4,tiny 中位 45.9 ms)约为其 2.5 倍,满足网页前端纯离线、低延迟 OCR 的需要。
同一台机器、同一浏览器交替运行,对比仅走 Vector<float> 的旧版本(tiny/small 为两轮平均,medium 为单轮):
| 模型 | Vector<float> Mean |
PackedSimd Mean | 提速 | 文本 Hash |
|---|---|---|---|---|
| tiny | 128.1 ms | 112.9 ms | 1.13× | 100 张完全一致 |
| small | 474.6 ms | 411.3 ms | 1.15× | 100 张完全一致 |
| medium | 1,654.6 ms | 1,360.4 ms | 1.22× | 100 张完全一致 |
PackedSimd 内核与 Vector<float> 路径逐 lane 保持相同的运算顺序(无 FMA,乘加分离),因此在有限输入下结果逐位一致(ReLU / HardSwish 用的 PseudoMax / PseudoMin 遇到 NaN 时返回 0,而 Vector.Max 会传播 NaN)。主要收益来源:
- 寄存器分块重排:V8 在 x64 上最多只给 v128 分配 15 个 XMM 寄存器,原分块会在内层循环溢出:NHWC 卷积在
Vector<float>.Count == 4时是 6 行 × 8 通道分两次(12 个累加器),packed MatMul 是 4 行 × 16 列(16 个累加器)。PackedSimd 的 NHWC pointwise / dense 卷积与 MatMul 改为 4 像素 × 8 输出通道(8 个累加器),单核微基准 1.4~1.9×。 - Dense 卷积预计算 k 偏移表:去掉内层循环里的 tap/通道簿记,3×3 dense 卷积 1.5~1.9×。
- MatMul + ArgMax(CTC 头):原
Vector<float>实现把乘加累加器放在stackalloc数组中,PackedSimd 版本的累加器驻留寄存器(每行的最大值 / 下标状态仍在栈上,每 8 列更新一次),2.3×。 - Depthwise、Erf/GELU 等访存或分支受限的算子收益较小(约 1.0~1.15×)。
以 dataset/metadata.json 真实标注为基准计算字符错误率(CER)与行绝对匹配率:
| 模型 | 字符准确率 (Char Accuracy) | 字符错误率 (CER) | 行绝对匹配 (Line Exact) | 行匹配率 |
|---|---|---|---|---|
| ChineseV6Tiny (tiny) | 97.70% | 2.30% | 734 / 1,026 | 71.54% |
| ChineseV6Small (small) | 99.58% | 0.42% | 940 / 1,026 | 91.62% |
| ChineseV6Medium (medium) | 99.86% | 0.14% | 994 / 1,026 | 96.88% |
准确率观察:WASM 下全托管推理引擎
OnnxSharp达成极高数值确定性。全量 100 张图实测下,Medium 模型的 CER 仅为 0.14%(字符准确率 99.86%),行绝对匹配率高达 96.88%,PackedSimd 与Vector<float>路径文本 Hash 完全一致,没有任何精度退化。
| 阶段 | ChineseV6Tiny (4w) | ChineseV6Small (4w) | ChineseV6Medium (4w) | 说明 |
|---|---|---|---|---|
| det_preprocess | 1.8 ms | 1.8 ms | 1.6 ms | 缩放、归一化与通道重排 |
| det_graph | 54.1 ms | 192.6 ms | 404.8 ms | 文本检测网络推断 |
| det_postprocess | 3.2 ms | 3.9 ms | 2.9 ms | 概率图二值化、轮廓与多边形拟合 |
| crop | 3.5 ms | 4.3 ms | 2.8 ms | 仿射透视校正提取文字行 |
| cls_graph | 29.8 ms | 32.1 ms | 24.3 ms | 文本行方向分类 (180° 翻转检测) |
| rec_preprocess | 8.1 ms | 8.9 ms | 6.9 ms | 动态宽高归一化 |
| rec_graph (总和) | 141.4 ms | 712.0 ms | 3,513.7 ms | 所有文字行识别网络的算力耗时总和 |
| lines_wall (实测墙钟) | 50.2 ms | 206.8 ms | 947.8 ms | 4 个 Web Worker 并发执行后的实际耗时 |
| rec_reshape | 2.5 ms | 5.7 ms | 5.5 ms | 动态宽度重塑与权重预备 |
多线程并行收益: 在 4 个并发 Worker 的协同下:
tiny的文字识别墙钟耗时从rec_graph141.4 ms 压缩到lines_wall50.2 ms(加速比 ~2.82×);small从 712.0 ms 压缩到 206.8 ms(加速比 ~3.44×);medium从 3,513.7 ms 压缩到 947.8 ms(加速比 ~3.71×,非常接近 4× 理论上限)。 充分证明了 WebAssembly 多线程与任务并行调度在行识别阶段的高效并发。
在 AOT 静态编译模式下,方法元数据和字节码直接编译为 native wasm 代码段,托管堆内存占用极低:
| 模型 | 平均托管内存占用 | 峰值托管内存占用 | 内存安全评价 (WASM 2GB 限制) |
|---|---|---|---|
| ChineseV6Tiny (tiny) | 60.9 MB | 74.0 MB | 极低,适合低内存移动端浏览器 |
| ChineseV6Small (small) | 126.8 MB | 158.0 MB | 极低且平稳 |
| ChineseV6Medium (medium) | 383.5 MB | 458.8 MB | 远低于 2GB/4GB 寻址上限,无内存溢出风险 |
WebAssembly 原生不支持传统 OS 级轻量进程,.NET 10 的 <WasmEnableThreads>true</WasmEnableThreads> 依靠浏览器底层的 Web Workers 和 SharedArrayBuffer:
- 浏览器出于安全规范,要求页面必须运行在跨域隔离上下文(
crossOriginIsolated == true)。 - 因此,Harness 的本地服务端(
server.mjs)配置了:Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp
- 若在纯 Node.js 宿主执行带线程的 WASM,Mono 运行时会直接触发
Assert failed: This build of dotnet is multi-threaded, it doesn't support shell environments like V8 or NodeJS保护异常。
- 在工程配置中默认开启
<RunAOTCompilation>true</RunAOTCompilation>。 - 需要本机安装
wasm-tools-net10负载(通过dotnet workload install wasm-tools-net10)。 - 编译时 .NET 自动调用 Emscripten 3.1.56 + LLVM 工具链生成高度优化的单一 WebAssembly 原生模块。
本项目提供了完整的自动化 Harness,位于 test/Sdcb.SimdPaddleOCR.WasmBench:
# 1. 运行 tiny 模型 4 线程全量基准测试 (默认 100 张图,AOT 模式)
.\test\Sdcb.SimdPaddleOCR.WasmBench\run.ps1 -Workers 4 -Model tiny -Count 100
# 2. 运行 small 模型 4 线程全量基准测试
.\test\Sdcb.SimdPaddleOCR.WasmBench\run.ps1 -Workers 4 -Model small -Count 100
# 3. 运行 medium 模型 4 线程全量基准测试
.\test\Sdcb.SimdPaddleOCR.WasmBench\run.ps1 -Workers 4 -Model medium -Count 100
# 4. 如需快速调试(跳过耗时的 AOT 编译),可加 -NoAot 参数使用 JIT 模式:
.\test\Sdcb.SimdPaddleOCR.WasmBench\run.ps1 -Workers 4 -Model tiny -NoAot
# 5. 打开浏览器交互式仪表盘(实时可视化进度条、指标卡片与文本 Hash)
.\test\Sdcb.SimdPaddleOCR.WasmBench\run.ps1 -Workers 4 -Model tiny -Open