181 lines
7.7 KiB
Markdown
181 lines
7.7 KiB
Markdown
# ocr-VL1.6
|
||
|
||
本地 CPU 部署 [PaddlePaddle/PaddleOCR-VL-1.6](https://github.com/PaddlePaddle/PaddleOCR) 的 OCR 识别项目,包含完整的性能 Benchmark 和多级优化方案。
|
||
|
||
> 在线 Demo: [HuggingFace Space](https://huggingface.co/spaces/PaddlePaddle/PaddleOCR-VL-1.6_Online_Demo) · 模型权重: [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.6)
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
ocr-VL1.6/
|
||
├── main.py # 单图 OCR + Benchmark(已集成 set_num_threads 加速)
|
||
├── batch_ocr.py # 批量 OCR(多进程并行加速)
|
||
├── pyproject.toml # 项目配置(uv 管理)
|
||
├── images/
|
||
│ └── 手写01.png # 测试图片:手写中文(1758×646)
|
||
└── README.md
|
||
```
|
||
|
||
## 技术栈
|
||
|
||
| 组件 | 版本 | 说明 |
|
||
| ---------------- | ----- | ---------------------------------------- |
|
||
| Python | 3.13 | |
|
||
| PaddlePaddle | 3.2.1 | CPU 版(无 CUDA),已编译 oneDNN/MKL-DNN |
|
||
| PaddleOCR | 3.7.0 | 带 `doc-parser` extra |
|
||
| PaddleOCR-VL-1.6 | 0.9B | 主 OCR 视觉语言模型(~1.8GB) |
|
||
| PP-DocLayoutV3 | - | 版面检测模型(~126MB) |
|
||
|
||
模型缓存目录:`~/.paddlex/official_models/`
|
||
|
||
## 快速开始
|
||
|
||
### 前提条件
|
||
|
||
- Python ≥ 3.13
|
||
- [uv](https://github.com/astral-sh/uv) 包管理器
|
||
|
||
### 安装
|
||
|
||
```bash
|
||
uv sync
|
||
```
|
||
|
||
### 运行
|
||
|
||
```bash
|
||
# 单张图片 OCR(自动使用全部 CPU 核心)
|
||
uv run python main.py
|
||
|
||
# 批量 OCR(多进程并行,安全默认值)
|
||
uv run python batch_ocr.py images/
|
||
```
|
||
|
||
首次运行会自动从 ModelScope 下载模型文件(约 2GB),后续使用缓存。
|
||
|
||
## 工作原理
|
||
|
||
`PaddleOCRVL` pipeline 分两阶段:
|
||
|
||
```
|
||
输入图片 → [PP-DocLayoutV3 版面检测] → [PaddleOCR-VL-1.6-0.9B 文字识别] → 结构化输出
|
||
```
|
||
|
||
1. **版面检测** — PP-DocLayoutV3 检测页面中的文本块区域(坐标 + 标签 + 置信度)
|
||
2. **OCR 识别** — PaddleOCR-VL-1.6-0.9B(GQA 架构视觉语言模型)逐块识别文字
|
||
3. **结果输出** — 返回 `PaddleOCRVLResult`,包含布局信息和识别文本
|
||
|
||
### 输出结构
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| ---------------------- | ---------------------- | ------------------------------------------------------------ |
|
||
| `layout_det_res.boxes` | list[dict] | 版面文本区域(cls_id, label, score, coordinate, polygon_points) |
|
||
| `parsing_res_list` | list[PaddleOCRVLBlock] | 识别文本块($.label, $.bbox, $.content, $.polygon_points) |
|
||
| `model_settings` | dict | 推理配置开关(版面检测/图表/印章等) |
|
||
| `width` / `height` | int | 图片尺寸 |
|
||
|
||
## 性能优化迭代
|
||
|
||
测试机器:Windows 11, CPU 20 核(逻辑线程), RAM 32GB, PaddlePaddle 3.2.1 CPU
|
||
|
||
### 迭代 0:初始状态(无任何优化)
|
||
|
||
直接调用 `pipeline.predict()`,未设置任何线程参数。
|
||
|
||
| 阶段 | 耗时 |
|
||
| ----------------------- | --------------- |
|
||
| 模型初始化(加载权重) | ~60s |
|
||
| 首次推理(含 JIT 编译) | ~285s |
|
||
| 后续推理 | ~238s(~4 min) |
|
||
|
||
### 迭代 1:算子级并行 — `core.set_num_threads()`
|
||
|
||
**探索过程:**
|
||
|
||
| 尝试 | 方法 | 结果 |
|
||
| ---- | -------------------------------------------------- | ---------------------------------------------------- |
|
||
| ❌ | `paddle.set_num_threads()` | Paddle 3.x 已移除该 API |
|
||
| ❌ | 环境变量 `OMP_NUM_THREADS` / `MKL_NUM_THREADS` | Paddle 3.x 内部使用 oneDNN,不读取这些变量 |
|
||
| ✅ | `from paddle import core; core.set_num_threads(N)` | **有效!** oneDNN 底层算子(matmul 等)受该 API 控制 |
|
||
|
||
**矩阵乘法微基准测试(4000×4000):**
|
||
|
||
| 线程数 | 耗时 (matmul) | 加速比 |
|
||
| ------ | ------------- | -------- |
|
||
| 1 | 0.952s | 1.0x |
|
||
| 4 | 0.419s | 2.3x |
|
||
| 8 | 0.323s | 2.9x |
|
||
| 16 | 0.240s | 4.0x |
|
||
| **20** | **0.223s** | **4.3x** |
|
||
|
||
**应用到 OCR 后的实际效果:**
|
||
|
||
设置 `core.set_num_threads(20)` 后重新评测:
|
||
|
||
| 阶段 | 优化前 | 优化后 | 提速 |
|
||
| ---------- | ------ | --------------------- | -------- |
|
||
| 模型初始化 | ~60s | ~40s | 1.5x |
|
||
| 推理 | ~238s | **~162s(~2.7 min)** | **1.5x** |
|
||
|
||
**为什么不是 4.3x?** 矩阵乘法只是 OCR pipeline 的一部分。自回归解码(逐 token 生成)天然串行、I/O 等待、版面检测中的非矩阵运算等不受线程数影响。
|
||
|
||
---
|
||
|
||
### 迭代 2:批量多进程并行 — `batch_ocr.py`
|
||
|
||
**思路:** 多张图片时,用 `multiprocessing.Pool` 启动多个独立进程,每个进程加载一份 pipeline 实例,同时处理不同图片。
|
||
|
||
**遇到的问题 & 修复(迭代 2.1):**
|
||
|
||
| 问题 | 原因 | 修复 |
|
||
|------|------|------|
|
||
| 系统卡顿/黑屏/无响应 | `Pool.starmap` 同时启动 N 个进程,同步加载 N×2GB 模型,CPU + 内存瞬间打满 | ① 进程错峰启动(随机延迟 0~15s)② `psutil` 降低进程优先级 ③ 预留 1 核给 OS ④ `imap_unordered` 替代 `starmap` |
|
||
|
||
**策略:**
|
||
- 每个子进程独立调用 `core.set_num_threads((总核心-1) / 进程数)`,预留核心给 OS
|
||
- 例如 2 进程 × 9 线程 = 18 核,留 2 核给系统
|
||
- `--stagger` 控制错峰窗口,默认 15s
|
||
|
||
```bash
|
||
# 2 进程并行(安全默认值)
|
||
uv run python batch_ocr.py images/
|
||
|
||
# 4 进程并行(需 32GB+ RAM)
|
||
uv run python batch_ocr.py images/ --workers 4
|
||
|
||
# 自定义错峰窗口(值越大内存峰值越低,但总耗时增加)
|
||
uv run python batch_ocr.py images/ --workers 4 --stagger 30
|
||
```
|
||
|
||
| 配置 | 适用场景 | 理论加速比 | 内存开销 | 实际限制 |
|
||
| -------------------- | -------- | --------------- | ---------- | -------------- |
|
||
| `set_num_threads(N)` | 单张图片 | ~1.5x | 无额外开销 | 自回归解码瓶颈 |
|
||
| `batch_ocr.py` | 批量多图 | ~Nx(N=进程数) | N × 2GB | 内存/内存带宽,需错峰避免打满系统 |
|
||
|
||
> ⚠️ 每个进程独立加载模型(~2GB),32GB RAM 建议 `--workers ≤ 4`。
|
||
> 默认 `--workers 2` 为安全值,不会导致系统卡顿。
|
||
|
||
---
|
||
|
||
### 优化总结
|
||
|
||
```
|
||
初始: 238s/image (单线程, 无优化)
|
||
│
|
||
├─ 迭代1: set_num_threads(20) → 162s (1.5x, 单图最优)
|
||
│
|
||
└─ 迭代2: batch_ocr.py (4进程) → ~40s/image (5.9x, 批量场景)
|
||
```
|
||
|
||
> 单图理论极限约 2.7 分钟(受自回归解码串行特征限制),批量场景通过粗粒度并行可进一步摊薄。
|
||
|
||
## 已知局限
|
||
|
||
| 问题 | 影响 | 说明 |
|
||
| ------------------ | ----------------------- | ------------------------------------- |
|
||
| CPU 推理极慢 | 单图 ~2.7 min(优化后) | 0.9B VL 模型不适合 CPU 实时场景 |
|
||
| 自回归解码串行 | 无法更细粒度并行 | 生成阶段逐 token 依赖,多线程收益有限 |
|
||
| 内存占用大 | 每进程需 ~2GB | 限制了 `batch_ocr.py` 并行度 |
|
||
| Windows 控制台乱码 | 中文输出显示为乱码 | GBK 编码问题,文件写入/pipe 正常 |
|
||
| ccache 警告 | 无实际影响 | 仅影响首次编译加速,可忽略 |
|