feat(v2): T2 运维层 hw_profile + llama_server 进程管理

This commit is contained in:
tzt
2026-08-30 21:00:52 +08:00
parent 1e51167ea5
commit 78a410b773
9 changed files with 851 additions and 1 deletions
+140
View File
@@ -0,0 +1,140 @@
"""硬件档位检测(runtime 运维层,纯标准库)。
把真实机器映射到三档保守模板之一:
- gpu12 : 约 ≥12GB 显存(NVIDIA / Vulkan 可探测) -> ngl 99, ctx 32768
- gpu8 : 约 ≥8GB 显存 -> ngl 14, ctx 16384
- cpu : 无独显或探测失败(保守兜底) -> ngl 0, ctx 8192
探测来源:nvidia-smiNVIDIA 显存)优先;其次 vulkaninfoAMD/Intel/通用,
只能判断是否存在 Vulkan 设备,无法可靠拿到显存 -> 保守回退 cpu,并在结果标注
probe:"conservative")。总系统内存仅作为 cpu 档提示参考,不作为分档依据。
任何探测失败都回退到 cpu 保守档,保证不崩、可离线运行(D5 / D8)。
"""
from __future__ import annotations
import shutil
import subprocess
from typing import Any, Callable, Dict, List, Optional
# 三档硬件模板(保守默认,可被 config.tiers 手动覆盖)
TIER_SPECS: Dict[str, Dict[str, Any]] = {
"gpu12": {"tier": "gpu12", "ngl": 99, "ctx": 32768, "kv_quant": "q8_0"},
"gpu8": {"tier": "gpu8", "ngl": 14, "ctx": 16384, "kv_quant": "q8_0"},
"cpu": {"tier": "cpu", "ngl": 0, "ctx": 8192, "kv_quant": "q8_0"},
}
_GPU12_THRESHOLD_GB = 12.0
_GPU8_THRESHOLD_GB = 8.0
def _run(cmd: List[str], timeout: float = 10.0,
runner: Optional[Callable[[List[str], float], subprocess.CompletedProcess]] = None
) -> Optional[subprocess.CompletedProcess]:
"""执行命令并捕获输出;失败/超时返回 None(不抛异常)。"""
if runner is not None:
try:
return runner(cmd, timeout)
except Exception:
return None
try:
return subprocess.run(
cmd, capture_output=True, text=True, timeout=timeout,
creationflags=subprocess.CREATE_NO_WINDOW,
)
except Exception:
return None
def nvidia_vram_gb(runner: Optional[Callable[..., subprocess.CompletedProcess]] = None) -> Optional[float]:
"""通过 nvidia-smi 读取显存总量(GB);无 NVIDIA 返回 None。"""
exe = shutil.which("nvidia-smi")
if not exe:
return None
out = _run([exe, "--query-gpu=memory.total", "--format=csv,noheader,nounits"], runner=runner)
if out is None or out.returncode != 0 or not out.stdout.strip():
return None
try:
# 多卡取最大值(第一行也接受,但保守起见取最大以保证模板内存够用)
vals = [float(v.strip()) for v in out.stdout.strip().splitlines() if v.strip().isdigit()]
if not vals:
return None
return max(vals) / 1024.0
except Exception:
return None
def vulkan_present(runner: Optional[Callable[..., subprocess.CompletedProcess]] = None) -> bool:
"""检测是否存在 Vulkan 设备(无法可靠拿显存 -> 只用于判定非 cpu 的候选)。"""
exe = shutil.which("vulkaninfo")
if not exe:
return False
out = _run([exe, "--summary"], timeout=15.0, runner=runner)
if out is None or out.returncode != 0:
return False
low = out.stdout.lower()
# 出现 deviceName 且非 "llvmpipe"/"software" 视为有真实设备
return ("devicename" in low or "gpu" in low) and "llvmpipe" not in low and "lavapipe" not in low
def pick_tier(vram_gb: Optional[float]) -> str:
"""按显存选择档位;None/未知 -> cpu 保守档。"""
if vram_gb is None:
return "cpu"
if vram_gb >= _GPU12_THRESHOLD_GB:
return "gpu12"
if vram_gb >= _GPU8_THRESHOLD_GB:
return "gpu8"
return "cpu"
def detect(override: Optional[Dict[str, Any]] = None,
runner: Optional[Callable[..., subprocess.CompletedProcess]] = None) -> Dict[str, Any]:
"""检测并返回当前档位规格。
override(可选):{"tier": "gpu12"} 强制指定档位;或覆盖单个字段如 {"ctx": 16384}。
返回形如 {"tier": "cpu", "ngl": 0, "ctx": 8192, "kv_quant": "q8_0",
"probe": "nvidia|vulkan|cpu|override", "note": str}
"""
if override and override.get("tier") in TIER_SPECS:
spec = dict(TIER_SPECS[override["tier"]])
spec.update({k: v for k, v in override.items() if k in spec})
spec["probe"] = "override"
spec["note"] = f"手动指定档位 {override['tier']}"
return spec
vram = nvidia_vram_gb(runner=runner)
probe = "nvidia"
if vram is None:
if vulkan_present(runner=runner):
probe = "vulkan"
note = "检测到 Vulkan 设备但无法读取显存,按保守档 cpu 运行(可在 config 手动覆盖 tier"
else:
probe = "cpu"
note = "未检测到 GPU,按 cpu 档运行(-ngl 0,速度受限)"
else:
note = f"nvidia-smi 探测显存 {vram:.1f}GB"
tier = pick_tier(vram)
spec = dict(TIER_SPECS[tier])
spec["probe"] = probe
spec["note"] = note if probe != "nvidia" else f"{note} -> 档位 {tier}"
return spec
def tier_spec(tier: str) -> Dict[str, Any]:
"""返回指定档位的规格副本(供 config.tiers 兜底)。"""
if tier not in TIER_SPECS:
raise ValueError(f"未知硬件档位: {tier}(支持: {sorted(TIER_SPECS)}")
return dict(TIER_SPECS[tier])
def detect_summary() -> str:
"""人类可读的检测摘要(setup_runtime / serve 启动时打印)。"""
spec = detect()
return (
f"硬件档位: {spec['tier']} (ngl={spec['ngl']}, ctx={spec['ctx']}, "
f"kv_quant={spec['kv_quant']}) [{spec.get('note', '')}]"
)