| from __future__ import annotations |
|
|
| from fractions import Fraction |
|
|
| import numpy as np |
| from mlenergy_data.modeling import LogisticModel |
|
|
| from openg2g.clock import SimulationClock |
| from openg2g.common import ThreePhase |
| from openg2g.controller.ofo import LogisticModelStore, OFOBatchSizeController, OFOConfig |
| from openg2g.coordinator import SimulationLog |
| from openg2g.datacenter.base import LLMBatchSizeControlledDatacenter, LLMDatacenterState |
| from openg2g.datacenter.command import DatacenterCommand |
| from openg2g.datacenter.config import InferenceModelSpec |
| from openg2g.events import EventEmitter |
| from openg2g.grid.base import BusVoltages, GridBackend, GridState, PhaseVoltages |
| from openg2g.grid.command import GridCommand |
| from openg2g.grid.opendss import OpenDSSGrid |
|
|
|
|
| class _GridStub(OpenDSSGrid): |
| def __init__(self): |
| GridBackend.__init__(self) |
| self._v_index_list = [("671", 0), ("671", 1), ("671", 2)] |
|
|
| def reset(self) -> None: |
| pass |
|
|
| @property |
| def dt_s(self) -> Fraction: |
| return Fraction(1) |
|
|
| @property |
| def v_index(self) -> list[tuple[str, int]]: |
| return self._v_index_list |
|
|
| def voltages_vector(self) -> np.ndarray: |
| return np.array([0.94, 0.96, 1.01], dtype=float) |
|
|
| def estimate_sensitivity(self, perturbation_kw: float = 100.0) -> tuple[np.ndarray, np.ndarray]: |
| H = np.eye(3, dtype=float) |
| v0 = np.array([1.0, 1.0, 1.0], dtype=float) |
| return H, v0 |
|
|
| def step( |
| self, |
| clock: SimulationClock, |
| power_samples_w: list[ThreePhase], |
| events: EventEmitter, |
| ) -> GridState: |
| return GridState( |
| time_s=clock.time_s, |
| voltages=BusVoltages({"671": PhaseVoltages(a=0.94, b=0.96, c=1.01)}), |
| ) |
|
|
| def set_state(self, state: GridState) -> None: |
| self._state = state |
|
|
| def apply_control(self, command: GridCommand, events: EventEmitter) -> None: |
| pass |
|
|
|
|
| class _DCStub(LLMBatchSizeControlledDatacenter): |
| def __init__(self): |
| super().__init__() |
|
|
| def reset(self) -> None: |
| pass |
|
|
| @property |
| def dt_s(self) -> Fraction: |
| return Fraction(1) |
|
|
| def step(self, clock: SimulationClock, events: EventEmitter) -> LLMDatacenterState: |
| if self._state is None: |
| self._state = LLMDatacenterState( |
| time_s=0.0, |
| power_w=ThreePhase(100.0, 100.0, 100.0), |
| batch_size_by_model={"M1": 64}, |
| active_replicas_by_model={"M1": 5}, |
| observed_itl_s_by_model={"M1": 0.2}, |
| ) |
| return self._state |
|
|
| def set_state(self, state: LLMDatacenterState) -> None: |
| self._state = state |
|
|
| def apply_control(self, command: DatacenterCommand, events: EventEmitter) -> None: |
| pass |
|
|
|
|
| def _make_model_store() -> LogisticModelStore: |
| """Create a LogisticModelStore with synthetic fits for a single model M1.""" |
| power = {"M1": LogisticModel(L=100.0, x0=6.0, k=1.0, b0=50.0)} |
| latency = {"M1": LogisticModel(L=0.05, x0=6.0, k=1.0, b0=0.02)} |
| throughput = {"M1": LogisticModel(L=10.0, x0=6.0, k=1.0, b0=1.0)} |
| return LogisticModelStore(power, latency, throughput) |
|
|
|
|
| def _build_controller() -> OFOBatchSizeController: |
| model = InferenceModelSpec( |
| model_label="M1", |
| num_replicas=10, |
| gpus_per_replica=1, |
| initial_batch_size=64, |
| itl_deadline_s=0.1, |
| feasible_batch_sizes=(8, 16, 32, 64, 128), |
| ) |
| return OFOBatchSizeController( |
| (model,), |
| models=_make_model_store(), |
| config=OFOConfig( |
| primal_step_size=0.05, |
| w_throughput=0.0, |
| w_switch=0.0, |
| v_min=0.95, |
| v_max=1.05, |
| voltage_dual_step_size=0.5, |
| latency_dual_step_size=1.0, |
| ), |
| dt_s=Fraction(1), |
| ) |
|
|
|
|
| def test_ofo_uses_observed_latency_for_dual_update(): |
| ctrl = _build_controller() |
| grid = _GridStub() |
| dc = _DCStub() |
|
|
| dc_state = LLMDatacenterState( |
| time_s=0.0, |
| power_w=ThreePhase(100.0, 100.0, 100.0), |
| batch_size_by_model={"M1": 64}, |
| active_replicas_by_model={"M1": 5}, |
| observed_itl_s_by_model={"M1": 0.2}, |
| ) |
| grid_state = GridState( |
| time_s=0.0, |
| voltages=BusVoltages({"671": PhaseVoltages(a=0.94, b=0.96, c=1.01)}), |
| ) |
| dc.set_state(dc_state) |
| grid.set_state(grid_state) |
| log = SimulationLog() |
| events = EventEmitter(SimulationClock(Fraction(1)), log, "controller") |
|
|
| action = ctrl.step(SimulationClock(Fraction(1)), dc, grid, events) |
| assert len(action) == 1 |
| assert ctrl._latency_dual_by_model["M1"] > 0.0 |
|
|
|
|
| def test_ofo_requires_observed_latency_map(): |
| ctrl = _build_controller() |
| grid = _GridStub() |
| dc = _DCStub() |
|
|
| dc_state = LLMDatacenterState( |
| time_s=0.0, |
| power_w=ThreePhase(100.0, 100.0, 100.0), |
| batch_size_by_model={"M1": 64}, |
| active_replicas_by_model={"M1": 5}, |
| observed_itl_s_by_model={}, |
| ) |
| dc.set_state(dc_state) |
| log = SimulationLog() |
| events = EventEmitter(SimulationClock(Fraction(1)), log, "controller") |
|
|
| try: |
| ctrl.step(SimulationClock(Fraction(1)), dc, grid, events) |
| except RuntimeError as exc: |
| assert "observed_itl_s_by_model" in str(exc) |
| else: |
| raise AssertionError("Expected RuntimeError for missing observed latency") |
|
|