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.
Files changed (57) hide show
  1. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/PKG-INFO +198 -18
  2. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/README.md +197 -17
  3. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/pyproject.toml +1 -1
  4. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/__init__.py +2 -1
  5. gridlinegpu-0.1.9/src/gridlinegpu/_source_access.py +97 -0
  6. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_admission.py +2 -0
  7. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_errors.py +20 -6
  8. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_interfaces.py +34 -4
  9. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_job.py +22 -8
  10. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_public.py +18 -46
  11. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_release.py +1 -1
  12. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/cli.py +13 -1
  13. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/client.py +23 -9
  14. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu.egg-info/PKG-INFO +198 -18
  15. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu.egg-info/SOURCES.txt +4 -0
  16. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_cli.py +24 -20
  17. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_client_source.py +18 -33
  18. gridlinegpu-0.1.9/tests/test_finetune_acceptance.py +263 -0
  19. gridlinegpu-0.1.9/tests/test_source_access.py +205 -0
  20. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_capture_gaps.py +2 -3
  21. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_contract.py +35 -3
  22. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_errors.py +6 -5
  23. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_job.py +6 -6
  24. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_job_results.py +2 -2
  25. gridlinegpu-0.1.9/tests/test_source_o6c_acceptance.py +90 -0
  26. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/setup.cfg +0 -0
  27. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/UNICODE-LICENSE.txt +0 -0
  28. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/__main__.py +0 -0
  29. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_capture.py +0 -0
  30. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_casefold.json +0 -0
  31. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_contract.py +0 -0
  32. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_credits.py +0 -0
  33. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_history.py +0 -0
  34. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_outputs.py +0 -0
  35. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_results.py +0 -0
  36. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_run.py +0 -0
  37. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_submission.py +0 -0
  38. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/_source_transfer.py +0 -0
  39. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/cli_auth.py +0 -0
  40. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu/errors.py +0 -0
  41. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu.egg-info/dependency_links.txt +0 -0
  42. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu.egg-info/entry_points.txt +0 -0
  43. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu.egg-info/requires.txt +0 -0
  44. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/src/gridlinegpu.egg-info/top_level.txt +0 -0
  45. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_cli_auth.py +0 -0
  46. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_client.py +0 -0
  47. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_admission.py +0 -0
  48. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_canary.py +0 -0
  49. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_capture.py +0 -0
  50. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_credits.py +0 -0
  51. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_curated_run.py +0 -0
  52. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_history.py +0 -0
  53. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_release.py +0 -0
  54. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_results.py +0 -0
  55. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_run.py +0 -0
  56. {gridlinegpu-0.1.7 → gridlinegpu-0.1.9}/tests/test_source_submission.py +0 -0
  57. {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.7
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. The package also installs
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.4,<0.2'
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
- an invite-only beta, funded by credits Gridline grants to design partners: there is no
139
- top-up or payment step in the SDK. Install the source extra and turn the beta on in your
140
- shell:
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.7,<0.2'
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
- Without `GRIDLINE_SOURCE_SDK_ENABLED=true`, `run()` raises `GridlineError` with code
149
- `source_lane_disabled` and touches no file, token or network.
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." Submit a new run; contact support if it repeats.
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 your
237
- provider bills its time. During the canary, a used-up attempt costs at most about $0.50:
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 billed by your provider for the time it ran, whatever
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. The package also installs
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.4,<0.2'
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
- an invite-only beta, funded by credits Gridline grants to design partners: there is no
121
- top-up or payment step in the SDK. Install the source extra and turn the beta on in your
122
- shell:
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.7,<0.2'
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
- Without `GRIDLINE_SOURCE_SDK_ENABLED=true`, `run()` raises `GridlineError` with code
131
- `source_lane_disabled` and touches no file, token or network.
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." Submit a new run; contact support if it repeats.
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 your
219
- provider bills its time. During the canary, a used-up attempt costs at most about $0.50:
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 billed by your provider for the time it ran, whatever
410
+ choice." A machine that did start is charged to your credits for the time it ran, whatever
231
411
  the outcome.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gridlinegpu"
7
- version = "0.1.7"
7
+ version = "0.1.9"
8
8
  description = "Python client for Gridline YAML workload routing"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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
- at activation, when the extra becomes a base dependency."""
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