hardware_survey
This commit is contained in:
501
Deep-SAD-PyTorch/hardware_survey/main.py
Normal file
501
Deep-SAD-PyTorch/hardware_survey/main.py
Normal file
@@ -0,0 +1,501 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Generate a LaTeX longtable with system + software info for a thesis (Linux + NVIDIA).
|
||||||
|
|
||||||
|
Requirements (preflight will check and error if missing):
|
||||||
|
- Linux OS
|
||||||
|
- lscpu (util-linux)
|
||||||
|
- Python packages: nvidia-ml-py3 (pynvml), torch, numpy, scipy, scikit-learn
|
||||||
|
- NVIDIA driver present and at least one GPU visible via NVML
|
||||||
|
|
||||||
|
What it reports (per user’s list):
|
||||||
|
System:
|
||||||
|
- OS name + version + distribution (Linux) + kernel version + system arch
|
||||||
|
- CPU model name, number of cores and threads, base frequencies (best-effort via lscpu)
|
||||||
|
- Total RAM capacity
|
||||||
|
- GPU(s): model name (only the newer one; prefer a name matching “4090”, else highest compute capability),
|
||||||
|
memory size, driver version, CUDA (driver) version, cuDNN version (if used via PyTorch)
|
||||||
|
|
||||||
|
Software environment:
|
||||||
|
- Python version
|
||||||
|
- PyTorch version + built CUDA/cuDNN version
|
||||||
|
- scikit-learn version
|
||||||
|
- NumPy / SciPy version (+ NumPy build config summary: MKL/OpenBLAS/etc.)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
|
# -------------------- Helper --------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _import_nvml():
|
||||||
|
"""
|
||||||
|
Try to import NVML from the supported packages:
|
||||||
|
- 'nvidia-ml-py' (preferred, maintained): provides module 'pynvml'
|
||||||
|
- legacy 'pynvml' (deprecated but still widely installed)
|
||||||
|
Returns the imported module object (with nvml... symbols).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import pynvml as _nvml # provided by 'nvidia-ml-py' or old 'pynvml'
|
||||||
|
|
||||||
|
return _nvml
|
||||||
|
except Exception as e:
|
||||||
|
raise ImportError(
|
||||||
|
"NVML not importable. Please install the maintained package:\n"
|
||||||
|
" pip install nvidia-ml-py\n"
|
||||||
|
"(and uninstall deprecated ones: pip uninstall nvidia-ml-py3 pynvml)"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
|
||||||
|
def _to_text(x) -> str:
|
||||||
|
"""Return a clean str whether NVML gives bytes or str."""
|
||||||
|
if isinstance(x, bytes):
|
||||||
|
try:
|
||||||
|
return x.decode(errors="ignore")
|
||||||
|
except Exception:
|
||||||
|
return x.decode("utf-8", "ignore")
|
||||||
|
return str(x)
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------- Utilities --------------------
|
||||||
|
|
||||||
|
|
||||||
|
def which(cmd: str) -> str:
|
||||||
|
return shutil.which(cmd) or ""
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd: List[str], timeout: int = 6) -> str:
|
||||||
|
try:
|
||||||
|
out = subprocess.check_output(cmd, stderr=subprocess.STDOUT, timeout=timeout)
|
||||||
|
return out.decode(errors="ignore").strip()
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def human_bytes(nbytes: int) -> str:
|
||||||
|
try:
|
||||||
|
n = float(nbytes)
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
units = ["B", "KiB", "MiB", "GiB", "TiB"]
|
||||||
|
i = 0
|
||||||
|
while n >= 1024 and i < len(units) - 1:
|
||||||
|
n /= 1024.0
|
||||||
|
i += 1
|
||||||
|
return f"{n:.2f} {units[i]}"
|
||||||
|
|
||||||
|
|
||||||
|
LATEX_SPECIALS = {
|
||||||
|
"\\": r"\textbackslash{}",
|
||||||
|
"&": r"\&",
|
||||||
|
"%": r"\%",
|
||||||
|
"$": r"\$",
|
||||||
|
"#": r"\#",
|
||||||
|
"_": r"\_",
|
||||||
|
"{": r"\{",
|
||||||
|
"}": r"\}",
|
||||||
|
"~": r"\textasciitilde{}",
|
||||||
|
"^": r"\textasciicircum{}",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def tex_escape(s: str) -> str:
|
||||||
|
if s is None:
|
||||||
|
return ""
|
||||||
|
return "".join(LATEX_SPECIALS.get(ch, ch) for ch in str(s))
|
||||||
|
|
||||||
|
|
||||||
|
def latex_table(sections: List[Tuple[str, Dict[str, str]]], caption: str) -> str:
|
||||||
|
lines = []
|
||||||
|
lines.append(r"\begin{table}[p]") # float; use [p] or [tbp] as you prefer
|
||||||
|
lines.append(r"\centering")
|
||||||
|
lines.append(r"\caption{" + tex_escape(caption) + r"} \label{tab:system_setup}")
|
||||||
|
lines.append(r"\begin{tabular}{p{0.34\linewidth} p{0.62\linewidth}}")
|
||||||
|
lines.append(r"\toprule")
|
||||||
|
lines.append(r"\textbf{Item} & \textbf{Details} \\")
|
||||||
|
lines.append(r"\midrule")
|
||||||
|
|
||||||
|
for title, kv in sections:
|
||||||
|
if not kv:
|
||||||
|
continue
|
||||||
|
lines.append(r"\multicolumn{2}{l}{\textbf{" + tex_escape(title) + r"}} \\")
|
||||||
|
for k, v in kv.items():
|
||||||
|
val = tex_escape(v)
|
||||||
|
if "\n" in v or len(v) > 120:
|
||||||
|
val = (
|
||||||
|
r"\begin{minipage}[t]{\linewidth}\ttfamily\small "
|
||||||
|
+ tex_escape(v)
|
||||||
|
+ r"\end{minipage}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
val = r"\ttfamily " + val
|
||||||
|
lines.append(tex_escape(k) + " & " + val + r" \\")
|
||||||
|
lines.append(r"\addlinespace")
|
||||||
|
|
||||||
|
lines.append(r"\bottomrule")
|
||||||
|
lines.append(r"\end{tabular}")
|
||||||
|
lines.append(r"\end{table}")
|
||||||
|
|
||||||
|
preamble_hint = r"""
|
||||||
|
% ---- Add to your LaTeX preamble ----
|
||||||
|
% \usepackage{booktabs}
|
||||||
|
% \usepackage{array}
|
||||||
|
% ------------------------------------
|
||||||
|
"""
|
||||||
|
return preamble_hint + "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def latex_longtable(sections: List[Tuple[str, Dict[str, str]]], caption: str) -> str:
|
||||||
|
lines = []
|
||||||
|
lines.append(r"\begin{longtable}{p{0.34\linewidth} p{0.62\linewidth}}")
|
||||||
|
lines.append(r"\caption{" + tex_escape(caption) + r"} \label{tab:system_setup}\\")
|
||||||
|
lines.append(r"\toprule")
|
||||||
|
lines.append(r"\textbf{Item} & \textbf{Details} \\")
|
||||||
|
lines.append(r"\midrule")
|
||||||
|
lines.append(r"\endfirsthead")
|
||||||
|
lines.append(r"\toprule \textbf{Item} & \textbf{Details} \\ \midrule")
|
||||||
|
lines.append(r"\endhead")
|
||||||
|
lines.append(r"\bottomrule")
|
||||||
|
lines.append(r"\endfoot")
|
||||||
|
lines.append(r"\bottomrule")
|
||||||
|
lines.append(r"\endlastfoot")
|
||||||
|
|
||||||
|
for title, kv in sections:
|
||||||
|
if not kv:
|
||||||
|
continue
|
||||||
|
lines.append(r"\multicolumn{2}{l}{\textbf{" + tex_escape(title) + r"}} \\")
|
||||||
|
for k, v in kv.items():
|
||||||
|
val = tex_escape(v)
|
||||||
|
if "\n" in v or len(v) > 120:
|
||||||
|
val = (
|
||||||
|
r"\begin{minipage}[t]{\linewidth}\ttfamily\small "
|
||||||
|
+ tex_escape(v)
|
||||||
|
+ r"\end{minipage}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
val = r"\ttfamily " + val
|
||||||
|
lines.append(tex_escape(k) + " & " + val + r" \\")
|
||||||
|
lines.append(r"\addlinespace")
|
||||||
|
lines.append(r"\end{longtable}")
|
||||||
|
|
||||||
|
preamble_hint = r"""
|
||||||
|
% ---- Add to your LaTeX preamble ----
|
||||||
|
% \usepackage{booktabs}
|
||||||
|
% \usepackage{longtable}
|
||||||
|
% \usepackage{array}
|
||||||
|
% ------------------------------------
|
||||||
|
"""
|
||||||
|
return preamble_hint + "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------- Preflight --------------------
|
||||||
|
|
||||||
|
REQUIRED_CMDS = ["lscpu"]
|
||||||
|
REQUIRED_MODULES = [
|
||||||
|
"torch",
|
||||||
|
"numpy",
|
||||||
|
"scipy",
|
||||||
|
"sklearn",
|
||||||
|
"pynvml",
|
||||||
|
] # provided by nvidia-ml-py
|
||||||
|
|
||||||
|
|
||||||
|
def preflight() -> List[str]:
|
||||||
|
errors = []
|
||||||
|
if platform.system().lower() != "linux":
|
||||||
|
errors.append(
|
||||||
|
f"This script supports Linux only (detected: {platform.system()})."
|
||||||
|
)
|
||||||
|
|
||||||
|
for c in ["lscpu"]:
|
||||||
|
if not which(c):
|
||||||
|
errors.append(f"Missing required command: {c}")
|
||||||
|
|
||||||
|
for m in REQUIRED_MODULES:
|
||||||
|
try:
|
||||||
|
__import__(m)
|
||||||
|
except Exception:
|
||||||
|
errors.append(f"Missing required Python package: {m}")
|
||||||
|
|
||||||
|
# NVML driver availability
|
||||||
|
if "pynvml" not in errors:
|
||||||
|
try:
|
||||||
|
pynvml = _import_nvml()
|
||||||
|
pynvml.nvmlInit()
|
||||||
|
count = pynvml.nvmlDeviceGetCount()
|
||||||
|
if count < 1:
|
||||||
|
errors.append("No NVIDIA GPUs detected by NVML.")
|
||||||
|
pynvml.nvmlShutdown()
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"NVIDIA NVML not available / driver not loaded: {e}")
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------- Collectors --------------------
|
||||||
|
|
||||||
|
|
||||||
|
def collect_system() -> Dict[str, str]:
|
||||||
|
info: Dict[str, str] = {}
|
||||||
|
|
||||||
|
# OS / distro / kernel / arch
|
||||||
|
os_pretty = ""
|
||||||
|
try:
|
||||||
|
with open("/etc/os-release", "r") as f:
|
||||||
|
txt = f.read()
|
||||||
|
m = re.search(r'^PRETTY_NAME="?(.*?)"?$', txt, flags=re.M)
|
||||||
|
if m:
|
||||||
|
os_pretty = m.group(1)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
info["Operating System"] = os_pretty or f"{platform.system()} {platform.release()}"
|
||||||
|
info["Kernel"] = platform.release()
|
||||||
|
info["Architecture"] = platform.machine()
|
||||||
|
|
||||||
|
# CPU (via lscpu)
|
||||||
|
lscpu = run(["lscpu"])
|
||||||
|
|
||||||
|
def kvs(text: str) -> Dict[str, str]:
|
||||||
|
out = {}
|
||||||
|
for line in text.splitlines():
|
||||||
|
if ":" in line:
|
||||||
|
k, v = line.split(":", 1)
|
||||||
|
out[k.strip()] = v.strip()
|
||||||
|
return out
|
||||||
|
|
||||||
|
d = kvs(lscpu)
|
||||||
|
info["CPU Model"] = d.get("Model name", d.get("Model Name", ""))
|
||||||
|
|
||||||
|
# cores / threads
|
||||||
|
sockets = d.get("Socket(s)", "")
|
||||||
|
cores_per_socket = d.get("Core(s) per socket", "")
|
||||||
|
threads_total = d.get("CPU(s)", "")
|
||||||
|
if sockets and cores_per_socket:
|
||||||
|
info["CPU Cores (physical)"] = f"{cores_per_socket} × {sockets}"
|
||||||
|
else:
|
||||||
|
info["CPU Cores (physical)"] = cores_per_socket or ""
|
||||||
|
info["CPU Threads (logical)"] = threads_total or str(os.cpu_count() or "")
|
||||||
|
|
||||||
|
# base / max freq
|
||||||
|
# Prefer "CPU max MHz" and "CPU min MHz"; lscpu sometimes exposes "CPU MHz" (current)
|
||||||
|
base = d.get("CPU min MHz", "")
|
||||||
|
maxf = d.get("CPU max MHz", "")
|
||||||
|
if base:
|
||||||
|
info["CPU Base Frequency"] = f"{float(base):.0f} MHz"
|
||||||
|
elif "@" in info["CPU Model"]:
|
||||||
|
# fallback: parse from model string like "Intel(R) ... @ 2.30GHz"
|
||||||
|
m = re.search(r"@\s*([\d.]+)\s*([GM]Hz)", info["CPU Model"])
|
||||||
|
if m:
|
||||||
|
info["CPU Base Frequency"] = f"{m.group(1)} {m.group(2)}"
|
||||||
|
else:
|
||||||
|
cur = d.get("CPU MHz", "")
|
||||||
|
if cur:
|
||||||
|
info["CPU (Current) Frequency"] = f"{float(cur):.0f} MHz"
|
||||||
|
if maxf:
|
||||||
|
info["CPU Max Frequency"] = f"{float(maxf):.0f} MHz"
|
||||||
|
|
||||||
|
# RAM total (/proc/meminfo)
|
||||||
|
try:
|
||||||
|
meminfo = open("/proc/meminfo").read()
|
||||||
|
m = re.search(r"^MemTotal:\s+(\d+)\s+kB", meminfo, flags=re.M)
|
||||||
|
if m:
|
||||||
|
total_bytes = int(m.group(1)) * 1024
|
||||||
|
info["Total RAM"] = human_bytes(total_bytes)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
def collect_gpu() -> Dict[str, str]:
|
||||||
|
"""
|
||||||
|
Use NVML to enumerate GPUs and select the 'newer' one:
|
||||||
|
1) Prefer a device whose name matches /4090/i
|
||||||
|
2) Else highest CUDA compute capability (major, minor), tiebreaker by total memory
|
||||||
|
Also reports driver version and CUDA driver version.
|
||||||
|
"""
|
||||||
|
pynvml = _import_nvml()
|
||||||
|
pynvml.nvmlInit()
|
||||||
|
try:
|
||||||
|
count = pynvml.nvmlDeviceGetCount()
|
||||||
|
if count < 1:
|
||||||
|
return {"Error": "No NVIDIA GPUs detected by NVML."}
|
||||||
|
|
||||||
|
devices = []
|
||||||
|
for i in range(count):
|
||||||
|
h = pynvml.nvmlDeviceGetHandleByIndex(i)
|
||||||
|
|
||||||
|
# name can be bytes or str depending on wheel; normalize
|
||||||
|
raw_name = pynvml.nvmlDeviceGetName(h)
|
||||||
|
name = _to_text(raw_name)
|
||||||
|
|
||||||
|
mem_info = pynvml.nvmlDeviceGetMemoryInfo(h)
|
||||||
|
total_mem = getattr(mem_info, "total", 0)
|
||||||
|
|
||||||
|
# compute capability may not exist on very old drivers
|
||||||
|
try:
|
||||||
|
maj, minr = pynvml.nvmlDeviceGetCudaComputeCapability(h)
|
||||||
|
except Exception:
|
||||||
|
maj, minr = (0, 0)
|
||||||
|
|
||||||
|
devices.append(
|
||||||
|
{
|
||||||
|
"index": i,
|
||||||
|
"handle": h,
|
||||||
|
"name": name,
|
||||||
|
"mem": total_mem,
|
||||||
|
"cc": (maj, minr),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Prefer explicit "4090"
|
||||||
|
pick = next(
|
||||||
|
(d for d in devices if re.search(r"4090", d["name"], flags=re.I)), None
|
||||||
|
)
|
||||||
|
if pick is None:
|
||||||
|
# Highest compute capability, then largest memory
|
||||||
|
devices.sort(key=lambda x: (x["cc"][0], x["cc"][1], x["mem"]), reverse=True)
|
||||||
|
pick = devices[0]
|
||||||
|
|
||||||
|
# Driver version and CUDA driver version can be bytes or str
|
||||||
|
drv_raw = pynvml.nvmlSystemGetDriverVersion()
|
||||||
|
drv = _to_text(drv_raw)
|
||||||
|
|
||||||
|
# CUDA driver version (integer like 12040 -> 12.4)
|
||||||
|
cuda_drv_ver = ""
|
||||||
|
try:
|
||||||
|
v = pynvml.nvmlSystemGetCudaDriverVersion_v2()
|
||||||
|
except Exception:
|
||||||
|
v = pynvml.nvmlSystemGetCudaDriverVersion()
|
||||||
|
try:
|
||||||
|
major = v // 1000
|
||||||
|
minor = (v % 1000) // 10
|
||||||
|
patch = v % 10
|
||||||
|
cuda_drv_ver = f"{major}.{minor}.{patch}" if patch else f"{major}.{minor}"
|
||||||
|
except Exception:
|
||||||
|
cuda_drv_ver = ""
|
||||||
|
|
||||||
|
gpu_info = {
|
||||||
|
"Selected GPU Name": pick["name"],
|
||||||
|
"Selected GPU Memory": human_bytes(pick["mem"]),
|
||||||
|
"Selected GPU Compute Capability": f"{pick['cc'][0]}.{pick['cc'][1]}",
|
||||||
|
"NVIDIA Driver Version": drv,
|
||||||
|
"CUDA (Driver) Version": cuda_drv_ver,
|
||||||
|
}
|
||||||
|
return gpu_info
|
||||||
|
finally:
|
||||||
|
pynvml.nvmlShutdown()
|
||||||
|
|
||||||
|
|
||||||
|
def summarize_numpy_build_config() -> str:
|
||||||
|
"""
|
||||||
|
Capture numpy.__config__.show() and try to extract the BLAS/LAPACK backend line(s).
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
from io import StringIO
|
||||||
|
import sys as _sys
|
||||||
|
|
||||||
|
buf = StringIO()
|
||||||
|
_stdout = _sys.stdout
|
||||||
|
try:
|
||||||
|
_sys.stdout = buf
|
||||||
|
np.__config__.show()
|
||||||
|
finally:
|
||||||
|
_sys.stdout = _stdout
|
||||||
|
txt = buf.getvalue()
|
||||||
|
|
||||||
|
# Heuristic: capture lines mentioning MKL, OpenBLAS, BLIS, LAPACK
|
||||||
|
lines = [
|
||||||
|
l
|
||||||
|
for l in txt.splitlines()
|
||||||
|
if re.search(r"(MKL|OpenBLAS|BLAS|LAPACK|BLIS)", l, re.I)
|
||||||
|
]
|
||||||
|
if not lines:
|
||||||
|
# fall back to first ~12 lines
|
||||||
|
lines = txt.splitlines()[:12]
|
||||||
|
# Keep it compact
|
||||||
|
return "\n".join(lines[:20]).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def collect_software() -> Dict[str, str]:
|
||||||
|
info: Dict[str, str] = {}
|
||||||
|
import sys as _sys
|
||||||
|
import torch
|
||||||
|
import numpy as _np
|
||||||
|
import scipy as _sp
|
||||||
|
import sklearn as _sk
|
||||||
|
|
||||||
|
info["Python"] = _sys.version.split()[0]
|
||||||
|
|
||||||
|
# PyTorch + built CUDA/cuDNN + visible GPUs
|
||||||
|
info["PyTorch"] = torch.__version__
|
||||||
|
info["PyTorch Built CUDA"] = getattr(torch.version, "cuda", "") or ""
|
||||||
|
try:
|
||||||
|
cudnn_build = torch.backends.cudnn.version() # integer
|
||||||
|
info["cuDNN (PyTorch build)"] = str(cudnn_build) if cudnn_build else ""
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# scikit-learn
|
||||||
|
info["scikit-learn"] = _sk.__version__
|
||||||
|
|
||||||
|
# NumPy / SciPy + build config
|
||||||
|
info["NumPy"] = _np.__version__
|
||||||
|
info["SciPy"] = _sp.__version__
|
||||||
|
info["NumPy Build Config"] = summarize_numpy_build_config()
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------- Main --------------------
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(
|
||||||
|
description="Generate LaTeX table of system/software environment for thesis (Linux + NVIDIA)."
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--output", "-o", type=str, help="Write LaTeX to this file instead of stdout."
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--caption", type=str, default="Computational Environment (Hardware & Software)"
|
||||||
|
)
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
errs = preflight()
|
||||||
|
if errs:
|
||||||
|
msg = (
|
||||||
|
"Preflight check failed:\n- "
|
||||||
|
+ "\n- ".join(errs)
|
||||||
|
+ "\n"
|
||||||
|
+ "Please install missing components and re-run."
|
||||||
|
)
|
||||||
|
print(msg, file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
sections: List[Tuple[str, Dict[str, str]]] = []
|
||||||
|
sections.append(("System", collect_system()))
|
||||||
|
sections.append(("GPU (Selected Newer Device)", collect_gpu()))
|
||||||
|
sections.append(("Software Environment", collect_software()))
|
||||||
|
|
||||||
|
latex = latex_table(sections, caption=args.caption)
|
||||||
|
|
||||||
|
if args.output:
|
||||||
|
with open(args.output, "w", encoding="utf-8") as f:
|
||||||
|
f.write(latex)
|
||||||
|
print(f"Wrote LaTeX to: {args.output}")
|
||||||
|
else:
|
||||||
|
print(latex)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user