B7: Apple Silicon worker support (v0.19.0)
gpu.py: darwin branch via system_profiler SPDisplaysDataType for GPU model/VRAM on macOS, falls back to rocm-smi on Linux. hardware.py: _get_vram_darwin() parses system_profiler output. node_agent/agent.py: get_load() reports VRAM on darwin via system_profiler. tests: 5 new gpu tests (linux/darwin/absent), 3 new hardware tests (darwin assessment + VRAM parsing).
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from gpu import get_gpu_stats
|
||||
|
||||
|
||||
def test_linux_gpu_stats_via_rocm_smi(monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
rocm_json = json.dumps({
|
||||
"card0": {"GPU use (%)": "42%", "GPU Memory Allocated (VRAM%)": "68%"}
|
||||
})
|
||||
|
||||
class MockProc:
|
||||
returncode = 0
|
||||
stdout = rocm_json
|
||||
|
||||
class MockSP:
|
||||
TimeoutExpired = subprocess.TimeoutExpired
|
||||
run = staticmethod(lambda cmd, **kw: MockProc())
|
||||
|
||||
monkeypatch.setattr("gpu.subprocess", MockSP())
|
||||
stats = get_gpu_stats()
|
||||
assert stats["gpu_percent"] == 42
|
||||
assert stats["vram_percent"] == 68
|
||||
assert stats["available"] is True
|
||||
|
||||
|
||||
def test_linux_gpu_stats_rocm_smi_absent(monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
|
||||
class MockSP:
|
||||
TimeoutExpired = subprocess.TimeoutExpired
|
||||
run = staticmethod(lambda cmd, **kw: (_ for _ in ()).throw(FileNotFoundError("rocm-smi not found")))
|
||||
|
||||
monkeypatch.setattr("gpu.subprocess", MockSP())
|
||||
stats = get_gpu_stats()
|
||||
assert stats["gpu_percent"] == 0
|
||||
assert stats["vram_percent"] == 0
|
||||
assert stats["available"] is False
|
||||
|
||||
|
||||
def test_darwin_gpu_stats_via_system_profiler(monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "darwin")
|
||||
sp_output = """Graphics/Displays:
|
||||
|
||||
Apple M2 Pro:
|
||||
|
||||
Chipset Model: Apple M2 Pro
|
||||
Type: GPU
|
||||
Bus: Built-In
|
||||
Total Number of Cores: 19
|
||||
VRAM (Dynamic, Max): 16 GB
|
||||
Displays:
|
||||
Color LCD:
|
||||
Display Type: Built-In Retina LCD
|
||||
Resolution: 3456x2234
|
||||
"""
|
||||
|
||||
class MockProc:
|
||||
returncode = 0
|
||||
stdout = sp_output
|
||||
|
||||
class MockSP:
|
||||
TimeoutExpired = subprocess.TimeoutExpired
|
||||
run = staticmethod(lambda cmd, **kw: MockProc())
|
||||
|
||||
monkeypatch.setattr("gpu.subprocess", MockSP())
|
||||
stats = get_gpu_stats()
|
||||
assert stats["available"] is True
|
||||
assert stats["gpu_model"] == "Apple M2 Pro"
|
||||
assert stats["vram_total_mb"] == 16384
|
||||
assert stats["gpu_percent"] == 0
|
||||
assert stats["vram_percent"] == 0
|
||||
|
||||
|
||||
def test_darwin_gpu_stats_system_profiler_absent(monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "darwin")
|
||||
|
||||
class MockSP:
|
||||
TimeoutExpired = subprocess.TimeoutExpired
|
||||
run = staticmethod(lambda cmd, **kw: (_ for _ in ()).throw(FileNotFoundError("no system_profiler")))
|
||||
|
||||
monkeypatch.setattr("gpu.subprocess", MockSP())
|
||||
stats = get_gpu_stats()
|
||||
assert stats["available"] is False
|
||||
assert stats["gpu_percent"] == 0
|
||||
assert stats["vram_percent"] == 0
|
||||
|
||||
|
||||
def test_darwin_no_gpu_found(monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "darwin")
|
||||
sp_output = "Graphics/Displays:\n\n No displays found.\n"
|
||||
|
||||
class MockProc:
|
||||
returncode = 0
|
||||
stdout = sp_output
|
||||
|
||||
class MockSP:
|
||||
TimeoutExpired = subprocess.TimeoutExpired
|
||||
run = staticmethod(lambda cmd, **kw: MockProc())
|
||||
|
||||
monkeypatch.setattr("gpu.subprocess", MockSP())
|
||||
stats = get_gpu_stats()
|
||||
assert stats["available"] is False
|
||||
assert stats["gpu_percent"] == 0
|
||||
assert stats["vram_percent"] == 0
|
||||
@@ -2,6 +2,7 @@ import asyncio
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
@@ -174,3 +175,62 @@ def test_get_hardware_endpoint(tmp_path: Path, monkeypatch):
|
||||
assert data["searxng_reachable"] is True
|
||||
assert "llama_models" in data
|
||||
assert "qdrant_collections" in data
|
||||
|
||||
|
||||
def test_assess_hardware_darwin(tmp_path: Path, monkeypatch):
|
||||
hardware.HARDWARE_STATE_PATH = tmp_path / "hardware_state.json"
|
||||
monkeypatch.setattr(psutil, "virtual_memory", lambda: type("M", (), {"total": 16 * 1024 ** 3, "available": 8 * 1024 ** 3})())
|
||||
monkeypatch.setattr(psutil, "cpu_count", lambda: 8)
|
||||
monkeypatch.setattr(sys, "platform", "darwin")
|
||||
monkeypatch.setattr(hardware, "_get_vram_darwin", lambda: (16384, 16384))
|
||||
|
||||
async def mock_get(self, url, *args, **kwargs):
|
||||
if "v1/models" in url:
|
||||
return _MockGet(200, {"data": [{"id": "qwen2.5:latest"}]})
|
||||
if "6333" in url:
|
||||
return _MockGet(200, {"result": {"collections": [{"name": "caic"}]}})
|
||||
if "8888" in url:
|
||||
return _MockGet(200, {})
|
||||
return _MockGet(200, {})
|
||||
monkeypatch.setattr(httpx.AsyncClient, "get", mock_get)
|
||||
|
||||
state = asyncio.run(hardware.assess_hardware())
|
||||
|
||||
assert state["vram_total_mb"] == 16384
|
||||
assert state["vram_free_mb"] == 16384
|
||||
assert state["ram_total_gb"] == 16.0
|
||||
assert state["llama_reachable"] is True
|
||||
|
||||
|
||||
def test_get_vram_darwin_parse(monkeypatch):
|
||||
sp_output = """Graphics/Displays:
|
||||
|
||||
Apple M2 Pro:
|
||||
|
||||
Chipset Model: Apple M2 Pro
|
||||
VRAM (Dynamic, Max): 16 GB
|
||||
"""
|
||||
|
||||
class MockProc:
|
||||
returncode = 0
|
||||
stdout = sp_output
|
||||
|
||||
class MockSP:
|
||||
TimeoutExpired = subprocess.TimeoutExpired
|
||||
run = staticmethod(lambda cmd, **kw: MockProc())
|
||||
|
||||
monkeypatch.setattr(hardware, "subprocess", MockSP())
|
||||
total, free = hardware._get_vram_darwin()
|
||||
assert total == 16384
|
||||
assert free == 16384
|
||||
|
||||
|
||||
def test_get_vram_darwin_system_profiler_absent(monkeypatch):
|
||||
class MockSP:
|
||||
TimeoutExpired = subprocess.TimeoutExpired
|
||||
run = staticmethod(lambda cmd, **kw: (_ for _ in ()).throw(FileNotFoundError("no system_profiler")))
|
||||
|
||||
monkeypatch.setattr(hardware, "subprocess", MockSP())
|
||||
total, free = hardware._get_vram_darwin()
|
||||
assert total == 0
|
||||
assert free == 0
|
||||
|
||||
Reference in New Issue
Block a user