node stats and benchmark, dynamic realocation working
This commit is contained in:
@@ -10,7 +10,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from meshnet_node.downloader import download_shard, write_shard_archive
|
||||
from meshnet_node.hardware import detect_hardware
|
||||
from meshnet_node.hardware import detect_hardware, benchmark_throughput
|
||||
from meshnet_node.startup import (
|
||||
_infer_relay_url_from_tracker,
|
||||
_probationary_status_line,
|
||||
@@ -31,6 +31,8 @@ def test_detect_hardware_returns_valid_profile():
|
||||
hw = detect_hardware()
|
||||
assert hw["device"] in {"cuda", "cpu"}
|
||||
assert isinstance(hw.get("vram_mb"), int)
|
||||
assert isinstance(hw.get("ram_mb"), int)
|
||||
assert hw["ram_mb"] > 0
|
||||
if hw["device"] == "cpu":
|
||||
assert hw["gpu_name"] is None
|
||||
assert hw["vram_mb"] == 0
|
||||
@@ -39,6 +41,62 @@ def test_detect_hardware_returns_valid_profile():
|
||||
assert hw["vram_mb"] > 0
|
||||
|
||||
|
||||
def test_benchmark_throughput_cpu_returns_positive():
|
||||
"""CPU benchmark returns a positive float greater than the 1.0 error fallback."""
|
||||
result = benchmark_throughput("cpu")
|
||||
assert isinstance(result, float)
|
||||
assert result > 1.0, f"expected benchmark > 1.0, got {result}"
|
||||
|
||||
|
||||
def test_benchmark_throughput_fallback_on_bad_device():
|
||||
"""benchmark_throughput returns 1.0 (not raises) when device is invalid."""
|
||||
result = benchmark_throughput("invalid_device_xyz")
|
||||
assert result == 1.0
|
||||
|
||||
|
||||
def test_benchmark_throughput_is_registered_in_payload(monkeypatch, tmp_path):
|
||||
"""benchmark_tokens_per_sec from the benchmark is included in the tracker registration."""
|
||||
import meshnet_node.startup as startup_mod
|
||||
|
||||
captured: dict = {}
|
||||
|
||||
class FakeNode:
|
||||
backend = None
|
||||
tracker_node_id = None
|
||||
|
||||
def start(self):
|
||||
return 7099
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
def apply_tracker_directives(self, directives):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(startup_mod, "detect_hardware",
|
||||
lambda: {"device": "cpu", "gpu_name": None, "vram_mb": 0, "ram_mb": 16384})
|
||||
monkeypatch.setattr(startup_mod, "benchmark_throughput", lambda _device: 42.5)
|
||||
monkeypatch.setattr(startup_mod, "TorchNodeServer", lambda **_kw: FakeNode())
|
||||
monkeypatch.setattr(startup_mod, "_detect_num_layers", lambda _model_id: 24)
|
||||
monkeypatch.setattr(startup_mod, "RelayHttpBridge", None)
|
||||
monkeypatch.setattr(startup_mod, "_get_json",
|
||||
lambda _url, timeout=10.0: {"relay_url": None, "nodes": []})
|
||||
monkeypatch.setattr(startup_mod, "_post_json",
|
||||
lambda _url, payload, timeout=10.0: (captured.update(payload) or {"node_id": "x"}))
|
||||
monkeypatch.setattr(startup_mod, "_start_heartbeat", lambda *a, **kw: None)
|
||||
|
||||
node = run_startup(
|
||||
tracker_url="http://localhost:8080",
|
||||
model_id="Qwen/Qwen2.5-0.5B-Instruct",
|
||||
shard_start=0,
|
||||
shard_end=23,
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
)
|
||||
node.stop()
|
||||
|
||||
assert captured.get("benchmark_tokens_per_sec") == 42.5
|
||||
|
||||
|
||||
def test_wallet_generates_new_keypair(tmp_path):
|
||||
"""A new wallet is created when none exists, saved to disk."""
|
||||
wallet_file = tmp_path / "wallet.json"
|
||||
@@ -490,6 +548,70 @@ def test_real_model_startup_summary_shows_total_layers(tmp_path, monkeypatch, ca
|
||||
assert "Node ID: node-test-123" in output
|
||||
|
||||
|
||||
def test_real_model_startup_autodetects_cpu_memory_budget_and_logs_shard_budget(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
capsys,
|
||||
):
|
||||
"""Without --memory, startup reports RAM-backed capacity to the tracker and operator."""
|
||||
import meshnet_node.startup as startup_mod
|
||||
captured_registration = {}
|
||||
|
||||
class FakeBackend:
|
||||
total_layers = 24
|
||||
|
||||
class FakeTorchNodeServer:
|
||||
def __init__(self, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
self.backend = FakeBackend()
|
||||
self.port = None
|
||||
self.total_requests = 0
|
||||
self.failed_requests = 0
|
||||
self.queue_depth = 0
|
||||
|
||||
def start(self):
|
||||
self.port = 8001
|
||||
return self.port
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(
|
||||
startup_mod,
|
||||
"detect_hardware",
|
||||
lambda: {"device": "cpu", "gpu_name": None, "vram_mb": 0, "ram_mb": 16384},
|
||||
)
|
||||
monkeypatch.setattr(startup_mod, "TorchNodeServer", FakeTorchNodeServer)
|
||||
monkeypatch.setattr(
|
||||
startup_mod,
|
||||
"_post_json",
|
||||
lambda _url, _payload, timeout=10.0: (
|
||||
captured_registration.update(_payload) or {"node_id": "node-auto-mem"}
|
||||
),
|
||||
)
|
||||
|
||||
node = run_startup(
|
||||
tracker_url="http://127.0.0.1:8080",
|
||||
model_id="Qwen/Qwen2.5-0.5B-Instruct",
|
||||
shard_start=0,
|
||||
shard_end=23,
|
||||
wallet_path=tmp_path / "wallet.json",
|
||||
)
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
node.stop()
|
||||
|
||||
assert captured_registration["vram_bytes"] == 0
|
||||
assert captured_registration["ram_bytes"] == 16384 * 1024 * 1024
|
||||
assert captured_registration["max_loaded_shards"] == 1
|
||||
output = capsys.readouterr().out
|
||||
assert "Memory budget: 16.0 GB RAM" in output
|
||||
assert "Shard budget: up to 24/24 layers at bfloat16" in output
|
||||
assert "GB remaining after full load" in output
|
||||
assert "Node ID: node-auto-mem" in output
|
||||
|
||||
|
||||
def test_public_tracker_model_node_registers_relay_metadata_from_tracker_url_only(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
|
||||
Reference in New Issue
Block a user