Compare commits
2 Commits
c4a63d9461
...
78834e5045
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78834e5045 | ||
|
|
2d833432bc |
@@ -58,6 +58,68 @@ No firewall rules, no `--advertise-host` needed — just point at the public tra
|
||||
Use this when the tracker is on another machine and you want Windows to host a
|
||||
reachable node on the LAN.
|
||||
|
||||
#### Option A — existing conda/miniforge environment with CUDA torch (recommended if you already have it)
|
||||
|
||||
First, make sure the conda base environment is active so that `python` and `pip` both
|
||||
resolve to the same miniforge installation:
|
||||
|
||||
```powershell
|
||||
conda activate base
|
||||
deactivate # drop any .venv that may be layered on top; safe no-op if none active
|
||||
```
|
||||
|
||||
Install project packages into the active conda/miniforge env:
|
||||
|
||||
```powershell
|
||||
cd D:\DEV\workspace\REPOS\git.d-popov.com\neuron-tai
|
||||
|
||||
pip install -e packages\tracker -e packages\node -e packages\p2p -e packages\gateway -e packages\relay
|
||||
pip install transformers accelerate safetensors # torch is already present
|
||||
```
|
||||
|
||||
Verify torch is importable and CUDA is live **before** starting the node:
|
||||
|
||||
```powershell
|
||||
python -c "import torch; print(torch.__version__, torch.cuda.is_available())"
|
||||
# Expected: 2.x.x+cuXXX True
|
||||
```
|
||||
|
||||
If you get `ModuleNotFoundError: No module named 'torch'` even though `pip install torch`
|
||||
says "already satisfied", the `torch/` package directory is missing while the metadata
|
||||
stub remains (can happen after a conda-managed install). Force-reinstall via pip:
|
||||
|
||||
```powershell
|
||||
pip install --force-reinstall torch --index-url https://download.pytorch.org/whl/cu118
|
||||
```
|
||||
|
||||
Then re-run the verify step above.
|
||||
|
||||
If that prints `True` but `meshnet-node` still can't find torch, the venv entry point
|
||||
is shadowing the conda one. Check which binary wins:
|
||||
|
||||
```powershell
|
||||
(Get-Command meshnet-node).Source
|
||||
# Should show: C:\Users\<you>\miniforge3\Scripts\meshnet-node.exe
|
||||
# If it shows .venv\Scripts\meshnet-node.exe, use the full path below instead
|
||||
```
|
||||
|
||||
To start a node:
|
||||
|
||||
```powershell
|
||||
$env:HF_HOME = "D:\DEV\models"
|
||||
meshnet-node start --tracker https://ai.neuron.d-popov.com --model Qwen/Qwen2.5-0.5B-Instruct
|
||||
```
|
||||
|
||||
If the wrong entry point is shadowing, invoke via the full conda path:
|
||||
|
||||
```powershell
|
||||
C:\Users\popov\miniforge3\Scripts\meshnet-node.exe start `
|
||||
--tracker https://ai.neuron.d-popov.com `
|
||||
--model Qwen/Qwen2.5-0.5B-Instruct
|
||||
```
|
||||
|
||||
#### Option B — isolated virtualenv (fresh machine, no existing torch)
|
||||
|
||||
1. Install prerequisites on Windows:
|
||||
- Python 3.11 or 3.12 from <https://www.python.org/downloads/windows/>
|
||||
- Git for Windows from <https://git-scm.com/download/win>
|
||||
|
||||
@@ -192,7 +192,7 @@ def detect_hardware() -> dict:
|
||||
}
|
||||
|
||||
|
||||
def benchmark_throughput(device_str: str = "cpu") -> float:
|
||||
def benchmark_throughput_checked(device_str: str = "cpu") -> tuple[float, bool, str | None]:
|
||||
"""
|
||||
Estimate compute throughput via a synthetic transformer GEMM benchmark.
|
||||
|
||||
@@ -201,7 +201,8 @@ def benchmark_throughput(device_str: str = "cpu") -> float:
|
||||
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.
|
||||
Returns (score, ok, error). Score falls back to 1.0 when the requested
|
||||
device cannot run the benchmark.
|
||||
"""
|
||||
try:
|
||||
import torch # type: ignore[import]
|
||||
@@ -233,6 +234,12 @@ def benchmark_throughput(device_str: str = "cpu") -> float:
|
||||
_sync()
|
||||
elapsed = time.perf_counter() - t0
|
||||
|
||||
return round(n_iters / max(elapsed, 1e-9), 2)
|
||||
except Exception:
|
||||
return 1.0
|
||||
return round(n_iters / max(elapsed, 1e-9), 2), True, None
|
||||
except Exception as exc:
|
||||
return 1.0, False, f"{type(exc).__name__}: {exc}"
|
||||
|
||||
|
||||
def benchmark_throughput(device_str: str = "cpu") -> float:
|
||||
"""Return only the numeric throughput index, preserving the legacy API."""
|
||||
score, _ok, _error = benchmark_throughput_checked(device_str)
|
||||
return score
|
||||
|
||||
@@ -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, benchmark_throughput
|
||||
from .hardware import detect_hardware, benchmark_throughput_checked
|
||||
from .relay_bridge import RelayHttpBridge, peer_id_from_wallet
|
||||
from .server import StubNodeServer
|
||||
from .torch_server import TorchNodeServer
|
||||
@@ -386,7 +386,18 @@ def run_startup(
|
||||
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)
|
||||
if device != "cuda" and gpu_name:
|
||||
_cuda_score, cuda_ok, cuda_error = benchmark_throughput_checked("cuda")
|
||||
hw["cuda_benchmark_ok"] = cuda_ok
|
||||
if cuda_error:
|
||||
hw["cuda_benchmark_error"] = cuda_error
|
||||
if not cuda_ok:
|
||||
print(f" CUDA benchmark unavailable: {cuda_error}; using CPU benchmark", flush=True)
|
||||
bench_tps, bench_ok, bench_error = benchmark_throughput_checked(device)
|
||||
hw["benchmark_device"] = device
|
||||
hw["benchmark_ok"] = bench_ok
|
||||
if bench_error:
|
||||
hw["benchmark_error"] = bench_error
|
||||
device_label = "GPU" if device == "cuda" else "CPU"
|
||||
print(f" {device_label} throughput index: {bench_tps:,.0f}", flush=True)
|
||||
|
||||
|
||||
@@ -14,6 +14,6 @@ def _stub_benchmark_throughput(monkeypatch):
|
||||
"""
|
||||
try:
|
||||
import meshnet_node.startup as startup_mod
|
||||
monkeypatch.setattr(startup_mod, "benchmark_throughput", lambda _device: 999.0)
|
||||
monkeypatch.setattr(startup_mod, "benchmark_throughput_checked", lambda _device: (999.0, True, None))
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -173,7 +173,7 @@ def test_benchmark_throughput_is_registered_in_payload(monkeypatch, tmp_path):
|
||||
|
||||
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, "benchmark_throughput_checked", lambda _device: (42.5, True, None))
|
||||
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)
|
||||
@@ -193,6 +193,67 @@ def test_benchmark_throughput_is_registered_in_payload(monkeypatch, tmp_path):
|
||||
node.stop()
|
||||
|
||||
assert captured.get("benchmark_tokens_per_sec") == 42.5
|
||||
assert captured["hardware_profile"]["benchmark_device"] == "cpu"
|
||||
assert captured["hardware_profile"]["benchmark_ok"] is True
|
||||
|
||||
|
||||
def test_cuda_benchmark_failure_is_registered_for_inventory_only_gpu(monkeypatch, tmp_path, capsys):
|
||||
import meshnet_node.startup as startup_mod
|
||||
|
||||
captured: dict = {}
|
||||
|
||||
class FakeNode:
|
||||
backend = None
|
||||
|
||||
def start(self):
|
||||
return 7099
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
def fake_benchmark(device):
|
||||
if device == "cuda":
|
||||
return 1.0, False, "AssertionError: Torch not compiled with CUDA enabled"
|
||||
return 55.0, True, None
|
||||
|
||||
monkeypatch.setattr(
|
||||
startup_mod,
|
||||
"detect_hardware",
|
||||
lambda: {
|
||||
"device": "cpu",
|
||||
"gpu_name": "NVIDIA GeForce RTX 4060 Laptop GPU",
|
||||
"vram_mb": 8188,
|
||||
"dedicated_vram_mb": 8188,
|
||||
"shared_vram_mb": 40555,
|
||||
"ram_mb": 81111,
|
||||
"cuda_available": False,
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(startup_mod, "benchmark_throughput_checked", fake_benchmark)
|
||||
monkeypatch.setattr(startup_mod, "TorchNodeServer", lambda **_kw: FakeNode())
|
||||
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()
|
||||
|
||||
output = capsys.readouterr().out
|
||||
assert "CUDA benchmark unavailable" in output
|
||||
assert "Hardware: CPU (CUDA inactive)" in output
|
||||
hw = captured["hardware_profile"]
|
||||
assert hw["cuda_benchmark_ok"] is False
|
||||
assert "Torch not compiled with CUDA enabled" in hw["cuda_benchmark_error"]
|
||||
assert hw["benchmark_device"] == "cpu"
|
||||
assert hw["benchmark_ok"] is True
|
||||
assert captured["ram_bytes"] == 81111 * 1024 * 1024
|
||||
assert captured["vram_bytes"] == 0
|
||||
|
||||
|
||||
def test_wallet_generates_new_keypair(tmp_path):
|
||||
|
||||
Reference in New Issue
Block a user