vhotseat 0.1.0__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 (63) hide show
  1. vhotseat-0.1.0/.gitignore +9 -0
  2. vhotseat-0.1.0/LICENSE +21 -0
  3. vhotseat-0.1.0/PKG-INFO +152 -0
  4. vhotseat-0.1.0/README.md +128 -0
  5. vhotseat-0.1.0/comfy-render +12 -0
  6. vhotseat-0.1.0/config.example.yaml +101 -0
  7. vhotseat-0.1.0/docs/comfyui-updates.md +99 -0
  8. vhotseat-0.1.0/docs/examples/vram-leak-tts-audio-suite.md +80 -0
  9. vhotseat-0.1.0/docs/guides/driver-authoring.md +205 -0
  10. vhotseat-0.1.0/docs/guides/proxy-gate.md +264 -0
  11. vhotseat-0.1.0/docs/guides/quickstart-strata-pi-comfyui.md +249 -0
  12. vhotseat-0.1.0/docs/how-it-works.md +468 -0
  13. vhotseat-0.1.0/docs/reference.md +220 -0
  14. vhotseat-0.1.0/examples/README.md +15 -0
  15. vhotseat-0.1.0/examples/comfyui.yaml +22 -0
  16. vhotseat-0.1.0/examples/ollama-proxy.yaml +28 -0
  17. vhotseat-0.1.0/examples/strata.yaml +32 -0
  18. vhotseat-0.1.0/integrations/comfyui/smoke_job.py +45 -0
  19. vhotseat-0.1.0/integrations/comfyui/vhotseat_guard/LICENSE +21 -0
  20. vhotseat-0.1.0/integrations/comfyui/vhotseat_guard/README.md +25 -0
  21. vhotseat-0.1.0/integrations/comfyui/vhotseat_guard/__init__.py +118 -0
  22. vhotseat-0.1.0/integrations/comfyui/vhotseat_guard/pyproject.toml +16 -0
  23. vhotseat-0.1.0/kit/README.md +38 -0
  24. vhotseat-0.1.0/kit/skills/comfyui-batching/SKILL.md +134 -0
  25. vhotseat-0.1.0/kit/skills/vhotseat-add-workload/SKILL.md +119 -0
  26. vhotseat-0.1.0/kit/skills/vhotseat-comfyui-update/SKILL.md +68 -0
  27. vhotseat-0.1.0/kit/skills/vhotseat-setup/SKILL.md +139 -0
  28. vhotseat-0.1.0/kit/skills/vhotseat-vram-leak/SKILL.md +102 -0
  29. vhotseat-0.1.0/pyproject.toml +71 -0
  30. vhotseat-0.1.0/src/vhotseat/__init__.py +16 -0
  31. vhotseat-0.1.0/src/vhotseat/api.py +89 -0
  32. vhotseat-0.1.0/src/vhotseat/arbiter.py +365 -0
  33. vhotseat-0.1.0/src/vhotseat/cli.py +130 -0
  34. vhotseat-0.1.0/src/vhotseat/config.py +231 -0
  35. vhotseat-0.1.0/src/vhotseat/doctor.py +254 -0
  36. vhotseat-0.1.0/src/vhotseat/drivers/__init__.py +6 -0
  37. vhotseat-0.1.0/src/vhotseat/drivers/base.py +134 -0
  38. vhotseat-0.1.0/src/vhotseat/drivers/comfyui.py +141 -0
  39. vhotseat-0.1.0/src/vhotseat/drivers/http.py +147 -0
  40. vhotseat-0.1.0/src/vhotseat/errors.py +19 -0
  41. vhotseat-0.1.0/src/vhotseat/events.py +35 -0
  42. vhotseat-0.1.0/src/vhotseat/gpu.py +216 -0
  43. vhotseat-0.1.0/src/vhotseat/host.py +95 -0
  44. vhotseat-0.1.0/src/vhotseat/kit/__init__.py +0 -0
  45. vhotseat-0.1.0/src/vhotseat/kit/comfy_render.py +580 -0
  46. vhotseat-0.1.0/src/vhotseat/lock.py +104 -0
  47. vhotseat-0.1.0/src/vhotseat/setup.py +50 -0
  48. vhotseat-0.1.0/src/vhotseat/telemetry.py +201 -0
  49. vhotseat-0.1.0/src/vhotseat/util.py +56 -0
  50. vhotseat-0.1.0/src/vhotseat/web-icon.svg +25 -0
  51. vhotseat-0.1.0/src/vhotseat/web.html +820 -0
  52. vhotseat-0.1.0/src/vhotseat/web.py +166 -0
  53. vhotseat-0.1.0/tests/conftest.py +50 -0
  54. vhotseat-0.1.0/tests/fakes.py +378 -0
  55. vhotseat-0.1.0/tests/run_cli.py +53 -0
  56. vhotseat-0.1.0/tests/test_api.py +128 -0
  57. vhotseat-0.1.0/tests/test_comfy_render.py +208 -0
  58. vhotseat-0.1.0/tests/test_examples.py +28 -0
  59. vhotseat-0.1.0/tests/test_handover.py +421 -0
  60. vhotseat-0.1.0/tests/test_phase2.py +148 -0
  61. vhotseat-0.1.0/tests/test_vhotseat.py +335 -0
  62. vhotseat-0.1.0/tests/test_web.py +174 -0
  63. vhotseat-0.1.0/vhotseat +13 -0
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ uv.lock
vhotseat-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wandermyz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.5
2
+ Name: vhotseat
3
+ Version: 0.1.0
4
+ Summary: Take turns on one GPU: hand it between a local LLM (e.g. Strata, driven by an agent like pi), ComfyUI and any other workload, never interrupting running work.
5
+ Project-URL: Homepage, https://github.com/wandermyz/vhotseat
6
+ Project-URL: Issues, https://github.com/wandermyz/vhotseat/issues
7
+ Author: wandermyz
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: comfyui,gpu,llm,local-llm,minimax-h3,pi-agent,scheduler,strata,vram
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: System :: Hardware
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: pyyaml>=6
23
+ Description-Content-Type: text/markdown
24
+
25
+ # vhotseat
26
+
27
+ [![CI](https://github.com/wandermyz/vhotseat/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/wandermyz/vhotseat/actions/workflows/ci.yml)
28
+ [![Release](https://img.shields.io/github/v/release/wandermyz/vhotseat?sort=semver)](https://github.com/wandermyz/vhotseat/releases/latest)
29
+
30
+ **Want your local LLM agent to drive ComfyUI on the same consumer graphics
31
+ card?** Without coordination, the first video render lands on top of the
32
+ loaded LLM, and something runs out of memory. **Boom, OOM!**
33
+
34
+ vhotseat makes [Strata](https://github.com/Niko1221/Strata) and
35
+ [ComfyUI](https://github.com/comfyanonymous/ComfyUI) take turns on VRAM *and*
36
+ RAM: when the agent queues a render, the LLM is unloaded; when it asks the LLM
37
+ its next question, ComfyUI is freed. Neither ever loads on top of the other,
38
+ and **running work is never interrupted**: not an answer the LLM is writing,
39
+ not a render in progress.
40
+
41
+ > v-hotseat: VRAM hotseat. Hot-seat multiplayer: one device, players take
42
+ > turns, nobody is kicked out mid-turn.
43
+
44
+ ![vhotseat: one GPU, local agentic GenAI. pi drives, ComfyUI renders, Strata reviews and composites.](https://raw.githubusercontent.com/wandermyz/vhotseat/a619e2e66e284521f3a8f66e2892790a562403df/branding/social-preview.png)
45
+
46
+ **Tested on:**
47
+
48
+ | Part | Tested with |
49
+ | --- | --- |
50
+ | GPU / RAM | RTX 5070 Ti (16 GB VRAM), 64 GB RAM |
51
+ | LLM | Strata serving Qwen3.8-Flash-Next IQ3_XXS (128K context) |
52
+ | Agent | [pi](https://github.com/earendil-works/pi) |
53
+ | ComfyUI | MiniMax H3 video (int8 `fl2va` / `ref2va`) |
54
+
55
+ With this setup, pi drove long-running video workflows unattended, over
56
+ several rounds of render → review → render, overnight.
57
+
58
+ Status: Linux, NVIDIA. Windows is designed for and planned. MIT licence.
59
+
60
+ **→ [Quick start for this exact setup](https://github.com/wandermyz/vhotseat/blob/main/docs/guides/quickstart-strata-pi-comfyui.md)**:
61
+ a step-by-step guide that you, or your coding agent, can follow. Or just
62
+ prompt your coding agent to set up the whole workflow:
63
+
64
+ > Set up vhotseat so my local LLM agent can drive ComfyUI on this GPU without
65
+ > running out of memory. Follow
66
+ > https://github.com/wandermyz/vhotseat/blob/main/docs/guides/quickstart-strata-pi-comfyui.md,
67
+ > adapting it to the servers and paths on this machine, and run each step's
68
+ > check before going on.
69
+
70
+ ## How it works
71
+
72
+ The servers stay as they are, and every client is covered: the agent, its
73
+ scripts, a web UI, curl. Nobody calls vhotseat; each workload asks for the GPU
74
+ through a **gate** right before it loads, and vhotseat makes room by releasing
75
+ the others:
76
+
77
+ - **The LLM** runs `vhotseat gate` from its pre-load hook. While ComfyUI has
78
+ work queued the load is refused (a 503 the agent's harness retries).
79
+ - **ComfyUI** runs `vhotseat switch` before every job through a small custom
80
+ node, the guard. It waits for any answer the LLM is writing, then unloads it.
81
+ - **Admission** checks there's enough free VRAM and RAM before a load, and
82
+ restarts an idle ComfyUI if its leftover RAM is what's in the way.
83
+ - **[comfy-render](https://github.com/wandermyz/vhotseat/blob/main/kit/README.md#comfy-render)** lets the agent run a batch of
84
+ workflows in one call, so a batch costs two handovers instead of two per job.
85
+
86
+ Strata and ComfyUI are just the built-in examples: anything that uses the GPU
87
+ (another LLM server, a TTS server, a training run) can join through a config
88
+ entry or a small driver, with a hook, plugin, proxy or `hold` as its gate.
89
+
90
+ **How it differs.** [llama-swap](https://github.com/mostlygeek/llama-swap) and
91
+ Ollama's scheduler swap models between LLM servers they control; they don't
92
+ take turns with a non-LLM app like ComfyUI, and don't wait for a running job.
93
+ vhotseat arbitrates between *different* servers, of any kind, and can sit on
94
+ top of either (they can be one of its workloads).
95
+
96
+ ## Install
97
+
98
+ ```sh
99
+ uv tool install vhotseat # or: pipx install vhotseat
100
+ ```
101
+
102
+ That installs two commands, `vhotseat` and `comfy-render`, with one dependency
103
+ (PyYAML). From a checkout, `./vhotseat` and `./comfy-render` run as they are;
104
+ symlink them into `~/.local/bin`.
105
+
106
+ Then set it up for your servers: follow the
107
+ [quick start](https://github.com/wandermyz/vhotseat/blob/main/docs/guides/quickstart-strata-pi-comfyui.md), or let a coding
108
+ agent do it with the [vhotseat-setup skill](https://github.com/wandermyz/vhotseat/blob/main/kit/skills/vhotseat-setup/SKILL.md).
109
+ [config.example.yaml](https://github.com/wandermyz/vhotseat/blob/main/config.example.yaml) is commented key by key, and
110
+ [examples/](https://github.com/wandermyz/vhotseat/blob/main/examples/) has configs for other servers.
111
+
112
+ ```sh
113
+ vhotseat doctor # check the config, the GPU, every workload and gate
114
+ vhotseat status # who owns the GPU and what each workload holds
115
+ vhotseat web # dashboard: RAM and VRAM over time, handovers, events, logs
116
+ ```
117
+
118
+ ![The dashboard on an RTX 5070 Ti: Strata hands the GPU to ComfyUI at 01:04, and a render is running.](https://raw.githubusercontent.com/wandermyz/vhotseat/24999a8e62e3a6342e9af6c3bc70042e77765c60/web-dashboard/desktop.png)
119
+
120
+ ## Documentation
121
+
122
+ - [Quick start](https://github.com/wandermyz/vhotseat/blob/main/docs/guides/quickstart-strata-pi-comfyui.md): Strata + pi +
123
+ ComfyUI on one 16 GB card, step by step.
124
+ - [How it works](https://github.com/wandermyz/vhotseat/blob/main/docs/how-it-works.md): a full agent → ComfyUI → agent flow,
125
+ the races and deadlocks, and how often the GPU changes hands.
126
+ - [Reference](https://github.com/wandermyz/vhotseat/blob/main/docs/reference.md): commands, gates, admission, the ComfyUI
127
+ guard, `comfy-render`, the dashboard and telemetry.
128
+ - [Agent kit](https://github.com/wandermyz/vhotseat/blob/main/kit/README.md): `comfy-render` and
129
+ [Agent Skills](https://agentskills.io) for the agent that renders, and for
130
+ the coding agent that sets vhotseat up and keeps it working.
131
+ - [Writing a driver](https://github.com/wandermyz/vhotseat/blob/main/docs/guides/driver-authoring.md) for another server, and
132
+ [building a proxy gate](https://github.com/wandermyz/vhotseat/blob/main/docs/guides/proxy-gate.md) for one with no pre-load
133
+ hook.
134
+ - [After a ComfyUI update](https://github.com/wandermyz/vhotseat/blob/main/docs/comfyui-updates.md): checking the guard still
135
+ works. [Fixing a VRAM leak](https://github.com/wandermyz/vhotseat/blob/main/docs/examples/vram-leak-tts-audio-suite.md) in a
136
+ custom node is a worked example.
137
+
138
+ ## Development
139
+
140
+ ```sh
141
+ uv run --group dev pytest # ~2 min; fake LLM and ComfyUI servers, no GPU needed
142
+ uvx ruff check .
143
+ ```
144
+
145
+ The tests run the real CLI and arbiter against fake servers over HTTP on
146
+ localhost; the config they use is [config.example.yaml](https://github.com/wandermyz/vhotseat/blob/main/config.example.yaml),
147
+ and every file in [examples/](https://github.com/wandermyz/vhotseat/blob/main/examples/) is validated. CI runs both on Python
148
+ 3.10 to 3.13.
149
+
150
+ ## Licence
151
+
152
+ MIT. See [LICENSE](https://github.com/wandermyz/vhotseat/blob/main/LICENSE).
@@ -0,0 +1,128 @@
1
+ # vhotseat
2
+
3
+ [![CI](https://github.com/wandermyz/vhotseat/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/wandermyz/vhotseat/actions/workflows/ci.yml)
4
+ [![Release](https://img.shields.io/github/v/release/wandermyz/vhotseat?sort=semver)](https://github.com/wandermyz/vhotseat/releases/latest)
5
+
6
+ **Want your local LLM agent to drive ComfyUI on the same consumer graphics
7
+ card?** Without coordination, the first video render lands on top of the
8
+ loaded LLM, and something runs out of memory. **Boom, OOM!**
9
+
10
+ vhotseat makes [Strata](https://github.com/Niko1221/Strata) and
11
+ [ComfyUI](https://github.com/comfyanonymous/ComfyUI) take turns on VRAM *and*
12
+ RAM: when the agent queues a render, the LLM is unloaded; when it asks the LLM
13
+ its next question, ComfyUI is freed. Neither ever loads on top of the other,
14
+ and **running work is never interrupted**: not an answer the LLM is writing,
15
+ not a render in progress.
16
+
17
+ > v-hotseat: VRAM hotseat. Hot-seat multiplayer: one device, players take
18
+ > turns, nobody is kicked out mid-turn.
19
+
20
+ ![vhotseat: one GPU, local agentic GenAI. pi drives, ComfyUI renders, Strata reviews and composites.](https://raw.githubusercontent.com/wandermyz/vhotseat/a619e2e66e284521f3a8f66e2892790a562403df/branding/social-preview.png)
21
+
22
+ **Tested on:**
23
+
24
+ | Part | Tested with |
25
+ | --- | --- |
26
+ | GPU / RAM | RTX 5070 Ti (16 GB VRAM), 64 GB RAM |
27
+ | LLM | Strata serving Qwen3.8-Flash-Next IQ3_XXS (128K context) |
28
+ | Agent | [pi](https://github.com/earendil-works/pi) |
29
+ | ComfyUI | MiniMax H3 video (int8 `fl2va` / `ref2va`) |
30
+
31
+ With this setup, pi drove long-running video workflows unattended, over
32
+ several rounds of render → review → render, overnight.
33
+
34
+ Status: Linux, NVIDIA. Windows is designed for and planned. MIT licence.
35
+
36
+ **→ [Quick start for this exact setup](docs/guides/quickstart-strata-pi-comfyui.md)**:
37
+ a step-by-step guide that you, or your coding agent, can follow. Or just
38
+ prompt your coding agent to set up the whole workflow:
39
+
40
+ > Set up vhotseat so my local LLM agent can drive ComfyUI on this GPU without
41
+ > running out of memory. Follow
42
+ > https://github.com/wandermyz/vhotseat/blob/main/docs/guides/quickstart-strata-pi-comfyui.md,
43
+ > adapting it to the servers and paths on this machine, and run each step's
44
+ > check before going on.
45
+
46
+ ## How it works
47
+
48
+ The servers stay as they are, and every client is covered: the agent, its
49
+ scripts, a web UI, curl. Nobody calls vhotseat; each workload asks for the GPU
50
+ through a **gate** right before it loads, and vhotseat makes room by releasing
51
+ the others:
52
+
53
+ - **The LLM** runs `vhotseat gate` from its pre-load hook. While ComfyUI has
54
+ work queued the load is refused (a 503 the agent's harness retries).
55
+ - **ComfyUI** runs `vhotseat switch` before every job through a small custom
56
+ node, the guard. It waits for any answer the LLM is writing, then unloads it.
57
+ - **Admission** checks there's enough free VRAM and RAM before a load, and
58
+ restarts an idle ComfyUI if its leftover RAM is what's in the way.
59
+ - **[comfy-render](kit/README.md#comfy-render)** lets the agent run a batch of
60
+ workflows in one call, so a batch costs two handovers instead of two per job.
61
+
62
+ Strata and ComfyUI are just the built-in examples: anything that uses the GPU
63
+ (another LLM server, a TTS server, a training run) can join through a config
64
+ entry or a small driver, with a hook, plugin, proxy or `hold` as its gate.
65
+
66
+ **How it differs.** [llama-swap](https://github.com/mostlygeek/llama-swap) and
67
+ Ollama's scheduler swap models between LLM servers they control; they don't
68
+ take turns with a non-LLM app like ComfyUI, and don't wait for a running job.
69
+ vhotseat arbitrates between *different* servers, of any kind, and can sit on
70
+ top of either (they can be one of its workloads).
71
+
72
+ ## Install
73
+
74
+ ```sh
75
+ uv tool install vhotseat # or: pipx install vhotseat
76
+ ```
77
+
78
+ That installs two commands, `vhotseat` and `comfy-render`, with one dependency
79
+ (PyYAML). From a checkout, `./vhotseat` and `./comfy-render` run as they are;
80
+ symlink them into `~/.local/bin`.
81
+
82
+ Then set it up for your servers: follow the
83
+ [quick start](docs/guides/quickstart-strata-pi-comfyui.md), or let a coding
84
+ agent do it with the [vhotseat-setup skill](kit/skills/vhotseat-setup/SKILL.md).
85
+ [config.example.yaml](config.example.yaml) is commented key by key, and
86
+ [examples/](examples/) has configs for other servers.
87
+
88
+ ```sh
89
+ vhotseat doctor # check the config, the GPU, every workload and gate
90
+ vhotseat status # who owns the GPU and what each workload holds
91
+ vhotseat web # dashboard: RAM and VRAM over time, handovers, events, logs
92
+ ```
93
+
94
+ ![The dashboard on an RTX 5070 Ti: Strata hands the GPU to ComfyUI at 01:04, and a render is running.](https://raw.githubusercontent.com/wandermyz/vhotseat/24999a8e62e3a6342e9af6c3bc70042e77765c60/web-dashboard/desktop.png)
95
+
96
+ ## Documentation
97
+
98
+ - [Quick start](docs/guides/quickstart-strata-pi-comfyui.md): Strata + pi +
99
+ ComfyUI on one 16 GB card, step by step.
100
+ - [How it works](docs/how-it-works.md): a full agent → ComfyUI → agent flow,
101
+ the races and deadlocks, and how often the GPU changes hands.
102
+ - [Reference](docs/reference.md): commands, gates, admission, the ComfyUI
103
+ guard, `comfy-render`, the dashboard and telemetry.
104
+ - [Agent kit](kit/README.md): `comfy-render` and
105
+ [Agent Skills](https://agentskills.io) for the agent that renders, and for
106
+ the coding agent that sets vhotseat up and keeps it working.
107
+ - [Writing a driver](docs/guides/driver-authoring.md) for another server, and
108
+ [building a proxy gate](docs/guides/proxy-gate.md) for one with no pre-load
109
+ hook.
110
+ - [After a ComfyUI update](docs/comfyui-updates.md): checking the guard still
111
+ works. [Fixing a VRAM leak](docs/examples/vram-leak-tts-audio-suite.md) in a
112
+ custom node is a worked example.
113
+
114
+ ## Development
115
+
116
+ ```sh
117
+ uv run --group dev pytest # ~2 min; fake LLM and ComfyUI servers, no GPU needed
118
+ uvx ruff check .
119
+ ```
120
+
121
+ The tests run the real CLI and arbiter against fake servers over HTTP on
122
+ localhost; the config they use is [config.example.yaml](config.example.yaml),
123
+ and every file in [examples/](examples/) is validated. CI runs both on Python
124
+ 3.10 to 3.13.
125
+
126
+ ## Licence
127
+
128
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python3
2
+ """comfy-render from a checkout (installed as a symlink, like ./vhotseat). See src/vhotseat/kit/comfy_render.py."""
3
+
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ sys.path.insert(0, str(Path(__file__).resolve().parent / "src"))
8
+
9
+ from vhotseat.kit.comfy_render import main # noqa: E402
10
+
11
+ if __name__ == "__main__":
12
+ sys.exit(main())
@@ -0,0 +1,101 @@
1
+ # vhotseat config: ~/.config/vhotseat/config.yaml (or $VHOTSEAT_CONFIG).
2
+ # Two workloads on one 16 GB card: Strata (a local LLM server) and ComfyUI.
3
+ # Every key is commented. Change the paths, units and figures to your machine,
4
+ # then run `vhotseat doctor`. examples/ has one snippet per workload.
5
+
6
+ gpu:
7
+ index: 0
8
+ backend: auto # nvml (no subprocess per read), nvidia-smi, or auto (nvml, else nvidia-smi)
9
+
10
+ workloads:
11
+ # A workload is named here; that name is what `vhotseat switch NAME` takes.
12
+ strata:
13
+ driver: http # described entirely by HTTP requests below
14
+ label: Strata
15
+ url: http://127.0.0.1:2335
16
+ service: {systemd-user: strata} # started when stopped; restarted when allow_restart
17
+ # Its VRAM is found by the systemd unit its processes run in. Without
18
+ # `vram:`, the service's unit is used; {process: SUBSTRING} matches the
19
+ # command line instead.
20
+ resident: {get: /health, path: loaded} # true while its model is in VRAM
21
+ release:
22
+ post: /unload?wait={wait} # {wait}: seconds to let a running answer finish
23
+ always: true # ask even when it looks unloaded: a load may be under way
24
+ busy_when: [{status: 409}, {path: status, in: [busy]}]
25
+ ok_when: [{status: 200, path: status, in: [unloaded, not loaded]}]
26
+ # timeout: 30 # seconds the request may take on top of {wait} (default 30)
27
+ # pre: {post: …} # a request first, e.g. a proxy's drain (docs/guides/proxy-gate.md)
28
+ confirm: {vram_at_most: 0} # released once its VRAM is down to this
29
+ load: {post: /load, timeout: 300} # `switch strata` leaves it loaded
30
+ model: {get: /health, path: model} # which model it serves; picks the admission below
31
+ gate:
32
+ kind: hook # Strata runs `vhotseat gate strata` before every load
33
+ config: ~/Strata/strata.json # its config file, checked by doctor
34
+ timeout: 90 # Strata's before_load_timeout_s: the hook must fit in it
35
+ admission:
36
+ # RAM the machine must be able to give Strata before it may load: its
37
+ # experts plus working memory, with some headroom. Compared with
38
+ # *available* RAM (MemAvailable), which counts page cache the kernel can
39
+ # drop on demand; *free* RAM is always small on a busy machine. When
40
+ # other workloads still hold too much after releasing their models, idle
41
+ # ones with allow_restart are restarted; if that still isn't enough, the
42
+ # load is refused and the event log says RAM was short.
43
+ # This default is for a model not listed under by_model (or when Strata
44
+ # doesn't say which it serves): the largest one, to stay safe.
45
+ min_available_ram_mib: 46000
46
+ # VRAM that must be free on the card before Strata may load. Other
47
+ # workloads always release the GPU first, whatever this says; this
48
+ # catches what vhotseat doesn't manage (the desktop, a game, a release
49
+ # that left VRAM behind).
50
+ min_free_vram_mib: 14000
51
+ # Per model, as /health reports it. For example, loading from idle,
52
+ # IQ3_XXS took 44.5 GiB of available RAM (42.5 GiB resident, 56 GiB at the peak)
53
+ # IQ2_XS took 38.2 GiB (35.6 GiB resident, 47 GiB at the peak)
54
+ # Both use ~14.7 GB of VRAM. To measure a model: load it (`vhotseat switch
55
+ # strata` from idle), note how much MemAvailable dropped, add ~1.5 GiB.
56
+ by_model:
57
+ qwen3.8-flash-next-iq3_xxs: {min_available_ram_mib: 46000}
58
+ qwen3.8-flash-next-iq2_xs: {min_available_ram_mib: 40000}
59
+ allow_restart: true
60
+
61
+ comfy:
62
+ driver: comfyui # built-in: /queue, /free, restart when /free isn't enough
63
+ label: ComfyUI
64
+ url: http://127.0.0.1:8188
65
+ service: {systemd-user: comfyui}
66
+ idle_vram_mib: 1500 # its CUDA context and caches; above this it holds models
67
+ gate:
68
+ kind: plugin # the guard node takes the GPU before every job
69
+ custom_nodes: ~/ComfyUI/custom_nodes # where the guard is installed; checked by doctor
70
+ timeout: 300 # how long a job waits for an answer Strata is writing
71
+ # No admission: what ComfyUI needs depends on the workflow, and it manages
72
+ # its own memory. Other workloads still release the GPU before every job.
73
+ # Restarted when its release leaves VRAM behind, or when another
74
+ # workload's admission needs RAM its idle process still holds (6-15 GiB
75
+ # after a video render). Only when its queue is empty and its last result
76
+ # finished at least history_settle_s ago (a restart wipes /history).
77
+ allow_restart: true
78
+
79
+ # Local patches to other projects' code that a handover depends on. An update
80
+ # of that project drops a patch silently, and the symptom shows up far away
81
+ # (a release that leaves VRAM behind, so every handover is refused). `vhotseat
82
+ # doctor` checks each marker is still in its file, and says when the installed
83
+ # version has the fix upstream so the patch can go. An entry looks like:
84
+ #
85
+ # - name: TTS Audio Suite IndexTTS unload
86
+ # file: ~/ComfyUI/custom_nodes/TTS-Audio-Suite/engines/index_tts/indextts/infer_v2_5.py
87
+ # marker: "LOCAL PATCH (vhotseat)" # a comment you put next to your change
88
+ # doc: ~/.config/vhotseat/patches/tts-audio-suite.md # optional: what and why
89
+ # fixed_upstream_in: {version: 5.10.4, version_file: ~/ComfyUI/custom_nodes/TTS-Audio-Suite/pyproject.toml}
90
+ patches: []
91
+
92
+ events:
93
+ keep: 500 # ~/.local/state/vhotseat/events.jsonl
94
+
95
+ # RAM and VRAM over time, events and log lines, for `vhotseat web`. Every
96
+ # vhotseat command records as it runs (~/.local/state/vhotseat/telemetry.db);
97
+ # `vhotseat record` (or `vhotseat web`) samples between commands too.
98
+ telemetry:
99
+ # enabled: true
100
+ retention_hours: 24 # ~6 MiB on disk with two workloads; ~0.8 MiB a day more per further workload
101
+ interval_s: 5 # between samples while something samples continuously
@@ -0,0 +1,99 @@
1
+ # Handling ComfyUI updates
2
+
3
+ The guard node, `integrations/comfyui/vhotseat_guard`, makes each ComfyUI job
4
+ take the GPU from the other workloads before it runs. It does that by wrapping
5
+ a **private** ComfyUI method, `execution.PromptExecutor.execute_async`.
6
+ ComfyUI doesn't promise to keep that method stable, so an update can break the
7
+ guard. The `vhotseat-comfyui-update` skill walks an agent through this page.
8
+
9
+ Below, `$COMFY` is ComfyUI's folder and the service is a systemd user unit
10
+ named `comfyui`; adjust both to your setup.
11
+
12
+ ## What breaks, and how you'd notice
13
+
14
+ | Change in ComfyUI | What happens | Visible as |
15
+ | --- | --- | --- |
16
+ | `execution.PromptExecutor` or `execute_async` renamed or removed | Guard turns itself **off**; ComfyUI keeps working, unguarded | `vhotseat_guard: OFF, ComfyUI internals changed` in the log at startup |
17
+ | `execute_async` gets new parameters | Guard is on, but every job fails with a `TypeError` | Every job errors right after you queue it |
18
+ | The worker stops calling `execute_async` once per job | Guard is on but never runs | Jobs run while the LLM is loaded; most likely an out-of-memory error |
19
+ | Fields read by `_fail_job` change (`status_messages`, `history_result`, `add_message`) | Only the failure path breaks: a refused job crashes instead of failing cleanly | Test 4 below |
20
+
21
+ The first row is the safe one. The others are why you should check after every
22
+ update rather than wait to notice.
23
+
24
+ ## After every ComfyUI update
25
+
26
+ 1. **Note the current commit, update, then restart:**
27
+ ```sh
28
+ cd $COMFY && git rev-parse --short HEAD # to roll back to, if needed
29
+ git pull && systemctl --user restart comfyui
30
+ ```
31
+
32
+ 2. **Confirm the guard loaded and is on:**
33
+ ```sh
34
+ journalctl --user -u comfyui --since "-5min" | grep vhotseat_guard
35
+ ```
36
+ Expect `vhotseat_guard: on (…/vhotseat, wait 300s)`. If you see `OFF` or
37
+ an import error, stop here and go to *Fixing it* below.
38
+
39
+ 3. **Check that the method's signature hasn't changed.** The guard mirrors it:
40
+ ```sh
41
+ grep -n "async def execute_async" $COMFY/execution.py
42
+ ```
43
+ Expect `async def execute_async(self, prompt, prompt_id, extra_data={}, execute_outputs=[]):`.
44
+ Also check that the worker still calls it once per job: `main.py` →
45
+ `prompt_worker` → `e.execute(...)` → `asyncio.run(self.execute_async(...))`.
46
+
47
+ 4. **Run the smoke tests.** These take a couple of minutes and need a few GB
48
+ of VRAM. `integrations/comfyui/smoke_job.py` queues a 1-step SDXL job that
49
+ ends in `PreviewImage`, so nothing is written to `output/`; pass a
50
+ checkpoint you have with `--ckpt NAME.safetensors`. It prints
51
+ `success in 9.0s`, or `error in 2.5s: GPU busy, job not run: …`. The tests
52
+ use Strata as the LLM; for another LLM, send it the same kind of requests.
53
+
54
+ | # | Do | Expect |
55
+ | --- | --- | --- |
56
+ | 1 | `vhotseat switch strata`, then queue a job | Job succeeds; `vhotseat status` shows `comfy`, Strata unloaded |
57
+ | 2 | Start a long Strata answer, queue a job 2–3 s later | Strata's answer completes; job runs after it and succeeds |
58
+ | 3 | Queue a job, send a Strata request, queue another | Both jobs succeed; each Strata request frees ComfyUI first |
59
+ | 4 | `systemctl --user set-environment VHOTSEAT_GUARD_WAIT_S=2`, restart ComfyUI, start a long Strata answer, queue a job | Job ends `status: error` with `GPU busy, job not run: …` in `/history/<id>`; Strata's answer completes. Then `systemctl --user unset-environment VHOTSEAT_GUARD_WAIT_S` and restart ComfyUI |
60
+ | 5 | `comfy-render` a batch of 2–3 small jobs (a `{"base": …, "jobs": [seeds]}` file ending in `SaveImage`) | Progress lines while it runs, one `ok` line per job with its file, exit code 0 |
61
+
62
+ For a long Strata answer:
63
+ ```sh
64
+ curl -s localhost:2335/v1/chat/completions -H 'Content-Type: application/json' \
65
+ -d '{"messages":[{"role":"user","content":"Count from 1 to 400."}],"max_tokens":1500}' >/dev/null &
66
+ ```
67
+
68
+ Test 5 checks comfy-render, which reads ComfyUI's public `/history` and
69
+ websocket formats rather than private methods, so it breaks less often than
70
+ the guard, but it does depend on them.
71
+
72
+ 5. **Record the commit** you verified (step 1's `git rev-parse` after the
73
+ pull), so the next rollback has a known-good target.
74
+
75
+ ## Fixing it
76
+
77
+ - **Method renamed:** find what `prompt_worker` in `main.py` now calls per job,
78
+ and wrap that instead. The requirements are:
79
+ - it runs on the worker thread (blocking is fine there; the web server's
80
+ thread must never block);
81
+ - it runs once per job, before any model loads;
82
+ - you can still report a failure that shows up in `/history`.
83
+ - **Do not move the guard into `PromptServer.add_on_prompt_handler`.** That
84
+ runs at submit time, on the web server's event loop. Blocking there for up
85
+ to 300 s would freeze ComfyUI's UI and API.
86
+ - **To run unguarded until it's fixed:** set `VHOTSEAT_GUARD=0` in the
87
+ unit's environment (`systemctl --user edit comfyui`). Then queue ComfyUI
88
+ work only after running `vhotseat switch comfy` by hand.
89
+
90
+ ## Rolling back ComfyUI
91
+
92
+ When a fix isn't quick, go back to the last commit the guard was verified on:
93
+
94
+ ```sh
95
+ cd $COMFY && git log --oneline -5 # find the previous commit
96
+ git checkout <commit> && systemctl --user restart comfyui
97
+ ```
98
+
99
+ The guard was last verified by the vhotseat project on ComfyUI v0.39.0.
@@ -0,0 +1,80 @@
1
+ # Example: a VRAM leak in TTS Audio Suite, from symptom to upstream fix
2
+
3
+ A worked example of the `vhotseat-vram-leak` skill. The machine shared one
4
+ 16 GB GPU between a local LLM (Strata) and ComfyUI, with the
5
+ [TTS Audio Suite](https://github.com/diodiogod/TTS-Audio-Suite) custom node
6
+ pack rendering speech with IndexTTS-2.5. Fixed upstream in **5.10.4**
7
+ ([PR #373](https://github.com/diodiogod/TTS-Audio-Suite/pull/373)); suite
8
+ versions up to 5.10.3 have the leak.
9
+
10
+ ## Symptom
11
+
12
+ After an IndexTTS render, ComfyUI's `POST /free` reported "freed 4804MB", but
13
+ ComfyUI still held about 2 GB, above its `idle_vram_mib` of 1500. Every LLM
14
+ load after a TTS render was refused:
15
+
16
+ ```
17
+ strata-load (allowed=False, reason=could not free ComfyUI's 2066 MiB:
18
+ timed out after 20s waiting for ComfyUI to release VRAM (below 1500 MiB))
19
+ ```
20
+
21
+ With `allow_restart: true` vhotseat restarts ComfyUI instead, which works but
22
+ adds ~20 s to that handover, and the leak still costs ComfyUI 2 GB whenever its
23
+ own model manager swaps the TTS model out for another one.
24
+
25
+ ## Diagnosis
26
+
27
+ Reproduced standalone: load IndexTTS2 the way the suite does, render once
28
+ under `torch.inference_mode()` (as ComfyUI does), call the suite's unload,
29
+ then list the `gc.get_objects()` tensors still `is_cuda`.
30
+
31
+ The suite wraps the IndexTTS2 engine (`engines/index_tts/indextts/infer_v2_5.py`)
32
+ for ComfyUI's model manager. On unload, the wrapper calls `model.to("cpu")` if
33
+ the object has a `to()`, and otherwise moves each `nn.Module` attribute one by
34
+ one. `IndexTTS2` had no `to()`, so only those modules moved. Left on the GPU:
35
+
36
+ | Left behind | Size |
37
+ | --- | --- |
38
+ | `qwen_emo` (QwenEmotion, a Qwen 0.6B text-to-emotion model held in a plain attribute, so never moved) | 1137 MiB |
39
+ | Plain tensor attributes inside modules, which `Module.to()` skips: old-style `weight_norm` weights, a `causal_mask` (64 MiB), `freqs_cis` | ~180 MiB |
40
+ | Cached reference-voice conditioning, emotion and speaker matrices | ~8 MiB |
41
+ | Allocator cache held because of the above | the rest |
42
+
43
+ ## Fix
44
+
45
+ A `to(device)` method on `IndexTTS2`, which the wrapper then uses for both
46
+ unload and reload. It moves every model module plus the plain tensor
47
+ attributes inside them, QwenEmotion if loaded, and the emotion and speaker
48
+ matrices, and drops the cached reference-voice conditioning (rebuilt on the
49
+ next render).
50
+
51
+ | After a render and unload | ComfyUI VRAM |
52
+ | --- | --- |
53
+ | Before | 2066 MiB: LLM refused |
54
+ | **Patched** | **~340 MiB** standalone, ~500 MiB in ComfyUI |
55
+ | Floor: any process that has used CUDA | ~270 MiB |
56
+
57
+ ## Recording it until upstream shipped it
58
+
59
+ While the fix was local, every changed spot carried `LOCAL PATCH (vhotseat)`,
60
+ and the config had:
61
+
62
+ ```yaml
63
+ patches:
64
+ - name: TTS Audio Suite IndexTTS unload
65
+ file: <ComfyUI>/custom_nodes/TTS-Audio-Suite/engines/index_tts/indextts/infer_v2_5.py
66
+ marker: "LOCAL PATCH (vhotseat)"
67
+ doc: ~/.config/vhotseat/patches/tts-audio-suite.md
68
+ fixed_upstream_in: {version: 5.10.4, version_file: <ComfyUI>/custom_nodes/TTS-Audio-Suite/pyproject.toml}
69
+ ```
70
+
71
+ When the suite updated to 5.10.4, `vhotseat doctor` reported that the
72
+ installed version had the fix upstream and the patch could go, before anyone
73
+ looked; the entry was then removed.
74
+
75
+ ## If it comes back
76
+
77
+ Other engines in the suite are wrapped the same way, so each needs a `to()`
78
+ that moves everything. The symptom is the same: ComfyUI above `idle_vram_mib`
79
+ after `/free`, or the event log showing ComfyUI restarted because it kept VRAM
80
+ after `/free`, right after renders with that engine. Diagnose it the same way.