fix(cuda): учтён отказ по неподдерживаемому типу вычислений
This commit is contained in:
1 parent
453b5d9dfa
commit
a8b8a8c0c9
5 files changed
+70
-15
No files matched your search
@@ -170,8 +170,9 @@ device = "cuda"
|
|||||||
```
|
```
|
||||||
|
|
||||||
Установка extra сама по себе не меняет `auto`: он всегда использует ONNX CPU.
|
Установка extra сама по себе не меняет `auto`: он всегда использует ONNX CPU.
|
||||||
Явная CUDA при ошибке завершает обработку с исходной причиной и подсказкой,
|
Явная CUDA при ошибке завершает обработку с исходной причиной; для распознанных
|
||||||
без молчаливого перехода на CPU. В батче ошибка учитывается для каждого файла.
|
причин выводится подсказка. Молчаливого перехода на CPU нет. В батче ошибка
|
||||||
|
учитывается для каждого файла.
|
||||||
|
|
||||||
На Windows нужен также [Visual C++ Runtime x64](https://aka.ms/vs/17/release/vc_redist.x64.exe).
|
На Windows нужен также [Visual C++ Runtime x64](https://aka.ms/vs/17/release/vc_redist.x64.exe).
|
||||||
Состав DLL проверен для закреплённых версий CTranslate2 и cuBLAS;
|
Состав DLL проверен для закреплённых версий CTranslate2 и cuBLAS;
|
||||||
|
|||||||
@@ -59,7 +59,9 @@ preload всё равно выполняется до создания CUDA-мо
|
|||||||
Явный `device` из CLI или TOML передаёт `strict_device=True` при загрузке и
|
Явный `device` из CLI или TOML передаёт `strict_device=True` при загрузке и
|
||||||
транскрипции. CUDA не заменяется молча на CPU. Подсказки различают отсутствие
|
транскрипции. CUDA не заменяется молча на CPU. Подсказки различают отсутствие
|
||||||
библиотек, проблемы драйвера, несовместимость GPU/runtime и нехватку памяти;
|
библиотек, проблемы драйвера, несовместимость GPU/runtime и нехватку памяти;
|
||||||
исходное сообщение сохраняется. Неизвестная ошибка не получает произвольный
|
отказ по неподдерживаемому типу вычислений учитывает устройство, чтобы
|
||||||
|
одинаковая ошибка CTranslate2 на CPU не получала CUDA-подсказку. Исходное
|
||||||
|
сообщение сохраняется. Неизвестная ошибка не получает произвольный
|
||||||
совет установить Toolkit. Это одинаково для одного файла и батча.
|
совет установить Toolkit. Это одинаково для одного файла и батча.
|
||||||
|
|
||||||
Прежний механизм fallback оркестратора при `strict_device=False` сохранён
|
Прежний механизм fallback оркестратора при `strict_device=False` сохранён
|
||||||
@@ -82,8 +84,11 @@ preload всё равно выполняется до создания CUDA-мо
|
|||||||
- `nvidia_cublas_cu12-12.9.1.4-py3-none-win_amd64.whl`, SHA-256
|
- `nvidia_cublas_cu12-12.9.1.4-py3-none-win_amd64.whl`, SHA-256
|
||||||
`1e5fee10662e6e52bd71dec533fbbd4971bb70a5f24f3bc3793e5c2e9dc640bf`.
|
`1e5fee10662e6e52bd71dec533fbbd4971bb70a5f24f3bc3793e5c2e9dc640bf`.
|
||||||
DLL находятся в `nvidia/cublas/bin`. cuBLAS импортирует cuBLAS Lt и
|
DLL находятся в `nvidia/cublas/bin`. cuBLAS импортирует cuBLAS Lt и
|
||||||
KERNEL32; cuBLAS Lt импортирует KERNEL32. `nvblas64_12.dll` не требуется
|
KERNEL32; cuBLAS Lt импортирует KERNEL32. В строках cuBLAS Lt также найдена
|
||||||
проверенному пути CTranslate2.
|
динамическая ссылка на `nvrtc64_120_0.dll`, которой нет в PE imports.
|
||||||
|
Обязательность NVRTC для Whisper этим статическим анализом не установлена;
|
||||||
|
первый реальный Windows-прогон должен проверить и этот путь загрузки.
|
||||||
|
`nvblas64_12.dll` не требуется проверенному пути CTranslate2.
|
||||||
|
|
||||||
Основания: [CTranslate2 4.8.1 на PyPI](https://pypi.org/project/ctranslate2/4.8.1/#files),
|
Основания: [CTranslate2 4.8.1 на PyPI](https://pypi.org/project/ctranslate2/4.8.1/#files),
|
||||||
[cuBLAS 12.9.1.4 на PyPI](https://pypi.org/project/nvidia-cublas-cu12/12.9.1.4/#files),
|
[cuBLAS 12.9.1.4 на PyPI](https://pypi.org/project/nvidia-cublas-cu12/12.9.1.4/#files),
|
||||||
@@ -97,7 +102,7 @@ Windows API не заменяют такую проверку.
|
|||||||
|
|
||||||
## Проверка поставки и поведения
|
## Проверка поставки и поведения
|
||||||
|
|
||||||
На Linux выполнены `uv run pytest`: 500 passed, 2 skipped. Пропуски —
|
На Linux выполнены `uv run pytest`: 524 passed, 2 skipped. Пропуски —
|
||||||
Windows-only кодировка OEM и интеграционный bootstrap без optional-пакета.
|
Windows-only кодировка OEM и интеграционный bootstrap без optional-пакета.
|
||||||
До удаления cuBLAS интеграционный тест Linux bootstrap проходил.
|
До удаления cuBLAS интеграционный тест Linux bootstrap проходил.
|
||||||
Обычный `uv sync --locked` удаляет прежнюю обязательную cuBLAS;
|
Обычный `uv sync --locked` удаляет прежнюю обязательную cuBLAS;
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ app = typer.Typer()
|
|||||||
console = Console(stderr=True)
|
console = Console(stderr=True)
|
||||||
|
|
||||||
|
|
||||||
def _print_cuda_hint(exc: BaseException) -> None:
|
def _print_cuda_hint(exc: BaseException, device: str | None = None) -> None:
|
||||||
"""Показывает подсказку, не заменяя исходную ошибку."""
|
"""Показывает подсказку, не заменяя исходную ошибку."""
|
||||||
hint = cuda_error_hint(exc)
|
hint = cuda_error_hint(exc, device=device)
|
||||||
if hint:
|
if hint:
|
||||||
console.print(hint, style="yellow", markup=False)
|
console.print(hint, style="yellow", markup=False)
|
||||||
|
|
||||||
@@ -348,6 +348,7 @@ def main(
|
|||||||
)
|
)
|
||||||
raise SystemExit(2)
|
raise SystemExit(2)
|
||||||
|
|
||||||
|
resolved_device: str | None = None
|
||||||
try:
|
try:
|
||||||
config = load_config()
|
config = load_config()
|
||||||
cli_values: CliValues = {
|
cli_values: CliValues = {
|
||||||
@@ -405,15 +406,15 @@ def main(
|
|||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
_print_cuda_hint(exc)
|
_print_cuda_hint(exc, resolved_device)
|
||||||
console.print(f"Ошибка: {exc}", style="red bold")
|
console.print(f"Ошибка: {exc}", style="red bold")
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
except (FileNotFoundError,) as exc:
|
except (FileNotFoundError,) as exc:
|
||||||
_print_cuda_hint(exc)
|
_print_cuda_hint(exc, resolved_device)
|
||||||
console.print(f"Ошибка: {exc}", style="red bold")
|
console.print(f"Ошибка: {exc}", style="red bold")
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
_print_cuda_hint(exc)
|
_print_cuda_hint(exc, resolved_device)
|
||||||
if verbose:
|
if verbose:
|
||||||
console.print_exception()
|
console.print_exception()
|
||||||
else:
|
else:
|
||||||
@@ -759,7 +760,7 @@ def _run_batch(
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise
|
raise
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
_print_cuda_hint(exc)
|
_print_cuda_hint(exc, actual_device)
|
||||||
if verbose:
|
if verbose:
|
||||||
console.print_exception()
|
console.print_exception()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -195,9 +195,17 @@ def _is_cuda_error(exc: BaseException) -> bool:
|
|||||||
return any(k in msg for k in ("cuda", "cublas", "cudnn", "out of memory"))
|
return any(k in msg for k in ("cuda", "cublas", "cudnn", "out of memory"))
|
||||||
|
|
||||||
|
|
||||||
def cuda_error_hint(exc: BaseException) -> str | None:
|
def cuda_error_hint(exc: BaseException, *, device: str | None = None) -> str | None:
|
||||||
"""Подсказывает действие только для распознанной причины CUDA-ошибки."""
|
"""Подсказывает действие только для распознанной причины CUDA-ошибки."""
|
||||||
msg = str(exc).lower()
|
msg = str(exc).lower()
|
||||||
|
if device == "cuda" and "requested " in msg and "compute type" in msg and (
|
||||||
|
"do not support efficient" in msg
|
||||||
|
):
|
||||||
|
return (
|
||||||
|
"Тип вычислений несовместим с выбранным GPU/CUDA runtime. "
|
||||||
|
"Установка extra не добавит аппаратную поддержку. Выберите "
|
||||||
|
"поддерживаемый --compute-type или --device onnx / --device cpu."
|
||||||
|
)
|
||||||
if any(k in msg for k in (
|
if any(k in msg for k in (
|
||||||
"no kernel image", "invalid device function", "unsupported gpu",
|
"no kernel image", "invalid device function", "unsupported gpu",
|
||||||
"cublas_status_arch_mismatch", "cuda_error_no_binary_for_gpu",
|
"cublas_status_arch_mismatch", "cuda_error_no_binary_for_gpu",
|
||||||
|
|||||||
+42
-2
@@ -805,14 +805,54 @@ def test_default_cli_stays_on_onnx_with_nvidia_driver(tmp_path, monkeypatch):
|
|||||||
patch("shutil.which", return_value="/usr/bin/nvidia-smi"),
|
patch("shutil.which", return_value="/usr/bin/nvidia-smi"),
|
||||||
patch("local_transcriber.transcriber.get_backend", return_value=backend) as get_backend,
|
patch("local_transcriber.transcriber.get_backend", return_value=backend) as get_backend,
|
||||||
patch("local_transcriber.cli.write_transcript"),
|
patch("local_transcriber.cli.write_transcript"),
|
||||||
patch("local_transcriber._cuda_bootstrap.ensure_cublas_loadable") as bootstrap,
|
|
||||||
):
|
):
|
||||||
out = runner.invoke(app, [str(audio)])
|
out = runner.invoke(app, [str(audio)])
|
||||||
|
|
||||||
assert out.exit_code == 0, out.output
|
assert out.exit_code == 0, out.output
|
||||||
get_backend.assert_called_once_with("onnx", compute_type_explicit=False)
|
get_backend.assert_called_once_with("onnx", compute_type_explicit=False)
|
||||||
assert backend.ensure_model_available.call_args.args[:2] == ("gigaam-v3-e2e-rnnt", "int8")
|
assert backend.ensure_model_available.call_args.args[:2] == ("gigaam-v3-e2e-rnnt", "int8")
|
||||||
bootstrap.assert_not_called()
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("from_config", [False, True])
|
||||||
|
@pytest.mark.parametrize("phase", ["load", "single", "batch"])
|
||||||
|
@pytest.mark.parametrize("device", ["cuda", "cpu"])
|
||||||
|
@pytest.mark.parametrize("compute_type", ["float16", "int8"])
|
||||||
|
def test_unsupported_compute_type_hint_requires_cuda_context(
|
||||||
|
tmp_path, monkeypatch, from_config, phase, device, compute_type
|
||||||
|
):
|
||||||
|
"""Одинаковая ошибка CTranslate2 получает CUDA-подсказку только на CUDA."""
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
files = [tmp_path / "one.wav"]
|
||||||
|
if phase == "batch":
|
||||||
|
files.append(tmp_path / "two.wav")
|
||||||
|
for file in files:
|
||||||
|
file.write_bytes(b"audio")
|
||||||
|
args = [str(file) for file in files]
|
||||||
|
if from_config:
|
||||||
|
(tmp_path / ".transcriber.toml").write_text(
|
||||||
|
f'device = "{device}"\ncompute_type = "{compute_type}"\n'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
args += ["--device", device, "--compute-type", compute_type]
|
||||||
|
message = (
|
||||||
|
f"Requested {compute_type} compute type, but the target device or backend "
|
||||||
|
f"do not support efficient {compute_type} computation."
|
||||||
|
)
|
||||||
|
backend = _make_backend()
|
||||||
|
error = ValueError(message)
|
||||||
|
if phase == "load":
|
||||||
|
backend.create_model.side_effect = error
|
||||||
|
else:
|
||||||
|
backend.transcribe.side_effect = error
|
||||||
|
|
||||||
|
with patch("local_transcriber.transcriber.get_backend", return_value=backend):
|
||||||
|
out = runner.invoke(app, args)
|
||||||
|
|
||||||
|
assert out.exit_code == 1
|
||||||
|
assert message in " ".join(out.output.split())
|
||||||
|
assert ("Тип вычислений несовместим" in out.output) is (device == "cuda")
|
||||||
|
assert "uv sync --extra cuda" not in out.output
|
||||||
|
assert "Переключение на CPU" not in out.output
|
||||||
|
|
||||||
|
|
||||||
def test_cli_device_fallback_warning(tmp_path):
|
def test_cli_device_fallback_warning(tmp_path):
|
||||||
|
|||||||
Reference in new issue
Block a user