gpu4agents 0.5.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.
- gpu4agents/__init__.py +5 -0
- gpu4agents/__main__.py +4 -0
- gpu4agents/agent/__init__.py +1 -0
- gpu4agents/agent/install.py +173 -0
- gpu4agents/agent/jobs.py +193 -0
- gpu4agents/agent/mcp.py +646 -0
- gpu4agents/agent/skill/SKILL.md +68 -0
- gpu4agents/cli/__init__.py +0 -0
- gpu4agents/cli/_common.py +131 -0
- gpu4agents/cli/account.py +330 -0
- gpu4agents/cli/agent.py +74 -0
- gpu4agents/cli/inspect.py +207 -0
- gpu4agents/cli/lifecycle.py +127 -0
- gpu4agents/cli/main.py +163 -0
- gpu4agents/cli/providers.py +96 -0
- gpu4agents/cli/run.py +38 -0
- gpu4agents/cli/setup.py +164 -0
- gpu4agents/cli/status.py +224 -0
- gpu4agents/cli/timer.py +143 -0
- gpu4agents/cli/validate.py +57 -0
- gpu4agents/core/__init__.py +0 -0
- gpu4agents/core/beacon.py +133 -0
- gpu4agents/core/config.py +647 -0
- gpu4agents/core/credentials.py +95 -0
- gpu4agents/core/durations.py +47 -0
- gpu4agents/core/engine.py +1335 -0
- gpu4agents/core/errors.py +94 -0
- gpu4agents/core/events.py +117 -0
- gpu4agents/core/execution.py +169 -0
- gpu4agents/core/health.py +62 -0
- gpu4agents/core/hosts.py +58 -0
- gpu4agents/core/model_inspect.py +368 -0
- gpu4agents/core/models.py +313 -0
- gpu4agents/core/paths.py +42 -0
- gpu4agents/core/policy.py +264 -0
- gpu4agents/core/state.py +406 -0
- gpu4agents/core/sweep.py +478 -0
- gpu4agents/core/tool_parsers.py +63 -0
- gpu4agents/providers/__init__.py +4 -0
- gpu4agents/providers/base.py +137 -0
- gpu4agents/providers/registry.py +63 -0
- gpu4agents/providers/vast/__init__.py +3 -0
- gpu4agents/providers/vast/client.py +255 -0
- gpu4agents/providers/vast/config.py +56 -0
- gpu4agents/providers/vast/mapper.py +292 -0
- gpu4agents/providers/vast/provider.py +438 -0
- gpu4agents/runtime/__init__.py +6 -0
- gpu4agents/runtime/base.py +184 -0
- gpu4agents/runtime/vllm.py +295 -0
- gpu4agents/watchdog/__init__.py +3 -0
- gpu4agents/watchdog/agent_body.py +959 -0
- gpu4agents/watchdog/builder.py +248 -0
- gpu4agents-0.5.0.dist-info/METADATA +157 -0
- gpu4agents-0.5.0.dist-info/RECORD +59 -0
- gpu4agents-0.5.0.dist-info/WHEEL +4 -0
- gpu4agents-0.5.0.dist-info/entry_points.txt +2 -0
- gpu4agents-0.5.0.dist-info/licenses/LICENSE +21 -0
- jambu_gpu/__init__.py +9 -0
- jambu_gpu/__main__.py +4 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Assembles the remote watchdog into a single self-contained script.
|
|
2
|
+
|
|
3
|
+
The lifecycle *policy* is owned by the core and inlined verbatim from
|
|
4
|
+
``core/policy.py`` so the remote guard and `gpu status` can never
|
|
5
|
+
disagree. The *provider actions* are injected by the adapter. Neither side
|
|
6
|
+
knows about the other.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import ast
|
|
12
|
+
import base64
|
|
13
|
+
import json
|
|
14
|
+
import zlib
|
|
15
|
+
import secrets
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
from ..core import policy as policy_module
|
|
20
|
+
from ..providers.base import WatchdogSpec
|
|
21
|
+
|
|
22
|
+
AGENT_REMOTE_PATH = "/opt/jambu/watchdog.py"
|
|
23
|
+
CONFIG_REMOTE_PATH = "/etc/jambu/watchdog.json"
|
|
24
|
+
STATE_REMOTE_PATH = "/var/lib/jambu/activity.json"
|
|
25
|
+
EVENTS_REMOTE_PATH = "/var/log/jambu/events.jsonl"
|
|
26
|
+
WATCHDOG_LOG_PATH = "/var/log/jambu/watchdog.log"
|
|
27
|
+
ONSTART_LOG_PATH = "/var/log/jambu/onstart.log"
|
|
28
|
+
SUPERVISOR_PID_PATH = "/var/run/jambu/watchdog.pid"
|
|
29
|
+
SUPERVISOR_RESPAWN_DELAY_S = 5
|
|
30
|
+
|
|
31
|
+
_HEADER = """#!/usr/bin/env python3
|
|
32
|
+
# Generated by gpu4agents. Do not edit on the instance.
|
|
33
|
+
# Lifecycle policy is inlined from gpu4agents/core/policy.py.
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
_FUTURE_IMPORT = "from __future__ import annotations"
|
|
38
|
+
|
|
39
|
+
_FALLBACK_ACTIONS = '''
|
|
40
|
+
def provider_stop(ctx):
|
|
41
|
+
raise RuntimeError("this provider supplied no stop action")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def provider_destroy(ctx):
|
|
45
|
+
raise RuntimeError("this provider supplied no destroy action")
|
|
46
|
+
'''
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def generate_token() -> str:
|
|
50
|
+
"""Shared secret protecting the watchdog's public control port."""
|
|
51
|
+
return secrets.token_urlsafe(32)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _strip_future(source: str) -> str:
|
|
55
|
+
return "\n".join(
|
|
56
|
+
line for line in source.splitlines() if line.strip() != _FUTURE_IMPORT
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _compact(source: str) -> str:
|
|
61
|
+
"""Same code without comments or docstrings.
|
|
62
|
+
|
|
63
|
+
The boot script rides in a size-capped provider field (Vast: 16 KB), so
|
|
64
|
+
what travels is the parsed-and-unparsed source: identical semantics, none
|
|
65
|
+
of the prose.
|
|
66
|
+
"""
|
|
67
|
+
tree = ast.parse(source)
|
|
68
|
+
for node in ast.walk(tree):
|
|
69
|
+
body = getattr(node, "body", None)
|
|
70
|
+
if not isinstance(body, list) or not body:
|
|
71
|
+
continue
|
|
72
|
+
first = body[0]
|
|
73
|
+
if (
|
|
74
|
+
isinstance(first, ast.Expr)
|
|
75
|
+
and isinstance(first.value, ast.Constant)
|
|
76
|
+
and isinstance(first.value.value, str)
|
|
77
|
+
):
|
|
78
|
+
node.body = body[1:] or [ast.Pass()]
|
|
79
|
+
return ast.unparse(tree)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def build_agent_source(actions_source: Optional[str] = None) -> str:
|
|
83
|
+
"""Return the full stdlib-only watchdog script."""
|
|
84
|
+
policy_source = _compact(_strip_future(Path(policy_module.__file__).read_text()))
|
|
85
|
+
body_source = _compact(
|
|
86
|
+
_strip_future((Path(__file__).parent / "agent_body.py").read_text())
|
|
87
|
+
)
|
|
88
|
+
actions = _compact(_strip_future(actions_source or _FALLBACK_ACTIONS))
|
|
89
|
+
return "\n\n".join(
|
|
90
|
+
[
|
|
91
|
+
_HEADER,
|
|
92
|
+
"# --- inlined: gpu4agents/core/policy.py ---",
|
|
93
|
+
policy_source,
|
|
94
|
+
"# --- injected: provider actions ---",
|
|
95
|
+
actions,
|
|
96
|
+
"# --- inlined: gpu4agents/watchdog/agent_body.py ---",
|
|
97
|
+
body_source,
|
|
98
|
+
]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def build_agent_config(
|
|
103
|
+
*,
|
|
104
|
+
provider: str,
|
|
105
|
+
instance_id: str,
|
|
106
|
+
token: str,
|
|
107
|
+
port: int,
|
|
108
|
+
interval_s: float,
|
|
109
|
+
startup_grace_s: float,
|
|
110
|
+
policy: "policy_module.PolicyConfig",
|
|
111
|
+
created_at: float,
|
|
112
|
+
watchdog_spec: Optional[WatchdogSpec] = None,
|
|
113
|
+
activity_probe: Optional[dict] = None,
|
|
114
|
+
runtime_pid_path: Optional[str] = None,
|
|
115
|
+
runtime_log_path: Optional[str] = None,
|
|
116
|
+
) -> dict:
|
|
117
|
+
"""Config the remote agent reads at boot.
|
|
118
|
+
|
|
119
|
+
``activity_probe`` lets inference traffic that bypasses `gpu run` count as
|
|
120
|
+
activity, e.g. ``{"kind": "prometheus", "url": ".../metrics",
|
|
121
|
+
"gauges": [...], "counters": [...]}``.
|
|
122
|
+
|
|
123
|
+
``runtime_pid_path`` lets the agent tell a dead model server from a slow
|
|
124
|
+
one; ``logs`` names the files its ``/logs`` route may serve.
|
|
125
|
+
"""
|
|
126
|
+
logs = {"onstart": ONSTART_LOG_PATH, "watchdog": WATCHDOG_LOG_PATH}
|
|
127
|
+
if runtime_log_path:
|
|
128
|
+
logs["runtime"] = runtime_log_path
|
|
129
|
+
config = {
|
|
130
|
+
"provider": provider,
|
|
131
|
+
"instance_id": instance_id,
|
|
132
|
+
"token": token,
|
|
133
|
+
"port": int(port),
|
|
134
|
+
"interval_s": float(interval_s),
|
|
135
|
+
"startup_grace_s": float(startup_grace_s),
|
|
136
|
+
"created_at": float(created_at),
|
|
137
|
+
"policy": policy.to_dict(),
|
|
138
|
+
"env": dict(watchdog_spec.env) if watchdog_spec else {},
|
|
139
|
+
"provider_context": dict(watchdog_spec.context) if watchdog_spec else {},
|
|
140
|
+
"logs": logs,
|
|
141
|
+
}
|
|
142
|
+
if runtime_pid_path:
|
|
143
|
+
config["runtime_pid_path"] = runtime_pid_path
|
|
144
|
+
if activity_probe:
|
|
145
|
+
config["activity_probe"] = dict(activity_probe)
|
|
146
|
+
return config
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _b64_gz_block(text: str, width: int = 76) -> str:
|
|
150
|
+
"""Compressed, line-wrapped payload (``width=0``: one line).
|
|
151
|
+
|
|
152
|
+
The boot script travels inside a provider API field, so the ~29 KB agent is
|
|
153
|
+
deflated to ~9 KB and wrapped, instead of riding on one enormous line.
|
|
154
|
+
"""
|
|
155
|
+
blob = base64.b64encode(zlib.compress(text.encode(), 9)).decode()
|
|
156
|
+
if not width:
|
|
157
|
+
return blob
|
|
158
|
+
return "\n".join(blob[i : i + width] for i in range(0, len(blob), width))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def build_onstart_script(
|
|
162
|
+
*,
|
|
163
|
+
agent_source: str,
|
|
164
|
+
agent_config: dict,
|
|
165
|
+
runtime_start_command: str,
|
|
166
|
+
runtime_env: Optional[dict] = None,
|
|
167
|
+
watchdog_enabled: bool = True,
|
|
168
|
+
) -> str:
|
|
169
|
+
"""Bash executed on the instance right after the container comes up.
|
|
170
|
+
|
|
171
|
+
Order matters: the watchdog starts FIRST so an instance whose model server
|
|
172
|
+
fails to boot is still protected by idle/max_lifetime rules.
|
|
173
|
+
"""
|
|
174
|
+
lines = [
|
|
175
|
+
"#!/bin/bash",
|
|
176
|
+
"# Generated by gpu4agents",
|
|
177
|
+
"set -u",
|
|
178
|
+
# mkdir first: a failed `exec` redirect would kill a non-interactive shell.
|
|
179
|
+
"mkdir -p /etc/jambu /var/lib/jambu /var/log/jambu /var/run/jambu /opt/jambu",
|
|
180
|
+
f"exec >> {ONSTART_LOG_PATH} 2>&1",
|
|
181
|
+
"date -u +'[%Y-%m-%dT%H:%M:%SZ] jambu onstart begin'",
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
for key, value in (runtime_env or {}).items():
|
|
185
|
+
lines.append(f"export {key}={_shell_quote(str(value))}")
|
|
186
|
+
lines.append(f"echo {_shell_quote(f'{key}={value}')} >> /etc/jambu/runtime.env")
|
|
187
|
+
|
|
188
|
+
if watchdog_enabled:
|
|
189
|
+
# Config and agent both travel deflated; the config carries the token,
|
|
190
|
+
# so it is written under a 077 umask.
|
|
191
|
+
decode = (
|
|
192
|
+
"import base64,os,zlib;os.umask(0o77);"
|
|
193
|
+
f"[open(p,'wb').write(zlib.decompress(base64.b64decode(open(p+'.b64','rb').read())))"
|
|
194
|
+
f" for p in ({CONFIG_REMOTE_PATH!r},{AGENT_REMOTE_PATH!r})]"
|
|
195
|
+
)
|
|
196
|
+
config_json = json.dumps(agent_config, separators=(",", ":"))
|
|
197
|
+
# Killing the supervisor pid must still take the watchdog down with it:
|
|
198
|
+
# the agent runs in the background so `wait` lets the TERM trap fire.
|
|
199
|
+
supervisor = (
|
|
200
|
+
"child=; "
|
|
201
|
+
"trap '[ -n \"$child\" ] && kill \"$child\" 2>/dev/null; exit 0' TERM INT; "
|
|
202
|
+
"while true; do "
|
|
203
|
+
f'"$PYBIN" -u {AGENT_REMOTE_PATH} >> {WATCHDOG_LOG_PATH} 2>&1 & '
|
|
204
|
+
"child=$!; "
|
|
205
|
+
'wait "$child"; '
|
|
206
|
+
"code=$?; "
|
|
207
|
+
"date -u +\"[%Y-%m-%dT%H:%M:%SZ] jambu watchdog exited code=$code, respawning\" "
|
|
208
|
+
f">> {WATCHDOG_LOG_PATH}; "
|
|
209
|
+
f"sleep {SUPERVISOR_RESPAWN_DELAY_S}; "
|
|
210
|
+
"done"
|
|
211
|
+
)
|
|
212
|
+
lines += [
|
|
213
|
+
"",
|
|
214
|
+
"# --- lifecycle watchdog (survives local CLI death) ---",
|
|
215
|
+
"PYBIN=$(command -v python3 || command -v python)",
|
|
216
|
+
'if [ -z "$PYBIN" ]; then',
|
|
217
|
+
' echo "jambu: no python interpreter in this image; watchdog NOT installed"',
|
|
218
|
+
"else",
|
|
219
|
+
f" (umask 077; echo {_b64_gz_block(config_json, width=0)} > {CONFIG_REMOTE_PATH}.b64)",
|
|
220
|
+
f" cat > {AGENT_REMOTE_PATH}.b64 <<'JAMBU_AGENT_EOF'",
|
|
221
|
+
_b64_gz_block(agent_source),
|
|
222
|
+
"JAMBU_AGENT_EOF",
|
|
223
|
+
f' "$PYBIN" -c {_shell_quote(decode)}',
|
|
224
|
+
f" rm -f {CONFIG_REMOTE_PATH}.b64 {AGENT_REMOTE_PATH}.b64",
|
|
225
|
+
f" chmod 600 {CONFIG_REMOTE_PATH}",
|
|
226
|
+
f" chmod 700 {AGENT_REMOTE_PATH}",
|
|
227
|
+
# One id per container start: the agent tells a restart (new
|
|
228
|
+
# session, re-arm the guard) from a respawn (same session, keep state).
|
|
229
|
+
" export JAMBU_SESSION_STARTED_AT=$(date +%s)",
|
|
230
|
+
" export PYBIN",
|
|
231
|
+
# Supervisor: a crashed watchdog must not leave the GPU unguarded.
|
|
232
|
+
f" nohup bash -c {_shell_quote(supervisor)} > /dev/null 2>&1 &",
|
|
233
|
+
f" echo $! > {SUPERVISOR_PID_PATH}",
|
|
234
|
+
f' echo "jambu: watchdog supervisor started pid $(cat {SUPERVISOR_PID_PATH})"',
|
|
235
|
+
"fi",
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
lines += [
|
|
239
|
+
"",
|
|
240
|
+
"# --- model runtime ---",
|
|
241
|
+
runtime_start_command,
|
|
242
|
+
"date -u +'[%Y-%m-%dT%H:%M:%SZ] jambu onstart done'",
|
|
243
|
+
]
|
|
244
|
+
return "\n".join(lines) + "\n"
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _shell_quote(value: str) -> str:
|
|
248
|
+
return "'" + value.replace("'", "'\"'\"'") + "'"
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: gpu4agents
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: GPU for Agents: your coding agent rents the GPU it needs, on its own. You pay only while it works.
|
|
5
|
+
Project-URL: Homepage, https://gpu4agents.jambu.ai
|
|
6
|
+
Project-URL: Documentation, https://github.com/jambuai/gpu4agents/tree/main/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/jambuai/gpu4agents
|
|
8
|
+
Project-URL: Issues, https://github.com/jambuai/gpu4agents/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/jambuai/gpu4agents/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Author, https://www.linkedin.com/in/joaogabriellima
|
|
11
|
+
Author: Jambu.ai
|
|
12
|
+
Author-email: João Gabriel Lima <joao.gabriel@jambu.ai>
|
|
13
|
+
License: MIT
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Keywords: agents,claude-code,cost,cursor,gpu,inference,llm,mcp,vast-ai,vllm
|
|
16
|
+
Classifier: Development Status :: 4 - Beta
|
|
17
|
+
Classifier: Environment :: Console
|
|
18
|
+
Classifier: Intended Audience :: Developers
|
|
19
|
+
Classifier: Intended Audience :: Science/Research
|
|
20
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
21
|
+
Classifier: Operating System :: MacOS
|
|
22
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
23
|
+
Classifier: Programming Language :: Python :: 3
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
28
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
29
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
30
|
+
Requires-Python: >=3.10
|
|
31
|
+
Requires-Dist: httpx>=0.27.0
|
|
32
|
+
Requires-Dist: pydantic>=2.6.0
|
|
33
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
34
|
+
Requires-Dist: rich>=13.7.0
|
|
35
|
+
Requires-Dist: typer>=0.12.0
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: respx>=0.21.0; extra == 'dev'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
<p align="center">
|
|
43
|
+
<a href="https://gpu4agents.jambu.ai">
|
|
44
|
+
<img src="docs/assets/banner.png" alt="GPU for Agents: your agent rents the GPU it needs, on its own. You pay only while it works." width="100%">
|
|
45
|
+
</a>
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
<p align="center">
|
|
49
|
+
<a href="https://pypi.org/project/gpu4agents/"><img src="https://img.shields.io/pypi/v/gpu4agents?color=5CBF3A&label=pypi" alt="PyPI"></a>
|
|
50
|
+
<a href="https://pypi.org/project/gpu4agents/"><img src="https://img.shields.io/pypi/pyversions/gpu4agents?color=5CBF3A" alt="Python versions"></a>
|
|
51
|
+
<a href="https://github.com/jambuai/gpu4agents/actions/workflows/ci.yml"><img src="https://github.com/jambuai/gpu4agents/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
52
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-5CBF3A" alt="MIT license"></a>
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
<p align="center">
|
|
56
|
+
<a href="https://gpu4agents.jambu.ai">Website</a> ·
|
|
57
|
+
<a href="https://gpu4agents.jambu.ai/#video">1-minute video</a> ·
|
|
58
|
+
<a href="docs/AGENTS.md">Agents guide</a> ·
|
|
59
|
+
<a href="https://github.com/jambuai/gpu4agents/wiki">Wiki</a> ·
|
|
60
|
+
<a href="CHANGELOG.md">Changelog</a>
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
# GPU for Agents
|
|
64
|
+
|
|
65
|
+
**Your agent rents the GPU it needs, on its own.**
|
|
66
|
+
**You pay only while it works: we watch every machine and shut down the idle ones.**
|
|
67
|
+
|
|
68
|
+
Ask Claude Code, Cursor or any MCP client to "compare Qwen 7B and Llama 8B on these 50 prompts". The agent finds a GPU under your budget, serves the model behind an OpenAI-compatible API with vLLM, and runs the experiment. A watchdog runs on the machine itself, so when nothing uses it for `idle_timeout` (15 minutes by default) it shuts down, even if the agent forgot, the chat closed or your laptop is asleep.
|
|
69
|
+
|
|
70
|
+
- **Self-service GPUs for agents.** A skill and local MCP tools (`gpu_offers`, `gpu_setup`, `gpu_run`, `gpu_status`, `gpu_destroy`, ...) give the agent the whole lifecycle. The skill tells it to show you the price and ask before renting.
|
|
71
|
+
- **Billing that ends when the work does.** Idle timeout, maximum lifetime, hourly and session budgets, and a sweep that cleans up anything left behind.
|
|
72
|
+
- **Any app, any language.** `gpu --json setup` returns `base_url`, `api_key` and `model` for any OpenAI SDK.
|
|
73
|
+
|
|
74
|
+
Vast.ai is the first provider; another provider is a new adapter, and the lifecycle rule stays the same.
|
|
75
|
+
|
|
76
|
+
## Quickstart: give your agent a GPU
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pipx install gpu4agents # or: uv tool install gpu4agents
|
|
80
|
+
cd your-project
|
|
81
|
+
echo "VAST_API_KEY=..." >> .env # or ~/.gpu/credentials; never in jambu.yaml
|
|
82
|
+
gpu agent install # Claude Code; -t cursor, -t desktop, --global
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then ask the agent for an experiment. See [Letting a coding agent rent GPUs](docs/AGENTS.md).
|
|
86
|
+
|
|
87
|
+
## Quickstart: from a shell or an app
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
gpu init --model Qwen/Qwen2.5-7B-Instruct
|
|
91
|
+
gpu validate
|
|
92
|
+
gpu offers # see prices, rent nothing
|
|
93
|
+
gpu setup # rent, boot vLLM, wait until the model answers
|
|
94
|
+
gpu run python experiment.py # OPENAI_BASE_URL / OPENAI_API_KEY are set for the job
|
|
95
|
+
gpu destroy # or walk away: the idle timeout does it
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
From an application in any language, `gpu --json setup` prints the endpoint as JSON. See [Integrating into an application](docs/INTEGRATION.md), with a ready TypeScript client.
|
|
99
|
+
|
|
100
|
+
## Declare the limits
|
|
101
|
+
|
|
102
|
+
```yaml
|
|
103
|
+
# jambu.yaml (only the gpu_runtime key is read, so other Jambu tools can share the file)
|
|
104
|
+
gpu_runtime:
|
|
105
|
+
provider: {name: vast}
|
|
106
|
+
model_profile: qwythos-9b
|
|
107
|
+
lifecycle:
|
|
108
|
+
idle_timeout: 15m # the watchdog on the machine stops it
|
|
109
|
+
max_lifetime: 6h # a hard ceiling, busy or not
|
|
110
|
+
budget:
|
|
111
|
+
max_hourly_cost_usd: 0.60 # offers above this are never rented
|
|
112
|
+
max_session_cost_usd: 5.00
|
|
113
|
+
health:
|
|
114
|
+
pull_stall_timeout: 10m # a host whose image pull stalls is replaced
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
A full example is [`jambu.example.yaml`](jambu.example.yaml). As a last line of defense, `gpu ls` shows every machine billing in the account and `gpu sweep --install-timer` guards every project on the machine and cleans up orphans. See [Keeping the account clean](docs/USAGE.md#keeping-the-account-clean).
|
|
118
|
+
|
|
119
|
+
The API key is read from the environment, a project `.env`, or `~/.gpu/credentials`. It never goes in the manifest, and the agent never sees it.
|
|
120
|
+
|
|
121
|
+
## Docs
|
|
122
|
+
|
|
123
|
+
- [Letting a coding agent rent GPUs (Claude Code, Cursor, Claude Desktop)](docs/AGENTS.md)
|
|
124
|
+
- [Integrating into an application (any language)](docs/INTEGRATION.md)
|
|
125
|
+
- [Commands, workloads, and agents](docs/USAGE.md)
|
|
126
|
+
- [Every config field](docs/CONFIG.md)
|
|
127
|
+
- [Choosing a model](docs/CHOOSING_A_MODEL.md)
|
|
128
|
+
- [How shutdown works](docs/LIFECYCLE.md)
|
|
129
|
+
- [Adding a provider](docs/ADDING_A_PROVIDER.md)
|
|
130
|
+
|
|
131
|
+
## Coming from `jambu-gpu`
|
|
132
|
+
|
|
133
|
+
The package was called `jambu-gpu` until 0.4.1. Same code, same `gpu` command, same `jambu.yaml`, same machines:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pipx uninstall jambu-gpu && pipx install gpu4agents
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Sweep timers installed by `jambu-gpu` keep working; `gpu sweep --install-timer` rewrites them for the new module.
|
|
140
|
+
|
|
141
|
+
## Contributing
|
|
142
|
+
|
|
143
|
+
Issues and pull requests are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) and the [Code of Conduct](CODE_OF_CONDUCT.md). Report security issues privately, as described in [SECURITY.md](SECURITY.md).
|
|
144
|
+
|
|
145
|
+
## Author
|
|
146
|
+
|
|
147
|
+
Created by [João Gabriel Lima](https://www.linkedin.com/in/joaogabriellima) at [Jambu.ai](https://jambu.ai).
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
[MIT](LICENSE).
|
|
152
|
+
|
|
153
|
+
<p align="center">
|
|
154
|
+
<a href="https://jambu.ai">
|
|
155
|
+
<img src="docs/assets/jambu-logo.jpg" alt="Jambu.ai — Applied Intelligence Lab" width="240">
|
|
156
|
+
</a>
|
|
157
|
+
</p>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
gpu4agents/__init__.py,sha256=0IDRXmAi8yT3vmS-RiYskc0oSuxjWjm26MBcWvXsfBw,135
|
|
2
|
+
gpu4agents/__main__.py,sha256=dBCaXiNWd2Tv9A9tb_B9kJG2OOY-b5FpWYwRyV1yQso,66
|
|
3
|
+
gpu4agents/agent/__init__.py,sha256=cfB1rx-9w3XfuPig-2Kl5LA_Vpbm-y3Dz1v7EmQ-RzE,87
|
|
4
|
+
gpu4agents/agent/install.py,sha256=57qHdxoVC5wzVwfIStbZPfNbaY_pKhZFbBQK69Civx0,7228
|
|
5
|
+
gpu4agents/agent/jobs.py,sha256=WZ9TpbD_ULT_rk58FSo5jDUfdyz2QGuqrj44ZmyfCBk,7342
|
|
6
|
+
gpu4agents/agent/mcp.py,sha256=-cBefHoowRJC5WV9jUpH2hJM6HYZYXTrjcQj_Jax1_0,27735
|
|
7
|
+
gpu4agents/agent/skill/SKILL.md,sha256=7FFFWzC6KrVTuW99yclv4Bj1pUGdGv-h0vYQjX5wGtY,6050
|
|
8
|
+
gpu4agents/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
gpu4agents/cli/_common.py,sha256=G8lzmHOX7v3jIJe_JvSOqXgO-t9vSC3vz1ScMCcXzLQ,3807
|
|
10
|
+
gpu4agents/cli/account.py,sha256=SHmRPnbhGSYz92ma3BDzFS44NXYYifsJo2hsWcD4Mks,13509
|
|
11
|
+
gpu4agents/cli/agent.py,sha256=54VOLuKRYUDjcayD5wkC1ohEltNIjg829KnE5WkqR0A,2236
|
|
12
|
+
gpu4agents/cli/inspect.py,sha256=Jldc5FAZEuAdZovNvESxjqJxmNEMQ5SUkHbOXzO8EeI,8086
|
|
13
|
+
gpu4agents/cli/lifecycle.py,sha256=zmgkUJNK9DSdzTumxDKW-gwyiIAjTaUOM00U8kpZy_M,3862
|
|
14
|
+
gpu4agents/cli/main.py,sha256=P6cqa910502WErIZHCedfT4WbaFP3omM96vh6S3EQH4,5129
|
|
15
|
+
gpu4agents/cli/providers.py,sha256=vDR5SgxsMVLWpZsdSLKuJy8qOUxv1M7-IHIJUCnAleI,3058
|
|
16
|
+
gpu4agents/cli/run.py,sha256=oFj0MGAb8I44VOf6ktT5niC8s8d4Fx0LAfRueeg64Bo,1271
|
|
17
|
+
gpu4agents/cli/setup.py,sha256=HGtTSeiSj0gnENXl6Nx6hVuGTMympTXe0cOaX3Io0D8,5487
|
|
18
|
+
gpu4agents/cli/status.py,sha256=VHnad5--8Lnhlq9nUSxn5LKV_gVmK42E9oM947UJiCw,7430
|
|
19
|
+
gpu4agents/cli/timer.py,sha256=FQa2d3tpyvjcOFJ7gUVjJU8POawiPSlEhzZmmvSZ-SU,4846
|
|
20
|
+
gpu4agents/cli/validate.py,sha256=9DFh8TLIcvPIct5FQk78VPfW29PMqkOcij8N_F8yXHs,2081
|
|
21
|
+
gpu4agents/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
gpu4agents/core/beacon.py,sha256=a5hsHEln8Y5jKgPTNmBZOysBzV0kceHG5KWZxDDwy7w,4818
|
|
23
|
+
gpu4agents/core/config.py,sha256=6OTTGs3MO9ZFqagTDGjSj33m6UhxxqSey2qlsVYJLao,22624
|
|
24
|
+
gpu4agents/core/credentials.py,sha256=o_YCynH5B--5ZL5eUTnoQe8zs_I1uDcvaygL-U24r04,3186
|
|
25
|
+
gpu4agents/core/durations.py,sha256=4-Gq--HNOBGXLLWQHssC08gkQwQjT_3oBHD4ootHKxs,1645
|
|
26
|
+
gpu4agents/core/engine.py,sha256=8Vme7QPnfMPC-2UuaHMEP85cuCDK2yW0dxYjvo8bcXM,57317
|
|
27
|
+
gpu4agents/core/errors.py,sha256=MqQI4he_xyotI1wBzavVXTK5KO5L-qWDmmIR0mhsOHU,2446
|
|
28
|
+
gpu4agents/core/events.py,sha256=b8OSRMf01VyXTyJxoDJ-kMTL9KEsKufTLluR0akJq_E,4092
|
|
29
|
+
gpu4agents/core/execution.py,sha256=6cXL-QbYg7rDeIpMwgTl9uAUh9LhEwtfyST1l_QfADk,5689
|
|
30
|
+
gpu4agents/core/health.py,sha256=nbd4JNvNF7-Lpd58Ce_48J7A5Sob3gek6ihv9Q36nkk,1909
|
|
31
|
+
gpu4agents/core/hosts.py,sha256=WhdckKl_Lohckov_T-h53kTx4kN-29dRFZyT74bmviM,2107
|
|
32
|
+
gpu4agents/core/model_inspect.py,sha256=vb22jQ0NaSOsKrKWE4BlxXgbh26_qMhRMsAHYLVofCA,15615
|
|
33
|
+
gpu4agents/core/models.py,sha256=1zJoIZAX63TEhXzjIbFCe1tYcWuExmnrjbEw-AUPNfA,9181
|
|
34
|
+
gpu4agents/core/paths.py,sha256=xvOyVW9WVdFpT8vrSCEnPPtlMK6BnB0AXyvHA5knffE,1416
|
|
35
|
+
gpu4agents/core/policy.py,sha256=F3r5UwJEQD_6coUgwd2hWqO1kDXlCLbVoXQci-bxqg0,9722
|
|
36
|
+
gpu4agents/core/state.py,sha256=CmvkUPUwfATPB4iQWeoOp7NjCWWIkdcd5E4FPjlhFOw,15798
|
|
37
|
+
gpu4agents/core/sweep.py,sha256=F8i4n7Ix5JVmA3rFxAjUuhJAL_OI4SuPkwlj31tUfjE,17325
|
|
38
|
+
gpu4agents/core/tool_parsers.py,sha256=zqaVJ0-m0H3B9gEhhe3-gWm7nhpwp9OBMAAsBx-Odu8,2475
|
|
39
|
+
gpu4agents/providers/__init__.py,sha256=qM_BUMeabdamPdHTQHcSfp8okfjidkGhj2y_YUpBbMg,168
|
|
40
|
+
gpu4agents/providers/base.py,sha256=GVDm2JtbL0mfd223KsAHeS-4o76f9U-afP9e7VEsGx0,5203
|
|
41
|
+
gpu4agents/providers/registry.py,sha256=tj63zPcEPmUxVLqHFiFIesS_rWes0B45mOwLmjilanE,2032
|
|
42
|
+
gpu4agents/providers/vast/__init__.py,sha256=LPaQrruQaRu59u7IMrOkx2eNb_176vcN9DGvevL8E4g,63
|
|
43
|
+
gpu4agents/providers/vast/client.py,sha256=kHxqSsYVqceaUtvS0yYNl3gOT35EiZhn5qYJ07AFFQE,10186
|
|
44
|
+
gpu4agents/providers/vast/config.py,sha256=a4UYYDJFhZGpSsqPoMyoWCokqAhQ98nz5b8VsqYbrd4,1959
|
|
45
|
+
gpu4agents/providers/vast/mapper.py,sha256=NtFLvLxH-j1ok3rPG8ZRo4u_nrZj7bXebCjpp0EOtEk,11423
|
|
46
|
+
gpu4agents/providers/vast/provider.py,sha256=sqJ8kJPxqzFM_4BzomiPdvnrYMO98fTch5WQ4XIkj0c,16919
|
|
47
|
+
gpu4agents/runtime/__init__.py,sha256=ncpE8jTc_dX4cVlO3IBKg5vDv-n1TREKj4aksisqTDM,218
|
|
48
|
+
gpu4agents/runtime/base.py,sha256=SUw2VoWuFIahtFUy8RK_thIrFRdIvPDXoxmkJFEApvc,6057
|
|
49
|
+
gpu4agents/runtime/vllm.py,sha256=peVZya8LYtboizUN5hUKDq_cKtsMNw6tab0hIsRqLBw,11929
|
|
50
|
+
gpu4agents/watchdog/__init__.py,sha256=79yCptDKSE8dA-PowfQzcxrNaK6eEqhe_WDTi9BR_Fk,154
|
|
51
|
+
gpu4agents/watchdog/agent_body.py,sha256=B_olBJlogwIyGjroGorK93qwdu_fUj56CkigTBPiVAE,33367
|
|
52
|
+
gpu4agents/watchdog/builder.py,sha256=9SmkQg0wdkqj2DKnms6-CbOwdIkHIDhKn7P_sJnh5os,8997
|
|
53
|
+
jambu_gpu/__init__.py,sha256=lyDgFBShjnhy-o0X92miaFUD7iyZHxdhkUQGp-xHlJo,285
|
|
54
|
+
jambu_gpu/__main__.py,sha256=eP5QKo2yEL9eW1hnPVemmnioIASlkJ1GG6UVbIt3Dww,76
|
|
55
|
+
gpu4agents-0.5.0.dist-info/METADATA,sha256=26UBx7jDHvDaQ7wkvEXxVY2BAAPE_OOE-ZhuYH3iRjc,7330
|
|
56
|
+
gpu4agents-0.5.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
57
|
+
gpu4agents-0.5.0.dist-info/entry_points.txt,sha256=iBCjwTEp4MsctV-KL63DldRnKmZ-pMV-8HNxvVG52rI,49
|
|
58
|
+
gpu4agents-0.5.0.dist-info/licenses/LICENSE,sha256=7ofvt29ghOnzKapRr48K5JoCiQNnA5AcGFu6fSrtmSQ,1088
|
|
59
|
+
gpu4agents-0.5.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jambu.ai and João Gabriel Lima
|
|
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.
|
jambu_gpu/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Compatibility alias: this package was renamed to gpu4agents in 0.5.0.
|
|
2
|
+
|
|
3
|
+
Kept so sweep timers installed by jambu-gpu (``python -m jambu_gpu sweep --apply``)
|
|
4
|
+
keep working after an upgrade. New code imports gpu4agents.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from gpu4agents import __version__
|
|
8
|
+
|
|
9
|
+
__all__ = ["__version__"]
|
jambu_gpu/__main__.py
ADDED