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 +23 -0
- vramd/__main__.py +7 -0
- vramd/adapter.py +154 -0
- vramd/backend_manager.py +1693 -0
- vramd/calibrate/__init__.py +59 -0
- vramd/calibrate/analysis.py +619 -0
- vramd/calibrate/compare.py +216 -0
- vramd/calibrate/emit.py +228 -0
- vramd/calibrate/hw_auto.py +148 -0
- vramd/calibrate/runner.py +392 -0
- vramd/calibrate/sampler.py +374 -0
- vramd/calibrate/serde.py +136 -0
- vramd/cli.py +1709 -0
- vramd/client.py +816 -0
- vramd/data/backends.yaml +51 -0
- vramd/dispatcher.py +418 -0
- vramd/errors.py +52 -0
- vramd/footprints.py +182 -0
- vramd/gpu.py +1029 -0
- vramd/idle_evictor.py +170 -0
- vramd/job_queue.py +727 -0
- vramd/logging.py +452 -0
- vramd/process_guard.py +406 -0
- vramd/protocol.py +230 -0
- vramd/py.typed +0 -0
- vramd/registry.py +379 -0
- vramd/scheduler.py +77 -0
- vramd/server.py +1090 -0
- vramd/stats.py +184 -0
- vramd/subprocess_pool.py +690 -0
- vramd/toolchain.py +77 -0
- vramd/vram_planner.py +136 -0
- vramd/worker/__init__.py +54 -0
- vramd/worker/adapter.py +183 -0
- vramd/worker/protocol.py +200 -0
- vramd/worker/serve.py +436 -0
- vramd-0.1.0.dist-info/METADATA +221 -0
- vramd-0.1.0.dist-info/RECORD +42 -0
- vramd-0.1.0.dist-info/WHEEL +5 -0
- vramd-0.1.0.dist-info/entry_points.txt +2 -0
- vramd-0.1.0.dist-info/licenses/LICENSE +21 -0
- vramd-0.1.0.dist-info/top_level.txt +1 -0
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
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
|