vramd 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
vramd/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ """vramd — controlo de admissão de VRAM para inferência generativa.
2
+
3
+ Um processo detém a GPU e decide **quem entra**: admite pelo pico real
4
+ (pesos + activação + margem), põe em fila com prioridade e afinidade, evicta
5
+ por peso+LRU, e corre cada modelo num processo/venv próprio.
6
+
7
+ Feito para inferência que dura segundos a minutos numa GPU de consumo — não
8
+ para throughput de tokens em datacenter.
9
+
10
+ Três peças:
11
+
12
+ - **supervisor** (:mod:`vramd.server`, :mod:`vramd.cli`) — fila, admissão, evicção;
13
+ - **worker SDK** (:mod:`vramd.worker`) — 3 métodos para embrulhar qualquer modelo;
14
+ - **cliente** (:mod:`vramd.client`) — submeter, esperar, cancelar.
15
+
16
+ E um calibrador (:mod:`vramd.calibrate`) que **mede** o footprint em vez de o
17
+ adivinhar: o número que o admit usa vem do driver, não de uma estimativa.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ __version__ = "0.1.0"
23
+ __all__ = ["__version__"]
vramd/__main__.py ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env python3
2
+ """Entry point: python -m vramd"""
3
+
4
+ from .cli import main
5
+
6
+ if __name__ == "__main__":
7
+ main()
vramd/adapter.py ADDED
@@ -0,0 +1,154 @@
1
+ """Contrato do adapter **in-process** — backends sem subprocesso.
2
+
3
+ Gémeo de :class:`vramd.worker.adapter.WorkerAdapter`, para backends que
4
+ correm dentro do próprio supervisor (descriptor sem ``tool:``/``runtime:``).
5
+ É o caminho legado: sem isolamento de venv e a partilhar o processo com a
6
+ fila. Prefere-se o worker em subprocesso — ver :mod:`vramd.worker`.
7
+
8
+ O adapter é **stateless quanto ao modelo**: o ciclo de vida (criar/manter/
9
+ evictar) é do ``BackendManager``; o adapter só sabe *como* carregar, usar
10
+ e libertar.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import contextlib
16
+ from abc import ABC, abstractmethod
17
+ from collections.abc import Callable
18
+ from typing import Any
19
+
20
+ from . import protocol as P
21
+ from .errors import GenerationAborted
22
+
23
+ __all__ = ["BackendAdapter", "GenerationAborted"]
24
+
25
+
26
+ class BackendAdapter(ABC):
27
+ """Contrato canónico que cada adapter de tool implementa.
28
+
29
+ O adapter é **stateless quanto ao modelo**: não retém o objeto carregado.
30
+ O ciclo de vida (criar/manter/evictar o model object) é responsabilidade do
31
+ ``BackendManager``. O adapter apenas sabe *como* carregar/usar/libertar um
32
+ modelo dessa tool.
33
+
34
+ Implementações concretas devem ser instanciáveis sem argumentos (o Registry
35
+ faz ``Adapter()``) e exportar a classe com o nome ``Adapter``.
36
+ """
37
+
38
+ name: str = ""
39
+
40
+ @abstractmethod
41
+ def load(self, **kwargs: Any) -> Any:
42
+ """Carrega e devolve o model object (pipeline/gerador pronto a gerar)."""
43
+
44
+ @abstractmethod
45
+ def generate(self, model: Any, request: dict[str, Any]) -> dict[str, Any]:
46
+ """Executa uma geração sobre o model object.
47
+
48
+ O request pode incluir:
49
+ - ``_progress``: ``(pct|None, msg|None) -> None``
50
+ - ``_abort``: ``() -> bool`` (True = cancel pedido)
51
+ """
52
+
53
+ @abstractmethod
54
+ def unload(self, model: Any) -> None:
55
+ """Liberta o model object (VRAM). Idempotente e à prova de exceções."""
56
+
57
+ @staticmethod
58
+ def report_progress(request: dict[str, Any], pct: float | None = None, msg: str | None = None) -> None:
59
+ """Helper: reporta progresso se o request trouxer ``_progress``."""
60
+ cb = request.get("_progress")
61
+ if callable(cb):
62
+ with contextlib.suppress(Exception):
63
+ cb(pct, msg)
64
+
65
+ @staticmethod
66
+ def should_abort(request: dict[str, Any]) -> bool:
67
+ """True se o vramd pediu cancel (``request["_abort"]``)."""
68
+ cb = request.get("_abort")
69
+ if not callable(cb):
70
+ return False
71
+ try:
72
+ return bool(cb())
73
+ except Exception:
74
+ return False
75
+
76
+ @staticmethod
77
+ def cancelled_response(reason: str = "cancelled") -> dict[str, Any]:
78
+ """Resposta canónica de cancel cooperativo."""
79
+ return {
80
+ "status": P.STATUS_ERROR,
81
+ "error": reason,
82
+ "error_code": P.ERR_CANCELLED,
83
+ }
84
+
85
+ @classmethod
86
+ def abort_hooks(
87
+ cls,
88
+ request: dict[str, Any],
89
+ *,
90
+ num_inference_steps: int,
91
+ ) -> tuple[Callable[[], bool] | None, Callable[[int, int], None] | None]:
92
+ """Constrói ``should_abort`` / ``on_step`` para generators 2D."""
93
+
94
+ def _abort() -> bool:
95
+ return cls.should_abort(request)
96
+
97
+ def _on_step(step: int, total: int) -> None:
98
+ pct = step / max(1, total)
99
+ cls.report_progress(request, pct, f"step {step}/{total}")
100
+
101
+ has_abort = callable(request.get("_abort"))
102
+ has_progress = callable(request.get("_progress"))
103
+ return (
104
+ _abort if has_abort else None,
105
+ _on_step if has_progress else None,
106
+ )
107
+
108
+ @classmethod
109
+ def apply_runtime_budget(
110
+ cls,
111
+ model: Any,
112
+ request: dict[str, Any],
113
+ *,
114
+ progress_pct: float | None = None,
115
+ **hints: Any,
116
+ ) -> dict[str, Any] | None:
117
+ """Reaplica o runtime VRAM budget do model object, se suportado.
118
+
119
+ Contrato canónico: model objects que suportam re-orçamento pós-load
120
+ (Paint3D ``PaintBatchProcessor``, Text3D ``HunyuanTextTo3DGenerator``, …)
121
+ expõem ``refresh_runtime_budget(**hints)`` e devolvem o dict do budget
122
+ (um dict livre, definido pelo model object). O adapter chama isto antes da
123
+ inferência; o BackendManager guarda o resultado em ``stats``.
124
+
125
+ Args:
126
+ model: model object carregado pelo adapter.
127
+ request: request (para ``report_progress``).
128
+ progress_pct: se dado, reporta o budget como progresso.
129
+ **hints: overrides por-request (ex: ``requested_views=...``).
130
+ Ignorados se o método do model não os aceitar.
131
+
132
+ Returns:
133
+ Dict do budget, ou ``None`` se o model não suporta / falhou.
134
+ """
135
+ refresh = getattr(model, "refresh_runtime_budget", None)
136
+ if not callable(refresh):
137
+ return None
138
+ try:
139
+ try:
140
+ budget = refresh(**hints) if hints else refresh()
141
+ except TypeError:
142
+ # Model object antigo sem suporte a hints — retry sem overrides.
143
+ budget = refresh()
144
+ except (RuntimeError, MemoryError):
145
+ # Gate VRAM (MeshRender headroom, OOM) — NÃO engolir; adapter aborta.
146
+ raise
147
+ except Exception:
148
+ return None
149
+ if budget and progress_pct is not None:
150
+ summary = ", ".join(
151
+ f"{k}={v}" for k, v in budget.items() if k in ("num_chunks", "max_views", "dino_device")
152
+ )
153
+ cls.report_progress(request, progress_pct, f"vram_budget {summary}" if summary else "vram_budget")
154
+ return budget if isinstance(budget, dict) else None