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.
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .pytest_cache/
6
+ dist/
7
+ *.egg-info/
8
+ .venv/
9
+ calibration/
10
+ /test*.sh
11
+ /hello.txt
12
+ /pp.py
13
+ .env
14
+ temp/
@@ -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.
@@ -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