429 lines
17 KiB
Markdown
429 lines
17 KiB
Markdown
# ocr-VL1.6
|
||
|
||
本地部署 [PaddlePaddle/PaddleOCR-VL-1.6](https://github.com/PaddlePaddle/PaddleOCR) 的实验项目,包含已实测的 CPU 版本和独立隔离、待 NVIDIA GPU 验证的 GPU 版本。
|
||
|
||
> 在线 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 # CPU 单图 OCR + Benchmark
|
||
├── batch_ocr.py # CPU 批量图片 OCR(系统友好的多进程版本)
|
||
├── pdf_ocr.py # CPU PDF OCR(逐页、可恢复)
|
||
├── pdf_ocr_core.py # CPU/GPU 共用的 PDF 渲染、恢复和导出逻辑
|
||
├── ocr_logging.py # CPU/GPU 共用的 UTF-8 结构化日志工具
|
||
├── pyproject.toml # CPU 项目依赖
|
||
├── uv.lock # CPU 锁文件
|
||
├── gpu/ # 独立 GPU 子项目
|
||
│ ├── main.py # GPU 单图 Benchmark
|
||
│ ├── pdf_ocr.py # GPU PDF OCR(复用公共核心)
|
||
│ ├── verify_env.py # CUDA 环境与计算验证
|
||
│ ├── setup_env.py # 按 CUDA Wheel 类型创建环境
|
||
│ ├── pyproject.toml # GPU 独立依赖
|
||
│ ├── .python-version # GPU 使用 Python 3.11
|
||
│ └── README.md # GPU 安装与运行说明
|
||
├── benchmarks/
|
||
│ └── gpu/ # GPU Benchmark JSON 输出目录
|
||
├── images/
|
||
│ └── 手写01.png # 测试图片:手写中文(1758×646)
|
||
└── README.md
|
||
```
|
||
|
||
## 技术栈
|
||
|
||
| 组件 | 版本 | 说明 |
|
||
| ---------------- | ----- | ---------------------------------------- |
|
||
| Python(CPU) | 3.13 | 根目录独立环境 |
|
||
| Python(GPU) | 3.11 | `gpu/` 独立环境,提升 GPU Wheel 兼容性 |
|
||
| PaddlePaddle | 3.2.1 | CPU 使用 `paddlepaddle`,GPU 使用 `paddlepaddle-gpu` |
|
||
| 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/
|
||
```
|
||
|
||
所有 OCR 入口默认同时输出控制台日志和 UTF-8 日志文件,详见“运行日志”章节。
|
||
|
||
首次运行会自动从 ModelScope 下载模型文件(约 2GB),后续使用缓存。
|
||
|
||
### GPU 子项目
|
||
|
||
> **状态:已实现、未实测。** 当前开发机器只有集成显卡,不能运行 NVIDIA CUDA。GPU 代码已通过语法、CLI 和无 CUDA 安全退出检查,但安装兼容性、显存占用和性能数据必须在目标 NVIDIA GPU 机器上验证。
|
||
|
||
CPU 与 GPU 使用不同虚拟环境,禁止在根目录 CPU `.venv` 中安装 `paddlepaddle-gpu`。
|
||
|
||
```bash
|
||
# 查看 GPU 安装命令,不实际安装
|
||
python gpu/setup_env.py --cuda cu118 --dry-run
|
||
|
||
# 在目标 NVIDIA GPU 机器创建 gpu/.venv;根据官方兼容表选择 cu118 或 cu126
|
||
python gpu/setup_env.py --cuda cu118
|
||
|
||
# 检查 CUDA 构建、GPU 设备和矩阵乘法
|
||
uv run --project gpu python gpu/verify_env.py
|
||
|
||
# 运行 GPU 单图 Benchmark
|
||
uv run --project gpu python gpu/main.py --warmup 1 --rounds 3
|
||
```
|
||
|
||
GPU Benchmark JSON 写入 `benchmarks/gpu/`。详细说明见 [`gpu/README.md`](gpu/README.md)。
|
||
|
||
## 运行日志
|
||
|
||
所有主要入口均使用统一日志格式:
|
||
|
||
```text
|
||
2026-07-16 14:28:02 | INFO | pid=27644 | PAGE_OCR_COMPLETED page=1 seconds=36.345
|
||
```
|
||
|
||
默认日志目录:
|
||
|
||
```text
|
||
logs/
|
||
├── single/ # main.py / gpu/main.py
|
||
├── batch/ # batch_ocr.py
|
||
└── pdf/ # pdf_ocr.py / gpu/pdf_ocr.py
|
||
```
|
||
|
||
默认文件名包含输入名、设备和时间戳,例如:
|
||
|
||
```text
|
||
logs/pdf/sample-cpu-20260716-142802.log
|
||
logs/single/手写01-gpu0-20260716-142802.log
|
||
```
|
||
|
||
可用参数:
|
||
|
||
```bash
|
||
# 指定日志文件
|
||
uv run python main.py images/手写01.png --log-file logs/custom.log
|
||
uv run python pdf_ocr.py documents/sample.pdf --log-file logs/pdf-sample.log
|
||
uv run python batch_ocr.py images/ --log-file logs/batch-images.log
|
||
|
||
# 输出详细异常堆栈和调试日志
|
||
uv run python pdf_ocr.py documents/sample.pdf --verbose
|
||
```
|
||
|
||
日志文件使用 UTF-8 编码。即使 Windows 控制台因 GBK 显示乱码,日志文件中的中文仍可正常查看。
|
||
|
||
### 单图日志统计
|
||
|
||
`main.py` 与 `gpu/main.py` 记录:
|
||
|
||
- 程序启动与输入图片大小
|
||
- Paddle/PaddleOCR 导入耗时
|
||
- CPU 线程数或 GPU/CUDA 初始化耗时
|
||
- 模型初始化耗时
|
||
- 每轮预热耗时
|
||
- 每轮正式推理耗时
|
||
- min/max/mean/median/stdev
|
||
- 图片尺寸、版面框数量、文本块数量
|
||
- OCR 文本块内容(可用 `--no-result` 关闭)
|
||
- 从程序启动到结果输出的总用时
|
||
- GPU 入口额外记录显存统计和 Benchmark JSON 路径
|
||
|
||
### 批量图片日志统计
|
||
|
||
`batch_ocr.py` 记录:
|
||
|
||
- 图片扫描耗时和图片数量
|
||
- Worker 数、每 Worker 线程数和预估内存
|
||
- 每个 Worker 的 PID、错峰等待、框架导入、模型初始化和启动总耗时
|
||
- 每张图片的 Worker PID、推理耗时、尺寸、版面框和文本块数量
|
||
- 任务进度、成功数和失败数
|
||
- Pool 总耗时、串行耗时估计、平均每图耗时和并行加速比
|
||
- 从程序启动到全部结果汇总的总用时
|
||
|
||
### PDF 日志统计
|
||
|
||
`pdf_ocr.py` 与 `gpu/pdf_ocr.py` 记录:
|
||
|
||
- PDF 预检、打开和 manifest 创建耗时
|
||
- 模型初始化耗时
|
||
- 每页渲染耗时
|
||
- 每页 OCR 推理耗时
|
||
- 每页 Markdown/JSON 导出耗时
|
||
- manifest 与合并文件保存耗时
|
||
- 每页总耗时、累计耗时和预计剩余时间(ETA)
|
||
- 每页图片尺寸、版面框数量和文本块数量
|
||
- 完成页、失败页和断点续传前已完成页数
|
||
- 各阶段累计值、平均每页耗时和任务总用时
|
||
- 从程序启动(含模型加载)到退出的程序总用时
|
||
|
||
PDF 的 `manifest.json` 同时包含 `summary.timing`:
|
||
|
||
```json
|
||
{
|
||
"pdf_open_seconds": 0.01,
|
||
"manifest_prepare_seconds": 0.03,
|
||
"render_total_seconds": 1.2,
|
||
"ocr_total_seconds": 324.5,
|
||
"export_total_seconds": 0.8,
|
||
"state_save_total_seconds": 0.2,
|
||
"page_total_seconds": 326.7,
|
||
"average_ocr_seconds": 162.25,
|
||
"average_page_seconds": 163.35,
|
||
"finalize_seconds": 0.1,
|
||
"task_total_seconds": 327.1
|
||
}
|
||
```
|
||
|
||
`task_total_seconds` 是 PDF 核心任务总时间,不含入口模型初始化;完整程序总时间记录在日志的 `PROGRAM_COMPLETED` 事件中。
|
||
|
||
## PDF OCR
|
||
|
||
PDF 使用 `pypdfium2` 逐页渲染,再将每一页交给 PaddleOCR-VL。默认采用安全的单进程串行模式,页面完成后立即保存,适合 CPU 长时间任务。CPU 默认预留 2 个逻辑核心给系统,可通过 `--threads` 覆盖。
|
||
|
||
### CPU 使用
|
||
|
||
```bash
|
||
# 处理整个 PDF,默认 DPI 144
|
||
uv run python pdf_ocr.py documents/sample.pdf
|
||
|
||
# 处理指定页:1-5、8、10 到末页
|
||
uv run python pdf_ocr.py documents/sample.pdf --pages "1-5,8,10-"
|
||
|
||
# 中断后继续,已完成页不会重复推理
|
||
uv run python pdf_ocr.py documents/sample.pdf --resume
|
||
|
||
# 删除已有输出并重新处理
|
||
uv run python pdf_ocr.py documents/sample.pdf --overwrite
|
||
|
||
# 保留每页渲染后的 PNG,便于检查输入质量
|
||
uv run python pdf_ocr.py documents/sample.pdf --keep-rendered
|
||
|
||
# 手动设置 CPU 线程数;长任务建议保留 1~2 个核心给系统
|
||
uv run python pdf_ocr.py documents/sample.pdf --threads 18
|
||
```
|
||
|
||
### GPU 使用
|
||
|
||
GPU 入口与 CPU 入口使用相同的 PDF 核心逻辑,但必须在 `gpu/.venv` 和 NVIDIA CUDA GPU 上运行:
|
||
|
||
```bash
|
||
uv run --project gpu python gpu/pdf_ocr.py documents/sample.pdf \
|
||
--device-id 0 \
|
||
--pages "1-10" \
|
||
--dpi 144
|
||
```
|
||
|
||
当前机器无 NVIDIA 独立显卡,因此 GPU PDF 入口仅完成静态检查,尚未实机验证。
|
||
|
||
### 页码语法
|
||
|
||
| 参数 | 含义 |
|
||
|------|------|
|
||
| `1` | 仅第 1 页 |
|
||
| `1-5` | 第 1~5 页 |
|
||
| `10-` | 第 10 页到最后一页 |
|
||
| `1-5,8,10-` | 多个页码范围组合 |
|
||
|
||
用户页码从 1 开始;内部 manifest 使用同样的一基页码记录。
|
||
|
||
### 输出结构
|
||
|
||
```text
|
||
outputs/
|
||
└── sample/
|
||
├── manifest.json # 任务配置、页状态、耗时和错误
|
||
├── document.md # 合并后的 Markdown
|
||
├── document.json # 合并后的 JSON
|
||
├── pages/
|
||
│ ├── page-0001.md
|
||
│ ├── page-0001.json
|
||
│ └── ...
|
||
├── assets/ # 表格、图片等 Markdown 资源
|
||
└── rendered/ # 仅使用 --keep-rendered 时保留
|
||
```
|
||
|
||
默认不保留中间渲染 PNG;OCR 完成后会删除临时图。每页 JSON 会将 `input_path` 恢复为原 PDF 路径,并记录 `page_index`、`page_number`、`page_count` 和 `render_dpi`。
|
||
|
||
### 恢复与错误处理
|
||
|
||
- 输出目录已存在时,必须显式使用 `--resume` 或 `--overwrite`
|
||
- `--resume` 会校验 PDF SHA-256 和 DPI,防止接续到错误任务
|
||
- 单页失败默认写入 manifest 并继续后续页面
|
||
- `--fail-fast` 可在第一页失败后立即停止
|
||
- `Ctrl+C` 会保存当前 manifest;下次使用 `--resume` 继续
|
||
- 逐页文件和 manifest 使用临时文件替换,降低中途退出造成文件损坏的概率
|
||
|
||
### DPI 建议
|
||
|
||
| 文档类型 | 建议 DPI |
|
||
|----------|---------:|
|
||
| 普通打印文字 | 120~144 |
|
||
| 小字号文档 | 150~200 |
|
||
| 手写或低质量扫描件 | 200~250 |
|
||
|
||
CPU 当前单图实测约 162 秒。长 PDF 总时间可粗略按 `待处理页数 × 单页耗时` 估算,因此建议先用 `--pages "1"` 测试效果和耗时,再扩大页码范围。DPI 越高通常越慢,不建议默认使用 300 DPI。
|
||
|
||
## 工作原理
|
||
|
||
`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 2` 开始测试。默认值是相对保守配置,但是否稳定仍取决于可用内存、散热、后台应用和图片复杂度。
|
||
|
||
---
|
||
|
||
### 迭代 3:独立 GPU 子项目(待实机验证)
|
||
|
||
为避免 `paddlepaddle` 和 `paddlepaddle-gpu` 相互覆盖,在同一仓库新增 `gpu/` 子项目,使用独立 Python、虚拟环境、依赖配置和锁文件。
|
||
|
||
已完成:
|
||
|
||
- `gpu/setup_env.py`:根据 `cu118` / `cu126` Wheel 索引创建环境
|
||
- `gpu/verify_env.py`:检查 CUDA 构建、设备数量并执行 GPU 矩阵乘法
|
||
- `gpu/main.py`:显式指定 `device="gpu:N"`,支持预热、多轮计时和 JSON 输出
|
||
- 无 CUDA 时立即退出,不静默回退到 CPU
|
||
- CPU 环境下已通过 Python 语法、CLI 和安全退出检查
|
||
|
||
尚未验证:
|
||
|
||
- NVIDIA 驱动、CUDA Wheel 与目标 GPU 的兼容性
|
||
- PaddleOCR-VL-1.6 GPU 模型初始化是否正常
|
||
- GPU 显存峰值和真实推理速度
|
||
- FP16/BF16、TensorRT 或批量推理收益
|
||
|
||
---
|
||
|
||
### 优化总结
|
||
|
||
| 迭代 | 状态 | 结果 |
|
||
|------|------|------|
|
||
| CPU 初始版本 | 已实测 | 后续单图约 238s |
|
||
| CPU `set_num_threads(20)` | 已实测 | 单图约 162s,约 1.5x 加速 |
|
||
| CPU 多进程批量 | 已实现,稳定性依机器而定 | 理论提升批量吞吐;当前没有足够的可靠实测数据支持固定加速比 |
|
||
| 独立 GPU 子项目 | 已实现,未实机验证 | 等待 NVIDIA CUDA GPU 测试 |
|
||
|
||
> CPU 单图当前实测约 2.7 分钟。批量多进程主要提高总吞吐,不会缩短某一张图片自身的推理延迟。GPU 性能在实机验证前不作预测。
|
||
|
||
## 已知局限
|
||
|
||
| 问题 | 影响 | 说明 |
|
||
| ------------------ | ----------------------- | ------------------------------------- |
|
||
| CPU 推理极慢 | 单图 ~2.7 min(优化后) | 0.9B VL 模型不适合 CPU 实时场景 |
|
||
| 自回归解码串行 | 无法更细粒度并行 | 生成阶段逐 token 依赖,多线程收益有限 |
|
||
| 内存占用大 | 每进程需 ~2GB | 限制了 `batch_ocr.py` 并行度 |
|
||
| Windows 控制台乱码 | 中文输出显示为乱码 | GBK 编码问题,文件写入/pipe 正常 |
|
||
| GPU 未实机验证 | 暂无 GPU 性能结论 | 当前机器只有集成显卡,需 NVIDIA CUDA GPU 验证 |
|
||
| ccache 警告 | 无实际影响 | 仅影响首次编译加速,可忽略 |
|