diff --git a/.gitignore b/.gitignore index 505a3b1..6c2409d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,7 @@ wheels/ # Virtual environments .venv + +# Generated benchmark results +benchmarks/gpu/*.json +!benchmarks/gpu/.gitkeep diff --git a/README.md b/README.md index 051c77c..c4caf74 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ocr-VL1.6 -本地 CPU 部署 [PaddlePaddle/PaddleOCR-VL-1.6](https://github.com/PaddlePaddle/PaddleOCR) 的 OCR 识别项目,包含完整的性能 Benchmark 和多级优化方案。 +本地部署 [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) @@ -8,11 +8,21 @@ ``` ocr-VL1.6/ -├── main.py # 单图 OCR + Benchmark(已集成 set_num_threads 加速) -├── batch_ocr.py # 批量 OCR(多进程并行加速) -├── pyproject.toml # 项目配置(uv 管理) +├── main.py # CPU 单图 OCR + Benchmark +├── batch_ocr.py # CPU 批量 OCR(系统友好的多进程版本) +├── pyproject.toml # CPU 项目依赖 +├── uv.lock # CPU 锁文件 +├── gpu/ # 独立 GPU 子项目 +│ ├── main.py # GPU 单图 Benchmark +│ ├── 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) +│ └── 手写01.png # 测试图片:手写中文(1758×646) └── README.md ``` @@ -20,11 +30,12 @@ ocr-VL1.6/ | 组件 | 版本 | 说明 | | ---------------- | ----- | ---------------------------------------- | -| 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) | +| 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/` @@ -53,6 +64,28 @@ uv run python batch_ocr.py images/ 首次运行会自动从 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)。 + ## 工作原理 `PaddleOCRVL` pipeline 分两阶段: @@ -152,22 +185,41 @@ 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` 为安全值,不会导致系统卡顿。 +> ⚠️ 每个进程独立加载模型(~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 或批量推理收益 --- ### 优化总结 -``` -初始: 238s/image (单线程, 无优化) - │ - ├─ 迭代1: set_num_threads(20) → 162s (1.5x, 单图最优) - │ - └─ 迭代2: batch_ocr.py (4进程) → ~40s/image (5.9x, 批量场景) -``` +| 迭代 | 状态 | 结果 | +|------|------|------| +| CPU 初始版本 | 已实测 | 后续单图约 238s | +| CPU `set_num_threads(20)` | 已实测 | 单图约 162s,约 1.5x 加速 | +| CPU 多进程批量 | 已实现,稳定性依机器而定 | 理论提升批量吞吐;当前没有足够的可靠实测数据支持固定加速比 | +| 独立 GPU 子项目 | 已实现,未实机验证 | 等待 NVIDIA CUDA GPU 测试 | -> 单图理论极限约 2.7 分钟(受自回归解码串行特征限制),批量场景通过粗粒度并行可进一步摊薄。 +> CPU 单图当前实测约 2.7 分钟。批量多进程主要提高总吞吐,不会缩短某一张图片自身的推理延迟。GPU 性能在实机验证前不作预测。 ## 已知局限 @@ -177,4 +229,5 @@ uv run python batch_ocr.py images/ --workers 4 --stagger 30 | 自回归解码串行 | 无法更细粒度并行 | 生成阶段逐 token 依赖,多线程收益有限 | | 内存占用大 | 每进程需 ~2GB | 限制了 `batch_ocr.py` 并行度 | | Windows 控制台乱码 | 中文输出显示为乱码 | GBK 编码问题,文件写入/pipe 正常 | +| GPU 未实机验证 | 暂无 GPU 性能结论 | 当前机器只有集成显卡,需 NVIDIA CUDA GPU 验证 | | ccache 警告 | 无实际影响 | 仅影响首次编译加速,可忽略 | diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 0000000..893c3ce --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,5 @@ +# Benchmark Results + +- `gpu/`: GPU Benchmark JSON,由 `gpu/main.py` 生成。 + +CPU 当前实测数据记录在根目录 `README.md`。后续可将 CPU 脚本也改为输出同结构 JSON,以进行自动对比。 diff --git a/benchmarks/gpu/.gitkeep b/benchmarks/gpu/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/gpu/.python-version b/gpu/.python-version new file mode 100644 index 0000000..2c07333 --- /dev/null +++ b/gpu/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/gpu/README.md b/gpu/README.md new file mode 100644 index 0000000..2e5380a --- /dev/null +++ b/gpu/README.md @@ -0,0 +1,107 @@ +# PaddleOCR-VL-1.6 GPU 子项目 + +此目录是与根目录 CPU 版本隔离的 GPU 实验环境。 + +> **验证状态:未在 NVIDIA GPU 上实测。** 当前开发机器只有集成显卡,无法运行 CUDA。代码仅完成静态检查;最终安装、兼容性和性能必须在目标 NVIDIA GPU 机器上验证。 + +## 为什么独立环境 + +`paddlepaddle` 与 `paddlepaddle-gpu` 都提供 `paddle` 模块,不能安全共用同一个虚拟环境。本目录具有独立的: + +- `pyproject.toml` +- `.python-version`(Python 3.11) +- `.venv`(执行安装后生成) +- `uv.lock`(在目标 GPU 机器安装后生成) + +根目录 CPU 环境不会被修改。 + +## 前置条件 + +1. NVIDIA CUDA GPU(Intel/AMD 集成显卡不能运行 Paddle CUDA 版本) +2. 兼容的 NVIDIA 驱动 +3. Python 3.11 +4. uv +5. 根据 PaddlePaddle 官方兼容表选择 CUDA Wheel + +先检查目标机器: + +```bash +nvidia-smi +``` + +`nvidia-smi` 显示的 CUDA Version 是驱动支持上限,不等同于本机安装的 CUDA Toolkit,也不能单独用于判断 Wheel 版本。 + +## 安装 + +在仓库根目录运行: + +```bash +# 只查看将执行的命令,不安装 +python gpu/setup_env.py --cuda cu118 --dry-run + +# CUDA 11.8 Wheel +python gpu/setup_env.py --cuda cu118 + +# 或 CUDA 12.6 Wheel +python gpu/setup_env.py --cuda cu126 +``` + +安装脚本将在 `gpu/.venv` 创建独立环境。若官方 Wheel 支持范围发生变化,请同步更新 `gpu/pyproject.toml` 与 `gpu/setup_env.py`。 + +## 验证环境 + +```bash +uv run --project gpu python gpu/verify_env.py +``` + +该脚本会检查: + +- PaddlePaddle 是否为 CUDA 构建 +- CUDA GPU 数量及名称 +- `gpu:0` 是否能完成矩阵乘法 + +任何检查失败都会以非零状态退出,不会自动回退到 CPU。 + +## 单图 Benchmark + +```bash +uv run --project gpu python gpu/main.py +``` + +常用参数: + +```bash +uv run --project gpu python gpu/main.py \ + --image images/手写01.png \ + --device-id 0 \ + --warmup 1 \ + --rounds 3 +``` + +Windows PowerShell 可写为单行,或使用反引号续行。 + +Benchmark 会记录: + +- GPU 型号和设备编号 +- Python/PaddlePaddle 版本 +- 模型初始化耗时 +- 预热和正式推理轮数 +- min/max/mean/median/stdev +- 可获取时的 CUDA 显存统计 +- 图片尺寸和文本块数量 + +结果写入: + +```text +benchmarks/gpu/gpu-benchmark-YYYYMMDD-HHMMSS.json +``` + +## 当前范围 + +当前只实现单 GPU、单图 Benchmark。暂未实现 GPU 多进程批处理,原因是: + +- 同一 GPU 上启动多个模型实例会重复占用显存 +- 多进程通常不会线性提升单卡吞吐 +- 容易引发显存不足和 CUDA 上下文争抢 + +后续应优先评估模型/pipeline 原生批处理能力,再决定是否增加多 GPU 或任务队列。 diff --git a/gpu/main.py b/gpu/main.py new file mode 100644 index 0000000..728c4c0 --- /dev/null +++ b/gpu/main.py @@ -0,0 +1,226 @@ +"""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 +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 文本") + 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: + args = parse_args() + try: + validate_args(args) + paddle, device, device_name = configure_cuda(args.device_id) + except (ValueError, RuntimeError) as exc: + print(f"[ERROR] {exc}", file=sys.stderr) + return 1 + + from paddleocr import PaddleOCRVL + + print("=" * 70) + print(f"Device: {device} ({device_name})") + print(f"PaddlePaddle: {paddle.__version__}") + print(f"Input image: {args.image}") + print(f"Warmup/Rounds: {args.warmup}/{args.rounds}") + print("=" * 70) + + 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 + print(f"Model init: {init_seconds:.3f}s") + + result = None + for index in range(args.warmup): + print(f"Warmup {index + 1}/{args.warmup}...", flush=True) + result = pipeline.predict(str(args.image)) + synchronize(paddle, args.device_id) + + 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) + print(f"Inference {index + 1}/{args.rounds}: {elapsed:.3f}s", flush=True) + + if result is None: + print("[ERROR] 未产生推理结果。", file=sys.stderr) + 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, + "model_init_seconds": round(init_seconds, 3), + "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), + } + + 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") + + print("\n[Benchmark]") + print(f"Image: {summary['width']} x {summary['height']}") + print(f"Layout boxes: {summary['layout_boxes']}") + print(f"Parsed blocks: {summary['parsed_blocks']}") + print(f"Average: {benchmark['inference_seconds']['mean']:.3f}s") + print(f"Min/Max: {benchmark['inference_seconds']['min']:.3f}s / {benchmark['inference_seconds']['max']:.3f}s") + print(f"Result JSON: {output_path}") + + if not args.no_result: + print_ocr_result(result) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/gpu/pyproject.toml b/gpu/pyproject.toml new file mode 100644 index 0000000..733f289 --- /dev/null +++ b/gpu/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "ocr-vl1-6-gpu" +version = "0.1.0" +description = "GPU benchmark for PaddleOCR-VL-1.6" +readme = "README.md" +requires-python = ">=3.11,<3.13" +dependencies = [ + "paddleocr[doc-parser]==3.7.0", + "paddlepaddle-gpu==3.2.1", + "setuptools>=83.0.0", +] + +# paddlepaddle-gpu 的实际 Wheel 由 CUDA 版本决定。 +# 不要直接执行无索引参数的 uv sync;请先运行: +# python setup_env.py --cuda cu118 +# 或: +# python setup_env.py --cuda cu126 diff --git a/gpu/setup_env.py b/gpu/setup_env.py new file mode 100644 index 0000000..b47895d --- /dev/null +++ b/gpu/setup_env.py @@ -0,0 +1,84 @@ +"""根据目标 CUDA 版本创建独立的 GPU uv 环境。""" + +import argparse +import shutil +import subprocess +import sys +from pathlib import Path + +PADDLE_INDEXES = { + "cu118": "https://www.paddlepaddle.org.cn/packages/stable/cu118/", + "cu126": "https://www.paddlepaddle.org.cn/packages/stable/cu126/", +} + + +def main() -> int: + parser = argparse.ArgumentParser( + description="安装 PaddleOCR-VL-1.6 GPU 子项目依赖", + ) + parser.add_argument( + "--cuda", + choices=sorted(PADDLE_INDEXES), + required=True, + help="目标机器的 CUDA Wheel 类型;必须依据 PaddlePaddle 官方兼容表选择", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="只显示命令,不创建环境", + ) + parser.add_argument( + "--allow-no-gpu", + action="store_true", + help="允许在未检测到 nvidia-smi 时创建环境(仅用于准备/CI,不代表可运行)", + ) + args = parser.parse_args() + + uv = shutil.which("uv") + if not uv: + print("[ERROR] 未找到 uv,请先安装 uv。", file=sys.stderr) + return 1 + + nvidia_smi = shutil.which("nvidia-smi") + if not args.dry_run and not nvidia_smi and not args.allow_no_gpu: + print( + "[ERROR] 未检测到 nvidia-smi,拒绝在无 NVIDIA GPU 的机器安装 CUDA 依赖。\n" + "如仅准备环境,请显式添加 --allow-no-gpu。", + file=sys.stderr, + ) + return 2 + + project_dir = Path(__file__).resolve().parent + index_url = PADDLE_INDEXES[args.cuda] + command = [ + uv, + "sync", + "--project", + str(project_dir), + "--index", + index_url, + ] + + print(f"目标 CUDA Wheel: {args.cuda}") + print(f"PaddlePaddle 索引: {index_url}") + print("执行命令:") + print(" " + " ".join(command)) + + if args.dry_run: + return 0 + + completed = subprocess.run(command, check=False) + if completed.returncode != 0: + print( + "[ERROR] 依赖安装失败。请检查 GPU、驱动、Python 和 PaddlePaddle Wheel 兼容性。", + file=sys.stderr, + ) + return completed.returncode + + print("\n[OK] GPU 子项目环境已创建。下一步运行:") + print(f' uv run --project "{project_dir}" python "{project_dir / "verify_env.py"}"') + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/gpu/verify_env.py b/gpu/verify_env.py new file mode 100644 index 0000000..b819e7d --- /dev/null +++ b/gpu/verify_env.py @@ -0,0 +1,53 @@ +"""只检查 GPU Paddle 环境,不加载 OCR 模型。""" + +import sys + + +def main() -> int: + try: + import paddle + except ImportError: + print("[ERROR] 未安装 PaddlePaddle GPU。请先运行 setup_env.py。") + return 1 + + print(f"Python: {sys.version.split()[0]}") + print(f"PaddlePaddle: {paddle.__version__}") + print(f"CUDA build: {paddle.is_compiled_with_cuda()}") + + if not paddle.is_compiled_with_cuda(): + print("[ERROR] 当前安装的 PaddlePaddle 不是 CUDA 版本。") + return 2 + + try: + device_count = paddle.device.cuda.device_count() + except Exception as exc: + print(f"[ERROR] 无法查询 CUDA 设备: {exc}") + return 3 + + print(f"CUDA device count: {device_count}") + if device_count < 1: + print("[ERROR] 未检测到可用的 NVIDIA CUDA GPU。") + return 4 + + for device_id in range(device_count): + try: + name = paddle.device.cuda.get_device_name(device_id) + except Exception: + name = "unknown" + print(f"GPU {device_id}: {name}") + + try: + paddle.set_device("gpu:0") + tensor = paddle.ones([1024, 1024], dtype="float32") + result = paddle.matmul(tensor, tensor) + paddle.device.cuda.synchronize() + print(f"CUDA smoke test: OK, result shape={list(result.shape)}") + except Exception as exc: + print(f"[ERROR] CUDA 计算测试失败: {exc}") + return 5 + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())