Files
SolitaryThinker db866dfb04 [misc] v2: simplify docstrings/comments + drop deleted-design-doc citations
Sweep all 296 v2 modules: simplify verbose docstrings/comments and remove 376 dangling
"(design_vN §X)" citations to the now-deleted design docs (v2/README.md is the source of
truth). Comment/docstring-only — AST-verified code-identical; the CPU suite holds at 237
passed / 5 pre-existing. Also applies yapf + ruff --fix auto-fixes (import ordering,
forward-ref annotation de-quoting under `from __future__ import annotations`; behavior-
neutral, suite-confirmed) and adds the legitimate domain terms mot/clen/te to the codespell
ignore-list. Remaining ruff (24) + mypy (68 no-untyped-call) findings are pre-existing v2
debt, untouched here.
2026-06-19 20:54:59 +00:00

111 lines
4.1 KiB
Python

"""The scheduler, in layers — each testable on a fake pool, no GPU.
Mini-fastvideo implements the load-bearing layers:
* ``AdmissionController`` — the reservation gate: do not admit a WorkUnit unless its
compute budget AND memory (resident + worst-case peak) can be reserved. Reservations are
*refundable* (compute is a concurrency gate, not a lifetime cap), and infeasible reservations
(need > pool capacity) fail fast with ``AdmissionInfeasible`` instead of spinning.
* ``BatchScheduler`` — groups compatible WorkPlans by ``shape_sig.batch_key``. NOTE: in this
single-process mini, grouping drives *accounting* (how many units would co-batch); kernels still
execute per-plan. Real cross-request batched execution is a GPU-path concern (documented, not faked).
"""
from __future__ import annotations
from collections import defaultdict
from dataclasses import dataclass, field
from typing import Any
from v2.loop.contracts import WorkPlan
from v2.memory.allocator import MemoryManager, Reservation
class AdmissionInfeasible(RuntimeError):
"""A reservation can never be satisfied (need > pool capacity) — fail fast, don't spin."""
@dataclass
class WorkUnit:
"""The smallest schedulable action."""
request_id: str
plan: WorkPlan
priority: int = 0
@dataclass
class StepTicket:
"""Returned by ``admit_step``; carries what ``release`` must refund (memory + compute)."""
memory_res: Reservation | None
compute_seconds: float
@dataclass
class SchedulerMetrics:
admitted: int = 0
deferred: int = 0
batches: int = 0
stepped_units: int = 0
gpu_seconds: float = 0.0
by_kind: dict[str, int] = field(default_factory=lambda: defaultdict(int))
class AdmissionController:
"""Reservation before admission. Compute budget + memory both gate; both refund."""
def __init__(self, memory: MemoryManager | None = None, compute_budget_seconds: float = float("inf")):
self.memory = memory or MemoryManager()
self.compute_budget = compute_budget_seconds
self.compute_spent = 0.0
self.metrics = SchedulerMetrics()
def feasible_resident(self, nbytes: int) -> bool:
return nbytes <= self.memory.total_bytes
def feasible_step(self, plan: WorkPlan) -> bool:
"""Could this step EVER be admitted on an empty pool? If not, the caller fails fast."""
return (plan.resources.peak_activation_bytes <= self.memory.total_bytes
and plan.resources.compute_seconds <= self.compute_budget)
def reserve_resident(self, tag: str, nbytes: int) -> Reservation | None:
if nbytes <= 0:
return None
if not self.memory.can_reserve(tag, nbytes):
return None
return self.memory.reserve(tag, nbytes)
def admit_step(self, plan: WorkPlan) -> StepTicket | None:
"""Reserve a step's peak activation + check the compute budget. None ⇒ deferred."""
c = plan.resources.compute_seconds
if self.compute_spent + c > self.compute_budget:
self.metrics.deferred += 1
return None
peak = plan.resources.peak_activation_bytes
res: Reservation | None = None
if peak > 0:
if not self.memory.can_reserve(plan.loop_id, peak):
self.metrics.deferred += 1
return None
res = self.memory.reserve(plan.loop_id, peak)
self.compute_spent += c
self.metrics.admitted += 1
self.metrics.gpu_seconds += c
self.metrics.by_kind[plan.kind.value] += 1
return StepTicket(res, c)
def release(self, ticket: StepTicket | None) -> None:
if ticket is None:
return
if ticket.memory_res is not None:
self.memory.release(ticket.memory_res)
self.compute_spent = max(0.0, self.compute_spent - ticket.compute_seconds) # refund
class BatchScheduler:
"""Group compatible WorkPlans across requests."""
@staticmethod
def group(units: list[WorkUnit]) -> list[list[WorkUnit]]:
groups: dict[Any, list[WorkUnit]] = defaultdict(list)
for u in units:
groups[u.plan.shape_sig.batch_key].append(u)
return list(groups.values())