krauncher 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.
- krauncher-0.1.0/.gitignore +14 -0
- krauncher-0.1.0/LICENSE +21 -0
- krauncher-0.1.0/PKG-INFO +280 -0
- krauncher-0.1.0/README.md +249 -0
- krauncher-0.1.0/krauncher/KrauncherClient.py +1106 -0
- krauncher-0.1.0/krauncher/__init__.py +88 -0
- krauncher-0.1.0/krauncher/_env.py +50 -0
- krauncher-0.1.0/krauncher/_inflight.py +88 -0
- krauncher-0.1.0/krauncher/analyzer.py +359 -0
- krauncher-0.1.0/krauncher/codeblock.py +336 -0
- krauncher-0.1.0/krauncher/crypto.py +82 -0
- krauncher-0.1.0/krauncher/data_source.py +83 -0
- krauncher-0.1.0/krauncher/exceptions.py +169 -0
- krauncher-0.1.0/krauncher/hf.py +125 -0
- krauncher-0.1.0/krauncher/models.py +1209 -0
- krauncher-0.1.0/krauncher/py.typed +0 -0
- krauncher-0.1.0/krauncher/relay_pb2.py +61 -0
- krauncher-0.1.0/krauncher/relay_pb2_grpc.py +328 -0
- krauncher-0.1.0/krauncher/s3.py +105 -0
- krauncher-0.1.0/krauncher/serializer.py +167 -0
- krauncher-0.1.0/krauncher/values.py +100 -0
- krauncher-0.1.0/krauncher/volume.py +174 -0
- krauncher-0.1.0/pyproject.toml +65 -0
krauncher-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ilya Sergeev
|
|
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.
|
krauncher-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: krauncher
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Run Python functions and notebook cells on the cheapest suitable remote GPU — per-task billing, no instances
|
|
5
|
+
Project-URL: Homepage, https://krauncher.com
|
|
6
|
+
Project-URL: Repository, https://github.com/Ilya-a-sergeyev-ger/krauncher
|
|
7
|
+
Author: Ilya Sergeev
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: cloud,gpu,jupyter,machine-learning,remote-execution,task
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: cryptography>=42.0
|
|
22
|
+
Requires-Dist: grpcio>=1.80
|
|
23
|
+
Requires-Dist: httpx>=0.26.0
|
|
24
|
+
Requires-Dist: protobuf>=4.25
|
|
25
|
+
Requires-Dist: websockets>=12.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.1.9; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# Krauncher
|
|
33
|
+
|
|
34
|
+
**Run your training script on a remote GPU. Nothing more.**
|
|
35
|
+
|
|
36
|
+
Krauncher is a minimal Python library for researchers who have a working
|
|
37
|
+
local script and need a GPU — not a platform.
|
|
38
|
+
|
|
39
|
+
Website & API keys: **[krauncher.com](https://krauncher.com)**
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Quickstart
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install krauncher
|
|
47
|
+
export CAS_API_KEY="cas_..." # krauncher.com → Account → API Keys
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Requires Python 3.11+.
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import asyncio
|
|
54
|
+
from krauncher import KrauncherClient
|
|
55
|
+
|
|
56
|
+
client = KrauncherClient() # reads CAS_API_KEY / CAS_BROKER_URL from env or .env
|
|
57
|
+
|
|
58
|
+
@client.task(vram_gb=1, timeout=120)
|
|
59
|
+
def multiply(size: int):
|
|
60
|
+
import numpy as np # imports go INSIDE the function
|
|
61
|
+
a, b = np.random.rand(size, size), np.random.rand(size, size)
|
|
62
|
+
return {"mean": float((a @ b).mean())}
|
|
63
|
+
|
|
64
|
+
async def main():
|
|
65
|
+
handle = await multiply(size=1000) # submit → TaskHandle
|
|
66
|
+
print("task:", handle.task_id)
|
|
67
|
+
result = await handle # await the handle → TaskResult
|
|
68
|
+
print("output:", result.output)
|
|
69
|
+
print("gpu:", result.actual_gpu, "·", f"{result.execution_time_sec:.1f}s")
|
|
70
|
+
|
|
71
|
+
asyncio.run(main())
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The decorated function becomes **async**: calling it submits the task and
|
|
75
|
+
returns a `TaskHandle`; awaiting the handle (or `await handle.wait(...)`)
|
|
76
|
+
returns a `TaskResult`.
|
|
77
|
+
|
|
78
|
+
> **Using an LLM / coding agent?** Read **[AGENTS.md](AGENTS.md)** — a single
|
|
79
|
+
> accurate reference of the API, parameters, result fields, errors and
|
|
80
|
+
> constraints. Runnable examples live in **[tutorial/](tutorial/)**.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## The problem with serverless ML platforms
|
|
85
|
+
|
|
86
|
+
Serverless orchestration platforms are genuinely impressive pieces of
|
|
87
|
+
infrastructure. They handle container builds, secret management, artifact
|
|
88
|
+
storage, scheduling, persistent volumes, and team dashboards.
|
|
89
|
+
|
|
90
|
+
They also charge you for all of it — whether you use it or not.
|
|
91
|
+
|
|
92
|
+
If you're fine-tuning a small model, running ablations, or iterating on
|
|
93
|
+
a research experiment with a dataset under 2 GB, you're likely paying for
|
|
94
|
+
an orchestration layer you don't need.
|
|
95
|
+
|
|
96
|
+
Krauncher does less, on purpose. It runs your existing Python function on
|
|
97
|
+
a remote GPU, returns the result, and gets out of the way.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## What Krauncher is (and isn't)
|
|
102
|
+
|
|
103
|
+
**Good fit:**
|
|
104
|
+
- Fine-tuning, LoRA, small-scale experiments with training datasets up to ~2 GB
|
|
105
|
+
- Researchers who already have a working local script
|
|
106
|
+
- Anyone tired of rewriting their code to fit a platform's abstractions
|
|
107
|
+
- Teams where "infrastructure" means one person and a credit card
|
|
108
|
+
|
|
109
|
+
**Not the right tool if:**
|
|
110
|
+
- You need managed versioned artifact storage
|
|
111
|
+
- Your team requires persistent shared volumes across runs
|
|
112
|
+
- Your dataset is hundreds of GBs with complex multi-node sharding
|
|
113
|
+
- You want a UI dashboard for experiment tracking
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## How it works
|
|
118
|
+
|
|
119
|
+
Add a decorator. Await your function. Get a result. Your existing code
|
|
120
|
+
doesn't change — no base images, no volume mounts, no platform imports.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
import asyncio
|
|
124
|
+
from krauncher import KrauncherClient
|
|
125
|
+
|
|
126
|
+
client = KrauncherClient()
|
|
127
|
+
|
|
128
|
+
@client.task(gpu_name="RTX4090", group_id="mistral-run", timeout=3600)
|
|
129
|
+
def finetune():
|
|
130
|
+
from transformers import AutoModelForCausalLM, Trainer, TrainingArguments
|
|
131
|
+
from datasets import load_dataset
|
|
132
|
+
|
|
133
|
+
# Weights download to worker storage on first run (~15 GB for 7B);
|
|
134
|
+
# later runs in the same group_id reuse the cached weights.
|
|
135
|
+
model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-v0.1")
|
|
136
|
+
dataset = load_dataset("tatsu-lab/alpaca", split="train[:2000]")
|
|
137
|
+
|
|
138
|
+
# ... your training logic, unchanged from local ...
|
|
139
|
+
|
|
140
|
+
model.save_pretrained("/tmp/output")
|
|
141
|
+
# Worker storage is ephemeral — sync checkpoints out before returning.
|
|
142
|
+
upload_to_s3("/tmp/output", "my-checkpoints/run-1")
|
|
143
|
+
return {"status": "done", "checkpoint": "s3://my-checkpoints/run-1"}
|
|
144
|
+
|
|
145
|
+
async def main():
|
|
146
|
+
result = await finetune() # submit and wait
|
|
147
|
+
print(result.output)
|
|
148
|
+
|
|
149
|
+
asyncio.run(main())
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
> The decorated function is **async** — always call it from an `async`
|
|
153
|
+
> context and `await` the handle (which submits and waits). See the
|
|
154
|
+
> [Quickstart](#quickstart) for the canonical shape.
|
|
155
|
+
|
|
156
|
+
### Choosing a GPU
|
|
157
|
+
|
|
158
|
+
| Decorator argument | Effect |
|
|
159
|
+
|-----------------------------|--------------------------------------------------------------|
|
|
160
|
+
| `vram_gb=24` | Require at least 24 GB VRAM |
|
|
161
|
+
| `gpu_name="H100"` | Require a specific model (case-insensitive substring) |
|
|
162
|
+
| `gpu_arch="Ada"` | Require a GPU architecture |
|
|
163
|
+
| *(omit `vram_gb`)* | **Auto-classify**: the analyzer inspects your code and picks the VRAM tier for you |
|
|
164
|
+
|
|
165
|
+
Leaving `vram_gb` unset is the recommended default — Krauncher analyzes your
|
|
166
|
+
code statically and sizes the GPU automatically.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Security model
|
|
171
|
+
|
|
172
|
+
Krauncher doesn't store anything. Your API key and training code are
|
|
173
|
+
encrypted on your machine before leaving it, and decrypted only inside the
|
|
174
|
+
ephemeral worker. The relay that routes your jobs cannot read the payload —
|
|
175
|
+
it doesn't have the keys.
|
|
176
|
+
|
|
177
|
+
| What | Visible to Krauncher |
|
|
178
|
+
|----------------------------|----------------------|
|
|
179
|
+
| Your storage credentials | No |
|
|
180
|
+
| Your training code | No |
|
|
181
|
+
| Your model weights/outputs | No |
|
|
182
|
+
| Job timing and GPU type | Yes |
|
|
183
|
+
|
|
184
|
+
This isn't a feature we added. It's a consequence of not wanting to be in
|
|
185
|
+
the data custody business. E2E encryption is on by default (`CAS_ENCRYPT=true`).
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Data locality
|
|
190
|
+
|
|
191
|
+
Tasks with the same `group_id` are routed to the same physical host, so
|
|
192
|
+
whatever your first run downloaded to local NVMe is still there for the next.
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
@client.task(gpu_name="RTX4090", group_id="my-experiment-v1")
|
|
196
|
+
def train_epoch(epoch: int):
|
|
197
|
+
import os
|
|
198
|
+
cache_path = "/tmp/dataset.bin"
|
|
199
|
+
if not os.path.exists(cache_path):
|
|
200
|
+
download_from_s3("my-bucket", "dataset.bin", cache_path)
|
|
201
|
+
# subsequent tasks in this group skip this step
|
|
202
|
+
run_training(cache_path, epoch=epoch)
|
|
203
|
+
return {"epoch": epoch, "status": "complete"}
|
|
204
|
+
|
|
205
|
+
async def main():
|
|
206
|
+
for epoch in range(10):
|
|
207
|
+
await train_epoch(epoch=epoch)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
For larger or registered datasets, use the **data bridge** (`data_urls=` /
|
|
211
|
+
`data=`), which downloads into `/data` inside the sandbox — see
|
|
212
|
+
[tutorial/06](tutorial/06_data_bridge.py) and
|
|
213
|
+
[tutorial/15](tutorial/15_data_sources_s3.py).
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Inspecting a finished task
|
|
218
|
+
|
|
219
|
+
After a task completes, the broker keeps a structured record — the same one
|
|
220
|
+
the web UI renders on the task detail page.
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
task = await client.get_task(task_id) # what GET /tasks/{id} returns
|
|
224
|
+
report = await client.get_task_report(task_id) # task + extended report
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`get_task` returns status, timing breakdown (queue / download / pip / setup /
|
|
228
|
+
execution), classification, costs, GPU and worker specs, and the result.
|
|
229
|
+
|
|
230
|
+
`get_task_report` adds an extended `report` field: peak/average GPU
|
|
231
|
+
utilization, peak VRAM, the actual GPU's hardware specs, and an estimated
|
|
232
|
+
time/cost comparison across all known GPUs at the worker's measured host
|
|
233
|
+
capabilities. It is intended as feedback for an LLM author of the user
|
|
234
|
+
code — pure data, no interpretation.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Examples
|
|
239
|
+
|
|
240
|
+
Numbered, runnable tutorials in [`tutorial/`](tutorial/):
|
|
241
|
+
|
|
242
|
+
| # | File | Demonstrates |
|
|
243
|
+
|-----|-----------------------------------|-----------------------------------------------|
|
|
244
|
+
| 01 | `01_remote_simple.py` | Minimal submit + await |
|
|
245
|
+
| 02 | `02_remote_with_deps.py` | `pip=` dependencies in the sandbox |
|
|
246
|
+
| 03 | `03_error_handling.py` | Catching `TaskError` / remote tracebacks |
|
|
247
|
+
| 04 | `04_timeout.py` | Execution timeout behaviour |
|
|
248
|
+
| 05 | `05_task_groups.py` | `group_id` host affinity |
|
|
249
|
+
| 06 | `06_data_bridge.py` | `data_urls=` downloads into `/data` |
|
|
250
|
+
| 09 | `09_streaming_logs.py` | Live logs via `wait(on_log=...)` |
|
|
251
|
+
| 10 | `10_progress_bar.py` | Progress reporting |
|
|
252
|
+
| 11 | `11_e2e_encryption.py` | End-to-end encryption |
|
|
253
|
+
| 12 | `12_helper_functions.py` | Shipping helper functions with the task |
|
|
254
|
+
| 13 | `13_bert_finetune.py` | Real ML code → analyzer classification |
|
|
255
|
+
| 15 | `15_data_sources_s3.py` | Registered S3 data sources |
|
|
256
|
+
| 17 | `17_multiphase_training.py` | Multi-phase training in one group |
|
|
257
|
+
| 18 | `18_resnet152_food101.py` | ResNet-152 on Food-101 |
|
|
258
|
+
| 19 | `19_huggingface_dataset.py` | HuggingFace dataset bridge |
|
|
259
|
+
| 20 | `20_bert_imdb.py` | BERT fine-tuning on IMDB |
|
|
260
|
+
| 21 | `21_qwen25_7b_lora_alpaca.py` | Qwen2.5-7B LoRA fine-tuning |
|
|
261
|
+
| 22 | `22_qwen25_7b_inference_gsm8k.py` | Qwen2.5-7B inference |
|
|
262
|
+
| 23 | `23_gnn_node_classification_cora.py` | GCN node classification |
|
|
263
|
+
| 30+ | `30_…`–`36_…` | LLM inference and batched inference |
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Install
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
pip install krauncher
|
|
271
|
+
export CAS_API_KEY="your_api_key"
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Requires Python 3.11+.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
MIT
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Krauncher
|
|
2
|
+
|
|
3
|
+
**Run your training script on a remote GPU. Nothing more.**
|
|
4
|
+
|
|
5
|
+
Krauncher is a minimal Python library for researchers who have a working
|
|
6
|
+
local script and need a GPU — not a platform.
|
|
7
|
+
|
|
8
|
+
Website & API keys: **[krauncher.com](https://krauncher.com)**
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Quickstart
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install krauncher
|
|
16
|
+
export CAS_API_KEY="cas_..." # krauncher.com → Account → API Keys
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires Python 3.11+.
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import asyncio
|
|
23
|
+
from krauncher import KrauncherClient
|
|
24
|
+
|
|
25
|
+
client = KrauncherClient() # reads CAS_API_KEY / CAS_BROKER_URL from env or .env
|
|
26
|
+
|
|
27
|
+
@client.task(vram_gb=1, timeout=120)
|
|
28
|
+
def multiply(size: int):
|
|
29
|
+
import numpy as np # imports go INSIDE the function
|
|
30
|
+
a, b = np.random.rand(size, size), np.random.rand(size, size)
|
|
31
|
+
return {"mean": float((a @ b).mean())}
|
|
32
|
+
|
|
33
|
+
async def main():
|
|
34
|
+
handle = await multiply(size=1000) # submit → TaskHandle
|
|
35
|
+
print("task:", handle.task_id)
|
|
36
|
+
result = await handle # await the handle → TaskResult
|
|
37
|
+
print("output:", result.output)
|
|
38
|
+
print("gpu:", result.actual_gpu, "·", f"{result.execution_time_sec:.1f}s")
|
|
39
|
+
|
|
40
|
+
asyncio.run(main())
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The decorated function becomes **async**: calling it submits the task and
|
|
44
|
+
returns a `TaskHandle`; awaiting the handle (or `await handle.wait(...)`)
|
|
45
|
+
returns a `TaskResult`.
|
|
46
|
+
|
|
47
|
+
> **Using an LLM / coding agent?** Read **[AGENTS.md](AGENTS.md)** — a single
|
|
48
|
+
> accurate reference of the API, parameters, result fields, errors and
|
|
49
|
+
> constraints. Runnable examples live in **[tutorial/](tutorial/)**.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## The problem with serverless ML platforms
|
|
54
|
+
|
|
55
|
+
Serverless orchestration platforms are genuinely impressive pieces of
|
|
56
|
+
infrastructure. They handle container builds, secret management, artifact
|
|
57
|
+
storage, scheduling, persistent volumes, and team dashboards.
|
|
58
|
+
|
|
59
|
+
They also charge you for all of it — whether you use it or not.
|
|
60
|
+
|
|
61
|
+
If you're fine-tuning a small model, running ablations, or iterating on
|
|
62
|
+
a research experiment with a dataset under 2 GB, you're likely paying for
|
|
63
|
+
an orchestration layer you don't need.
|
|
64
|
+
|
|
65
|
+
Krauncher does less, on purpose. It runs your existing Python function on
|
|
66
|
+
a remote GPU, returns the result, and gets out of the way.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## What Krauncher is (and isn't)
|
|
71
|
+
|
|
72
|
+
**Good fit:**
|
|
73
|
+
- Fine-tuning, LoRA, small-scale experiments with training datasets up to ~2 GB
|
|
74
|
+
- Researchers who already have a working local script
|
|
75
|
+
- Anyone tired of rewriting their code to fit a platform's abstractions
|
|
76
|
+
- Teams where "infrastructure" means one person and a credit card
|
|
77
|
+
|
|
78
|
+
**Not the right tool if:**
|
|
79
|
+
- You need managed versioned artifact storage
|
|
80
|
+
- Your team requires persistent shared volumes across runs
|
|
81
|
+
- Your dataset is hundreds of GBs with complex multi-node sharding
|
|
82
|
+
- You want a UI dashboard for experiment tracking
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## How it works
|
|
87
|
+
|
|
88
|
+
Add a decorator. Await your function. Get a result. Your existing code
|
|
89
|
+
doesn't change — no base images, no volume mounts, no platform imports.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import asyncio
|
|
93
|
+
from krauncher import KrauncherClient
|
|
94
|
+
|
|
95
|
+
client = KrauncherClient()
|
|
96
|
+
|
|
97
|
+
@client.task(gpu_name="RTX4090", group_id="mistral-run", timeout=3600)
|
|
98
|
+
def finetune():
|
|
99
|
+
from transformers import AutoModelForCausalLM, Trainer, TrainingArguments
|
|
100
|
+
from datasets import load_dataset
|
|
101
|
+
|
|
102
|
+
# Weights download to worker storage on first run (~15 GB for 7B);
|
|
103
|
+
# later runs in the same group_id reuse the cached weights.
|
|
104
|
+
model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-v0.1")
|
|
105
|
+
dataset = load_dataset("tatsu-lab/alpaca", split="train[:2000]")
|
|
106
|
+
|
|
107
|
+
# ... your training logic, unchanged from local ...
|
|
108
|
+
|
|
109
|
+
model.save_pretrained("/tmp/output")
|
|
110
|
+
# Worker storage is ephemeral — sync checkpoints out before returning.
|
|
111
|
+
upload_to_s3("/tmp/output", "my-checkpoints/run-1")
|
|
112
|
+
return {"status": "done", "checkpoint": "s3://my-checkpoints/run-1"}
|
|
113
|
+
|
|
114
|
+
async def main():
|
|
115
|
+
result = await finetune() # submit and wait
|
|
116
|
+
print(result.output)
|
|
117
|
+
|
|
118
|
+
asyncio.run(main())
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
> The decorated function is **async** — always call it from an `async`
|
|
122
|
+
> context and `await` the handle (which submits and waits). See the
|
|
123
|
+
> [Quickstart](#quickstart) for the canonical shape.
|
|
124
|
+
|
|
125
|
+
### Choosing a GPU
|
|
126
|
+
|
|
127
|
+
| Decorator argument | Effect |
|
|
128
|
+
|-----------------------------|--------------------------------------------------------------|
|
|
129
|
+
| `vram_gb=24` | Require at least 24 GB VRAM |
|
|
130
|
+
| `gpu_name="H100"` | Require a specific model (case-insensitive substring) |
|
|
131
|
+
| `gpu_arch="Ada"` | Require a GPU architecture |
|
|
132
|
+
| *(omit `vram_gb`)* | **Auto-classify**: the analyzer inspects your code and picks the VRAM tier for you |
|
|
133
|
+
|
|
134
|
+
Leaving `vram_gb` unset is the recommended default — Krauncher analyzes your
|
|
135
|
+
code statically and sizes the GPU automatically.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Security model
|
|
140
|
+
|
|
141
|
+
Krauncher doesn't store anything. Your API key and training code are
|
|
142
|
+
encrypted on your machine before leaving it, and decrypted only inside the
|
|
143
|
+
ephemeral worker. The relay that routes your jobs cannot read the payload —
|
|
144
|
+
it doesn't have the keys.
|
|
145
|
+
|
|
146
|
+
| What | Visible to Krauncher |
|
|
147
|
+
|----------------------------|----------------------|
|
|
148
|
+
| Your storage credentials | No |
|
|
149
|
+
| Your training code | No |
|
|
150
|
+
| Your model weights/outputs | No |
|
|
151
|
+
| Job timing and GPU type | Yes |
|
|
152
|
+
|
|
153
|
+
This isn't a feature we added. It's a consequence of not wanting to be in
|
|
154
|
+
the data custody business. E2E encryption is on by default (`CAS_ENCRYPT=true`).
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Data locality
|
|
159
|
+
|
|
160
|
+
Tasks with the same `group_id` are routed to the same physical host, so
|
|
161
|
+
whatever your first run downloaded to local NVMe is still there for the next.
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
@client.task(gpu_name="RTX4090", group_id="my-experiment-v1")
|
|
165
|
+
def train_epoch(epoch: int):
|
|
166
|
+
import os
|
|
167
|
+
cache_path = "/tmp/dataset.bin"
|
|
168
|
+
if not os.path.exists(cache_path):
|
|
169
|
+
download_from_s3("my-bucket", "dataset.bin", cache_path)
|
|
170
|
+
# subsequent tasks in this group skip this step
|
|
171
|
+
run_training(cache_path, epoch=epoch)
|
|
172
|
+
return {"epoch": epoch, "status": "complete"}
|
|
173
|
+
|
|
174
|
+
async def main():
|
|
175
|
+
for epoch in range(10):
|
|
176
|
+
await train_epoch(epoch=epoch)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
For larger or registered datasets, use the **data bridge** (`data_urls=` /
|
|
180
|
+
`data=`), which downloads into `/data` inside the sandbox — see
|
|
181
|
+
[tutorial/06](tutorial/06_data_bridge.py) and
|
|
182
|
+
[tutorial/15](tutorial/15_data_sources_s3.py).
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Inspecting a finished task
|
|
187
|
+
|
|
188
|
+
After a task completes, the broker keeps a structured record — the same one
|
|
189
|
+
the web UI renders on the task detail page.
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
task = await client.get_task(task_id) # what GET /tasks/{id} returns
|
|
193
|
+
report = await client.get_task_report(task_id) # task + extended report
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`get_task` returns status, timing breakdown (queue / download / pip / setup /
|
|
197
|
+
execution), classification, costs, GPU and worker specs, and the result.
|
|
198
|
+
|
|
199
|
+
`get_task_report` adds an extended `report` field: peak/average GPU
|
|
200
|
+
utilization, peak VRAM, the actual GPU's hardware specs, and an estimated
|
|
201
|
+
time/cost comparison across all known GPUs at the worker's measured host
|
|
202
|
+
capabilities. It is intended as feedback for an LLM author of the user
|
|
203
|
+
code — pure data, no interpretation.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Examples
|
|
208
|
+
|
|
209
|
+
Numbered, runnable tutorials in [`tutorial/`](tutorial/):
|
|
210
|
+
|
|
211
|
+
| # | File | Demonstrates |
|
|
212
|
+
|-----|-----------------------------------|-----------------------------------------------|
|
|
213
|
+
| 01 | `01_remote_simple.py` | Minimal submit + await |
|
|
214
|
+
| 02 | `02_remote_with_deps.py` | `pip=` dependencies in the sandbox |
|
|
215
|
+
| 03 | `03_error_handling.py` | Catching `TaskError` / remote tracebacks |
|
|
216
|
+
| 04 | `04_timeout.py` | Execution timeout behaviour |
|
|
217
|
+
| 05 | `05_task_groups.py` | `group_id` host affinity |
|
|
218
|
+
| 06 | `06_data_bridge.py` | `data_urls=` downloads into `/data` |
|
|
219
|
+
| 09 | `09_streaming_logs.py` | Live logs via `wait(on_log=...)` |
|
|
220
|
+
| 10 | `10_progress_bar.py` | Progress reporting |
|
|
221
|
+
| 11 | `11_e2e_encryption.py` | End-to-end encryption |
|
|
222
|
+
| 12 | `12_helper_functions.py` | Shipping helper functions with the task |
|
|
223
|
+
| 13 | `13_bert_finetune.py` | Real ML code → analyzer classification |
|
|
224
|
+
| 15 | `15_data_sources_s3.py` | Registered S3 data sources |
|
|
225
|
+
| 17 | `17_multiphase_training.py` | Multi-phase training in one group |
|
|
226
|
+
| 18 | `18_resnet152_food101.py` | ResNet-152 on Food-101 |
|
|
227
|
+
| 19 | `19_huggingface_dataset.py` | HuggingFace dataset bridge |
|
|
228
|
+
| 20 | `20_bert_imdb.py` | BERT fine-tuning on IMDB |
|
|
229
|
+
| 21 | `21_qwen25_7b_lora_alpaca.py` | Qwen2.5-7B LoRA fine-tuning |
|
|
230
|
+
| 22 | `22_qwen25_7b_inference_gsm8k.py` | Qwen2.5-7B inference |
|
|
231
|
+
| 23 | `23_gnn_node_classification_cora.py` | GCN node classification |
|
|
232
|
+
| 30+ | `30_…`–`36_…` | LLM inference and batched inference |
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Install
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
pip install krauncher
|
|
240
|
+
export CAS_API_KEY="your_api_key"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Requires Python 3.11+.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
MIT
|