diff --git a/Deep-SAD-PyTorch/hardware_survey/main.py b/Deep-SAD-PyTorch/hardware_survey/main.py new file mode 100644 index 0000000..5966a2a --- /dev/null +++ b/Deep-SAD-PyTorch/hardware_survey/main.py @@ -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()