mesh-deck 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 (46) hide show
  1. mesh_deck-0.1.0/CHANGELOG.md +30 -0
  2. mesh_deck-0.1.0/CONTRIBUTING.md +101 -0
  3. mesh_deck-0.1.0/LICENSE +21 -0
  4. mesh_deck-0.1.0/MANIFEST.in +5 -0
  5. mesh_deck-0.1.0/PKG-INFO +238 -0
  6. mesh_deck-0.1.0/README.md +196 -0
  7. mesh_deck-0.1.0/SECURITY.md +20 -0
  8. mesh_deck-0.1.0/docs/assets/logo-dark.svg +14 -0
  9. mesh_deck-0.1.0/docs/assets/logo-light.svg +14 -0
  10. mesh_deck-0.1.0/docs/commands.md +215 -0
  11. mesh_deck-0.1.0/docs/config.md +91 -0
  12. mesh_deck-0.1.0/docs/how-it-works.md +95 -0
  13. mesh_deck-0.1.0/examples/dual-gpu.yaml +74 -0
  14. mesh_deck-0.1.0/examples/single-gpu.yaml +51 -0
  15. mesh_deck-0.1.0/pyproject.toml +77 -0
  16. mesh_deck-0.1.0/setup.cfg +4 -0
  17. mesh_deck-0.1.0/src/mesh_cli/__init__.py +1 -0
  18. mesh_deck-0.1.0/src/mesh_cli/__main__.py +7 -0
  19. mesh_deck-0.1.0/src/mesh_cli/cli.py +333 -0
  20. mesh_deck-0.1.0/src/mesh_cli/completion.py +192 -0
  21. mesh_deck-0.1.0/src/mesh_cli/config.py +96 -0
  22. mesh_deck-0.1.0/src/mesh_cli/gpu.py +88 -0
  23. mesh_deck-0.1.0/src/mesh_cli/holders.py +310 -0
  24. mesh_deck-0.1.0/src/mesh_cli/planner.py +113 -0
  25. mesh_deck-0.1.0/src/mesh_cli/py.typed +0 -0
  26. mesh_deck-0.1.0/src/mesh_cli/runner.py +388 -0
  27. mesh_deck-0.1.0/src/mesh_cli/suggest.py +17 -0
  28. mesh_deck-0.1.0/src/mesh_cli/ui.py +251 -0
  29. mesh_deck-0.1.0/src/mesh_deck.egg-info/PKG-INFO +238 -0
  30. mesh_deck-0.1.0/src/mesh_deck.egg-info/SOURCES.txt +44 -0
  31. mesh_deck-0.1.0/src/mesh_deck.egg-info/dependency_links.txt +1 -0
  32. mesh_deck-0.1.0/src/mesh_deck.egg-info/entry_points.txt +2 -0
  33. mesh_deck-0.1.0/src/mesh_deck.egg-info/requires.txt +11 -0
  34. mesh_deck-0.1.0/src/mesh_deck.egg-info/top_level.txt +1 -0
  35. mesh_deck-0.1.0/tests/fixtures/compute_apps_running.csv +5 -0
  36. mesh_deck-0.1.0/tests/fixtures/gpu_query_running.csv +3 -0
  37. mesh_deck-0.1.0/tests/fixtures/gpu_query_util.csv +3 -0
  38. mesh_deck-0.1.0/tests/fixtures/nvidia_smi_processes.xml +30 -0
  39. mesh_deck-0.1.0/tests/test_cli.py +214 -0
  40. mesh_deck-0.1.0/tests/test_config.py +56 -0
  41. mesh_deck-0.1.0/tests/test_gpu.py +39 -0
  42. mesh_deck-0.1.0/tests/test_holders.py +233 -0
  43. mesh_deck-0.1.0/tests/test_planner.py +104 -0
  44. mesh_deck-0.1.0/tests/test_runner.py +305 -0
  45. mesh_deck-0.1.0/tests/test_suggest.py +152 -0
  46. mesh_deck-0.1.0/tests/test_ui.py +172 -0
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ Nothing yet.
10
+
11
+ ## [0.1.0] - 2026-10-26
12
+
13
+ First public release, published on PyPI as `mesh-deck` (the command is `mesh`).
14
+
15
+ ### Added
16
+ - `mesh gpu`: shows each GPU's memory and utilization and who is holding the memory.
17
+ - Progress and completion messages when stopping services (`Ctrl+C` or `mesh down`).
18
+ - `mesh logs -n all` to print a whole log.
19
+ - `python -m mesh_cli` as an alternative to the `mesh` command.
20
+ - Terminal UI: colour, tables, memory bars and a live start-up line (spinner and time) when output is an
21
+ interactive terminal. Piped output and `NO_COLOR` keep the plain text. Adds the `rich` dependency.
22
+ - "Did you mean" hints for mistyped commands, service names, config fields and config file names.
23
+ - `mesh completion bash|zsh|fish` prints a Tab-completion script; `mesh logs` completes service names.
24
+ - `mesh up` prints a one-line plan summary when everything fits, and the full plan only when there is something
25
+ to read or with `--plan`.
26
+ - Type information is shipped with the package (`py.typed`).
27
+
28
+ ### Fixed
29
+ - A second Ctrl+C (or `mesh down`) while `mesh up` was stopping services no longer aborts the cleanup and leaves
30
+ services running.
@@ -0,0 +1,101 @@
1
+ # Contributing to mesh-deck
2
+
3
+ Thanks for helping. This project is small and early, so the most useful contributions are real-world data and
4
+ bug reports. Please read [How it works](docs/how-it-works.md) first, especially "What it does not do", so you
5
+ know what is in scope.
6
+
7
+ ## The most useful thing you can send: output from your GPU
8
+
9
+ The planner has only been checked on two RTX 4090s. If you have a different GPU, driver or vLLM version,
10
+ real output from it is worth more than code.
11
+
12
+ 1. Run these two commands, ideally with some services running:
13
+
14
+ ```bash
15
+ nvidia-smi --query-gpu=index,uuid,name,memory.total,memory.used,memory.free --format=csv
16
+ nvidia-smi --query-compute-apps=gpu_uuid,pid,process_name,used_memory --format=csv
17
+ ```
18
+
19
+ If you can, also run `nvidia-smi -q -x` (it lists graphics processes such as the desktop) and keep only the
20
+ `<gpu>` blocks with their `<uuid>` and `<processes>`.
21
+ 2. Replace the `uuid` values with dummy ones (for example `GPU-00000000-0000-0000-0000-000000000000`).
22
+ 3. Open a pull request that adds the output to `tests/fixtures/` and a test in `tests/test_gpu.py` or
23
+ `tests/test_planner.py` that parses it. If the planned memory is wrong for your card, say so in the pull
24
+ request. That is a bug we want to know about.
25
+
26
+ ## Reporting a bug
27
+
28
+ Open an issue with:
29
+
30
+ - `mesh --version`, your OS, GPU model(s), driver version, and the engine you run (for example vLLM and its version).
31
+ - The command you ran and the full output.
32
+ - Your `models.yaml` **with private paths and model names removed**.
33
+ - The two `nvidia-smi` outputs above, from just before the failure if you can.
34
+
35
+ For a service that fails to start, also include the last lines of its `logs/NAME.log`.
36
+
37
+ ## Development setup
38
+
39
+ ```bash
40
+ git clone https://github.com/mrahmedromman/mesh-deck.git
41
+ cd mesh-deck
42
+ python3 -m venv .venv
43
+ source .venv/bin/activate
44
+ pip install -e ".[dev]"
45
+ pytest
46
+ ```
47
+
48
+ Before opening a pull request, also run the style and type checks that CI runs:
49
+
50
+ ```bash
51
+ ruff check src tests
52
+ mypy
53
+ ```
54
+
55
+ The tests need no GPU and no model. Runner tests start throwaway `http.server` processes, so they take a few
56
+ seconds.
57
+
58
+ ### Layout
59
+
60
+ | Path | What it holds |
61
+ |---|---|
62
+ | `src/mesh_cli/config.py` | `models.yaml` loading and validation |
63
+ | `src/mesh_cli/gpu.py` | `nvidia-smi` output parsing |
64
+ | `src/mesh_cli/holders.py` | working out who holds GPU memory (`mesh gpu`) |
65
+ | `src/mesh_cli/planner.py` | the memory simulation |
66
+ | `src/mesh_cli/runner.py` | starting, health-checking, logging and stopping services |
67
+ | `src/mesh_cli/suggest.py` | "did you mean" hints |
68
+ | `src/mesh_cli/completion.py` | bash, zsh and fish completion scripts, generated from the argument parser |
69
+ | `src/mesh_cli/ui.py` | colour, tables, memory bars and the live start-up line (terminal only) |
70
+ | `src/mesh_cli/cli.py` | the `mesh` command |
71
+ | `tests/` | tests, with real `nvidia-smi` output in `tests/fixtures/` |
72
+ | `examples/`, `docs/` | example configs and the documentation |
73
+
74
+ ## Pull requests
75
+
76
+ - **Keep them small** and about one thing.
77
+ - **Add or update a test** for any behaviour change. A bug fix should come with a test that fails without it.
78
+ - **Run `pytest`, `ruff check src tests` and `mypy`** before opening the pull request.
79
+ - **Add a line to `CHANGELOG.md`** under "Unreleased" for anything a user would notice.
80
+ - **Update the docs** if you change a command, an option or a config field: `README.md` and the pages in `docs/`.
81
+ - **Keep private details out.** Examples and fixtures must not contain your hostnames, home paths, model names
82
+ or GPU UUIDs. The examples use generic names (`llm`, `stt`, `tts`, `embed`) on purpose.
83
+ - Follow the style of the surrounding code: type hints, short functions, comments only where the reason is not
84
+ obvious.
85
+
86
+ ## Scope
87
+
88
+ mesh checks GPU memory, starts services in order and keeps their logs. It deliberately does not proxy requests,
89
+ swap models on demand or replace a process manager. Tools such as llama-swap, LiteLLM and Docker Compose
90
+ already do those well. If you are unsure whether an idea fits, open an issue to ask before writing code.
91
+
92
+ Ideas that would fit well:
93
+
94
+ - Support for AMD GPUs (a parser for `rocm-smi`) and other vendors.
95
+ - Recognising Podman and Kubernetes containers in `mesh gpu`.
96
+ - Checking that other engines (not only vLLM) are planned correctly.
97
+ - Exporting a config for another tool, such as llama-swap.
98
+
99
+ ## License
100
+
101
+ By contributing you agree that your contributions are licensed under the [MIT License](LICENSE).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) AhmedRomman
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,5 @@
1
+ include CONTRIBUTING.md CHANGELOG.md SECURITY.md
2
+ graft examples
3
+ graft docs
4
+ graft tests
5
+ global-exclude __pycache__ *.py[cod] .DS_Store
@@ -0,0 +1,238 @@
1
+ Metadata-Version: 2.4
2
+ Name: mesh-deck
3
+ Version: 0.1.0
4
+ Summary: Plan and start multi-service local AI stacks (vLLM, speech-to-text, embeddings) on shared GPUs without OOM surprises.
5
+ Author: AhmedRomman
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/mrahmedromman/mesh-deck
8
+ Project-URL: Documentation, https://github.com/mrahmedromman/mesh-deck/tree/main/docs
9
+ Project-URL: Changelog, https://github.com/mrahmedromman/mesh-deck/blob/main/CHANGELOG.md
10
+ Project-URL: Issues, https://github.com/mrahmedromman/mesh-deck/issues
11
+ Project-URL: Source, https://github.com/mrahmedromman/mesh-deck
12
+ Keywords: mesh-deck,vllm,gpu,llm,nvidia,cli,self-hosted,inference,vram,local-ai
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Environment :: GPU :: NVIDIA CUDA
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: System Administrators
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Typing :: Typed
26
+ Classifier: Topic :: Software Development
27
+ Classifier: Topic :: System :: Systems Administration
28
+ Requires-Python: >=3.10
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Requires-Dist: pydantic>=2.5
32
+ Requires-Dist: pyyaml>=6.0.1
33
+ Requires-Dist: rich>=13
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=8; extra == "dev"
36
+ Requires-Dist: ruff>=0.6; extra == "dev"
37
+ Requires-Dist: mypy>=1.10; extra == "dev"
38
+ Requires-Dist: types-PyYAML; extra == "dev"
39
+ Requires-Dist: build; extra == "dev"
40
+ Requires-Dist: twine; extra == "dev"
41
+ Dynamic: license-file
42
+
43
+ <p align="center">
44
+ <picture>
45
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/mrahmedromman/mesh-deck/main/docs/assets/logo-dark.svg">
46
+ <img alt="mesh-deck" src="https://raw.githubusercontent.com/mrahmedromman/mesh-deck/main/docs/assets/logo-light.svg" width="320">
47
+ </picture>
48
+ </p>
49
+
50
+ <p align="center"><b>Know your AI stack fits your GPUs before you start it.</b></p>
51
+
52
+ <p align="center">
53
+ <img alt="license: MIT" src="https://img.shields.io/badge/license-MIT-blue">
54
+ <img alt="python: 3.10+" src="https://img.shields.io/badge/python-3.10%2B-blue">
55
+ <img alt="status: early" src="https://img.shields.io/badge/status-early-orange">
56
+ </p>
57
+
58
+ <p align="center">
59
+ <a href="#quickstart">Quickstart</a> ·
60
+ <a href="https://github.com/mrahmedromman/mesh-deck/blob/main/docs/commands.md">Commands</a> ·
61
+ <a href="https://github.com/mrahmedromman/mesh-deck/blob/main/docs/config.md">Config</a> ·
62
+ <a href="https://github.com/mrahmedromman/mesh-deck/blob/main/docs/how-it-works.md">How it works</a> ·
63
+ <a href="https://github.com/mrahmedromman/mesh-deck/blob/main/CONTRIBUTING.md">Contributing</a>
64
+ </p>
65
+
66
+ `mesh` plans and starts a stack of local AI services (vLLM, speech-to-text, embeddings, TTS) on shared GPUs,
67
+ in the right order, with one log file per service.
68
+
69
+ ```
70
+ $ mesh up models.yaml
71
+ [1/4] starting llm on port 8001 (log: logs/llm.log)
72
+ llm is up (memory it added: GPU 0: ~16260 MiB, GPU 1: ~16260 MiB)
73
+ [2/4] starting stt on port 8004 (log: logs/stt.log)
74
+ stt is up (memory it added: GPU 0: ~6436 MiB)
75
+ [3/4] starting tts on port 8005 (log: logs/tts.log)
76
+ tts is up (memory it added: GPU 1: ~2353 MiB)
77
+ [4/4] starting embed on port 8003 (log: logs/embed.log)
78
+ embed is up (memory it added: GPU 1: ~3059 MiB)
79
+ All services are up. Press Ctrl+C to stop everything.
80
+ ```
81
+
82
+ > **Early release.** Built and checked on one machine with two RTX 4090s. It reads `nvidia-smi`, so it needs an
83
+ > NVIDIA GPU and a Unix-like system (tested on Linux). Reports from other GPUs are very welcome.
84
+
85
+ ## Why
86
+
87
+ If you run several vLLM or Docker services on one machine, you have probably hit this: the second service
88
+ crashes at startup with *"Free memory on device ... is less than desired GPU memory utilization"*. Each service
89
+ measures free memory when it starts, and the first one already took its share. Working out fractions that fit,
90
+ starting things in the right order, and finding which log holds the error is tedious to redo by hand.
91
+
92
+ ## Features
93
+
94
+ - **Checks first.** Simulates GPU memory for your services in start order and tells you which will not fit,
95
+ with a suggested fraction.
96
+ - **Starts in order.** Each service waits for the previous one's health check, so they never race for memory.
97
+ - **Any runtime.** A service is a shell command: a venv, a conda environment or `docker run`.
98
+ - **One log per service.** `mesh logs llm -f` follows one; `-n all` prints the whole file.
99
+ - **Reports real memory.** After each start it prints how much GPU memory that service added.
100
+ - **Says who holds the memory.** `mesh gpu` shows each GPU's memory and utilization and names the holder: one of
101
+ your services, a Docker container, the desktop, or another program. It prints this automatically when a start
102
+ fails for lack of memory.
103
+ - **Clean shutdown.** One Ctrl+C (or `mesh down`) stops everything in reverse order, tells you as each service
104
+ stops, and ends with a completion message.
105
+ - **Readable in a terminal.** Colour, tables, memory bars, and a live line while a model loads (a spinner and
106
+ the time so far). When output is piped or `NO_COLOR` is set, it prints plain text.
107
+ - **Helpful errors and Tab completion.** A mistyped command, service name, config field or file gets a "did you
108
+ mean" hint, and `mesh completion bash|zsh|fish` adds Tab completion, including your service names.
109
+ - **Just a YAML file.** No daemon, no proxy, nothing listening on a port.
110
+
111
+ ## Install
112
+
113
+ Use a separate environment from the one that runs your models; `mesh` needs only `pydantic`, `pyyaml` and `rich`.
114
+
115
+ ```bash
116
+ pipx install mesh-deck # or: pip install mesh-deck
117
+ ```
118
+
119
+ To run the latest unreleased code instead:
120
+
121
+ ```bash
122
+ pipx install git+https://github.com/mrahmedromman/mesh-deck.git
123
+ ```
124
+
125
+ ## Quickstart
126
+
127
+ 1. Write a `models.yaml` (see [examples/](https://github.com/mrahmedromman/mesh-deck/tree/main/examples/) for complete ones):
128
+
129
+ ```yaml
130
+ services:
131
+ - name: chat
132
+ port: 8001
133
+ gpus: [0]
134
+ gpu_memory_utilization: 0.55
135
+ overhead_mib: 1500
136
+ command: |
137
+ source ~/venvs/vllm/bin/activate
138
+ exec vllm serve /path/to/chat-model --port 8001 --gpu-memory-utilization 0.55
139
+
140
+ - name: embeddings
141
+ port: 8003
142
+ gpus: [0]
143
+ gpu_memory_utilization: 0.15
144
+ overhead_mib: 500
145
+ command: |
146
+ source ~/venvs/vllm/bin/activate
147
+ exec vllm serve /path/to/embedding-model --port 8003 --gpu-memory-utilization 0.15
148
+ ```
149
+
150
+ 2. Check that it fits, without starting anything:
151
+
152
+ ```bash
153
+ mesh plan models.yaml
154
+ ```
155
+
156
+ 3. Start it:
157
+
158
+ ```bash
159
+ mesh up models.yaml
160
+ ```
161
+
162
+ 4. In other terminals, from the same directory:
163
+
164
+ ```bash
165
+ mesh status # what is running, and whether it answers
166
+ mesh logs chat -f # follow one service's log
167
+ mesh down # stop everything (Ctrl+C in the `mesh up` terminal does the same)
168
+ ```
169
+
170
+ If the plan says a service will not fit, `mesh up` refuses to start unless you add `--force`. Output looks
171
+ like this (illustrative numbers):
172
+
173
+ ```
174
+ 2. embeddings FAIL
175
+ GPU 0: needs 3612 MiB free but only 2100 MiB is free
176
+ try --gpu-memory-utilization 0.08 or less
177
+ ```
178
+
179
+ The first time, run `mesh up` once and copy the printed "memory it added" numbers into `measured_mib`. The
180
+ planner is most accurate with measured numbers. See [Config](https://github.com/mrahmedromman/mesh-deck/blob/main/docs/config.md#getting-real-numbers-for-measured_mib).
181
+
182
+ ## Commands
183
+
184
+ | Command | What it does |
185
+ |---|---|
186
+ | `mesh plan CONFIG` | Simulates starting the services in order and reports which ones fit. |
187
+ | `mesh up CONFIG` | Plans, starts each service in order, waits for its health check, and reports the memory it added. |
188
+ | `mesh down` | Stops a stack started by `mesh up`. |
189
+ | `mesh status` | Shows each service's state, process id, port and health. |
190
+ | `mesh logs NAME [-n N\|all] [-f]` | Shows or follows a service's log. |
191
+ | `mesh gpu` | Shows each GPU's memory and utilization, and who is holding the memory. |
192
+ | `mesh completion SHELL` | Prints a Tab-completion script for `bash`, `zsh` or `fish`. |
193
+
194
+ All options, `mesh logs` examples, exit codes and where files go: [docs/commands.md](https://github.com/mrahmedromman/mesh-deck/blob/main/docs/commands.md).
195
+
196
+ ## How it works
197
+
198
+ The driver keeps back some GPU memory, and vLLM measures free memory when each service starts. `mesh` subtracts
199
+ the reserved memory and anything other programs are using, then checks each service in order, reducing what is
200
+ free as services start. On two RTX 4090s the planned free memory matched the real `nvidia-smi` numbers to within
201
+ about 20 MiB once real footprints were entered. Details and limits: [docs/how-it-works.md](https://github.com/mrahmedromman/mesh-deck/blob/main/docs/how-it-works.md).
202
+
203
+ ## Compared with
204
+
205
+ - **Docker Compose** can order containers with `depends_on` and healthchecks. It is the right tool if
206
+ everything is a container. `mesh` also handles plain commands (a venv or conda environment) and checks
207
+ GPU memory before starting.
208
+ - **[llama-swap](https://github.com/mostlygeek/llama-swap)** swaps models on demand behind one endpoint.
209
+ `mesh` does not proxy requests or swap: it starts a fixed set of services that stay loaded.
210
+ - **A shell script** works fine for one machine. `mesh` replaces the parts that are tedious to redo: working
211
+ out the fractions, waiting on health checks, separate logs, and a clean shutdown.
212
+
213
+ ## Security
214
+
215
+ `command` and `stop` run through `bash` with your user's permissions, so a `models.yaml` is a script. Only run
216
+ configs you trust. `mesh` itself sends nothing anywhere: it runs `nvidia-smi`, starts your commands, and checks
217
+ `127.0.0.1` ports.
218
+
219
+ ## Limitations
220
+
221
+ - NVIDIA GPUs and Unix-like systems only. Not tested on Windows, AMD or Intel GPUs.
222
+ - No request proxy or single endpoint; each service keeps its own port.
223
+ - It does not restart a service that crashes after startup; it prints a warning.
224
+ - `mesh gpu` needs Linux and reports a snapshot only: no live view, no per-process utilization, no killing of
225
+ processes. It recognises Docker containers but not Podman or Kubernetes.
226
+ - Estimates for services nobody has measured are rough.
227
+ - Logs are overwritten on each `mesh up`, with no rotation.
228
+
229
+ ## Contributing
230
+
231
+ Tests need no GPU: `pip install -e ".[dev]"` then `pytest`. The most useful contribution is real `nvidia-smi`
232
+ output from a GPU other than an RTX 4090, added as a test fixture. Bug reports and ideas are welcome as issues.
233
+ See [CONTRIBUTING.md](https://github.com/mrahmedromman/mesh-deck/blob/main/CONTRIBUTING.md) for the exact commands, what to include in a bug report, and what is in
234
+ scope.
235
+
236
+ ## License
237
+
238
+ [MIT](https://github.com/mrahmedromman/mesh-deck/blob/main/LICENSE)
@@ -0,0 +1,196 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/mrahmedromman/mesh-deck/main/docs/assets/logo-dark.svg">
4
+ <img alt="mesh-deck" src="https://raw.githubusercontent.com/mrahmedromman/mesh-deck/main/docs/assets/logo-light.svg" width="320">
5
+ </picture>
6
+ </p>
7
+
8
+ <p align="center"><b>Know your AI stack fits your GPUs before you start it.</b></p>
9
+
10
+ <p align="center">
11
+ <img alt="license: MIT" src="https://img.shields.io/badge/license-MIT-blue">
12
+ <img alt="python: 3.10+" src="https://img.shields.io/badge/python-3.10%2B-blue">
13
+ <img alt="status: early" src="https://img.shields.io/badge/status-early-orange">
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="#quickstart">Quickstart</a> ·
18
+ <a href="https://github.com/mrahmedromman/mesh-deck/blob/main/docs/commands.md">Commands</a> ·
19
+ <a href="https://github.com/mrahmedromman/mesh-deck/blob/main/docs/config.md">Config</a> ·
20
+ <a href="https://github.com/mrahmedromman/mesh-deck/blob/main/docs/how-it-works.md">How it works</a> ·
21
+ <a href="https://github.com/mrahmedromman/mesh-deck/blob/main/CONTRIBUTING.md">Contributing</a>
22
+ </p>
23
+
24
+ `mesh` plans and starts a stack of local AI services (vLLM, speech-to-text, embeddings, TTS) on shared GPUs,
25
+ in the right order, with one log file per service.
26
+
27
+ ```
28
+ $ mesh up models.yaml
29
+ [1/4] starting llm on port 8001 (log: logs/llm.log)
30
+ llm is up (memory it added: GPU 0: ~16260 MiB, GPU 1: ~16260 MiB)
31
+ [2/4] starting stt on port 8004 (log: logs/stt.log)
32
+ stt is up (memory it added: GPU 0: ~6436 MiB)
33
+ [3/4] starting tts on port 8005 (log: logs/tts.log)
34
+ tts is up (memory it added: GPU 1: ~2353 MiB)
35
+ [4/4] starting embed on port 8003 (log: logs/embed.log)
36
+ embed is up (memory it added: GPU 1: ~3059 MiB)
37
+ All services are up. Press Ctrl+C to stop everything.
38
+ ```
39
+
40
+ > **Early release.** Built and checked on one machine with two RTX 4090s. It reads `nvidia-smi`, so it needs an
41
+ > NVIDIA GPU and a Unix-like system (tested on Linux). Reports from other GPUs are very welcome.
42
+
43
+ ## Why
44
+
45
+ If you run several vLLM or Docker services on one machine, you have probably hit this: the second service
46
+ crashes at startup with *"Free memory on device ... is less than desired GPU memory utilization"*. Each service
47
+ measures free memory when it starts, and the first one already took its share. Working out fractions that fit,
48
+ starting things in the right order, and finding which log holds the error is tedious to redo by hand.
49
+
50
+ ## Features
51
+
52
+ - **Checks first.** Simulates GPU memory for your services in start order and tells you which will not fit,
53
+ with a suggested fraction.
54
+ - **Starts in order.** Each service waits for the previous one's health check, so they never race for memory.
55
+ - **Any runtime.** A service is a shell command: a venv, a conda environment or `docker run`.
56
+ - **One log per service.** `mesh logs llm -f` follows one; `-n all` prints the whole file.
57
+ - **Reports real memory.** After each start it prints how much GPU memory that service added.
58
+ - **Says who holds the memory.** `mesh gpu` shows each GPU's memory and utilization and names the holder: one of
59
+ your services, a Docker container, the desktop, or another program. It prints this automatically when a start
60
+ fails for lack of memory.
61
+ - **Clean shutdown.** One Ctrl+C (or `mesh down`) stops everything in reverse order, tells you as each service
62
+ stops, and ends with a completion message.
63
+ - **Readable in a terminal.** Colour, tables, memory bars, and a live line while a model loads (a spinner and
64
+ the time so far). When output is piped or `NO_COLOR` is set, it prints plain text.
65
+ - **Helpful errors and Tab completion.** A mistyped command, service name, config field or file gets a "did you
66
+ mean" hint, and `mesh completion bash|zsh|fish` adds Tab completion, including your service names.
67
+ - **Just a YAML file.** No daemon, no proxy, nothing listening on a port.
68
+
69
+ ## Install
70
+
71
+ Use a separate environment from the one that runs your models; `mesh` needs only `pydantic`, `pyyaml` and `rich`.
72
+
73
+ ```bash
74
+ pipx install mesh-deck # or: pip install mesh-deck
75
+ ```
76
+
77
+ To run the latest unreleased code instead:
78
+
79
+ ```bash
80
+ pipx install git+https://github.com/mrahmedromman/mesh-deck.git
81
+ ```
82
+
83
+ ## Quickstart
84
+
85
+ 1. Write a `models.yaml` (see [examples/](https://github.com/mrahmedromman/mesh-deck/tree/main/examples/) for complete ones):
86
+
87
+ ```yaml
88
+ services:
89
+ - name: chat
90
+ port: 8001
91
+ gpus: [0]
92
+ gpu_memory_utilization: 0.55
93
+ overhead_mib: 1500
94
+ command: |
95
+ source ~/venvs/vllm/bin/activate
96
+ exec vllm serve /path/to/chat-model --port 8001 --gpu-memory-utilization 0.55
97
+
98
+ - name: embeddings
99
+ port: 8003
100
+ gpus: [0]
101
+ gpu_memory_utilization: 0.15
102
+ overhead_mib: 500
103
+ command: |
104
+ source ~/venvs/vllm/bin/activate
105
+ exec vllm serve /path/to/embedding-model --port 8003 --gpu-memory-utilization 0.15
106
+ ```
107
+
108
+ 2. Check that it fits, without starting anything:
109
+
110
+ ```bash
111
+ mesh plan models.yaml
112
+ ```
113
+
114
+ 3. Start it:
115
+
116
+ ```bash
117
+ mesh up models.yaml
118
+ ```
119
+
120
+ 4. In other terminals, from the same directory:
121
+
122
+ ```bash
123
+ mesh status # what is running, and whether it answers
124
+ mesh logs chat -f # follow one service's log
125
+ mesh down # stop everything (Ctrl+C in the `mesh up` terminal does the same)
126
+ ```
127
+
128
+ If the plan says a service will not fit, `mesh up` refuses to start unless you add `--force`. Output looks
129
+ like this (illustrative numbers):
130
+
131
+ ```
132
+ 2. embeddings FAIL
133
+ GPU 0: needs 3612 MiB free but only 2100 MiB is free
134
+ try --gpu-memory-utilization 0.08 or less
135
+ ```
136
+
137
+ The first time, run `mesh up` once and copy the printed "memory it added" numbers into `measured_mib`. The
138
+ planner is most accurate with measured numbers. See [Config](https://github.com/mrahmedromman/mesh-deck/blob/main/docs/config.md#getting-real-numbers-for-measured_mib).
139
+
140
+ ## Commands
141
+
142
+ | Command | What it does |
143
+ |---|---|
144
+ | `mesh plan CONFIG` | Simulates starting the services in order and reports which ones fit. |
145
+ | `mesh up CONFIG` | Plans, starts each service in order, waits for its health check, and reports the memory it added. |
146
+ | `mesh down` | Stops a stack started by `mesh up`. |
147
+ | `mesh status` | Shows each service's state, process id, port and health. |
148
+ | `mesh logs NAME [-n N\|all] [-f]` | Shows or follows a service's log. |
149
+ | `mesh gpu` | Shows each GPU's memory and utilization, and who is holding the memory. |
150
+ | `mesh completion SHELL` | Prints a Tab-completion script for `bash`, `zsh` or `fish`. |
151
+
152
+ All options, `mesh logs` examples, exit codes and where files go: [docs/commands.md](https://github.com/mrahmedromman/mesh-deck/blob/main/docs/commands.md).
153
+
154
+ ## How it works
155
+
156
+ The driver keeps back some GPU memory, and vLLM measures free memory when each service starts. `mesh` subtracts
157
+ the reserved memory and anything other programs are using, then checks each service in order, reducing what is
158
+ free as services start. On two RTX 4090s the planned free memory matched the real `nvidia-smi` numbers to within
159
+ about 20 MiB once real footprints were entered. Details and limits: [docs/how-it-works.md](https://github.com/mrahmedromman/mesh-deck/blob/main/docs/how-it-works.md).
160
+
161
+ ## Compared with
162
+
163
+ - **Docker Compose** can order containers with `depends_on` and healthchecks. It is the right tool if
164
+ everything is a container. `mesh` also handles plain commands (a venv or conda environment) and checks
165
+ GPU memory before starting.
166
+ - **[llama-swap](https://github.com/mostlygeek/llama-swap)** swaps models on demand behind one endpoint.
167
+ `mesh` does not proxy requests or swap: it starts a fixed set of services that stay loaded.
168
+ - **A shell script** works fine for one machine. `mesh` replaces the parts that are tedious to redo: working
169
+ out the fractions, waiting on health checks, separate logs, and a clean shutdown.
170
+
171
+ ## Security
172
+
173
+ `command` and `stop` run through `bash` with your user's permissions, so a `models.yaml` is a script. Only run
174
+ configs you trust. `mesh` itself sends nothing anywhere: it runs `nvidia-smi`, starts your commands, and checks
175
+ `127.0.0.1` ports.
176
+
177
+ ## Limitations
178
+
179
+ - NVIDIA GPUs and Unix-like systems only. Not tested on Windows, AMD or Intel GPUs.
180
+ - No request proxy or single endpoint; each service keeps its own port.
181
+ - It does not restart a service that crashes after startup; it prints a warning.
182
+ - `mesh gpu` needs Linux and reports a snapshot only: no live view, no per-process utilization, no killing of
183
+ processes. It recognises Docker containers but not Podman or Kubernetes.
184
+ - Estimates for services nobody has measured are rough.
185
+ - Logs are overwritten on each `mesh up`, with no rotation.
186
+
187
+ ## Contributing
188
+
189
+ Tests need no GPU: `pip install -e ".[dev]"` then `pytest`. The most useful contribution is real `nvidia-smi`
190
+ output from a GPU other than an RTX 4090, added as a test fixture. Bug reports and ideas are welcome as issues.
191
+ See [CONTRIBUTING.md](https://github.com/mrahmedromman/mesh-deck/blob/main/CONTRIBUTING.md) for the exact commands, what to include in a bug report, and what is in
192
+ scope.
193
+
194
+ ## License
195
+
196
+ [MIT](https://github.com/mrahmedromman/mesh-deck/blob/main/LICENSE)
@@ -0,0 +1,20 @@
1
+ # Security policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Please do not open a public issue for a security problem. Use GitHub's private reporting instead: open the
6
+ repository's **Security** tab and choose **Report a vulnerability**.
7
+
8
+ Include the `mesh --version`, what you did, and what you expected. You can expect a reply within a week.
9
+
10
+ ## What counts
11
+
12
+ `mesh` runs the `command` and `stop` lines of a `models.yaml` through `bash` with your user's permissions, by design.
13
+ A config file is a script, so running a config you do not trust is not a vulnerability in `mesh`.
14
+
15
+ In scope: a way to make `mesh` run something that is not in the config, write outside the log directory, or signal
16
+ processes that do not belong to the stack.
17
+
18
+ ## Supported versions
19
+
20
+ Only the latest release receives fixes while the project is in alpha.
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="340" height="96" viewBox="0 0 340 96" role="img" aria-label="mesh-deck">
2
+ <title>mesh-deck</title>
3
+ <!-- Two GPUs as tall outlines. Coloured blocks are services sharing the memory; the empty top is free memory. -->
4
+ <rect x="8" y="8" width="32" height="80" rx="6" fill="none" stroke="#d1d5db" stroke-width="3"/>
5
+ <rect x="12" y="50" width="24" height="34" rx="3" fill="#60a5fa"/>
6
+ <rect x="12" y="28" width="24" height="20" rx="3" fill="#2dd4bf"/>
7
+
8
+ <rect x="48" y="8" width="32" height="80" rx="6" fill="none" stroke="#d1d5db" stroke-width="3"/>
9
+ <rect x="52" y="50" width="24" height="34" rx="3" fill="#60a5fa"/>
10
+ <rect x="52" y="36" width="24" height="12" rx="3" fill="#fbbf24"/>
11
+ <rect x="52" y="22" width="24" height="12" rx="3" fill="#a78bfa"/>
12
+
13
+ <text x="100" y="62" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace" font-size="40" font-weight="700" fill="#f9fafb">mesh-deck</text>
14
+ </svg>
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="340" height="96" viewBox="0 0 340 96" role="img" aria-label="mesh-deck">
2
+ <title>mesh-deck</title>
3
+ <!-- Two GPUs as tall outlines. Coloured blocks are services sharing the memory; the empty top is free memory. -->
4
+ <rect x="8" y="8" width="32" height="80" rx="6" fill="none" stroke="#374151" stroke-width="3"/>
5
+ <rect x="12" y="50" width="24" height="34" rx="3" fill="#3b82f6"/>
6
+ <rect x="12" y="28" width="24" height="20" rx="3" fill="#14b8a6"/>
7
+
8
+ <rect x="48" y="8" width="32" height="80" rx="6" fill="none" stroke="#374151" stroke-width="3"/>
9
+ <rect x="52" y="50" width="24" height="34" rx="3" fill="#3b82f6"/>
10
+ <rect x="52" y="36" width="24" height="12" rx="3" fill="#f59e0b"/>
11
+ <rect x="52" y="22" width="24" height="12" rx="3" fill="#8b5cf6"/>
12
+
13
+ <text x="100" y="62" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace" font-size="40" font-weight="700" fill="#111827">mesh-deck</text>
14
+ </svg>