hermes-hub/benchmarks/measure_concurrent_models.py
ochenstarik-ui 3949605115 feat(benchmark): актуализация отчёта A40/A44 по чистому потреблению VRAM, 64k порогу и llama-swap
- Столбец VRAM пересчитан на чистое потребление процессов (nvidia-smi --query-compute-apps)
- В условиях измерений явно зафиксирован контекст 32k токенов и сопоставлен со штатным режимом 192k (13.6 ток/с)
- Добавлен раздел по соответствию 64k порогу Hermes (Qwen3.8-27B, Qwen2.5-Coder-14B, Granite-4.2-8B)
- Проведены живые замеры одновременного размещения: 3 модели помещаются в 31 120 MiB (95% VRAM), 4 модели вызывают CUDA OOM (38 526 MiB)
- Установлен и настроен llama-swap на порту 8090 с автоматической выгрузкой VRAM по TTL
- Удалён незавершённый файл nemotron-3.5-30b
2026-08-31 12:03:35 +07:00

282 lines
12 KiB
Python

"""Script to perform live, honest VRAM measurements of solo and concurrent model deployments."""
import json
import os
import subprocess
import time
import urllib.request
from typing import Dict, List, Tuple
def get_gpu_compute_apps() -> List[Dict[str, str]]:
"""Query nvidia-smi for all active compute processes on GPU."""
try:
res = subprocess.run(
["nvidia-smi", "--query-compute-apps=pid,process_name,used_memory", "--format=csv,noheader,nounits"],
capture_output=True,
text=True,
timeout=5,
)
apps = []
for line in res.stdout.strip().split("\n"):
line = line.strip()
if not line:
continue
parts = [p.strip() for p in line.split(",")]
if len(parts) >= 3:
apps.append({
"pid": int(parts[0]),
"process_name": parts[1],
"used_memory_mib": int(parts[2]),
})
return apps
except Exception as e:
print(f"Error querying nvidia-smi: {e}")
return []
def get_total_vram_used() -> int:
try:
res = subprocess.run(
["nvidia-smi", "--query-gpu=memory.used", "--format=csv,noheader,nounits"],
capture_output=True,
text=True,
timeout=5,
)
return int(res.stdout.strip().split()[0])
except Exception:
return 0
def ping_health(port: int, timeout: int = 40) -> bool:
t0 = time.time()
while time.time() - t0 < timeout:
try:
req = urllib.request.Request(f"http://127.0.0.1:{port}/health")
with urllib.request.urlopen(req, timeout=2) as resp:
data = json.loads(resp.read().decode())
if data.get("status") == "ok":
return True
except Exception:
pass
time.sleep(1)
return False
def test_completion(port: int, model_path: str) -> Tuple[bool, float, str]:
req_body = {
"model": model_path,
"messages": [{"role": "user", "content": "Respond exact word 'PONG'"}],
"max_tokens": 10,
"temperature": 0.1,
}
t0 = time.monotonic()
try:
req = urllib.request.Request(
f"http://127.0.0.1:{port}/v1/chat/completions",
data=json.dumps(req_body).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=30) as resp:
elapsed = time.monotonic() - t0
raw = json.loads(resp.read().decode())
content = raw["choices"][0]["message"]["content"]
return True, elapsed, content.strip()
except Exception as e:
return False, time.monotonic() - t0, str(e)
def run_solo_measurement(name: str, model_path: str, ctx: int, port: int = 8089) -> Dict[str, any]:
print(f"\n[*] Measuring Solo: {name} (ctx={ctx})...", flush=True)
cmd = [
"/home/ochenstarik/llama.cpp/build/bin/llama-server",
"-m", model_path,
"-ngl", "99",
"-c", str(ctx),
"--parallel", "1",
"--flash-attn", "on",
"--cache-type-k", "q8_0",
"--cache-type-v", "q8_0",
"--reasoning", "off",
"--temp", "0.2",
"--host", "127.0.0.1",
"--port", str(port),
]
p = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try:
ok = ping_health(port, timeout=45)
if not ok:
print(f"[-] Failed to start {name} on port {port}", flush=True)
return {"name": name, "model_path": model_path, "ctx": ctx, "status": "START_FAILED"}
# Warmup
comp_ok, comp_time, comp_resp = test_completion(port, model_path)
apps = get_gpu_compute_apps()
proc_vram = next((a["used_memory_mib"] for a in apps if a["pid"] == p.pid), 0)
total_vram = get_total_vram_used()
print(f"[+] {name} (ctx={ctx}): Process VRAM = {proc_vram} MiB | Total GPU = {total_vram} MiB | Ping = {comp_time:.2f}s ('{comp_resp}')", flush=True)
return {
"name": name,
"model_path": model_path,
"ctx": ctx,
"process_vram_mib": proc_vram,
"total_gpu_mib": total_vram,
"status": "OK",
}
finally:
p.terminate()
try:
p.wait(timeout=5)
except Exception:
p.kill()
time.sleep(1)
def main():
print("===================================================================", flush=True)
print(" LIVE VRAM & CONCURRENCY BENCHMARK ON TESLA V100 32GB", flush=True)
print("===================================================================", flush=True)
models = [
("Qwen3.8-27B", "/srv/ai/models/qwen3.8-27b/Qwen3.8-27B-Q4_K_M.gguf", 32768),
("Qwen3.8-27B (192k ctx)", "/srv/ai/models/qwen3.8-27b/Qwen3.8-27B-Q4_K_M.gguf", 196608),
("Qwen2.5-Coder-32B", "/srv/ai/models/qwen2.5-coder-32b/Qwen2.5-Coder-32B-Instruct-Q4_K_M.gguf", 32768),
("Qwen2.5-Coder-14B", "/srv/ai/models/qwen2.5-coder-14b/qwen2.5-coder-14b-instruct-q4_k_m.gguf", 32768),
("Qwen2.5-Coder-14B (64k ctx)", "/srv/ai/models/qwen2.5-coder-14b/qwen2.5-coder-14b-instruct-q4_k_m.gguf", 65536),
("Phi-4-14B", "/srv/ai/models/phi-4-14b/phi-4-Q4_K_M.gguf", 16384),
("DeepSeek-Coder-V2-Lite", "/srv/ai/models/deepseek-coder-v2-lite/DeepSeek-Coder-V2-Lite-Instruct-Q4_K_M.gguf", 32768),
("DeepSeek-Coder-V2-Lite (64k ctx)", "/srv/ai/models/deepseek-coder-v2-lite/DeepSeek-Coder-V2-Lite-Instruct-Q4_K_M.gguf", 65536),
("Granite-4.2-8B", "/srv/ai/models/granite-4.2-8b/granite-4.2-8b-Q4_K_M.gguf", 32768),
("Granite-4.2-8B (64k ctx)", "/srv/ai/models/granite-4.2-8b/granite-4.2-8b-Q4_K_M.gguf", 65536),
("Granite-3.2-8B-Preview", "/srv/ai/models/granite-3.2-8b/granite-3.2-8b-instruct-preview.Q4_K_M.gguf", 32768),
("Qwen3-4B-Compressor", "/srv/ai/models/qwen3-4b-compressor/Qwen_Qwen3-4B-Instruct-2507-Q4_K_M.gguf", 32768),
("LFM2.5-2.6B", "/srv/ai/models/lfm2.5-2.6b/LFM2.5-2.6B-Q4_K_M.gguf", 16384),
]
solo_results = []
for name, path, ctx in models:
res = run_solo_measurement(name, path, ctx, port=8089)
solo_results.append(res)
with open("benchmarks/solo_vram_results.json", "w", encoding="utf-8") as f:
json.dump(solo_results, f, indent=2)
# -------------------------------------------------------------
# CONCURRENCY TEST: 3 Models simultaneously
# (Qwen2.5-Coder-14B + Granite-4.2-8B + Qwen3-4B-Compressor)
# -------------------------------------------------------------
print("\n=======================================================", flush=True)
print(" [*] TEST SCENARIO A: 3 Models Simultaneously on GPU", flush=True)
print(" 1. Qwen2.5-Coder-14B (32k)")
print(" 2. Granite-4.2-8B (32k)")
print(" 3. Qwen3-4B-Compressor (32k)")
print("=======================================================", flush=True)
procs = []
configs_3 = [
("qwen2.5-coder-14b", "/srv/ai/models/qwen2.5-coder-14b/qwen2.5-coder-14b-instruct-q4_k_m.gguf", 32768, 8083),
("granite-4.2-8b", "/srv/ai/models/granite-4.2-8b/granite-4.2-8b-Q4_K_M.gguf", 32768, 8084),
("qwen3-4b-compressor", "/srv/ai/models/qwen3-4b-compressor/Qwen_Qwen3-4B-Instruct-2507-Q4_K_M.gguf", 32768, 8085),
]
try:
for name, path, ctx, port in configs_3:
cmd = [
"/home/ochenstarik/llama.cpp/build/bin/llama-server",
"-m", path,
"-ngl", "99",
"-c", str(ctx),
"--parallel", "1",
"--flash-attn", "on",
"--cache-type-k", "q8_0",
"--cache-type-v", "q8_0",
"--reasoning", "off",
"--temp", "0.2",
"--host", "127.0.0.1",
"--port", str(port),
]
p = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
procs.append((name, p, port, path))
ok = ping_health(port, timeout=45)
print(f" - {name} on port {port}: {'STARTED' if ok else 'FAILED'}", flush=True)
time.sleep(2)
apps = get_gpu_compute_apps()
total_vram = get_total_vram_used()
print(f"\n[+] 3-MODEL CONCURRENT RESULT (Total GPU VRAM = {total_vram} MiB / 32768 MiB):", flush=True)
for name, p, port, path in procs:
proc_mem = next((a["used_memory_mib"] for a in apps if a["pid"] == p.pid), 0)
comp_ok, comp_time, comp_resp = test_completion(port, path)
print(f" - {name:22s} (PID {p.pid:7d}): {proc_mem:5d} MiB | Req = {'OK' if comp_ok else 'ERR'} ({comp_time:.2f}s)", flush=True)
finally:
for name, p, port, path in procs:
p.terminate()
try:
p.wait(timeout=5)
except Exception:
p.kill()
time.sleep(2)
# -------------------------------------------------------------
# CONCURRENCY TEST: 4 Models simultaneously
# (+ DeepSeek-Coder-V2-Lite)
# -------------------------------------------------------------
print("\n=======================================================", flush=True)
print(" [*] TEST SCENARIO B: 4 Models Simultaneously on GPU (ctx=32k)", flush=True)
print(" 1. Qwen2.5-Coder-14B (32k)")
print(" 2. DeepSeek-Coder-V2-Lite (32k)")
print(" 3. Granite-4.2-8B (32k)")
print(" 4. Qwen3-4B-Compressor (32k)")
print("=======================================================", flush=True)
procs_4 = []
configs_4 = [
("qwen2.5-coder-14b", "/srv/ai/models/qwen2.5-coder-14b/qwen2.5-coder-14b-instruct-q4_k_m.gguf", 32768, 8083),
("deepseek-coder-v2-lite", "/srv/ai/models/deepseek-coder-v2-lite/DeepSeek-Coder-V2-Lite-Instruct-Q4_K_M.gguf", 32768, 8086),
("granite-4.2-8b", "/srv/ai/models/granite-4.2-8b/granite-4.2-8b-Q4_K_M.gguf", 32768, 8084),
("qwen3-4b-compressor", "/srv/ai/models/qwen3-4b-compressor/Qwen_Qwen3-4B-Instruct-2507-Q4_K_M.gguf", 32768, 8085),
]
try:
for name, path, ctx, port in configs_4:
cmd = [
"/home/ochenstarik/llama.cpp/build/bin/llama-server",
"-m", path,
"-ngl", "99",
"-c", str(ctx),
"--parallel", "1",
"--flash-attn", "on",
"--cache-type-k", "q8_0",
"--cache-type-v", "q8_0",
"--reasoning", "off",
"--temp", "0.2",
"--host", "127.0.0.1",
"--port", str(port),
]
p = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
procs_4.append((name, p, port, path))
ok = ping_health(port, timeout=45)
print(f" - {name} on port {port}: {'STARTED' if ok else 'FAILED'}", flush=True)
time.sleep(2)
apps = get_gpu_compute_apps()
total_vram = get_total_vram_used()
print(f"\n[+] 4-MODEL CONCURRENT RESULT (Total GPU VRAM = {total_vram} MiB / 32768 MiB):", flush=True)
for name, p, port, path in procs_4:
proc_mem = next((a["used_memory_mib"] for a in apps if a["pid"] == p.pid), 0)
comp_ok, comp_time, comp_resp = test_completion(port, path)
print(f" - {name:24s} (PID {p.pid:7d}): {proc_mem:5d} MiB | Req = {'OK' if comp_ok else 'ERR'} ({comp_time:.2f}s)", flush=True)
finally:
for name, p, port, path in procs_4:
p.terminate()
try:
p.wait(timeout=5)
except Exception:
p.kill()
time.sleep(2)
if __name__ == "__main__":
main()