gridlinegpu 0.1.7__tar.gz → 0.1.9__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.
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/PKG-INFO +198 -18
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/README.md +197 -17
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/pyproject.toml +1 -1
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/__init__.py +2 -1
- gridlinegpu-0.1.9/src/gridlinegpu/_source_access.py +97 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_admission.py +2 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_errors.py +20 -6
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_interfaces.py +34 -4
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_job.py +22 -8
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_public.py +18 -46
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_release.py +1 -1
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/cli.py +13 -1
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/client.py +23 -9
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu.egg-info/PKG-INFO +198 -18
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu.egg-info/SOURCES.txt +4 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_cli.py +24 -20
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_client_source.py +18 -33
- gridlinegpu-0.1.9/tests/test_finetune_acceptance.py +263 -0
- gridlinegpu-0.1.9/tests/test_source_access.py +205 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_capture_gaps.py +2 -3
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_contract.py +35 -3
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_errors.py +6 -5
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_job.py +6 -6
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_job_results.py +2 -2
- gridlinegpu-0.1.9/tests/test_source_o6c_acceptance.py +90 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/setup.cfg +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/UNICODE-LICENSE.txt +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/__main__.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_capture.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_casefold.json +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_contract.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_credits.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_history.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_outputs.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_results.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_run.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_submission.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_transfer.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/cli_auth.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/errors.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu.egg-info/dependency_links.txt +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu.egg-info/entry_points.txt +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu.egg-info/requires.txt +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu.egg-info/top_level.txt +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_cli_auth.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_client.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_admission.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_canary.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_capture.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_credits.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_curated_run.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_history.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_release.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_results.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_run.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_submission.py +0 -0
- {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_transfer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gridlinegpu
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Python client for Gridline YAML workload routing
|
|
5
5
|
Author: Gridline GPU
|
|
6
6
|
License: Proprietary
|
|
@@ -18,15 +18,147 @@ Requires-Dist: unicodedata2==16.0.0; extra == "source-contract"
|
|
|
18
18
|
|
|
19
19
|
# Gridline Python SDK
|
|
20
20
|
|
|
21
|
+
Run your own Python code on one GPU as a finite job (**source runs**: batch inference, fine-tuning,
|
|
22
|
+
evaluation) and get your declared output files back, verified. The package also submits legacy YAML
|
|
23
|
+
routing workloads, and installs the `gridline` command in the same Python environment on macOS and
|
|
24
|
+
Linux.
|
|
25
|
+
|
|
26
|
+
## Self-serve quickstart (source runs)
|
|
27
|
+
|
|
28
|
+
1. **Sign up** at https://gridlinegpu.com/signup and verify your email.
|
|
29
|
+
2. **Request access** at https://gridlinegpu.com/get-started: accept the partner beta terms and
|
|
30
|
+
describe your use case in a few lines.
|
|
31
|
+
3. **Wait for approval.** Gridline approves each request by hand and grants your account beta
|
|
32
|
+
credit; the get-started page shows the status. There is no payment step: jobs draw on that
|
|
33
|
+
credit.
|
|
34
|
+
4. **Install** the SDK with the source extra (Python 3.10 or newer):
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
python -m pip install 'gridlinegpu[source-contract]>=0.1.9,<0.2'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
5. **Create a token** once. Browser approval saves a private, scoped token file; it is never
|
|
41
|
+
printed. Keep it out of source control and AI chats. Then check your access and credit:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
gridline setup --apply
|
|
45
|
+
gridline access # prints your status and the next step; "approved" means you can run
|
|
46
|
+
python -c "from gridlinegpu import GridlineClient; print(GridlineClient().credits()['availableUsdMicros'])"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
6. **Run a first job** on the curated `gridline/hf-torch-2.5@r1` runtime (CPython 3.11, CUDA 12.4,
|
|
50
|
+
torch 2.5.1, transformers 4.46.3). Put two files in an empty folder. `gridline.yaml`:
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
version: 2
|
|
54
|
+
name: first-job
|
|
55
|
+
task:
|
|
56
|
+
kind: application
|
|
57
|
+
class: inference
|
|
58
|
+
source:
|
|
59
|
+
include: [job.py]
|
|
60
|
+
command: [python, job.py]
|
|
61
|
+
runtime: gridline/hf-torch-2.5@r1
|
|
62
|
+
outputs:
|
|
63
|
+
results:
|
|
64
|
+
path: outputs/results.json
|
|
65
|
+
max_bytes: 1048576
|
|
66
|
+
resources:
|
|
67
|
+
gpu:
|
|
68
|
+
count: 1
|
|
69
|
+
minimum_vram_gib: 8
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
and `job.py`:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import json, os, torch
|
|
76
|
+
|
|
77
|
+
os.makedirs("outputs", exist_ok=True)
|
|
78
|
+
x = torch.randn(1024, 1024, device="cuda")
|
|
79
|
+
with open("outputs/results.json", "w") as f:
|
|
80
|
+
json.dump({"gpu": torch.cuda.get_device_name(0), "sum": float((x @ x).sum())}, f)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Then submit it, wait, and download the verified output:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from gridlinegpu import GridlineClient, SubmissionOutcomeUnknown
|
|
87
|
+
|
|
88
|
+
client = GridlineClient()
|
|
89
|
+
try:
|
|
90
|
+
job = client.run("gridline.yaml")
|
|
91
|
+
except SubmissionOutcomeUnknown as error:
|
|
92
|
+
job = client.resume(error.submission_id) # never run() again for the same submission
|
|
93
|
+
result = job.wait() # a local timeout never cancels the job
|
|
94
|
+
print(result.outcome, result.failure_code)
|
|
95
|
+
if result.succeeded:
|
|
96
|
+
job.outputs("results") # length and SHA-256 checked
|
|
97
|
+
print(job.report()["settlement"]) # what the job held, charged and released
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Gridline's curated-beta example (`examples/workloads/curated-beta/`) is the same job with a
|
|
101
|
+
small text generation step.
|
|
102
|
+
|
|
103
|
+
7. **Fine-tune.** The fine-tune example (`examples/workloads/finetune-lora-smollm2/`,
|
|
104
|
+
`gridline-models.yaml`) trains LoRA and QLoRA adapters for SmolLM2-135M on the
|
|
105
|
+
`gridline/hf-finetune-2.5@r1` runtime. `models:` makes the platform fetch the pinned base model
|
|
106
|
+
into `models/base` before your code starts; your code never downloads it:
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
version: 2
|
|
110
|
+
name: finetune-lora
|
|
111
|
+
task:
|
|
112
|
+
kind: application
|
|
113
|
+
class: training
|
|
114
|
+
source:
|
|
115
|
+
include: [train.py, data/train.jsonl, data/eval.jsonl]
|
|
116
|
+
command: [python, train.py]
|
|
117
|
+
runtime:
|
|
118
|
+
curated: gridline/hf-finetune-2.5@r1
|
|
119
|
+
models:
|
|
120
|
+
base:
|
|
121
|
+
source: hf://HuggingFaceTB/SmolLM2-135M
|
|
122
|
+
revision: 93efa2f097d58c2a74874c7e644dbc9b0cee75a2
|
|
123
|
+
files: [config.json, generation_config.json, model.safetensors, special_tokens_map.json,
|
|
124
|
+
tokenizer.json, tokenizer_config.json]
|
|
125
|
+
outputs:
|
|
126
|
+
metrics:
|
|
127
|
+
path: outputs/metrics.json
|
|
128
|
+
max_bytes: 262144
|
|
129
|
+
lora_adapter:
|
|
130
|
+
path: outputs/lora/adapter_model.safetensors
|
|
131
|
+
max_bytes: 33554432
|
|
132
|
+
lora_config:
|
|
133
|
+
path: outputs/lora/adapter_config.json
|
|
134
|
+
max_bytes: 65536
|
|
135
|
+
resources:
|
|
136
|
+
gpu:
|
|
137
|
+
count: 1
|
|
138
|
+
minimum_vram_gib: 16
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`train.py` loads `models/base` with `local_files_only=True`, trains, and writes each declared
|
|
142
|
+
file under `outputs/`. Submit it like the first job. **Mark every output you need as required**
|
|
143
|
+
(the default). For now an optional output written after a required one may be left out of the
|
|
144
|
+
results, even when the job succeeds.
|
|
145
|
+
|
|
146
|
+
Without approved access, `run()` is refused with `partner_not_admitted` before anything is
|
|
147
|
+
submitted, and the error points back to the get-started page. Source runs need the
|
|
148
|
+
`source-contract` extra: it stays optional because one of its packages (`unicodedata2`) has no
|
|
149
|
+
prebuilt wheel for some platforms (for example CPython 3.12+ on Linux aarch64), where it needs a C
|
|
150
|
+
compiler. Without it, the YAML commands and `client.access()` still work and source calls raise
|
|
151
|
+
`source_dependencies_missing` with the install line. The "Source runs (beta)" section below has the
|
|
152
|
+
limits, costs and failure codes.
|
|
153
|
+
|
|
154
|
+
## YAML routing workloads
|
|
155
|
+
|
|
21
156
|
Submit one YAML workload through Gridline's authenticated API. The Python client stages
|
|
22
157
|
files referenced by YAML, returns a route for review, then launches only that exact
|
|
23
|
-
fresh plan when you supply a cost ceiling and idempotency key.
|
|
24
|
-
the `gridline` command in the same Python environment on macOS and Linux.
|
|
25
|
-
|
|
26
|
-
## Install
|
|
158
|
+
fresh plan when you supply a cost ceiling and idempotency key.
|
|
27
159
|
|
|
28
160
|
```sh
|
|
29
|
-
python -m pip install 'gridlinegpu>=0.1.
|
|
161
|
+
python -m pip install 'gridlinegpu>=0.1.9,<0.2'
|
|
30
162
|
```
|
|
31
163
|
|
|
32
164
|
Run browser setup once. It creates a 30-day scoped token in a private file, attempts to
|
|
@@ -134,19 +266,20 @@ its existing completion behavior and does not stream progress callbacks.
|
|
|
134
266
|
## Source runs (beta)
|
|
135
267
|
|
|
136
268
|
Source runs send your own Python code and inputs to one GPU, run it as a finite job on an
|
|
137
|
-
operator-qualified **curated runtime**, and return your declared output files. They are in
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
269
|
+
operator-qualified **curated runtime**, and return your declared output files. They are in a
|
|
270
|
+
beta funded by credits Gridline grants to each approved account: there is no top-up or payment
|
|
271
|
+
step in the SDK. Request access first (see the quickstart above); `client.access()` and
|
|
272
|
+
`gridline access` show where your request stands.
|
|
141
273
|
|
|
142
274
|
```sh
|
|
143
|
-
python -m pip install 'gridlinegpu[source-contract]>=0.1.
|
|
144
|
-
export GRIDLINE_SOURCE_SDK_ENABLED=true
|
|
275
|
+
python -m pip install 'gridlinegpu[source-contract]>=0.1.9,<0.2'
|
|
145
276
|
gridline setup --apply # once: saves a private, scoped token file
|
|
277
|
+
gridline access # "approved" before your first run
|
|
146
278
|
```
|
|
147
279
|
|
|
148
|
-
|
|
149
|
-
`
|
|
280
|
+
Since 0.1.9 the SDK has no switch of its own: `GRIDLINE_SOURCE_SDK_ENABLED` is ignored, and the
|
|
281
|
+
server decides access (`partner_not_admitted` or `partner_suspended` refuse a run before anything
|
|
282
|
+
is submitted).
|
|
150
283
|
|
|
151
284
|
**The workload.** Put a `gridline.yaml` beside your code. Name a curated runtime instead of
|
|
152
285
|
declaring dependencies: `gridline/hf-torch-2.5@r1` provides CPython 3.11 and CUDA 12.4 with
|
|
@@ -186,11 +319,20 @@ and home directories are the platform's and cannot be redirected (`TMPDIR`,
|
|
|
186
319
|
- Inputs: each local input file is at most 512 MiB.
|
|
187
320
|
- Outputs: declare each one under `outputs`. Each file is at most 128 MiB (`max_bytes`;
|
|
188
321
|
8 MiB when omitted), at most 1 GiB in total, and at most 32 outputs per job.
|
|
322
|
+
- Outputs are required unless marked `required: false`. During the beta, an optional
|
|
323
|
+
output can be left out of the results even when your code wrote it; the job still
|
|
324
|
+
succeeds. Keep every output you need required.
|
|
189
325
|
- No network at run time (details below): the code, inputs and models it needs must
|
|
190
326
|
arrive with the job.
|
|
191
327
|
- Leave out `budget` and `execution.deadline_seconds`: beta routing places jobs best-effort
|
|
192
328
|
and refuses either constraint (`budget_feasibility_unknown` /
|
|
193
329
|
`deadline_feasibility_unknown`).
|
|
330
|
+
- One GPU per job (`resources.gpu.count: 1`).
|
|
331
|
+
- Credit holds: while a job runs, its funded hour window holds about $1.78 of your credit (one
|
|
332
|
+
hour of compute plus a 5-minute stop at the $1.50 per hour price ceiling, disk and settlement).
|
|
333
|
+
A few minutes after the machine is removed, the job is charged what it actually used and the
|
|
334
|
+
rest of the hold is released. `client.credits()` shows granted, held, charged and available
|
|
335
|
+
credit; a run with too little available credit is refused with `credit_insufficient`.
|
|
194
336
|
|
|
195
337
|
**Run it.** `run()` validates, captures your selected files, uploads them and returns a
|
|
196
338
|
`JobHandle` once the job is admitted. Everything after that happens server-side.
|
|
@@ -213,11 +355,48 @@ print(job.report()["settlement"]) # reserved, held, charged and released credit
|
|
|
213
355
|
```
|
|
214
356
|
|
|
215
357
|
`job.status()` reads the current state without waiting, and `job.cancel()` requests a stop.
|
|
358
|
+
`job.isolation_tier()` names the isolation tier the job's code ran under (`kernel_cgroup` or
|
|
359
|
+
`hardened_user_separation`, see the security page), or `None` before a machine is allocated.
|
|
360
|
+
Under `hardened_user_separation` each process of your code has an 8 GiB data-memory limit
|
|
361
|
+
(and 4 GiB per written file): an allocation past it fails inside your code (`MemoryError` in
|
|
362
|
+
Python), so the job usually ends `workload_runtime_failed`, not `workload_out_of_memory`.
|
|
216
363
|
`client.history(limit=20)` lists your recent jobs, newest first. `report()` and `history()`
|
|
217
364
|
raise `report_unavailable` / `history_unavailable` until those reads are switched on for
|
|
218
365
|
the beta; the job itself is unaffected. A complete minimal
|
|
219
366
|
job is in `examples/workloads/curated-beta/`.
|
|
220
367
|
|
|
368
|
+
**Fine-tuning (beta): LoRA/QLoRA on `gridline/hf-finetune-2.5@r1`.** The
|
|
369
|
+
runtime provides CPython 3.11 and CUDA 12.4 with `torch==2.5.1`, `transformers==4.46.3`,
|
|
370
|
+
`peft==0.13.2`, `trl==0.12.1`, `bitsandbytes==0.44.1`, `accelerate==1.1.1` and
|
|
371
|
+
`datasets==3.1.0`. Your code has no network, so name the base model under `models:` with a
|
|
372
|
+
pinned Hugging Face commit revision; the platform fetches and verifies it into
|
|
373
|
+
`models/base` before your code starts:
|
|
374
|
+
|
|
375
|
+
```yaml
|
|
376
|
+
runtime: gridline/hf-finetune-2.5@r1
|
|
377
|
+
models:
|
|
378
|
+
base:
|
|
379
|
+
source: hf://HuggingFaceTB/SmolLM2-135M
|
|
380
|
+
revision: 93efa2f097d58c2a74874c7e644dbc9b0cee75a2
|
|
381
|
+
files: [config.json, model.safetensors, tokenizer.json, tokenizer_config.json]
|
|
382
|
+
outputs:
|
|
383
|
+
lora_adapter:
|
|
384
|
+
path: outputs/lora/adapter_model.safetensors
|
|
385
|
+
max_bytes: 33554432
|
|
386
|
+
lora_config:
|
|
387
|
+
path: outputs/lora/adapter_config.json
|
|
388
|
+
max_bytes: 65536
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
A complete job (LoRA in bf16 and QLoRA in 4-bit on SmolLM2-135M) is
|
|
392
|
+
`examples/workloads/finetune-lora-smollm2/gridline-models.yaml`. Keep every adapter output
|
|
393
|
+
required (see the limits above): each file at most 128 MiB, at most 1 GiB per job, one GPU.
|
|
394
|
+
Under `hardened_user_separation` each process has an 8 GiB data-memory limit, so load the
|
|
395
|
+
base model straight to the GPU (`device_map` or 4-bit loading) rather than building it in
|
|
396
|
+
CPU memory. While a job runs, about $1.78 of credits is held per window of about an hour;
|
|
397
|
+
it settles at the actual cost after teardown (the acceptance job cost about $0.06). Full
|
|
398
|
+
fine-tunes that return merged weights need larger outputs and are coming soon.
|
|
399
|
+
|
|
221
400
|
The rest of this section describes how source runs behave.
|
|
222
401
|
|
|
223
402
|
**Network.** Source runs have no network access, only Unix domain sockets. No TCP or UDP,
|
|
@@ -229,12 +408,13 @@ offline error. Declare models and inputs: they are fetched by the platform, not
|
|
|
229
408
|
code, and staged before your code starts. io_uring is not available either. If the
|
|
230
409
|
machine cannot enforce this isolation, your code does not start and the job ends with
|
|
231
410
|
`isolation_unsupported`: "The allocated machine cannot enforce the workload's required
|
|
232
|
-
isolation."
|
|
411
|
+
isolation." The job's credit hold is released and you are not charged for it. Submit a
|
|
412
|
+
new run; contact support if it repeats.
|
|
233
413
|
|
|
234
414
|
**Pinned models.** Gridline fetches each declared model at its pinned commit. If a pinned
|
|
235
415
|
model file has changed or is missing when it is fetched for your run, the run stops and
|
|
236
|
-
that attempt is used up; it is not retried. A machine was already started, so
|
|
237
|
-
|
|
416
|
+
that attempt is used up; it is not retried. A machine was already started, so its time
|
|
417
|
+
is charged to your credits. During the beta, a used-up attempt costs at most about $0.50:
|
|
238
418
|
up to roughly 20 minutes (15 minutes of startup plus one 5-minute stop cycle) at the $1.50
|
|
239
419
|
per hour price ceiling, disk included.
|
|
240
420
|
Check that pinned models are still public, then run again.
|
|
@@ -245,5 +425,5 @@ machine was started and this attempt was not charged. Trying the next matching o
|
|
|
245
425
|
no matching GPU is left and no machine was started for the job, it ends with
|
|
246
426
|
`routing_attempts_exhausted`: "No matching GPU was available right now. No machine was
|
|
247
427
|
started for this job, so these attempts were not charged. Try again later or widen the GPU
|
|
248
|
-
choice." A machine that did start is
|
|
428
|
+
choice." A machine that did start is charged to your credits for the time it ran, whatever
|
|
249
429
|
the outcome.
|
|
@@ -1,14 +1,146 @@
|
|
|
1
1
|
# Gridline Python SDK
|
|
2
2
|
|
|
3
|
+
Run your own Python code on one GPU as a finite job (**source runs**: batch inference, fine-tuning,
|
|
4
|
+
evaluation) and get your declared output files back, verified. The package also submits legacy YAML
|
|
5
|
+
routing workloads, and installs the `gridline` command in the same Python environment on macOS and
|
|
6
|
+
Linux.
|
|
7
|
+
|
|
8
|
+
## Self-serve quickstart (source runs)
|
|
9
|
+
|
|
10
|
+
1. **Sign up** at https://gridlinegpu.com/signup and verify your email.
|
|
11
|
+
2. **Request access** at https://gridlinegpu.com/get-started: accept the partner beta terms and
|
|
12
|
+
describe your use case in a few lines.
|
|
13
|
+
3. **Wait for approval.** Gridline approves each request by hand and grants your account beta
|
|
14
|
+
credit; the get-started page shows the status. There is no payment step: jobs draw on that
|
|
15
|
+
credit.
|
|
16
|
+
4. **Install** the SDK with the source extra (Python 3.10 or newer):
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
python -m pip install 'gridlinegpu[source-contract]>=0.1.9,<0.2'
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
5. **Create a token** once. Browser approval saves a private, scoped token file; it is never
|
|
23
|
+
printed. Keep it out of source control and AI chats. Then check your access and credit:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
gridline setup --apply
|
|
27
|
+
gridline access # prints your status and the next step; "approved" means you can run
|
|
28
|
+
python -c "from gridlinegpu import GridlineClient; print(GridlineClient().credits()['availableUsdMicros'])"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
6. **Run a first job** on the curated `gridline/hf-torch-2.5@r1` runtime (CPython 3.11, CUDA 12.4,
|
|
32
|
+
torch 2.5.1, transformers 4.46.3). Put two files in an empty folder. `gridline.yaml`:
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
version: 2
|
|
36
|
+
name: first-job
|
|
37
|
+
task:
|
|
38
|
+
kind: application
|
|
39
|
+
class: inference
|
|
40
|
+
source:
|
|
41
|
+
include: [job.py]
|
|
42
|
+
command: [python, job.py]
|
|
43
|
+
runtime: gridline/hf-torch-2.5@r1
|
|
44
|
+
outputs:
|
|
45
|
+
results:
|
|
46
|
+
path: outputs/results.json
|
|
47
|
+
max_bytes: 1048576
|
|
48
|
+
resources:
|
|
49
|
+
gpu:
|
|
50
|
+
count: 1
|
|
51
|
+
minimum_vram_gib: 8
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
and `job.py`:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import json, os, torch
|
|
58
|
+
|
|
59
|
+
os.makedirs("outputs", exist_ok=True)
|
|
60
|
+
x = torch.randn(1024, 1024, device="cuda")
|
|
61
|
+
with open("outputs/results.json", "w") as f:
|
|
62
|
+
json.dump({"gpu": torch.cuda.get_device_name(0), "sum": float((x @ x).sum())}, f)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then submit it, wait, and download the verified output:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from gridlinegpu import GridlineClient, SubmissionOutcomeUnknown
|
|
69
|
+
|
|
70
|
+
client = GridlineClient()
|
|
71
|
+
try:
|
|
72
|
+
job = client.run("gridline.yaml")
|
|
73
|
+
except SubmissionOutcomeUnknown as error:
|
|
74
|
+
job = client.resume(error.submission_id) # never run() again for the same submission
|
|
75
|
+
result = job.wait() # a local timeout never cancels the job
|
|
76
|
+
print(result.outcome, result.failure_code)
|
|
77
|
+
if result.succeeded:
|
|
78
|
+
job.outputs("results") # length and SHA-256 checked
|
|
79
|
+
print(job.report()["settlement"]) # what the job held, charged and released
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Gridline's curated-beta example (`examples/workloads/curated-beta/`) is the same job with a
|
|
83
|
+
small text generation step.
|
|
84
|
+
|
|
85
|
+
7. **Fine-tune.** The fine-tune example (`examples/workloads/finetune-lora-smollm2/`,
|
|
86
|
+
`gridline-models.yaml`) trains LoRA and QLoRA adapters for SmolLM2-135M on the
|
|
87
|
+
`gridline/hf-finetune-2.5@r1` runtime. `models:` makes the platform fetch the pinned base model
|
|
88
|
+
into `models/base` before your code starts; your code never downloads it:
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
version: 2
|
|
92
|
+
name: finetune-lora
|
|
93
|
+
task:
|
|
94
|
+
kind: application
|
|
95
|
+
class: training
|
|
96
|
+
source:
|
|
97
|
+
include: [train.py, data/train.jsonl, data/eval.jsonl]
|
|
98
|
+
command: [python, train.py]
|
|
99
|
+
runtime:
|
|
100
|
+
curated: gridline/hf-finetune-2.5@r1
|
|
101
|
+
models:
|
|
102
|
+
base:
|
|
103
|
+
source: hf://HuggingFaceTB/SmolLM2-135M
|
|
104
|
+
revision: 93efa2f097d58c2a74874c7e644dbc9b0cee75a2
|
|
105
|
+
files: [config.json, generation_config.json, model.safetensors, special_tokens_map.json,
|
|
106
|
+
tokenizer.json, tokenizer_config.json]
|
|
107
|
+
outputs:
|
|
108
|
+
metrics:
|
|
109
|
+
path: outputs/metrics.json
|
|
110
|
+
max_bytes: 262144
|
|
111
|
+
lora_adapter:
|
|
112
|
+
path: outputs/lora/adapter_model.safetensors
|
|
113
|
+
max_bytes: 33554432
|
|
114
|
+
lora_config:
|
|
115
|
+
path: outputs/lora/adapter_config.json
|
|
116
|
+
max_bytes: 65536
|
|
117
|
+
resources:
|
|
118
|
+
gpu:
|
|
119
|
+
count: 1
|
|
120
|
+
minimum_vram_gib: 16
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`train.py` loads `models/base` with `local_files_only=True`, trains, and writes each declared
|
|
124
|
+
file under `outputs/`. Submit it like the first job. **Mark every output you need as required**
|
|
125
|
+
(the default). For now an optional output written after a required one may be left out of the
|
|
126
|
+
results, even when the job succeeds.
|
|
127
|
+
|
|
128
|
+
Without approved access, `run()` is refused with `partner_not_admitted` before anything is
|
|
129
|
+
submitted, and the error points back to the get-started page. Source runs need the
|
|
130
|
+
`source-contract` extra: it stays optional because one of its packages (`unicodedata2`) has no
|
|
131
|
+
prebuilt wheel for some platforms (for example CPython 3.12+ on Linux aarch64), where it needs a C
|
|
132
|
+
compiler. Without it, the YAML commands and `client.access()` still work and source calls raise
|
|
133
|
+
`source_dependencies_missing` with the install line. The "Source runs (beta)" section below has the
|
|
134
|
+
limits, costs and failure codes.
|
|
135
|
+
|
|
136
|
+
## YAML routing workloads
|
|
137
|
+
|
|
3
138
|
Submit one YAML workload through Gridline's authenticated API. The Python client stages
|
|
4
139
|
files referenced by YAML, returns a route for review, then launches only that exact
|
|
5
|
-
fresh plan when you supply a cost ceiling and idempotency key.
|
|
6
|
-
the `gridline` command in the same Python environment on macOS and Linux.
|
|
7
|
-
|
|
8
|
-
## Install
|
|
140
|
+
fresh plan when you supply a cost ceiling and idempotency key.
|
|
9
141
|
|
|
10
142
|
```sh
|
|
11
|
-
python -m pip install 'gridlinegpu>=0.1.
|
|
143
|
+
python -m pip install 'gridlinegpu>=0.1.9,<0.2'
|
|
12
144
|
```
|
|
13
145
|
|
|
14
146
|
Run browser setup once. It creates a 30-day scoped token in a private file, attempts to
|
|
@@ -116,19 +248,20 @@ its existing completion behavior and does not stream progress callbacks.
|
|
|
116
248
|
## Source runs (beta)
|
|
117
249
|
|
|
118
250
|
Source runs send your own Python code and inputs to one GPU, run it as a finite job on an
|
|
119
|
-
operator-qualified **curated runtime**, and return your declared output files. They are in
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
251
|
+
operator-qualified **curated runtime**, and return your declared output files. They are in a
|
|
252
|
+
beta funded by credits Gridline grants to each approved account: there is no top-up or payment
|
|
253
|
+
step in the SDK. Request access first (see the quickstart above); `client.access()` and
|
|
254
|
+
`gridline access` show where your request stands.
|
|
123
255
|
|
|
124
256
|
```sh
|
|
125
|
-
python -m pip install 'gridlinegpu[source-contract]>=0.1.
|
|
126
|
-
export GRIDLINE_SOURCE_SDK_ENABLED=true
|
|
257
|
+
python -m pip install 'gridlinegpu[source-contract]>=0.1.9,<0.2'
|
|
127
258
|
gridline setup --apply # once: saves a private, scoped token file
|
|
259
|
+
gridline access # "approved" before your first run
|
|
128
260
|
```
|
|
129
261
|
|
|
130
|
-
|
|
131
|
-
`
|
|
262
|
+
Since 0.1.9 the SDK has no switch of its own: `GRIDLINE_SOURCE_SDK_ENABLED` is ignored, and the
|
|
263
|
+
server decides access (`partner_not_admitted` or `partner_suspended` refuse a run before anything
|
|
264
|
+
is submitted).
|
|
132
265
|
|
|
133
266
|
**The workload.** Put a `gridline.yaml` beside your code. Name a curated runtime instead of
|
|
134
267
|
declaring dependencies: `gridline/hf-torch-2.5@r1` provides CPython 3.11 and CUDA 12.4 with
|
|
@@ -168,11 +301,20 @@ and home directories are the platform's and cannot be redirected (`TMPDIR`,
|
|
|
168
301
|
- Inputs: each local input file is at most 512 MiB.
|
|
169
302
|
- Outputs: declare each one under `outputs`. Each file is at most 128 MiB (`max_bytes`;
|
|
170
303
|
8 MiB when omitted), at most 1 GiB in total, and at most 32 outputs per job.
|
|
304
|
+
- Outputs are required unless marked `required: false`. During the beta, an optional
|
|
305
|
+
output can be left out of the results even when your code wrote it; the job still
|
|
306
|
+
succeeds. Keep every output you need required.
|
|
171
307
|
- No network at run time (details below): the code, inputs and models it needs must
|
|
172
308
|
arrive with the job.
|
|
173
309
|
- Leave out `budget` and `execution.deadline_seconds`: beta routing places jobs best-effort
|
|
174
310
|
and refuses either constraint (`budget_feasibility_unknown` /
|
|
175
311
|
`deadline_feasibility_unknown`).
|
|
312
|
+
- One GPU per job (`resources.gpu.count: 1`).
|
|
313
|
+
- Credit holds: while a job runs, its funded hour window holds about $1.78 of your credit (one
|
|
314
|
+
hour of compute plus a 5-minute stop at the $1.50 per hour price ceiling, disk and settlement).
|
|
315
|
+
A few minutes after the machine is removed, the job is charged what it actually used and the
|
|
316
|
+
rest of the hold is released. `client.credits()` shows granted, held, charged and available
|
|
317
|
+
credit; a run with too little available credit is refused with `credit_insufficient`.
|
|
176
318
|
|
|
177
319
|
**Run it.** `run()` validates, captures your selected files, uploads them and returns a
|
|
178
320
|
`JobHandle` once the job is admitted. Everything after that happens server-side.
|
|
@@ -195,11 +337,48 @@ print(job.report()["settlement"]) # reserved, held, charged and released credit
|
|
|
195
337
|
```
|
|
196
338
|
|
|
197
339
|
`job.status()` reads the current state without waiting, and `job.cancel()` requests a stop.
|
|
340
|
+
`job.isolation_tier()` names the isolation tier the job's code ran under (`kernel_cgroup` or
|
|
341
|
+
`hardened_user_separation`, see the security page), or `None` before a machine is allocated.
|
|
342
|
+
Under `hardened_user_separation` each process of your code has an 8 GiB data-memory limit
|
|
343
|
+
(and 4 GiB per written file): an allocation past it fails inside your code (`MemoryError` in
|
|
344
|
+
Python), so the job usually ends `workload_runtime_failed`, not `workload_out_of_memory`.
|
|
198
345
|
`client.history(limit=20)` lists your recent jobs, newest first. `report()` and `history()`
|
|
199
346
|
raise `report_unavailable` / `history_unavailable` until those reads are switched on for
|
|
200
347
|
the beta; the job itself is unaffected. A complete minimal
|
|
201
348
|
job is in `examples/workloads/curated-beta/`.
|
|
202
349
|
|
|
350
|
+
**Fine-tuning (beta): LoRA/QLoRA on `gridline/hf-finetune-2.5@r1`.** The
|
|
351
|
+
runtime provides CPython 3.11 and CUDA 12.4 with `torch==2.5.1`, `transformers==4.46.3`,
|
|
352
|
+
`peft==0.13.2`, `trl==0.12.1`, `bitsandbytes==0.44.1`, `accelerate==1.1.1` and
|
|
353
|
+
`datasets==3.1.0`. Your code has no network, so name the base model under `models:` with a
|
|
354
|
+
pinned Hugging Face commit revision; the platform fetches and verifies it into
|
|
355
|
+
`models/base` before your code starts:
|
|
356
|
+
|
|
357
|
+
```yaml
|
|
358
|
+
runtime: gridline/hf-finetune-2.5@r1
|
|
359
|
+
models:
|
|
360
|
+
base:
|
|
361
|
+
source: hf://HuggingFaceTB/SmolLM2-135M
|
|
362
|
+
revision: 93efa2f097d58c2a74874c7e644dbc9b0cee75a2
|
|
363
|
+
files: [config.json, model.safetensors, tokenizer.json, tokenizer_config.json]
|
|
364
|
+
outputs:
|
|
365
|
+
lora_adapter:
|
|
366
|
+
path: outputs/lora/adapter_model.safetensors
|
|
367
|
+
max_bytes: 33554432
|
|
368
|
+
lora_config:
|
|
369
|
+
path: outputs/lora/adapter_config.json
|
|
370
|
+
max_bytes: 65536
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
A complete job (LoRA in bf16 and QLoRA in 4-bit on SmolLM2-135M) is
|
|
374
|
+
`examples/workloads/finetune-lora-smollm2/gridline-models.yaml`. Keep every adapter output
|
|
375
|
+
required (see the limits above): each file at most 128 MiB, at most 1 GiB per job, one GPU.
|
|
376
|
+
Under `hardened_user_separation` each process has an 8 GiB data-memory limit, so load the
|
|
377
|
+
base model straight to the GPU (`device_map` or 4-bit loading) rather than building it in
|
|
378
|
+
CPU memory. While a job runs, about $1.78 of credits is held per window of about an hour;
|
|
379
|
+
it settles at the actual cost after teardown (the acceptance job cost about $0.06). Full
|
|
380
|
+
fine-tunes that return merged weights need larger outputs and are coming soon.
|
|
381
|
+
|
|
203
382
|
The rest of this section describes how source runs behave.
|
|
204
383
|
|
|
205
384
|
**Network.** Source runs have no network access, only Unix domain sockets. No TCP or UDP,
|
|
@@ -211,12 +390,13 @@ offline error. Declare models and inputs: they are fetched by the platform, not
|
|
|
211
390
|
code, and staged before your code starts. io_uring is not available either. If the
|
|
212
391
|
machine cannot enforce this isolation, your code does not start and the job ends with
|
|
213
392
|
`isolation_unsupported`: "The allocated machine cannot enforce the workload's required
|
|
214
|
-
isolation."
|
|
393
|
+
isolation." The job's credit hold is released and you are not charged for it. Submit a
|
|
394
|
+
new run; contact support if it repeats.
|
|
215
395
|
|
|
216
396
|
**Pinned models.** Gridline fetches each declared model at its pinned commit. If a pinned
|
|
217
397
|
model file has changed or is missing when it is fetched for your run, the run stops and
|
|
218
|
-
that attempt is used up; it is not retried. A machine was already started, so
|
|
219
|
-
|
|
398
|
+
that attempt is used up; it is not retried. A machine was already started, so its time
|
|
399
|
+
is charged to your credits. During the beta, a used-up attempt costs at most about $0.50:
|
|
220
400
|
up to roughly 20 minutes (15 minutes of startup plus one 5-minute stop cycle) at the $1.50
|
|
221
401
|
per hour price ceiling, disk included.
|
|
222
402
|
Check that pinned models are still public, then run again.
|
|
@@ -227,5 +407,5 @@ machine was started and this attempt was not charged. Trying the next matching o
|
|
|
227
407
|
no matching GPU is left and no machine was started for the job, it ends with
|
|
228
408
|
`routing_attempts_exhausted`: "No matching GPU was available right now. No machine was
|
|
229
409
|
started for this job, so these attempts were not charged. Try again later or widen the GPU
|
|
230
|
-
choice." A machine that did start is
|
|
410
|
+
choice." A machine that did start is charged to your credits for the time it ran, whatever
|
|
231
411
|
the outcome.
|
|
@@ -17,7 +17,8 @@ def __getattr__(name):
|
|
|
17
17
|
first use. Without the extra, ``gridlinegpu.JobHandle`` raises GridlineError
|
|
18
18
|
``source_dependencies_missing``, which is also an AttributeError (``hasattr`` is False; a
|
|
19
19
|
``from gridlinegpu import JobHandle`` raises Python's ImportError). It joins ``__all__``
|
|
20
|
-
|
|
20
|
+
if the extra becomes a base dependency (it stays optional in 0.1.9: unicodedata2 publishes
|
|
21
|
+
no wheel for some platforms, such as CPython 3.12+ on Linux aarch64)."""
|
|
21
22
|
if name == "JobHandle":
|
|
22
23
|
from ._source_public import job_handle_type
|
|
23
24
|
|