node stats and benchmark, dynamic realocation working
This commit is contained in:
@@ -94,6 +94,19 @@ def _make_bar(pct: float, width: int = 10) -> str:
|
||||
return "█" * filled + "░" * (width - filled)
|
||||
|
||||
|
||||
def _node_stats(node) -> dict:
|
||||
total = int(getattr(node, "total_requests", getattr(node, "chat_completion_count", 0)) or 0)
|
||||
failed = int(getattr(node, "failed_requests", 0) or 0)
|
||||
queue_depth = int(getattr(node, "queue_depth", 0) or 0)
|
||||
success_rate = ((total - failed) / total * 100.0) if total else 100.0
|
||||
return {
|
||||
"total_requests": total,
|
||||
"failed_requests": failed,
|
||||
"queue_depth": queue_depth,
|
||||
"success_rate": success_rate,
|
||||
}
|
||||
|
||||
|
||||
def run_dashboard(node, config: dict, start_time: float) -> None:
|
||||
"""Start the live dashboard. Blocks until Ctrl-C. Returns cleanly."""
|
||||
if not is_interactive_tty():
|
||||
@@ -117,7 +130,8 @@ def _build_rich_renderable(
|
||||
from rich.text import Text # type: ignore[import]
|
||||
|
||||
uptime = time.monotonic() - start_time
|
||||
req_count = getattr(node, "chat_completion_count", 0)
|
||||
stats = _node_stats(node)
|
||||
req_count = stats["total_requests"]
|
||||
|
||||
# Tokens/sec EMA (approximate: 20 tokens per request heuristic when no real counter)
|
||||
delta_req = req_count - prev_req[0]
|
||||
@@ -163,6 +177,7 @@ def _build_rich_renderable(
|
||||
stats_lines = [
|
||||
f"Tokens/sec {tps_bar} {tps:.1f} t/s (EMA)",
|
||||
f"Requests {req_count:,} served",
|
||||
f"Success {stats['success_rate']:.1f}% failed {stats['failed_requests']:,} queue {stats['queue_depth']}",
|
||||
f"Peers 0 connected (gossip: US-017)",
|
||||
f"TAI earned 0.00 TAI (payments: US-006)",
|
||||
f"Uptime {_format_uptime(uptime)}",
|
||||
@@ -205,14 +220,17 @@ def _run_plain_loop(node, config: dict, start_time: float) -> None:
|
||||
try:
|
||||
while True:
|
||||
uptime = time.monotonic() - start_time
|
||||
req = getattr(node, "chat_completion_count", 0)
|
||||
stats = _node_stats(node)
|
||||
req = stats["total_requests"]
|
||||
gpu_stats = _gpu_stats()
|
||||
vram_str = ""
|
||||
if gpu_stats:
|
||||
g = gpu_stats[0]
|
||||
vram_str = f" VRAM{g['used_gb']:.1f}GB"
|
||||
print(
|
||||
f"[{model_name} req{req}{vram_str} up{_format_uptime(uptime)}]",
|
||||
f"[{model_name} req{req} ok{stats['success_rate']:.1f}% "
|
||||
f"fail{stats['failed_requests']} q{stats['queue_depth']}"
|
||||
f"{vram_str} up{_format_uptime(uptime)}]",
|
||||
flush=True,
|
||||
)
|
||||
time.sleep(2)
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
"""GPU hardware detection with graceful CPU fallback."""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
|
||||
def _detect_ram_mb() -> int:
|
||||
"""Return host physical RAM in MB, or 0 if unavailable."""
|
||||
try:
|
||||
pages = os.sysconf("SC_PHYS_PAGES")
|
||||
page_size = os.sysconf("SC_PAGE_SIZE")
|
||||
return int((pages * page_size) // (1024 * 1024))
|
||||
except (AttributeError, OSError, ValueError):
|
||||
return 0
|
||||
|
||||
|
||||
def detect_hardware() -> dict:
|
||||
"""Detect GPU model and available VRAM. Returns hardware profile dict."""
|
||||
ram_mb = _detect_ram_mb()
|
||||
try:
|
||||
import torch # type: ignore[import]
|
||||
if torch.cuda.is_available():
|
||||
@@ -12,7 +25,7 @@ def detect_hardware() -> dict:
|
||||
name = torch.cuda.get_device_name(idx)
|
||||
props = torch.cuda.get_device_properties(idx)
|
||||
vram_mb = props.total_memory // (1024 * 1024)
|
||||
return {"device": "cuda", "gpu_name": name, "vram_mb": vram_mb}
|
||||
return {"device": "cuda", "gpu_name": name, "vram_mb": vram_mb, "ram_mb": ram_mb}
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -26,8 +39,54 @@ def detect_hardware() -> dict:
|
||||
parts = line.split(",", 1)
|
||||
gpu_name = parts[0].strip()
|
||||
vram_mb = int(parts[1].strip()) if len(parts) > 1 else 0
|
||||
return {"device": "cuda", "gpu_name": gpu_name, "vram_mb": vram_mb}
|
||||
return {"device": "cuda", "gpu_name": gpu_name, "vram_mb": vram_mb, "ram_mb": ram_mb}
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired, ValueError, IndexError):
|
||||
pass
|
||||
|
||||
return {"device": "cpu", "gpu_name": None, "vram_mb": 0}
|
||||
return {"device": "cpu", "gpu_name": None, "vram_mb": 0, "ram_mb": ram_mb}
|
||||
|
||||
|
||||
def benchmark_throughput(device_str: str = "cpu") -> float:
|
||||
"""
|
||||
Estimate compute throughput via a synthetic transformer GEMM benchmark.
|
||||
|
||||
Runs hidden_size × (hidden_size*4) matmul — the dominant op in FFN layers —
|
||||
and returns iterations/second as a relative speed index. Higher = faster.
|
||||
The value is used as benchmark_tokens_per_sec in tracker registration for
|
||||
routing tiebreaks; it is not an absolute token rate.
|
||||
|
||||
Falls back to 1.0 if torch is unavailable.
|
||||
"""
|
||||
try:
|
||||
import torch # type: ignore[import]
|
||||
|
||||
device = torch.device(device_str)
|
||||
# bfloat16 on CUDA matches real inference dtype; float32 on CPU avoids
|
||||
# precision-downcast surprises on older hardware without bfloat16 support.
|
||||
dtype = torch.bfloat16 if device_str == "cuda" else torch.float32
|
||||
|
||||
# hidden_size=2048 is representative of a mid-sized model; large enough
|
||||
# that BLAS finds an efficient kernel on both GPU and CPU.
|
||||
hidden_size = 2048
|
||||
a = torch.randn(1, hidden_size, dtype=dtype, device=device)
|
||||
b = torch.randn(hidden_size, hidden_size * 4, dtype=dtype, device=device)
|
||||
|
||||
def _sync() -> None:
|
||||
if device_str == "cuda":
|
||||
torch.cuda.synchronize()
|
||||
|
||||
# Warmup: prime caches and JIT compilation.
|
||||
for _ in range(10):
|
||||
torch.matmul(a, b)
|
||||
_sync()
|
||||
|
||||
n_iters = 50
|
||||
t0 = time.perf_counter()
|
||||
for _ in range(n_iters):
|
||||
torch.matmul(a, b)
|
||||
_sync()
|
||||
elapsed = time.perf_counter() - t0
|
||||
|
||||
return round(n_iters / max(elapsed, 1e-9), 2)
|
||||
except Exception:
|
||||
return 1.0
|
||||
|
||||
@@ -14,7 +14,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .downloader import compute_shard_checksum, download_shard
|
||||
from .hardware import detect_hardware
|
||||
from .hardware import detect_hardware, benchmark_throughput
|
||||
from .relay_bridge import RelayHttpBridge, peer_id_from_wallet
|
||||
from .server import StubNodeServer
|
||||
from .torch_server import TorchNodeServer
|
||||
@@ -359,12 +359,18 @@ def run_startup(
|
||||
print(f" GPU: {gpu_name} ({vram_mb / 1024:.1f} GB VRAM, {ram_mb / 1024:.1f} GB RAM)", flush=True)
|
||||
|
||||
memory_budget_mb, memory_budget_source = _memory_budget(vram_mb, ram_mb)
|
||||
print(f" Memory budget: {memory_budget_mb} MB {memory_budget_source}", flush=True)
|
||||
print(f" Memory budget: {memory_budget_mb / 1024:.1f} GB {memory_budget_source}", flush=True)
|
||||
|
||||
print("Benchmarking compute...", flush=True)
|
||||
bench_tps = benchmark_throughput(device)
|
||||
device_label = "GPU" if device == "cuda" else "CPU"
|
||||
print(f" {device_label} throughput index: {bench_tps:,.0f}", flush=True)
|
||||
|
||||
registration_capabilities = {
|
||||
"vram_bytes": max(0, int(vram_mb)) * 1024 * 1024,
|
||||
"ram_bytes": max(0, int(ram_mb)) * 1024 * 1024,
|
||||
"max_loaded_shards": max_loaded_shards,
|
||||
"benchmark_tokens_per_sec": bench_tps,
|
||||
}
|
||||
# 2. Wallet
|
||||
print("Loading wallet...", flush=True)
|
||||
@@ -479,6 +485,7 @@ def run_startup(
|
||||
f" Endpoint: {endpoint}\n"
|
||||
f" Node ID: {tracker_node_id or 'unregistered'}\n"
|
||||
f" Hardware: {device.upper()}\n"
|
||||
f" Benchmark: {bench_tps:,.0f} (throughput index)\n"
|
||||
f"{'=' * 32}",
|
||||
flush=True,
|
||||
)
|
||||
@@ -571,6 +578,7 @@ def run_startup(
|
||||
f" Endpoint: {endpoint}\n"
|
||||
f" Node ID: {tracker_node_id or 'unregistered'}\n"
|
||||
f" Hardware: {device.upper()}\n"
|
||||
f" Benchmark: {bench_tps:,.0f} (throughput index)\n"
|
||||
f"{'=' * 32}",
|
||||
flush=True,
|
||||
)
|
||||
@@ -671,6 +679,7 @@ def run_startup(
|
||||
f" Endpoint: {endpoint}\n"
|
||||
f" Node ID: {node_id}\n"
|
||||
f" Hardware: {hw_str}\n"
|
||||
f" Benchmark: {bench_tps:,.0f} (throughput index)\n"
|
||||
f"{'=' * 32}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user