在 RunPod Serverless 上跑 PP-OCRv6 的 OCR 服务,支持图片和 PDF。
模型权重和 Paddle 运行时都在构建时烤进镜像,冷启动只付镜像拉取 + 进程启动的钱, 不会像动态安装方案那样每个新 worker 首次请求要等十分钟。
- 基础镜像:
nvidia/cuda:12.6.3-cudnn-runtime-ubuntu22.04,Paddle 从官方 wheel 装。 不用paddlepaddle/paddle官方镜像:那个镜像压缩后 12.6 GB,在 Serverless 上这不是 便利而是冷启动本身——worker 落到没缓存该镜像的节点时要把它整个拉一遍。 - 模型:
PP-OCRv6_small_*和PP-OCRv6_medium_*两档都烤进镜像(合计 171 MB), 运行时可切换,不必为了对比重新构建 - PDF:
pypdfium2逐页渲染后 OCR,与桌面端 pdfium 同源;所有 pdfium 调用串行化 (见pdf_input.py顶部说明,这是防进程崩溃的必需品,不是保守起见) - 入口:标准
runpod.serverless.start
输入接受 image_url / image_base64 / file_url / file_base64 / 裸 image,
PDF 和图片走同一个入口,按内容的 magic bytes 自动分流。
# 图片
curl -X POST "https://api.runpod.ai/v2/$ENDPOINT_ID/run" \
-H "Authorization: Bearer $RUNPOD_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"input": {"image_base64": "'"$(base64 -i page.png)"'"}}'
# PDF(同一个字段,直接塞 PDF 的 base64)
curl -X POST "https://api.runpod.ai/v2/$ENDPOINT_ID/run" \
-H "Authorization: Bearer $RUNPOD_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"input": {"file_base64": "'"$(base64 -i doc.pdf)"'"}}'返回:
{
"markdown": "全文",
"layout_json": [{"text": "...", "score": 0.99, "box": [...]}],
"pages": [{"page_index": 0, "markdown": "...", "processing_ms": 472.07}],
"meta": {
"input_type": "pdf", "page_count": 4, "processing_ms": 1888.3,
"total_ms": 2103.5, "worker_id": "...",
"det_model": "PP-OCRv6_small_det", "pool_size": 8,
"import_seconds": 6.1, "process_ready_seconds": 14.3
}
}params 里的键会原样传给 PaddleOCR 的 predict(),只在这一次请求生效:
{"input": {"image_base64": "...", "params": {"text_det_box_thresh": 0.75}}}支持 text_det_limit_side_len、text_det_limit_type、text_det_thresh、
text_det_box_thresh、text_det_unclip_ratio、text_rec_score_thresh。
传别的键会报错而不是被忽略——调参脚本里打错一个键,应该看到错误,而不是默默测了两遍默认值。
| 变量 | 默认 | 说明 |
|---|---|---|
OCR_DET_MODEL / OCR_REC_MODEL |
PP-OCRv6_small_det / _rec |
两档权重都在镜像里,改这里即可切换 |
OCR_LIMIT_SIDE_LEN |
960 | 检测阶段长边上限 |
OCR_LIMIT_TYPE |
max | 上限作用于长边(min 则作用于短边) |
OCR_REC_BATCH_SIZE |
16 | 识别批大小 |
OCR_POOL_SIZE |
8 | 单卡上的模型副本数,应与 CONCURRENCY 相同 |
CONCURRENCY |
8 | worker 内并发处理的请求数 |
OCR_INPUT_MAX_EDGE |
1600 | 送进检测前先把图缩到这个长边(与桌面端渲染尺度一致) |
OCR_INPUT_MAX_PIXELS |
4000000 | 输入图超过 4MP 先等比缩小 |
OCR_INPUT_MODE |
ndarray | ndarray 直接传数组;file 写 PNG 再传路径(对照用) |
OCR_ALLOW_TUNE |
0 | 允许运行时重建模型池,见下节。生产环境保持 0 |
PDF_MAX_EDGE |
1600 | PDF 页渲染的目标长边像素 |
PDF_MAX_PAGES |
50 | 单个 PDF 最多处理多少页 |
MAX_INPUT_BYTES |
10485760 | 输入大小上限(10MB) |
OCR_DEVICE |
gpu:0 | 改成 cpu 可在无卡机器上调试 |
权重档位、识别批大小、池大小都是 PaddleOCR 构造期参数,按常规做法扫这些参数要
「改环境变量 → 等滚动更新 → 冷启动」,每个数据点一次冷启动。设 OCR_ALLOW_TUNE=1
后可以直接让热 worker 就地重建:
curl -X POST "https://api.runpod.ai/v2/$ENDPOINT_ID/run" \
-H "Authorization: Bearer $RUNPOD_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"input": {"tune": {"det_model": "PP-OCRv6_medium_det",
"rec_model": "PP-OCRv6_medium_rec",
"pool_size": 8, "rec_batch_size": 2}}}'只改 input_max_edge / input_max_pixels / input_mode 时不重建模型,下一次请求即生效。
这个开关会让任何调用方改变 worker 提供的服务,生产环境必须保持关闭。
无 GPU 也能验证输入解析和 PDF 渲染逻辑:
python3 local_test.py path/to/file.pdf --no-ocr