feat:添加gpu版本
This commit is contained in:
parent
7f804d285e
commit
d63fbbd9c7
|
|
@ -8,3 +8,7 @@ wheels/
|
|||
|
||||
# Virtual environments
|
||||
.venv
|
||||
|
||||
# Generated benchmark results
|
||||
benchmarks/gpu/*.json
|
||||
!benchmarks/gpu/.gitkeep
|
||||
|
|
|
|||
85
README.md
85
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,9 +8,19 @@
|
|||
|
||||
```
|
||||
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)
|
||||
└── README.md
|
||||
|
|
@ -20,8 +30,9 @@ ocr-VL1.6/
|
|||
|
||||
| 组件 | 版本 | 说明 |
|
||||
| ---------------- | ----- | ---------------------------------------- |
|
||||
| Python | 3.13 | |
|
||||
| PaddlePaddle | 3.2.1 | CPU 版(无 CUDA),已编译 oneDNN/MKL-DNN |
|
||||
| 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) |
|
||||
|
|
@ -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 警告 | 无实际影响 | 仅影响首次编译加速,可忽略 |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
# Benchmark Results
|
||||
|
||||
- `gpu/`: GPU Benchmark JSON,由 `gpu/main.py` 生成。
|
||||
|
||||
CPU 当前实测数据记录在根目录 `README.md`。后续可将 CPU 脚本也改为输出同结构 JSON,以进行自动对比。
|
||||
|
|
@ -0,0 +1 @@
|
|||
3.11
|
||||
|
|
@ -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 或任务队列。
|
||||
|
|
@ -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())
|
||||
|
|
@ -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
|
||||
|
|
@ -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())
|
||||
|
|
@ -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())
|
||||
Loading…
Reference in New Issue