Nexus-Grid / server /curriculum.py
Abineshsdata's picture
Add manifest.json endpoint, update dashboard and app
74965f9
Raw
History Blame Contribute Delete
3.15 kB
"""Curriculum scheduling for NexusGrid training runs."""
from __future__ import annotations
from typing import Dict, List
class CurriculumManager:
"""Track recent task scores and recommend the next training task."""
def __init__(self, unlock_threshold: float = 0.7, window: int = 10, num_tasks: int = 6):
if num_tasks <= 0:
raise ValueError("num_tasks must be positive")
if window <= 0:
raise ValueError("window must be positive")
self.threshold = float(unlock_threshold)
self.window = int(window)
self.num_tasks = int(num_tasks)
self.scores: Dict[int, List[float]] = {task_id: [] for task_id in range(self.num_tasks)}
def reset(self) -> None:
"""Clear curriculum history."""
self.scores = {task_id: [] for task_id in range(self.num_tasks)}
def record_score(self, task_id: int, score: float) -> None:
"""Record a task score and keep only the latest `window` entries."""
self._validate_task_id(task_id)
bounded_score = max(0.0, min(1.0, float(score)))
history = self.scores[task_id]
history.append(bounded_score)
if len(history) > self.window:
del history[0 : len(history) - self.window]
def get_task_average(self, task_id: int) -> float:
"""Return the recent rolling average for a task."""
self._validate_task_id(task_id)
history = self.scores[task_id]
if not history:
return 0.0
return sum(history) / len(history)
def is_unlocked(self, task_id: int) -> bool:
"""Whether a task is unlocked by the previous task's recent average."""
self._validate_task_id(task_id)
if task_id == 0:
return True
return self.get_task_average(task_id - 1) >= self.threshold
def get_unlocked_tasks(self) -> List[int]:
"""Return all currently unlocked tasks."""
return [task_id for task_id in range(self.num_tasks) if self.is_unlocked(task_id)]
def get_recommended_task(self) -> int:
"""
Recommend the next task to train on.
Starts at Task 0 and unlocks subsequent tasks once the previous task's
rolling average meets the threshold.
"""
for task_id in range(self.num_tasks):
if not self.is_unlocked(task_id):
return max(0, task_id - 1)
if self.get_task_average(task_id) < self.threshold:
return task_id
return self.num_tasks - 1
def to_dict(self) -> Dict[str, object]:
"""Serialize the curriculum state for logs or dashboards."""
return {
"threshold": self.threshold,
"window": self.window,
"recommended_task": self.get_recommended_task(),
"unlocked_tasks": self.get_unlocked_tasks(),
"averages": {
str(task_id): self.get_task_average(task_id) for task_id in range(self.num_tasks)
},
}
def _validate_task_id(self, task_id: int) -> None:
if task_id not in self.scores:
raise ValueError(f"Unknown task_id: {task_id}")