A scoped, CPU-testable realization of design_v3.md — the architecture where the atomic unit is a typed (recipe, runtime) ModelCard, the model owns loop semantics while the runtime owns loop lifecycle, one resident instance runs many loops, and training records behavior on the same loops it serves. Implements: - card/ loop/ runtime/ cache/ memory/ parallel/ parity/ extend/ program/ request/ training/ spanning design_v3 §4-§13: ModelCard + validate(); driven loops (init/next/advance/finalize); step-interleaving Engine with reservation-before-admission + per-class caches keyed by CacheKey; the C0-C4 consistency ladder + the non-negotiable batch-of-N interleave parity gate. - Inference: Wan2.1-1.3B (T2V), LTX2.3 (two-stage distilled, shared transformer), Wan-causal (chunk rollout + slab-KV streaming). - Training (Wan2.1-1.3B), each driving the SAME loops the engine serves: finetune (flow-match), DMD2 (teacher/critic distribution matching), DiffusionNFT (likelihood-free C2, samples from the decay-blended old policy, group-relative advantages, shared-prompt cache reuse), self-forcing (causal chunk loop). The engine never imports training (the §10 dependency rule, grep-verified). numpy-only core (no torch/GPU here); heavy Wan/LTX forwards are deterministic toy stand-ins with lazy torch-adapter seams (ComponentSpec.load_id/factory) for a GPU box. 49 tests pass via pytest and a zero-dependency runner; interleave parity verified (and a buggy module-global interceptor provably breaks it). Omni-ready spine (ar_decode/chunk_step loop kinds, multi-loop instances, LoopState.extension) for the phase-2 Cosmos3 + vllm-omni omni ports. Run: python3 -m pytest mini_fastvideo/tests/ -q ; python3 -m mini_fastvideo.examples
89 lines
3.2 KiB
Python
89 lines
3.2 KiB
Python
"""Observers and interceptors (design_v3 §11) — the optimization/debug/parity surface.
|
|
|
|
They compose with §5 cleanly: the hooks wrap ``ctx.execute(plan)``.
|
|
* Observers (read-only): cannot mutate state. An unused hook is *literally absent* from
|
|
the hot path (the bus only iterates when observers are attached).
|
|
* Interceptors (compute-altering): ``before_step`` may supply a cached prediction (skip);
|
|
``after_step`` updates calibration state. State lives in ``LoopState.plugin_state[id]``,
|
|
keyed per request AND per CFG branch — the structural fix for module-global residual
|
|
state that corrupts cache-dit/TeaCache forks under concurrency.
|
|
|
|
Trust boundary: plugins are enabled at deploy scope only (registry), never via a per-request
|
|
``plugins=[...]`` field. Requests only *parameterize* pre-enabled plugins.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Protocol, runtime_checkable
|
|
|
|
|
|
@runtime_checkable
|
|
class Observer(Protocol):
|
|
def observe(self, event: str, **kw) -> None: ...
|
|
|
|
|
|
@runtime_checkable
|
|
class Interceptor(Protocol):
|
|
plugin_id: str
|
|
distribution_altering: bool
|
|
graph_safe: bool
|
|
|
|
def before_step(self, plan: Any, state: Any) -> Any | None: ... # return override output to skip, or None
|
|
def after_step(self, plan: Any, state: Any, result: Any) -> None: ...
|
|
|
|
|
|
class ObserverBus:
|
|
"""Read-only event fan-out. Cheap when empty (the absent-hook rule)."""
|
|
|
|
def __init__(self, observers: list[Observer] | None = None):
|
|
self._observers = list(observers or [])
|
|
|
|
def add(self, observer: Observer) -> None:
|
|
self._observers.append(observer)
|
|
|
|
@property
|
|
def active(self) -> bool:
|
|
return bool(self._observers)
|
|
|
|
def emit(self, event: str, **kw) -> None:
|
|
if not self._observers: # absent from the hot path when off
|
|
return
|
|
for obs in self._observers:
|
|
obs.observe(event, **kw)
|
|
|
|
|
|
class InterceptorConflict(ValueError):
|
|
pass
|
|
|
|
|
|
class InterceptorChain:
|
|
"""Ordered interceptor chain; conflicting interceptors rejected pre-flight (§11)."""
|
|
|
|
def __init__(self, interceptors: list[Interceptor] | None = None, *, exact_mode: bool = False):
|
|
self._chain = list(interceptors or [])
|
|
self._validate(exact_mode)
|
|
|
|
def _validate(self, exact_mode: bool) -> None:
|
|
skippers = [i for i in self._chain if getattr(i, "distribution_altering", False)]
|
|
if len(skippers) > 1:
|
|
raise InterceptorConflict(
|
|
f"multiple distribution-altering interceptors {[i.plugin_id for i in skippers]} "
|
|
"conflict — only one step-skipper allowed (design_v3 §11)")
|
|
if exact_mode and skippers:
|
|
raise InterceptorConflict(
|
|
f"exact-mode rejects distribution_altering interceptors {[i.plugin_id for i in skippers]}")
|
|
|
|
@property
|
|
def active(self) -> bool:
|
|
return bool(self._chain)
|
|
|
|
def before(self, plan: Any, state: Any) -> Any | None:
|
|
for i in self._chain:
|
|
override = i.before_step(plan, state)
|
|
if override is not None:
|
|
return override
|
|
return None
|
|
|
|
def after(self, plan: Any, state: Any, result: Any) -> None:
|
|
for i in self._chain:
|
|
i.after_step(plan, state, result)
|