File size: 2,707 Bytes
bb59211
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""本机 GPU 读取 —— 直接问 ``nvidia-smi``。

这一层**不认识 HTTP**:它返回普通 dict,由 ``app/routes/training.py`` 直接
交给 FastAPI 序列化。所以它可以被将来任何地方复用(比如训练 runner 起子进程
前先确认显卡可用),而不必绕道走一遍路由。
"""

from __future__ import annotations

import shutil
import subprocess

# nvidia-smi 的查询字段 —— 顺序即解析顺序,改这里必须同步改 _parse 的索引。
_QUERY = "index,name,memory.used,memory.total,utilization.gpu,temperature.gpu"


def _parse(stdout: str) -> list[dict]:
    gpus: list[dict] = []
    for line in stdout.strip().splitlines():
        parts = [p.strip() for p in line.split(",")]
        if len(parts) < 6:
            continue
        try:
            gpus.append({
                "index": int(parts[0]),
                "name": parts[1],
                "memory_used_mb": int(float(parts[2])),
                "memory_total_mb": int(float(parts[3])),
                "utilization": int(float(parts[4])),
                "temperature": int(float(parts[5])),
            })
        except ValueError:
            continue                  # 单卡读不出来不该让整张列表失败
    return gpus


def list_gpus() -> dict:
    """``{"available": bool, "error": str, "gpus": [...]}``。

    **驱动没起来时如实报错,不要返回一排 0。** 实测这台机器上 ``nvidia-smi``
    会以 "couldn't communicate with the NVIDIA driver" 失败;若把它当成
    "利用率 0%、显存 0" 渲染出去,看起来是"显卡闲着可以训",而真相是"根本
    问不到显卡"。前端据此显示明确的错误条 —— `available` 这个字段存在的唯一
    理由就是让调用方**必须**区分这两种情况。
    """
    nvidia_smi = shutil.which("nvidia-smi")
    if not nvidia_smi:
        return {"available": False, "error": "nvidia-smi not found", "gpus": []}

    try:
        proc = subprocess.run(
            [nvidia_smi, f"--query-gpu={_QUERY}", "--format=csv,noheader,nounits"],
            capture_output=True, text=True, timeout=10, check=False,
        )
    except (OSError, subprocess.SubprocessError) as exc:
        return {"available": False, "error": str(exc), "gpus": []}

    if proc.returncode != 0:
        # 驱动没起来走的就是这条 —— nvidia-smi 把原因写在 stderr。
        lines = (proc.stderr or proc.stdout or "").strip().splitlines()
        return {
            "available": False,
            "error": lines[0] if lines else f"nvidia-smi exited {proc.returncode}",
            "gpus": [],
        }

    return {"available": True, "error": "", "gpus": _parse(proc.stdout)}