fix: fallback from broken GPU OCR in auto mode

This commit is contained in:
OLmatter
2026-06-13 19:33:37 +08:00
parent a787a48ee0
commit 4e5474405c
3 changed files with 94 additions and 13 deletions
+19 -8
View File
@@ -33,10 +33,11 @@ function Has-NvidiaGpu {
return $LASTEXITCODE -eq 0
}
$Selected = $Target
if ($Selected -eq "auto") {
$Selected = if (Has-NvidiaGpu) { "gpu" } else { "cpu" }
$InstallTarget = $Target
if ($InstallTarget -eq "auto") {
$InstallTarget = if (Has-NvidiaGpu) { "gpu" } else { "cpu" }
}
$StartMode = if ($Target -eq "auto") { "auto" } else { $InstallTarget }
$CpuPython = Join-Path $Root ".venv_paddle\Scripts\python.exe"
$GpuPython = Join-Path $Root ".venv_paddle_gpu\Scripts\python.exe"
@@ -44,7 +45,7 @@ $ImportCode = "import ultralytics, paddleocr, paddlex, cv2, PIL, numpy"
$Ready = $false
$SelectedPython = ""
if ($Selected -eq "gpu") {
if ($InstallTarget -eq "gpu") {
$SelectedPython = $GpuPython
$Ready = Test-PythonImports $GpuPython $ImportCode
} else {
@@ -53,8 +54,8 @@ if ($Selected -eq "gpu") {
}
if (-not $Ready) {
Write-Host "Backend environment is missing or incomplete. Installing $Selected environment..."
$argsList = @("-Target", $Selected)
Write-Host "Backend environment is missing or incomplete. Installing $InstallTarget environment..."
$argsList = @("-Target", $InstallTarget)
if ($SelectedPython -and (Test-Path $SelectedPython)) {
Write-Host "Existing backend environment failed import checks. Recreating it..."
$argsList += "-Recreate"
@@ -66,5 +67,15 @@ if (-not $Ready) {
& powershell -NoProfile -ExecutionPolicy Bypass -File "scripts\bootstrap_windows.ps1" @argsList
}
Write-Host "Starting backend in $Selected mode on port $Port..."
& powershell -NoProfile -ExecutionPolicy Bypass -File "scripts\start_backend.ps1" -Mode $Selected -Port $Port
if ($Target -eq "auto" -and $InstallTarget -eq "gpu" -and -not (Test-PythonImports $CpuPython $ImportCode)) {
Write-Host "CPU fallback environment is missing. Installing CPU environment for auto fallback..."
$fallbackArgs = @("-Target", "cpu")
foreach ($arg in $PipArg) {
$fallbackArgs += "-PipArg"
$fallbackArgs += $arg
}
& powershell -NoProfile -ExecutionPolicy Bypass -File "scripts\bootstrap_windows.ps1" @fallbackArgs
}
Write-Host "Starting backend in $StartMode mode on port $Port..."
& powershell -NoProfile -ExecutionPolicy Bypass -File "scripts\start_backend.ps1" -Mode $StartMode -Port $Port
+14
View File
@@ -74,6 +74,20 @@ def smoke_test(py: Path, mode: str) -> None:
"print('cuda_count=', paddle.device.cuda.device_count() if paddle.is_compiled_with_cuda() else 0)"
)
run([str(py), "-c", gpu_code])
gpu_ocr_code = (
"import os; "
f"os.environ['HOME'] = {str(ROOT / '.paddle_home_gpu')!r}; "
f"os.environ['USERPROFILE'] = {str(ROOT / '.paddle_home_gpu')!r}; "
f"os.environ['PADDLE_HOME'] = {str(ROOT / '.paddle_home_gpu' / '.cache' / 'paddle')!r}; "
f"os.environ['PADDLE_PDX_CACHE_HOME'] = {str(ROOT / '.paddlex_cache_gpu')!r}; "
"os.environ.setdefault('PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK', 'True'); "
"from paddleocr import TextRecognition; "
"r = TextRecognition(model_name='PP-OCRv5_server_rec', device='gpu:0', engine='paddle_dynamic'); "
"close = getattr(r, 'close', None); "
"close() if callable(close) else None; "
"print('gpu ocr ok')"
)
run([str(py), "-c", gpu_ocr_code])
def check_assets() -> None:
+61 -5
View File
@@ -74,7 +74,21 @@ def _venv_python(name: str) -> Path:
return ROOT / name / "bin" / "python"
def detect_gpu(timeout: float = 8.0) -> tuple[bool, str]:
def _gpu_probe_env() -> dict[str, str]:
env = os.environ.copy()
paddle_home = ROOT / ".paddle_home_gpu"
paddlex_cache = ROOT / ".paddlex_cache_gpu"
env["HOME"] = str(paddle_home)
env["USERPROFILE"] = str(paddle_home)
env["PADDLE_HOME"] = str(paddle_home / ".cache" / "paddle")
env["PADDLE_PDX_CACHE_HOME"] = str(paddlex_cache)
env.setdefault("PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK", "True")
env.setdefault("PYTHONUTF8", "1")
env.setdefault("PYTHONIOENCODING", "utf-8")
return env
def detect_gpu(timeout: float = 45.0) -> tuple[bool, str]:
if parse_bool(os.environ.get("CNCAPTCHA_SKIP_GPU_DETECT"), False):
return False, "skipped by CNCAPTCHA_SKIP_GPU_DETECT"
@@ -82,6 +96,8 @@ def detect_gpu(timeout: float = 8.0) -> tuple[bool, str]:
if not gpu_python.exists():
return False, f"missing {gpu_python}"
env = _gpu_probe_env()
probe = (
"import paddle; "
"ok = paddle.is_compiled_with_cuda(); "
@@ -92,20 +108,60 @@ def detect_gpu(timeout: float = 8.0) -> tuple[bool, str]:
proc = subprocess.run(
[str(gpu_python), "-c", probe],
cwd=str(ROOT),
env=env,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=timeout,
timeout=min(timeout, 10.0),
check=False,
)
except Exception as exc:
return False, f"probe failed: {exc}"
output = (proc.stdout or "").strip()
if proc.returncode == 0 and output.startswith("ok"):
if not (proc.returncode == 0 and output.startswith("ok")):
reason = output or (proc.stderr or "").strip().splitlines()[-1:] or [f"returncode={proc.returncode}"]
return False, str(reason[0])
if not parse_bool(os.environ.get("CNCAPTCHA_STRICT_GPU_DETECT"), True):
return True, output
reason = output or (proc.stderr or "").strip().splitlines()[-1:] or [f"returncode={proc.returncode}"]
return False, str(reason[0])
model_name = os.environ.get("CNCAPTCHA_GPU_OCR_MODEL", "PP-OCRv5_server_rec")
device = os.environ.get("CNCAPTCHA_GPU_OCR_DEVICE", "gpu:0")
engine = os.environ.get("CNCAPTCHA_GPU_OCR_ENGINE", "paddle_dynamic")
ocr_probe = (
"from paddleocr import TextRecognition; "
f"r = TextRecognition(model_name={model_name!r}, device={device!r}, engine={engine!r}); "
"close = getattr(r, 'close', None); "
"close() if callable(close) else None; "
"print('ocr_ok')"
)
try:
ocr_proc = subprocess.run(
[str(gpu_python), "-c", ocr_probe],
cwd=str(ROOT),
env=env,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=timeout,
check=False,
)
except subprocess.TimeoutExpired:
return False, f"GPU OCR probe timeout after {timeout:.0f}s"
except Exception as exc:
return False, f"GPU OCR probe failed: {exc}"
if ocr_proc.returncode == 0 and "ocr_ok" in (ocr_proc.stdout or ""):
return True, output + "; ocr_ok"
reason_lines = [
line.strip()
for line in ((ocr_proc.stderr or "") + "\n" + (ocr_proc.stdout or "")).splitlines()
if line.strip()
]
reason = reason_lines[-1] if reason_lines else f"returncode={ocr_proc.returncode}"
return False, f"GPU OCR unavailable: {reason}"
def resolve_backend_config(source: str = "env") -> BackendConfig: