2026-10-27 · 深度笔记

文档解析和 OCR 跑在云主机上:CPU 核数怎么选

OCR 看着该配 GPU,实际瓶颈在 CPU 的并行度和内存带宽。这篇给一份按文档量级选核数的经验区间,外加三个我踩过的反直觉现象。

云主机AI工程OCR文档处理

我第一次做文档解析,是把 PaddleOCR 塞进一台带 GPU 的机器上的,容器跑起来 GPU 利用率常年 3%,CPU 却满载。后来换成一台纯 CPU 的机器,反而快了。

这件事让我重新算了一遍 OCR 流水线里 CPU 和 GPU 的分工,结论跟直觉相反。

先把流水线拆开,看谁在耗什么

一个完整的 OCR 任务大致是这么走的:

阶段主要资源能不能并行
PDF 解码 / 页面栅格化CPU + 内存带宽页面之间完全独立
预处理(灰度、二值化、纠偏)CPU页面之间完全独立
版面检测GPU 友好,但小模型也够用批处理友好
文字识别GPU 或 CPU 都行批处理友好
后处理(段落重组、表格对齐)CPU页面之间独立

看出规律了吗:真正吃满 CPU 的是前三段,而它们恰好是最不需要 GPU 的部分。

OCR 是个典型的吞吐型任务——一次处理几千页文档,你关心的是每小时能出多少页,不是单页延迟。这种任务对核数的敏感度是接近线性的,一台 16 核机器能干的活,理论上就是两台 8 核机器的量。

反直觉之一:加 GPU 的收益,小于加核数的收益

OCR 里唯一真能吃到 GPU 的是版面检测和识别两个模型。但它们都很小:

我那台带卡机器上真正的现象是:GPU 大部分时间在等数据,CPU 在忙着把 PDF 拆页、渲染成位图、再喂给框架。

结论是:OCR 这类流水线,核数比显卡更值钱。先按核数往上堆,GPU 放到确实需要的那一段。

反直觉之二:核数越多,内存先崩

我第二次踩坑是给一台 16 核 32G 的机器起了 16 个并行 worker,想榨干 CPU。结果进程被 OOM killer 一个个杀掉,日志里全是"进程退出,没有错误"。

原因是单个 worker 的内存占用非常吓人:

300dpi 的 A4 页面栅格化成 RGB 位图:
2480 × 3508 × 3 字节 ≈ 26MB

这只是一个页面。worker 内部还要同时持有:解码器缓冲区、灰度图、二值化结果、检测模型的输入张量、识别模型的中间特征。实际一个 worker 稳定在 500MB 到 1GB 很常见。

16 个 worker 就是 8 到 16GB,再叠加模型本身,32G 机器根本不够。

所以正确的公式不是"worker 数 = 核数",而是:

worker 数 = min(核数 - 2, 可用内存 / 单 worker 峰值内存)

留两个核给主进程和 IO,两边都别打满。

services:
  ocr:
    image: my-ocr:latest
    deploy:
      resources:
        limits:
          cpus: "14.0"
          memory: 28G
    environment:
      - OMP_NUM_THREADS=1
      - OMP_WAIT_POLICY=PASSIVE
      - WORKER_COUNT=12

上面那个 OMP_NUM_THREADS=1 是个容易漏的坑。OpenMP 默认每个进程按核数起线程,12 个 worker × 14 核 = 168 个线程互相抢,调度开销能把吞吐打下来一半,而且内存翻倍。解析类任务基本是单页单次计算,让它一个线程老老实实跑反而更快。

反直觉之三:文件类型比参数更影响体验

同样是"解析",我见过的速度差异:

输入页/分钟(8 核机器)主要耗时
电子版 PDF(纯文本层)数千页几乎不耗时,直接抽文本
扫描 PDF(300dpi 位图)几十到一百页栅格化 + 识别
手机翻拍的照片十几张纠偏 + 去阴影 + 识别
表格密集的报表二十几页表格结构还原最慢

这里最反直觉的是:很多"我们只能做 OCR"的场景,其实电子版文档直接抽文本层就行,快几十倍。

判断方法很简单,扫一遍 PDF 里有没有可提取的文字层:

import fitz

doc = fitz.open(path)
page = doc[0]
text = page.get_text("text").strip()
has_text_layer = len(text) > 50

超过一半的页面没有文字层,才真的需要上 OCR。这个判断在项目早期省掉了我一整轮的无用功——因为需求方给的样本里,本以为全是扫描件,结果八成都有文字层。

按量级选配置

我现在的经验区间:

场景建议配置备注
每天几百页4-8 核 / 8-16G单进程加线程池就够
每天几千页16 核 / 32G进程池,worker 数按内存倒推
每天几万页32 核 / 64G + 独立对象存储队列拆任务,分批处理
强依赖表格结构还原16 核 / 32G 起步这类任务单页耗时最不可控

有一点要提前想清楚:文档解析是典型的"批处理 + 可延迟"任务。它不需要和在线接口抢资源,最好是丢进队列由专门的 worker 机器慢慢消化,跟在线服务分开放。这样在线接口的延迟不会因为队列里堆了 2000 页待处理文档而波动。

最后一句,关于 CPU 型号,同代次里单核性能差异不大,真正拉开差距的是物理核数和内存带宽。文档解析对内存带宽的敏感度高于普通后端服务,因为它一直在搬大块位图。核数没跑满但吞吐上不去的时候,往内存带宽方向查比往 CPU 型号方向查更有效。

具体的实例规格和按量计费规则各家都在改,写死数字容易过期,以官方页面为准。

接着读