multilingual-chatbot / setup /check_env.py
momenalhamza's picture
Deploy chatbot: code + RAG + Qwen (3 BERT classifiers loaded from HF Hub)
469ef7f verified
"""Environment verification script for the Multilingual Code-Switching Chatbot.
Checks Python version, CUDA availability, GPU info, available disk space,
and prints a clean summary table. Run this before installing dependencies.
"""
from __future__ import annotations
import os
import platform
import shutil
import subprocess
import sys
from typing import Any
# ---------------------------------------------------------------------------
# Individual checks
# ---------------------------------------------------------------------------
def check_python_version() -> dict[str, Any]:
"""Check that Python version is 3.9 or newer."""
major, minor = sys.version_info.major, sys.version_info.minor
ok = (major, minor) >= (3, 9)
return {
"name": "Python version",
"value": f"{major}.{minor}.{sys.version_info.micro}",
"ok": ok,
"detail": "needs >= 3.9" if not ok else "",
}
def check_platform() -> dict[str, Any]:
"""Report basic OS/platform info (always considered OK)."""
return {
"name": "Platform",
"value": f"{platform.system()} {platform.release()} ({platform.machine()})",
"ok": True,
"detail": "",
}
def check_pip() -> dict[str, Any]:
"""Check that pip is available."""
try:
out = subprocess.run(
[sys.executable, "-m", "pip", "--version"],
capture_output=True, text=True, check=True,
)
return {
"name": "pip",
"value": out.stdout.strip().split(" (")[0],
"ok": True,
"detail": "",
}
except Exception as exc: # noqa: BLE001
return {
"name": "pip",
"value": "NOT FOUND",
"ok": False,
"detail": str(exc),
}
def check_torch_and_cuda() -> list[dict[str, Any]]:
"""Check whether torch is installed and whether CUDA is visible.
Returns multiple rows: torch presence, CUDA availability, GPU details.
"""
rows: list[dict[str, Any]] = []
try:
import torch # type: ignore
rows.append({
"name": "PyTorch",
"value": torch.__version__,
"ok": True,
"detail": "",
})
cuda_ok = torch.cuda.is_available()
rows.append({
"name": "CUDA available",
"value": "yes" if cuda_ok else "no (CPU only)",
"ok": True, # CPU-only is acceptable, just informational
"detail": "",
})
if cuda_ok:
rows.append({
"name": "CUDA version (torch)",
"value": torch.version.cuda or "unknown",
"ok": True,
"detail": "",
})
n = torch.cuda.device_count()
for i in range(n):
props = torch.cuda.get_device_properties(i)
vram_gb = props.total_memory / (1024 ** 3)
rows.append({
"name": f"GPU {i}",
"value": f"{props.name} | {vram_gb:.1f} GB VRAM | CC {props.major}.{props.minor}",
"ok": True,
"detail": "",
})
except ImportError:
rows.append({
"name": "PyTorch",
"value": "NOT INSTALLED",
"ok": False,
"detail": "will be installed in setup/install.py",
})
# Fall back to nvidia-smi to still report the GPU if present
rows.extend(_nvidia_smi_fallback())
except Exception as exc: # noqa: BLE001
rows.append({
"name": "PyTorch",
"value": "ERROR",
"ok": False,
"detail": str(exc),
})
return rows
def _nvidia_smi_fallback() -> list[dict[str, Any]]:
"""If torch is unavailable, try nvidia-smi to report GPU presence."""
rows: list[dict[str, Any]] = []
if shutil.which("nvidia-smi") is None:
rows.append({
"name": "nvidia-smi",
"value": "not found (no NVIDIA GPU detected, or driver not installed)",
"ok": True,
"detail": "",
})
return rows
try:
out = subprocess.run(
["nvidia-smi", "--query-gpu=name,memory.total,driver_version",
"--format=csv,noheader"],
capture_output=True, text=True, check=True,
)
for idx, line in enumerate(out.stdout.strip().splitlines()):
parts = [p.strip() for p in line.split(",")]
if len(parts) >= 2:
name = parts[0]
mem = parts[1]
drv = parts[2] if len(parts) > 2 else "?"
rows.append({
"name": f"GPU {idx} (nvidia-smi)",
"value": f"{name} | {mem} | driver {drv}",
"ok": True,
"detail": "",
})
except Exception as exc: # noqa: BLE001
rows.append({
"name": "nvidia-smi",
"value": "ERROR",
"ok": False,
"detail": str(exc),
})
return rows
def check_disk_space(path: str = ".") -> dict[str, Any]:
"""Report free / total disk space at the given path."""
try:
usage = shutil.disk_usage(os.path.abspath(path))
free_gb = usage.free / (1024 ** 3)
total_gb = usage.total / (1024 ** 3)
# 10 GB recommended minimum for models + datasets
ok = free_gb >= 10
return {
"name": "Disk space (cwd)",
"value": f"{free_gb:.1f} GB free / {total_gb:.1f} GB total",
"ok": ok,
"detail": "" if ok else "recommend >= 10 GB free",
}
except Exception as exc: # noqa: BLE001
return {
"name": "Disk space (cwd)",
"value": "ERROR",
"ok": False,
"detail": str(exc),
}
def check_ram() -> dict[str, Any]:
"""Best-effort RAM check via /proc/meminfo (Linux) or psutil if available."""
# Try /proc/meminfo first
meminfo = "/proc/meminfo"
if os.path.exists(meminfo):
try:
with open(meminfo) as f:
lines = f.readlines()
total_kb = 0
avail_kb = 0
for ln in lines:
if ln.startswith("MemTotal:"):
total_kb = int(ln.split()[1])
elif ln.startswith("MemAvailable:"):
avail_kb = int(ln.split()[1])
total_gb = total_kb / (1024 ** 2)
avail_gb = avail_kb / (1024 ** 2)
return {
"name": "RAM",
"value": f"{avail_gb:.1f} GB available / {total_gb:.1f} GB total",
"ok": total_gb >= 4,
"detail": "" if total_gb >= 4 else "recommend >= 4 GB",
}
except Exception: # noqa: BLE001
pass
return {
"name": "RAM",
"value": "unknown",
"ok": True,
"detail": "could not read /proc/meminfo",
}
# ---------------------------------------------------------------------------
# Pretty-printer
# ---------------------------------------------------------------------------
def print_table(rows: list[dict[str, Any]]) -> None:
"""Print a clean fixed-width summary table."""
name_w = max(len(r["name"]) for r in rows) + 2
val_w = max(len(str(r["value"])) for r in rows) + 2
name_w = max(name_w, 22)
val_w = max(val_w, 30)
sep = "+" + "-" * (name_w + 2) + "+" + "-" * (val_w + 2) + "+--------+"
print(sep)
print(f"| {'CHECK'.ljust(name_w)} | {'VALUE'.ljust(val_w)} | STATUS |")
print(sep)
for r in rows:
status = " OK " if r["ok"] else " FAIL "
print(f"| {r['name'].ljust(name_w)} | {str(r['value']).ljust(val_w)} | {status} |")
if r["detail"]:
print(f"| {''.ljust(name_w)} | -> {r['detail'][:val_w-3].ljust(val_w-3)} | |")
print(sep)
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main() -> int:
"""Run all environment checks and return non-zero on hard failure."""
print("=" * 70)
print("Multilingual Chatbot — Environment Check")
print("=" * 70)
rows: list[dict[str, Any]] = []
rows.append(check_python_version())
rows.append(check_platform())
rows.append(check_pip())
rows.extend(check_torch_and_cuda())
rows.append(check_ram())
rows.append(check_disk_space("."))
print_table(rows)
failures = [r for r in rows if not r["ok"]]
if failures:
print("\nHard failures:")
for r in failures:
print(f" - {r['name']}: {r['value']} ({r['detail']})")
# Only Python version + pip are truly blocking. Torch missing is OK.
blocking = [r for r in failures if r["name"] in ("Python version", "pip")]
if blocking:
print("\nBlocking issues found. Please fix before continuing.")
return 1
print("\nNon-blocking issues only — safe to continue to install.py.")
else:
print("\nAll checks passed.")
print("\nSummary:")
print(f" - Working dir : {os.getcwd()}")
print(f" - Python exe : {sys.executable}")
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
print("\nAborted by user.")
sys.exit(130)