"""PaddleOCR-VL-1.6 GPU 单图推理与 Benchmark。""" import argparse import json import platform import statistics import sys import time from datetime import datetime from pathlib import Path from typing import Any 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 DEFAULT_IMAGE = PROJECT_ROOT / "images" / "手写01.png" DEFAULT_OUTPUT_DIR = PROJECT_ROOT / "benchmarks" / "gpu" def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="PaddleOCR-VL-1.6 GPU Benchmark") parser.add_argument("--image", type=Path, default=DEFAULT_IMAGE, help="待识别图片") parser.add_argument("--device-id", type=int, default=0, help="CUDA GPU 编号") parser.add_argument("--warmup", type=int, default=1, help="预热轮数") parser.add_argument("--rounds", type=int, default=3, help="正式测试轮数") parser.add_argument( "--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR, help="Benchmark JSON 输出目录", ) parser.add_argument("--no-result", action="store_true", help="不在控制台输出 OCR 文本") parser.add_argument("--log-file", type=Path, default=None, help="日志文件路径") parser.add_argument("--verbose", action="store_true", help="输出详细日志") return parser.parse_args() def validate_args(args: argparse.Namespace) -> None: args.image = args.image.expanduser().resolve() args.output_dir = args.output_dir.expanduser().resolve() if not args.image.is_file(): raise ValueError(f"图片不存在: {args.image}") if args.device_id < 0: raise ValueError("--device-id 不能小于 0") if args.warmup < 0: raise ValueError("--warmup 不能小于 0") if args.rounds < 1: raise ValueError("--rounds 必须大于等于 1") 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 支持。请确认安装的是 paddlepaddle-gpu," "且正在使用 gpu/.venv。" ) 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 >= device_count: raise RuntimeError(f"GPU {device_id} 不存在,当前仅检测到 {device_count} 个 CUDA 设备。") device = f"gpu:{device_id}" try: paddle.set_device(device) paddle.device.cuda.synchronize(device_id) except Exception as exc: raise RuntimeError(f"无法启用 {device}: {exc}") from exc try: device_name = paddle.device.cuda.get_device_name(device_id) except Exception: device_name = "unknown" return paddle, device, device_name def synchronize(paddle: Any, device_id: int) -> None: paddle.device.cuda.synchronize(device_id) def read_gpu_memory(paddle: Any, device_id: int) -> dict[str, float | None]: stats: dict[str, float | None] = { "allocated_mb": None, "reserved_mb": None, "max_allocated_mb": None, "max_reserved_mb": None, } functions = { "allocated_mb": "memory_allocated", "reserved_mb": "memory_reserved", "max_allocated_mb": "max_memory_allocated", "max_reserved_mb": "max_memory_reserved", } for key, function_name in functions.items(): function = getattr(paddle.device.cuda, function_name, None) if function is None: continue try: stats[key] = round(float(function(device_id)) / (1024**2), 2) except Exception: pass return stats def result_summary(result: list[Any]) -> dict[str, Any]: first = result[0] blocks = first["parsing_res_list"] return { "width": first["width"], "height": first["height"], "layout_boxes": len(first["layout_det_res"]["boxes"]), "parsed_blocks": len(blocks), "non_empty_blocks": sum(bool(block.content.strip()) for block in blocks), } def print_ocr_result(result: list[Any]) -> None: print("\n[OCR Result]") for item in result: for block in item["parsing_res_list"]: if block.content.strip(): print(f"[{block.label}] {block.bbox}") print(block.content) print() def main() -> int: program_started = time.perf_counter() args = parse_args() log_file = args.log_file or default_log_path( PROJECT_ROOT, "single", args.image.stem, device=f"gpu{args.device_id}", ) logger = setup_run_logger("ocr.single.gpu", log_file, verbose=args.verbose) logger.info( "PROGRAM_STARTED image=%s device_id=%d warmup=%d rounds=%d output_dir=%s", args.image, args.device_id, args.warmup, args.rounds, args.output_dir, ) try: validate_args(args) cuda_started = time.perf_counter() paddle, device, device_name = configure_cuda(args.device_id) cuda_setup_seconds = time.perf_counter() - cuda_started except (ValueError, RuntimeError) as exc: logger.error("VALIDATION_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( "RUNTIME_READY cuda_setup_seconds=%.3f import_seconds=%.3f device=%s device_name=%s paddle_version=%s image_size_bytes=%d", cuda_setup_seconds, import_seconds, device, device_name, paddle.__version__, args.image.stat().st_size, ) logger.info("MODEL_INITIALIZATION_STARTED pipeline_version=v1.6 device=%s", device) synchronize(paddle, args.device_id) init_started = time.perf_counter() pipeline = PaddleOCRVL(pipeline_version="v1.6", device=device) synchronize(paddle, args.device_id) init_seconds = time.perf_counter() - init_started logger.info("MODEL_INITIALIZED seconds=%.3f", init_seconds) result = None warmup_times: list[float] = [] for index in range(args.warmup): started = time.perf_counter() result = pipeline.predict(str(args.image)) synchronize(paddle, args.device_id) elapsed = time.perf_counter() - started warmup_times.append(elapsed) logger.info("WARMUP_COMPLETED round=%d/%d seconds=%.3f", index + 1, args.warmup, elapsed) inference_times: list[float] = [] for index in range(args.rounds): synchronize(paddle, args.device_id) started = time.perf_counter() result = pipeline.predict(str(args.image)) synchronize(paddle, args.device_id) elapsed = time.perf_counter() - started inference_times.append(elapsed) logger.info("INFERENCE_COMPLETED round=%d/%d seconds=%.3f", index + 1, args.rounds, elapsed) if result is None: logger.error("EMPTY_RESULT") return 2 summary = result_summary(result) benchmark = { "status": "completed", "timestamp": datetime.now().astimezone().isoformat(), "platform": platform.platform(), "python_version": platform.python_version(), "paddle_version": paddle.__version__, "pipeline_version": "v1.6", "device": device, "device_name": device_name, "image_path": str(args.image), "image": summary, "warmup_rounds": args.warmup, "benchmark_rounds": args.rounds, "cuda_setup_seconds": round(cuda_setup_seconds, 3), "runtime_import_seconds": round(import_seconds, 3), "model_init_seconds": round(init_seconds, 3), "warmup_seconds": [round(value, 3) for value in warmup_times], "inference_seconds": { "all": [round(value, 3) for value in inference_times], "min": round(min(inference_times), 3), "max": round(max(inference_times), 3), "mean": round(statistics.fmean(inference_times), 3), "median": round(statistics.median(inference_times), 3), "stdev": round(statistics.pstdev(inference_times), 3), }, "gpu_memory": read_gpu_memory(paddle, args.device_id), "program_total_seconds": round(time.perf_counter() - program_started, 3), "log_file": str(log_file.resolve()), } args.output_dir.mkdir(parents=True, exist_ok=True) timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") output_path = args.output_dir / f"gpu-benchmark-{timestamp}.json" output_path.write_text(json.dumps(benchmark, ensure_ascii=False, indent=2), encoding="utf-8") logger.info( "RESULT_SUMMARY width=%d height=%d layout_boxes=%d parsed_blocks=%d non_empty_blocks=%d gpu_memory=%s", summary["width"], summary["height"], summary["layout_boxes"], summary["parsed_blocks"], summary["non_empty_blocks"], benchmark["gpu_memory"], ) logger.info( "BENCHMARK_SUMMARY cuda_setup_seconds=%.3f import_seconds=%.3f model_init_seconds=%.3f warmup_total_seconds=%.3f inference_total_seconds=%.3f inference_min_seconds=%.3f inference_max_seconds=%.3f inference_mean_seconds=%.3f inference_median_seconds=%.3f inference_stdev_seconds=%.3f program_total_seconds=%.3f result_json=%s log=%s", cuda_setup_seconds, import_seconds, init_seconds, sum(warmup_times), sum(inference_times), min(inference_times), max(inference_times), statistics.fmean(inference_times), statistics.median(inference_times), statistics.pstdev(inference_times), time.perf_counter() - program_started, output_path, log_file.resolve(), ) if not args.no_result: for index, block in enumerate(result[0]["parsing_res_list"], start=1): if block.content.strip(): logger.info( "OCR_BLOCK index=%d label=%s bbox=%s content=%s", index, block.label, block.bbox, block.content.replace("\r", "").replace("\n", "\\n"), ) logger.info("PROGRAM_COMPLETED") return 0 if __name__ == "__main__": raise SystemExit(main())