PaddleOCR-VL-1.6_Demo/gpu/pdf_ocr.py

215 lines
8.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""GPU entry point for page-by-page PaddleOCR-VL PDF recognition."""
from __future__ import annotations
import argparse
import platform
import sys
import time
from pathlib import Path
GPU_DIR = Path(__file__).resolve().parent
PROJECT_ROOT = GPU_DIR.parent
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from ocr_logging import default_log_path, setup_run_logger
from pdf_ocr_core import preflight_pdf, process_pdf
DEFAULT_OUTPUT = PROJECT_ROOT / "outputs"
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="PaddleOCR-VL-1.6 GPU PDF OCR逐页、可恢复",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("pdf", type=Path, help="输入 PDF 文件")
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT, help="输出根目录")
parser.add_argument("--pages", help="一页或多个页码范围,例如 1-5,8,10-")
parser.add_argument("--dpi", type=int, default=144, help="PDF 页面渲染 DPI")
parser.add_argument("--password", help="加密 PDF 密码")
parser.add_argument("--device-id", type=int, default=0, help="CUDA GPU 编号")
parser.add_argument("--resume", action="store_true", help="跳过已完成页,继续现有任务")
parser.add_argument("--overwrite", action="store_true", help="删除已有输出并重新处理")
parser.add_argument("--keep-rendered", action="store_true", help="保留逐页渲染 PNG")
parser.add_argument("--fail-fast", action="store_true", help="任一页失败后立即停止")
parser.add_argument("--max-new-tokens", type=int, default=None, help="限制每个文本块最大生成 token")
parser.add_argument("--min-pixels", type=int, default=None, help="VLM 最小输入像素参数")
parser.add_argument("--max-pixels", type=int, default=None, help="VLM 最大输入像素参数")
parser.add_argument("--log-file", type=Path, default=None, help="日志文件路径")
parser.add_argument("--verbose", action="store_true", help="输出详细日志")
return parser.parse_args()
def configure_cuda(device_id: int):
try:
import paddle
except ImportError as exc:
raise RuntimeError("未安装 GPU 子项目依赖,请先运行 gpu/setup_env.py。") from exc
if not paddle.is_compiled_with_cuda():
raise RuntimeError("当前 PaddlePaddle 不是 CUDA 构建;本程序不会回退到 CPU。")
try:
device_count = paddle.device.cuda.device_count()
except Exception as exc:
raise RuntimeError(f"无法查询 CUDA 设备: {exc}") from exc
if device_count < 1:
raise RuntimeError("未检测到 NVIDIA CUDA GPU本程序不会回退到 CPU。")
if device_id < 0 or device_id >= device_count:
raise RuntimeError(f"GPU {device_id} 不存在,当前检测到 {device_count} 个设备。")
device = f"gpu:{device_id}"
paddle.set_device(device)
paddle.device.cuda.synchronize(device_id)
try:
name = paddle.device.cuda.get_device_name(device_id)
except Exception:
name = "unknown"
return paddle, device, name
def main() -> int:
program_started = time.perf_counter()
args = parse_args()
log_file = args.log_file or default_log_path(
PROJECT_ROOT,
"pdf",
args.pdf.stem,
device=f"gpu{args.device_id}",
)
logger = setup_run_logger("ocr.pdf.gpu", log_file, verbose=args.verbose)
logger.info(
"PROGRAM_STARTED input=%s output=%s pages=%s dpi=%d device_id=%d resume=%s overwrite=%s keep_rendered=%s fail_fast=%s",
args.pdf,
args.output,
args.pages or "all",
args.dpi,
args.device_id,
args.resume,
args.overwrite,
args.keep_rendered,
args.fail_fast,
)
try:
preflight_started = time.perf_counter()
preflight = preflight_pdf(
pdf_path=args.pdf,
output_root=args.output,
pages=args.pages,
dpi=args.dpi,
password=args.password,
resume=args.resume,
overwrite=args.overwrite,
)
preflight_seconds = time.perf_counter() - preflight_started
cuda_started = time.perf_counter()
paddle, device, device_name = configure_cuda(args.device_id)
cuda_setup_seconds = time.perf_counter() - cuda_started
except Exception as exc:
logger.error("PREFLIGHT_OR_CUDA_FAILED type=%s error=%s", type(exc).__name__, exc, exc_info=args.verbose)
return 1
import_started = time.perf_counter()
from paddleocr import PaddleOCRVL
import_seconds = time.perf_counter() - import_started
logger.info(
"PREFLIGHT_COMPLETED seconds=%.3f page_count=%d selected_pages=%d document_dir=%s",
preflight_seconds,
preflight["page_count"],
len(preflight["selected_pages"]),
preflight["document_dir"],
)
logger.info(
"RUNTIME_READY cuda_setup_seconds=%.3f import_seconds=%.3f device=%s device_name=%s paddle_version=%s",
cuda_setup_seconds,
import_seconds,
device,
device_name,
paddle.__version__,
)
logger.info("MODEL_INITIALIZATION_STARTED pipeline_version=v1.6 device=%s", device)
init_started = time.perf_counter()
pipeline = PaddleOCRVL(pipeline_version="v1.6", device=device)
paddle.device.cuda.synchronize(args.device_id)
init_seconds = time.perf_counter() - init_started
logger.info("MODEL_INITIALIZED seconds=%.3f pipeline_version=v1.6 device=%s", init_seconds, device)
predict_kwargs = {
key: value
for key, value in {
"max_new_tokens": args.max_new_tokens,
"min_pixels": args.min_pixels,
"max_pixels": args.max_pixels,
}.items()
if value is not None
}
metadata = {
"device": device,
"device_name": device_name,
"python_version": platform.python_version(),
"platform": platform.platform(),
"paddle_version": paddle.__version__,
"model_init_seconds": round(init_seconds, 3),
"pipeline_version": "v1.6",
"preflight_seconds": round(preflight_seconds, 3),
"cuda_setup_seconds": round(cuda_setup_seconds, 3),
"runtime_import_seconds": round(import_seconds, 3),
"log_file": str(log_file.resolve()),
}
try:
summary = process_pdf(
pipeline=pipeline,
pdf_path=args.pdf,
output_root=args.output,
pages=args.pages,
dpi=args.dpi,
password=args.password,
resume=args.resume,
overwrite=args.overwrite,
keep_rendered=args.keep_rendered,
fail_fast=args.fail_fast,
run_metadata=metadata,
predict_kwargs=predict_kwargs,
synchronize=lambda: paddle.device.cuda.synchronize(args.device_id),
logger=logger,
)
except KeyboardInterrupt:
logger.warning(
"PROGRAM_INTERRUPTED total_seconds=%.3f resume_hint=--resume",
time.perf_counter() - program_started,
)
return 130
except Exception as exc:
logger.exception(
"PROGRAM_FAILED type=%s error=%s total_seconds=%.3f",
type(exc).__name__,
exc,
time.perf_counter() - program_started,
)
return 1
program_total = time.perf_counter() - program_started
timing = summary.get("timing", {})
logger.info(
"PROGRAM_COMPLETED status=%s completed_pages=%d selected_pages=%d failed_pages=%s model_init_seconds=%.3f pdf_task_seconds=%.3f program_total_seconds=%.3f output=%s log=%s",
summary["status"],
summary["completed_pages"],
summary["selected_pages"],
summary["failed_pages"],
init_seconds,
timing.get("task_total_seconds", 0.0),
program_total,
summary["document_dir"],
log_file.resolve(),
)
return 0 if not summary["failed_pages"] else 3
if __name__ == "__main__":
raise SystemExit(main())