vramd 0.2.0__tar.gz → 0.2.2__tar.gz

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.
Files changed (84) hide show
  1. {vramd-0.2.0/src/vramd.egg-info → vramd-0.2.2}/PKG-INFO +1 -1
  2. {vramd-0.2.0 → vramd-0.2.2}/pyproject.toml +1 -1
  3. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/__init__.py +1 -1
  4. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/cli.py +11 -5
  5. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/registry.py +13 -0
  6. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/toolchain.py +19 -1
  7. {vramd-0.2.0 → vramd-0.2.2/src/vramd.egg-info}/PKG-INFO +1 -1
  8. {vramd-0.2.0 → vramd-0.2.2}/src/vramd.egg-info/SOURCES.txt +1 -0
  9. {vramd-0.2.0 → vramd-0.2.2}/tests/test_calibrate_cli.py +64 -0
  10. vramd-0.2.2/tests/test_toolchain.py +61 -0
  11. {vramd-0.2.0 → vramd-0.2.2}/LICENSE +0 -0
  12. {vramd-0.2.0 → vramd-0.2.2}/README.md +0 -0
  13. {vramd-0.2.0 → vramd-0.2.2}/setup.cfg +0 -0
  14. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/__main__.py +0 -0
  15. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/adapter.py +0 -0
  16. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/backend_manager.py +0 -0
  17. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/calibrate/__init__.py +0 -0
  18. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/calibrate/analysis.py +0 -0
  19. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/calibrate/compare.py +0 -0
  20. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/calibrate/emit.py +0 -0
  21. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/calibrate/hw_auto.py +0 -0
  22. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/calibrate/runner.py +0 -0
  23. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/calibrate/sampler.py +0 -0
  24. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/calibrate/serde.py +0 -0
  25. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/client.py +0 -0
  26. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/data/backends.yaml +0 -0
  27. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/dispatcher.py +0 -0
  28. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/errors.py +0 -0
  29. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/footprints.py +0 -0
  30. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/gpu.py +0 -0
  31. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/idle_evictor.py +0 -0
  32. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/job_queue.py +0 -0
  33. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/logging.py +0 -0
  34. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/process_guard.py +0 -0
  35. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/protocol.py +0 -0
  36. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/py.typed +0 -0
  37. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/scheduler.py +0 -0
  38. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/server.py +0 -0
  39. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/stats.py +0 -0
  40. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/subprocess_pool.py +0 -0
  41. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/vram_planner.py +0 -0
  42. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/worker/__init__.py +0 -0
  43. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/worker/adapter.py +0 -0
  44. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/worker/protocol.py +0 -0
  45. {vramd-0.2.0 → vramd-0.2.2}/src/vramd/worker/serve.py +0 -0
  46. {vramd-0.2.0 → vramd-0.2.2}/src/vramd.egg-info/dependency_links.txt +0 -0
  47. {vramd-0.2.0 → vramd-0.2.2}/src/vramd.egg-info/entry_points.txt +0 -0
  48. {vramd-0.2.0 → vramd-0.2.2}/src/vramd.egg-info/requires.txt +0 -0
  49. {vramd-0.2.0 → vramd-0.2.2}/src/vramd.egg-info/top_level.txt +0 -0
  50. {vramd-0.2.0 → vramd-0.2.2}/tests/test_abort_progress.py +0 -0
  51. {vramd-0.2.0 → vramd-0.2.2}/tests/test_backend_manager.py +0 -0
  52. {vramd-0.2.0 → vramd-0.2.2}/tests/test_backend_manager_hybrid.py +0 -0
  53. {vramd-0.2.0 → vramd-0.2.2}/tests/test_calibrate_analysis.py +0 -0
  54. {vramd-0.2.0 → vramd-0.2.2}/tests/test_calibrate_emit_compare.py +0 -0
  55. {vramd-0.2.0 → vramd-0.2.2}/tests/test_calibrate_hw_auto.py +0 -0
  56. {vramd-0.2.0 → vramd-0.2.2}/tests/test_calibrate_runner.py +0 -0
  57. {vramd-0.2.0 → vramd-0.2.2}/tests/test_calibrate_sampler.py +0 -0
  58. {vramd-0.2.0 → vramd-0.2.2}/tests/test_calibrate_serde.py +0 -0
  59. {vramd-0.2.0 → vramd-0.2.2}/tests/test_cli_debug_stats_bench.py +0 -0
  60. {vramd-0.2.0 → vramd-0.2.2}/tests/test_gpu_ids_load.py +0 -0
  61. {vramd-0.2.0 → vramd-0.2.2}/tests/test_idle_evictor.py +0 -0
  62. {vramd-0.2.0 → vramd-0.2.2}/tests/test_job_queue.py +0 -0
  63. {vramd-0.2.0 → vramd-0.2.2}/tests/test_job_wal.py +0 -0
  64. {vramd-0.2.0 → vramd-0.2.2}/tests/test_peak_profile.py +0 -0
  65. {vramd-0.2.0 → vramd-0.2.2}/tests/test_phase_progress.py +0 -0
  66. {vramd-0.2.0 → vramd-0.2.2}/tests/test_process_guard.py +0 -0
  67. {vramd-0.2.0 → vramd-0.2.2}/tests/test_protocol_env.py +0 -0
  68. {vramd-0.2.0 → vramd-0.2.2}/tests/test_queue_metrics_eta.py +0 -0
  69. {vramd-0.2.0 → vramd-0.2.2}/tests/test_registry.py +0 -0
  70. {vramd-0.2.0 → vramd-0.2.2}/tests/test_registry_layers.py +0 -0
  71. {vramd-0.2.0 → vramd-0.2.2}/tests/test_respawn.py +0 -0
  72. {vramd-0.2.0 → vramd-0.2.2}/tests/test_runtime_spawn.py +0 -0
  73. {vramd-0.2.0 → vramd-0.2.2}/tests/test_scheduler.py +0 -0
  74. {vramd-0.2.0 → vramd-0.2.2}/tests/test_server.py +0 -0
  75. {vramd-0.2.0 → vramd-0.2.2}/tests/test_stats.py +0 -0
  76. {vramd-0.2.0 → vramd-0.2.2}/tests/test_subprocess_integration.py +0 -0
  77. {vramd-0.2.0 → vramd-0.2.2}/tests/test_subprocess_per_backend_e2e.py +0 -0
  78. {vramd-0.2.0 → vramd-0.2.2}/tests/test_subprocess_pool.py +0 -0
  79. {vramd-0.2.0 → vramd-0.2.2}/tests/test_unload_frees_vram.py +0 -0
  80. {vramd-0.2.0 → vramd-0.2.2}/tests/test_vram_admit.py +0 -0
  81. {vramd-0.2.0 → vramd-0.2.2}/tests/test_vram_planner.py +0 -0
  82. {vramd-0.2.0 → vramd-0.2.2}/tests/test_vramd_coverage_100.py +0 -0
  83. {vramd-0.2.0 → vramd-0.2.2}/tests/test_worker.py +0 -0
  84. {vramd-0.2.0 → vramd-0.2.2}/tests/test_zero_vram.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vramd
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Controlo de admissão de VRAM para inferência generativa em GPUs de consumo
5
5
  Author: Maikeu Locatelli
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vramd"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Controlo de admissão de VRAM para inferência generativa em GPUs de consumo"
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Maikeu Locatelli" }]
@@ -19,5 +19,5 @@ adivinhar: o número que o admit usa vem do driver, não de uma estimativa.
19
19
 
