| """M16 — Predictive Throttling Layer. |
| |
| Battre throttLL'eM (arxiv 2408.05235). Predict next 10 steps power usage par |
| GPU. Throttle preemptively before thermal/power limit hit. |
| |
| Cible: -30% energy/token, 0% SLO break. |
| """ |
| from __future__ import annotations |
|
|
| from collections import deque |
| from dataclasses import dataclass |
| from typing import Optional |
|
|
| import torch |
| import torch.nn as nn |
|
|
|
|
| @dataclass |
| class ThrottleConfig: |
| """Predictive throttling hyper-params.""" |
|
|
| horizon_steps: int = 10 |
| history_steps: int = 64 |
| power_limit_w: float = 700.0 |
| temp_limit_c: float = 85.0 |
| energy_target_pct: float = 0.7 |
| slo_break_max_pct: float = 0.0 |
|
|
|
|
| class PowerPredictor(nn.Module): |
| """LSTM predictor: history -> next K steps power.""" |
|
|
| def __init__(self, cfg: ThrottleConfig = ThrottleConfig()): |
| super().__init__() |
| self.cfg = cfg |
| self.lstm = nn.LSTM(input_size=3, hidden_size=128, num_layers=2, batch_first=True) |
| self.head = nn.Linear(128, cfg.horizon_steps) |
|
|
| def forward(self, history: torch.Tensor) -> torch.Tensor: |
| """history: [B, T, 3] (power, util, temp) -> [B, horizon_steps] predicted power.""" |
| out, _ = self.lstm(history) |
| last_h = out[:, -1, :] |
| future_power = self.head(last_h) |
| return future_power |
|
|
|
|
| class PredictiveThrottler: |
| """Per-GPU throttling controller. |
| |
| Maintains rolling buffer of telemetry. Each forward step: |
| 1. Update history |
| 2. Predict next horizon |
| 3. If predicted exceeds power_limit OR temp_limit: throttle |
| 4. If predicted under energy_target: full speed |
| """ |
|
|
| def __init__(self, predictor: PowerPredictor, cfg: ThrottleConfig = ThrottleConfig()): |
| self.predictor = predictor |
| self.cfg = cfg |
| self.history: deque = deque(maxlen=cfg.history_steps) |
| self.stats = {"throttles": 0, "max_predicted_w": 0.0, "energy_saved_pct": 0.0} |
|
|
| def step(self, power_w: float, util_pct: float, temp_c: float) -> float: |
| """Process telemetry; return throttle ratio [0.5, 1.0].""" |
| self.history.append([power_w, util_pct, temp_c]) |
| if len(self.history) < 8: |
| return 1.0 |
|
|
| hist = torch.tensor([list(self.history)], dtype=torch.float32) |
| with torch.no_grad(): |
| future = self.predictor(hist).squeeze(0) |
| max_predicted = float(future.max().item()) |
| self.stats["max_predicted_w"] = max(self.stats["max_predicted_w"], max_predicted) |
|
|
| |
| if max_predicted > self.cfg.power_limit_w: |
| throttle = self.cfg.power_limit_w / max_predicted |
| throttle = max(0.5, throttle) |
| self.stats["throttles"] += 1 |
| return throttle |
| if temp_c > self.cfg.temp_limit_c: |
| self.stats["throttles"] += 1 |
| return 0.7 |
| return 1.0 |
|
|
| def report(self) -> dict: |
| return dict(self.stats) |
|
|
|
|
| if __name__ == "__main__": |
| cfg = ThrottleConfig() |
| pred = PowerPredictor(cfg) |
| fake_hist = torch.randn(1, 32, 3) * 100 + 400 |
| out = pred(fake_hist) |
| print(f"[M16 throttle] predicted shape {out.shape}, sample: {out[0, :3].tolist()}") |
| thr = PredictiveThrottler(pred, cfg) |
| for step in range(40): |
| ratio = thr.step(power_w=500.0 + step * 5, util_pct=80.0, temp_c=70.0) |
| print(f"[M16 throttle] stats after 40 steps: {thr.report()}, last ratio={ratio:.3f}") |
| print(f"[M16 throttle] params: {sum(p.numel() for p in pred.parameters())/1e6:.2f}M") |
| print("[smoke M16] OK") |
|
|