sail / sail_scripts /agents /base_agent.py
muterornament's picture
Industrialize: Backup sovereign training pipeline
e5b79b7 verified
Raw
History Blame Contribute Delete
4.6 kB
import os
import json
import time
import threading
import subprocess
from datetime import datetime
from abc import ABC, abstractmethod
class BaseAgent(ABC):
def __init__(self, name, status_file="agents/agent_status.json"):
self.name = name
self.status_file = status_file
self.status = "idle"
self.progress = 0
self.message = ""
self.start_time = None
self.gpu_available = self._check_gpu()
self._lock = threading.Lock()
self._running = False
os.makedirs(os.path.dirname(status_file), exist_ok=True)
self._write_status()
def _check_gpu(self):
try:
result = subprocess.run(
["nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader"],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0 and result.stdout.strip():
return result.stdout.strip()
except Exception:
pass
return None
def get_gpu_stats(self):
try:
result = subprocess.run(
["nvidia-smi", "--query-gpu=temperature.gpu,memory.used,memory.total,utilization.gpu",
"--format=csv,noheader,nounits"],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0:
parts = result.stdout.strip().split(",")
return {
"temp": int(parts[0].strip()),
"mem_used": int(parts[1].strip()),
"mem_total": int(parts[2].strip()),
"util": int(parts[3].strip())
}
except Exception:
pass
return None
def set_status(self, status, progress=None, message=""):
with self._lock:
self.status = status
if progress is not None:
self.progress = progress
if message:
self.message = message
if status == "running" and self.start_time is None:
self.start_time = time.time()
elif status in ("completed", "failed", "idle"):
self.start_time = None
self._write_status()
def _write_status(self):
try:
all_status = {}
if os.path.exists(self.status_file):
with open(self.status_file, "r") as f:
all_status = json.load(f)
gpu = self.get_gpu_stats()
all_status[self.name] = {
"status": self.status,
"progress": self.progress,
"message": self.message,
"gpu_available": self.gpu_available is not None,
"gpu_stats": gpu,
"start_time": self.start_time,
"updated_at": datetime.now().isoformat()
}
with open(self.status_file, "w") as f:
json.dump(all_status, f, indent=2)
except Exception:
pass
def log(self, msg):
ts = datetime.now().strftime("%H:%M:%S")
print(f"[{ts}] [{self.name}] {msg}")
self.message = msg
self._write_status()
def wait_for_gpu(self, max_temp=80, max_mem_pct=90):
self.log(f"Waiting for GPU (max temp: {max_temp}C, max mem: {max_mem_pct}%)...")
while True:
stats = self.get_gpu_stats()
if stats is None:
self.log("No GPU stats available, proceeding anyway")
return True
temp_ok = stats["temp"] < max_temp
mem_pct = (stats["mem_used"] / max(1, stats["mem_total"])) * 100
mem_ok = mem_pct < max_mem_pct
if temp_ok and mem_ok:
self.log(f"GPU ready: {stats['temp']}C, {mem_pct:.0f}% mem used")
return True
self.log(f"GPU busy: {stats['temp']}C, {mem_pct:.0f}% mem, {stats['util']}% util - waiting 10s")
time.sleep(10)
@abstractmethod
def run(self, **kwargs):
pass
def start(self, **kwargs):
self._running = True
self.set_status("running", 0, "Starting...")
try:
self.run(**kwargs)
self.set_status("completed", 100, "Done")
except Exception as e:
self.set_status("failed", self.progress, f"Error: {str(e)}")
self.log(f"FAILED: {e}")
raise
finally:
self._running = False
def stop(self):
self._running = False
self.set_status("stopped", self.progress, "Stopped by user")