20
20
  from __future__ import annotations
21
21
 
22
- __version__ = "0.2.0"
22
+ __version__ = "0.2.2"
23
23
  __all__ = ["__version__"]
@@ -1534,7 +1534,9 @@ def calibrate_cmd(
1534
1534
  elif auto_kwargs:
1535
1535
  console.print(f"[dim]hw-auto ({desc.tool}): {auto_kwargs}[/dim]")
1536
1536
  load_kwargs.update(auto_kwargs)
1537
- # Explícito vence o automático: o operador pode querer medir outro ponto.
1537
+ # ``calibrate_load_kwargs`` do descriptor (config real de produção do
1538
+ # backend) ganha ao hw-auto; o explícito do CLI ganha a ambos.
1539
+ load_kwargs.update(desc.calibrate_load_kwargs)
1538
1540
  load_kwargs.update(explicit_kwargs)
1539
1541
  if quant:
1540
1542
  load_kwargs.setdefault("sdnq_preset", quant)
@@ -1543,10 +1545,14 @@ def calibrate_cmd(
1543
1545
  if request_json:
1544
1546
  request = json.loads(Path(request_json).read_text(encoding="utf-8"))
1545
1547
  else:
1546
- request = {
1547
- "prompt": prompt or "calibration job",
1548
- "output": output_path or f"/tmp/ums-calibrate-{backend}.bin",
1549
- }
1548
+ # Default do descriptor (prompt/output com a extensão certa por
1549
+ # backend) — sem isto ``calibrate <backend>`` falha em backends que
1550
+ # exigem inputs (mesh_path/output) ou formatos específicos.
1551
+ request = dict(desc.calibrate_request)
1552
+ request.setdefault("prompt", prompt or "calibration job")
1553
+ if output_path:
1554
+ request["output"] = output_path
1555
+ request.setdefault("output", f"/tmp/vramd-calibrate-{backend}.bin")
1550
1556
 
1551
1557
  pool = SubprocessWorkerPool()
1552
1558
  runner = CalibrationRunner(pool)
@@ -201,6 +201,15 @@ class BackendDescriptor:
201
201
  shape_keys: Subconjunto que força reload quando muda. ``None`` = global.
202
202
  vram: Bloco ``vram:`` medido (`vramd calibrate`), quando existe.
203
203
  peak_profile: Bloco ``peak_profile:`` (quant, staged, unload_frees_vram…).
204
+ calibrate_request: Request de geração default para ``vramd calibrate``
205
+ (ex.: prompt + output com a extensão certa). Sem isto, o calibrador
206
+ usa ``{}`` e backends que exigem inputs (mesh_path/output) falham
207
+ com mensagens pouco acionáveis — o default vive no descriptor para
208
+ que ``vramd calibrate <backend>`` funcione sempre.
209
+ calibrate_load_kwargs: Kwargs de load default para ``vramd calibrate``
210
+ (ex.: ``sdnq_preset`` quando o perfil da tool não o resolve).
211
+ Fundidos com o hw-auto (este ganha) e com os explícitos do CLI
212
+ (``--load-kwargs``/``--quant`` ganham a este).
204
213
  """
205
214
 
206
215
  name: str
@@ -214,6 +223,8 @@ class BackendDescriptor:
214
223
  shape_keys: frozenset[str] | None = None
215
224
  vram: Mapping[str, Any] = field(default_factory=dict)
216
225
  peak_profile: Mapping[str, Any] = field(default_factory=dict)
226
+ calibrate_request: Mapping[str, Any] = field(default_factory=dict)
227
+ calibrate_load_kwargs: Mapping[str, Any] = field(default_factory=dict)
217
228
 
218
229
  @property
219
230
  def unload_frees_vram(self) -> bool:
@@ -316,6 +327,8 @@ def _to_descriptor(entry: Mapping[str, Any], *, source: str) -> BackendDescripto
316
327
  shape_keys=frozenset(str(k) for k in shape_keys) if shape_keys else None,
317
328
  vram=dict(entry.get("vram") or {}),
318
329
  peak_profile=dict(entry.get("peak_profile") or {}),
330
+ calibrate_request=dict(entry.get("calibrate_request") or {}),
331
+ calibrate_load_kwargs=dict(entry.get("calibrate_load_kwargs") or {}),
319
332
  )
320
333
 
321
334
 
@@ -25,10 +25,28 @@ ENV_TOOLS_ROOT = "VRAMD_TOOLS_ROOT"
25
25
 
26
26
  # Nomes de pasta tentados para cada ``tool`` (o mesmo nome, e a forma
27
27
  # capitalizada que muitos projetos usam para a pasta do componente).
28
+ def _camel_title(tool: str) -> str:
29
+ """``text2icon`` → ``Text2Icon``; ``skymap2d`` → ``Skymap2D``.
30
+
31
+ O ``str.capitalize()`` só capitaliza a primeira letra — pastas com
32
+ camelCase após um dígito (Text2Icon, Paint3D, Skymap2D…) não eram
33
+ encontradas. Capitaliza o início de cada segmento alfanumérico.
34
+ """
35
+ import re
36
+
37
+ return "".join(part.capitalize() if not part.isdigit() else part for part in re.split(r"(\d+)", tool))
38
+
39
+
28
40
  def _candidate_dirs(tool: str) -> list[str]:
29
41
  """Variações de nome de pasta a tentar para ``tool``."""
30
42
  seen: list[str] = []
31
- for name in (tool, tool.capitalize(), tool.upper(), tool.replace("_", "-")):
43
+ for name in (
44
+ tool,
45
+ tool.capitalize(),
46
+ _camel_title(tool),
47
+ tool.upper(),
48
+ tool.replace("_", "-"),
49
+ ):
32
50
  if name and name not in seen:
33
51
  seen.append(name)
34
52
  return seen
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vramd
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Controlo de admissão de VRAM para inferência generativa em GPUs de consumo
5
5
  Author: Maikeu Locatelli
6
6
  License-Expression: MIT
@@ -73,6 +73,7 @@ tests/test_stats.py
73
73
  tests/test_subprocess_integration.py
74
74
  tests/test_subprocess_per_backend_e2e.py
75
75
  tests/test_subprocess_pool.py
76
+ tests/test_toolchain.py
76
77
  tests/test_unload_frees_vram.py
77
78
  tests/test_vram_admit.py
78
79
  tests/test_vram_planner.py
@@ -115,6 +115,70 @@ class TestCalibrateCommand:
115
115
  assert result.exit_code == 0
116
116
  assert patched["spec"].request["prompt"] == "uma pedra"
117
117
 
118
+ def test_descriptor_calibrate_defaults_are_used(self, runner, patched, tmp_path, monkeypatch):
119
+ """`vramd calibrate <backend>` funciona sem flags quando o descriptor
120
+ declara ``calibrate_request``/``calibrate_load_kwargs`` — o utilizador
121
+ comum não precisa de saber que o backend exige mesh_path/output/quant."""
122
+ yaml_path = tmp_path / "backends.yaml"
123
+ yaml_path.write_text(
124
+ yaml.safe_dump(
125
+ {
126
+ "version": 2,
127
+ "backends": [
128
+ {
129
+ "name": "text3d",
130
+ "adapter": "x",
131
+ "vram_mib": 4000,
132
+ "tool": "text3d",
133
+ "calibrate_request": {"mesh_path": "/tmp/m.glb", "output": "/tmp/out.glb"},
134
+ "calibrate_load_kwargs": {"sdnq_preset": "sdnq-int4"},
135
+ }
136
+ ],
137
+ }
138
+ ),
139
+ encoding="utf-8",
140
+ )
141
+ monkeypatch.setenv("VRAMD_BACKENDS_FILE", str(yaml_path))
142
+ result = runner.invoke(cli_mod.cli, ["calibrate", "text3d", "--no-compare"])
143
+ assert result.exit_code == 0
144
+ spec = patched["spec"]
145
+ assert spec.request["mesh_path"] == "/tmp/m.glb"
146
+ assert spec.request["output"] == "/tmp/out.glb"
147
+ assert spec.load_kwargs["sdnq_preset"] == "sdnq-int4"
148
+
149
+ def test_explicit_flags_override_descriptor_defaults(self, runner, patched, tmp_path, monkeypatch):
150
+ """--request-json/--load-kwargs do operador ganham ao descriptor."""
151
+ yaml_path = tmp_path / "backends.yaml"
152
+ yaml_path.write_text(
153
+ yaml.safe_dump(
154
+ {
155
+ "version": 2,
156
+ "backends": [
157
+ {
158
+ "name": "text3d",
159
+ "adapter": "x",
160
+ "vram_mib": 4000,
161
+ "tool": "text3d",
162
+ "calibrate_request": {"mesh_path": "/a.glb", "output": "/a-out.glb"},
163
+ "calibrate_load_kwargs": {"sdnq_preset": "sdnq-int4"},
164
+ }
165
+ ],
166
+ }
167
+ ),
168
+ encoding="utf-8",
169
+ )
170
+ monkeypatch.setenv("VRAMD_BACKENDS_FILE", str(yaml_path))
171
+ job = tmp_path / "job.json"
172
+ job.write_text(json.dumps({"mesh_path": "/b.glb", "output": "/b-out.glb"}), encoding="utf-8")
173
+ result = runner.invoke(
174
+ cli_mod.cli,
175
+ ["calibrate", "text3d", "--no-compare", "--request-json", str(job), "--load-kwarg", "sdnq_preset=none"],
176
+ )
177
+ assert result.exit_code == 0
178
+ spec = patched["spec"]
179
+ assert spec.request["mesh_path"] == "/b.glb"
180
+ assert spec.load_kwargs["sdnq_preset"] is None # "none" é coergido a None
181
+
118
182
  def test_repeats_and_cycles_flow_through(self, runner, patched):
119
183
  runner.invoke(cli_mod.cli, ["calibrate", "text3d", "--no-compare", "--repeats", "5", "--cycles", "2"])
120
184
  assert patched["spec"].repeats == 5
@@ -0,0 +1,61 @@
1
+ """Testes da descoberta do interpretador das tools (``VRAMD_TOOLS_ROOT``).
2
+
3
+ Regressão: pastas com camelCase após um dígito (Text2Icon, Paint3D,
4
+ Skymap2D — o layout do AiGameKit) não eram encontradas porque
5
+ ``str.capitalize()`` só capitaliza a primeira letra.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ from vramd.toolchain import _camel_title, _candidate_dirs, resolve_tool_python
13
+
14
+
15
+ class TestCamelTitle:
16
+ def test_camel_case_after_digit(self) -> None:
17
+ # O layout do AiGameKit: pasta capitalizada por segmento.
18
+ assert _camel_title("text2icon") == "Text2Icon"
19
+ assert _camel_title("skymap2d") == "Skymap2D"
20
+ assert _camel_title("paint3d") == "Paint3D"
21
+ assert _camel_title("text2sound") == "Text2Sound"
22
+ assert _camel_title("motion3d") == "Motion3D"
23
+
24
+ def test_simple_names_unchanged_in_spirit(self) -> None:
25
+ assert _camel_title("whisper") == "Whisper"
26
+ assert _camel_title("my_tool") == "My_tool"
27
+
28
+
29
+ class TestCandidateDirs:
30
+ def test_includes_camel_title(self) -> None:
31
+ dirs = _candidate_dirs("text2icon")
32
+ assert "Text2Icon" in dirs # o que o capitalize() não dava
33
+ assert "Text2icon" in dirs
34
+ assert "text2icon" in dirs
35
+
36
+ def test_no_duplicates(self) -> None:
37
+ assert len(_candidate_dirs("text2icon")) == len(set(_candidate_dirs("text2icon")))
38
+
39
+
40
+ class TestResolveToolPython:
41
+ def test_finds_camel_case_folder(self, tmp_path: Path, monkeypatch) -> None:
42
+ venv = tmp_path / "Text2Icon" / ".venv" / "bin"
43
+ venv.mkdir(parents=True)
44
+ python = venv / "python"
45
+ python.write_text("#!/bin/sh\n")
46
+ python.chmod(0o755)
47
+ monkeypatch.setenv("VRAMD_TOOLS_ROOT", str(tmp_path))
48
+ assert resolve_tool_python("text2icon") == str(python)
49
+
50
+ def test_plain_name_still_works(self, tmp_path: Path, monkeypatch) -> None:
51
+ venv = tmp_path / "whisper" / ".venv" / "bin"
52
+ venv.mkdir(parents=True)
53
+ python = venv / "python"
54
+ python.write_text("#!/bin/sh\n")
55
+ python.chmod(0o755)
56
+ monkeypatch.setenv("VRAMD_TOOLS_ROOT", str(tmp_path))
57
+ assert resolve_tool_python("whisper") == str(python)
58
+
59
+ def test_no_env_means_no_discovery(self, tmp_path: Path, monkeypatch) -> None:
60
+ monkeypatch.delenv("VRAMD_TOOLS_ROOT", raising=False)
61
+ assert resolve_tool_python("text2icon") is None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes