Spaces:
Running
Running
File size: 8,350 Bytes
e9015b1 95aaea6 e9015b1 95aaea6 e9015b1 95aaea6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | """Cài torch bản CUDA phù hợp để bật GPU cho CTranslate2.
Engine mặc định (CT2) cần thư viện cuBLAS/cuDNN để chạy GPU; cách đơn giản nhất
là cài bản torch CUDA (đã đóng gói sẵn các DLL đó). Module này:
- chọn channel cu1xx cao nhất mà driver hỗ trợ,
- chạy `pip install torch --index-url ...` vào CHÍNH python đang chạy (sys.executable),
- stream log để UI hiển thị tiến trình.
Sau khi cài xong PHẢI khởi động lại app: torch phải có mặt TRƯỚC khi import
ctranslate2 (xem hardware._guard_ct2_cuda_before_import) thì GPU mới được bật.
"""
from __future__ import annotations
import subprocess
import os
import sys
from typing import Callable, Iterator
# Các channel CUDA mà PyTorch stable phát hành (cao → thấp). Xác minh tại
# https://pytorch.org/get-started/locally/ (hiện: cu118, cu126, cu128).
# (major, minor, "cuXXX")
_TORCH_CUDA_CHANNELS = [
(12, 8, "cu128"),
(12, 6, "cu126"),
(11, 8, "cu118"),
]
def choose_cuda_channel(driver_cuda: str | None) -> str | None:
"""Chọn channel torch cao nhất mà driver còn hỗ trợ (driver_cuda dạng '13.2').
NVIDIA backward-compatible: driver hỗ trợ tới CUDA X chạy được mọi wheel <= X.
Trả None nếu driver quá cũ hơn cả bản thấp nhất (cu118).
"""
if not driver_cuda:
# Không biết driver → chọn bản phổ biến tương thích rộng nhất.
return "cu118"
try:
major, minor = (int(part) for part in driver_cuda.split(".")[:2])
except (ValueError, TypeError):
return "cu118"
for ch_major, ch_minor, channel in _TORCH_CUDA_CHANNELS:
if (major, minor) >= (ch_major, ch_minor):
return channel
return None
def torch_install_command(channel: str) -> list[str]:
# --upgrade --force-reinstall: BẮT BUỘC. Nếu user đã có torch-CPU (vd từ setup
# cũ / requirements-pytorch), `pip install torch` thường báo "already satisfied"
# và KHÔNG ghi đè → cài xong vẫn là CPU. Force-reinstall đảm bảo lấy bản CUDA.
return [
sys.executable,
"-m",
"pip",
"install",
"--upgrade",
"--force-reinstall",
"torch",
"--index-url",
f"https://download.pytorch.org/whl/{channel}",
]
def verify_torch_cuda() -> tuple[bool, str]:
"""Sau khi cài, kiểm tra torch có thật sự thấy CUDA không (subprocess sạch).
Chạy trong tiến trình con KHÔNG bị mask CUDA_VISIBLE_DEVICES=-1 (guard của app
có thể đã set ở tiến trình hiện tại). Bắt trường hợp 'cài xong nhưng vẫn CPU'.
"""
code = (
"import torch,sys;"
"print('TORCH_VERSION='+torch.__version__);"
"print('CUDA_OK='+str(torch.cuda.is_available()))"
)
env = dict(os.environ)
env.pop("CUDA_VISIBLE_DEVICES", None) # bỏ mask để torch nhìn thấy GPU thật
try:
result = subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True,
timeout=120,
env=env,
)
except Exception as exc:
return False, f"Không kiểm tra được torch sau cài: {exc}"
out = result.stdout
version = ""
for line in out.splitlines():
if line.startswith("TORCH_VERSION="):
version = line.split("=", 1)[1]
cuda_ok = "CUDA_OK=True" in out
if cuda_ok:
return True, f"torch {version} đã nhận GPU."
return False, (
f"Đã cài torch {version or '(?)'} nhưng torch.cuda vẫn = False — "
"có thể driver chưa phù hợp hoặc bản torch không khớp. Xem README."
)
def verify_ct2_cuda() -> tuple[bool, str]:
"""Kiểm tra tiến trình mới có thể import torch CUDA rồi import CT2 CUDA.
Đây là smoke test đúng đường app sau restart: torch phải được import trước để
đăng ký thư mục DLL CUDA, rồi CTranslate2 mới dò và nạp CUDA/cuBLAS.
"""
code = (
"import os,sys;"
"os.environ.pop('CUDA_VISIBLE_DEVICES', None);"
"import torch;"
"print('TORCH_VERSION='+torch.__version__);"
"print('TORCH_CUDA_OK='+str(torch.cuda.is_available()));"
"import ctranslate2;"
"count=ctranslate2.get_cuda_device_count();"
"print('CT2_VERSION='+ctranslate2.__version__);"
"print('CT2_CUDA_COUNT='+str(count));"
"types=ctranslate2.get_supported_compute_types('cuda') if count else set();"
"print('CT2_CUDA_TYPES='+','.join(sorted(types)));"
"sys.exit(0 if count > 0 else 2)"
)
env = dict(os.environ)
env.pop("CUDA_VISIBLE_DEVICES", None)
try:
result = subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True,
timeout=120,
env=env,
)
except Exception as exc:
return False, f"Không kiểm tra được CTranslate2 CUDA: {exc}"
stdout = result.stdout.strip()
stderr = result.stderr.strip()
count = ""
version = ""
types = ""
for line in stdout.splitlines():
if line.startswith("CT2_CUDA_COUNT="):
count = line.split("=", 1)[1]
elif line.startswith("CT2_VERSION="):
version = line.split("=", 1)[1]
elif line.startswith("CT2_CUDA_TYPES="):
types = line.split("=", 1)[1]
try:
cuda_count = int(count)
except ValueError:
cuda_count = 0
if result.returncode == 0 and cuda_count > 0:
type_text = f", compute={types}" if types else ""
return True, f"CTranslate2 {version or '(?)'} đã thấy {count} GPU CUDA{type_text}."
detail = stdout or stderr or f"exit={result.returncode}"
return False, (
"torch đã nhận GPU nhưng CTranslate2 chưa qua smoke test CUDA. "
f"Chi tiết: {detail}"
)
def _stream_pip(cmd: list[str]) -> Iterator[str]:
"""Chạy pip, yield từng dòng output (cả stdout/stderr gộp)."""
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
)
assert proc.stdout is not None
for line in proc.stdout:
yield line.rstrip()
proc.wait()
yield f"__EXIT__:{proc.returncode}"
def install_torch_cuda(
driver_cuda: str | None,
on_log: Callable[[str], None] | None = None,
) -> tuple[bool, str]:
"""Cài torch CUDA vào env hiện tại. Trả (thành công, thông điệp cuối).
on_log nhận từng dòng log (để UI cập nhật). Đây là hàm blocking — gọi trong
thread/generator của Gradio, đừng gọi thẳng trên event loop chính.
"""
channel = choose_cuda_channel(driver_cuda)
if channel is None:
return False, (
"Driver NVIDIA quá cũ so với các bản torch CUDA hiện có. "
"Hãy cập nhật driver rồi thử lại, hoặc cài torch thủ công."
)
cmd = torch_install_command(channel)
if on_log:
on_log(f"Cài torch CUDA ({channel}) — tải ~2–3 GB, cần ~5 GB ổ trống, vui lòng đợi…")
on_log(" ".join(cmd))
exit_code: int | None = None
for line in _stream_pip(cmd):
if line.startswith("__EXIT__:"):
exit_code = int(line.split(":", 1)[1])
continue
if on_log and line:
on_log(line)
if exit_code != 0:
return False, (
f"Cài torch thất bại (mã lỗi {exit_code}). "
"Kiểm tra mạng/dung lượng đĩa, hoặc cài thủ công theo README."
)
# Cài xong chưa đủ — xác minh torch và CT2 THẬT SỰ thấy CUDA (bắt 'already
# satisfied', bản không khớp driver, hoặc CT2 không nạp được CUDA/cuBLAS).
ok, verify_msg = verify_torch_cuda()
if not ok:
return False, verify_msg
ct2_ok, ct2_msg = verify_ct2_cuda()
if not ct2_ok:
return False, f"{verify_msg} {ct2_msg}"
return True, (
f"Đã cài torch CUDA ({channel}) — {verify_msg} {ct2_msg} "
"Hãy TẮT và MỞ LẠI app (stop rồi start) để bật GPU."
)
|