gridlinegpu 0.1.9__tar.gz → 0.2.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.
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/PKG-INFO +232 -8
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/README.md +231 -7
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/pyproject.toml +1 -1
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/__init__.py +9 -2
- gridlinegpu-0.2.0/src/gridlinegpu/_app_entry.py +114 -0
- gridlinegpu-0.2.0/src/gridlinegpu/_app_remote.py +175 -0
- gridlinegpu-0.2.0/src/gridlinegpu/_batch.py +197 -0
- gridlinegpu-0.2.0/src/gridlinegpu/_finetune_job.py +121 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_capture.py +3 -1
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_contract.py +10 -1
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_errors.py +127 -2
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_interfaces.py +71 -5
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_job.py +41 -10
- gridlinegpu-0.2.0/src/gridlinegpu/_source_pool.py +611 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_public.py +49 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_release.py +1 -1
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_run.py +12 -1
- gridlinegpu-0.2.0/src/gridlinegpu/app.py +770 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/cli.py +7 -1
- gridlinegpu-0.2.0/src/gridlinegpu/cli_app.py +218 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/client.py +79 -1
- gridlinegpu-0.2.0/src/gridlinegpu/finetune.py +78 -0
- gridlinegpu-0.2.0/src/gridlinegpu/worker.py +208 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu.egg-info/PKG-INFO +232 -8
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu.egg-info/SOURCES.txt +17 -1
- gridlinegpu-0.2.0/tests/test_app.py +349 -0
- gridlinegpu-0.2.0/tests/test_app_map.py +360 -0
- gridlinegpu-0.2.0/tests/test_batch.py +248 -0
- gridlinegpu-0.2.0/tests/test_cli_app.py +167 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_client_source.py +1 -1
- gridlinegpu-0.2.0/tests/test_finetune_helper.py +101 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_contract.py +78 -3
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_errors.py +8 -3
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_job.py +22 -7
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_job_results.py +2 -2
- gridlinegpu-0.2.0/tests/test_source_pool.py +435 -0
- gridlinegpu-0.2.0/tests/test_worker.py +161 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/setup.cfg +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/UNICODE-LICENSE.txt +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/__main__.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_access.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_admission.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_casefold.json +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_credits.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_history.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_outputs.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_results.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_submission.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/_source_transfer.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/cli_auth.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu/errors.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu.egg-info/dependency_links.txt +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu.egg-info/entry_points.txt +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu.egg-info/requires.txt +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/src/gridlinegpu.egg-info/top_level.txt +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_cli.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_cli_auth.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_client.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_finetune_acceptance.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_access.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_admission.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_canary.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_capture.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_capture_gaps.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_credits.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_curated_run.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_history.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_o6c_acceptance.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_release.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_results.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_run.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_submission.py +0 -0
- {gridlinegpu-0.1.9 → gridlinegpu-0.2.0}/tests/test_source_transfer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gridlinegpu
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Python client for Gridline YAML workload routing
|
|
5
5
|
Author: Gridline GPU
|
|
6
6
|
License: Proprietary
|
|
@@ -28,13 +28,13 @@ Linux.
|
|
|
28
28
|
1. **Sign up** at https://gridlinegpu.com/signup and verify your email.
|
|
29
29
|
2. **Request access** at https://gridlinegpu.com/get-started: accept the partner beta terms and
|
|
30
30
|
describe your use case in a few lines.
|
|
31
|
-
3. **Wait for approval.** Gridline
|
|
32
|
-
credit
|
|
33
|
-
credit.
|
|
31
|
+
3. **Wait for approval.** Gridline checks each request; approval grants your account beta
|
|
32
|
+
credit, and the get-started page shows the status. There is no payment step: jobs draw on
|
|
33
|
+
that credit.
|
|
34
34
|
4. **Install** the SDK with the source extra (Python 3.10 or newer):
|
|
35
35
|
|
|
36
36
|
```sh
|
|
37
|
-
python -m pip install 'gridlinegpu[source-contract]>=0.
|
|
37
|
+
python -m pip install 'gridlinegpu[source-contract]>=0.2.0,<0.3'
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
5. **Create a token** once. Browser approval saves a private, scoped token file; it is never
|
|
@@ -151,6 +151,230 @@ compiler. Without it, the YAML commands and `client.access()` still work and sou
|
|
|
151
151
|
`source_dependencies_missing` with the install line. The "Source runs (beta)" section below has the
|
|
152
152
|
limits, costs and failure codes.
|
|
153
153
|
|
|
154
|
+
## Functions: `App`, `@app.function`, `.remote()` and `.spawn()` (preview)
|
|
155
|
+
|
|
156
|
+
Decorate a Python function instead of writing YAML. Each call becomes one ordinary source job,
|
|
157
|
+
with the same access, credit holds, limits, isolation and billing as `run()`:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
import gridlinegpu as gl
|
|
161
|
+
|
|
162
|
+
app = gl.App("transcribe")
|
|
163
|
+
image = gl.Image.curated("gridline/hf-torch-2.5@r1")
|
|
164
|
+
|
|
165
|
+
@app.function(image=image, gpu="any:16GB", outputs={"transcript": "transcript.txt"})
|
|
166
|
+
def transcribe(audio: gl.File, language: str = "en") -> dict:
|
|
167
|
+
text = run_model(audio.path, language)
|
|
168
|
+
(gl.output_dir() / "transcript.txt").write_text(text)
|
|
169
|
+
return {"chars": len(text)}
|
|
170
|
+
|
|
171
|
+
print(transcribe.remote(gl.File("audio/a.flac"))) # waits; files land in gridline-outputs/
|
|
172
|
+
call = transcribe.spawn(gl.File("audio/b.flac")) # returns once the job is admitted
|
|
173
|
+
print(call.job_id, call.get())
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
How the parts work:
|
|
177
|
+
|
|
178
|
+
- **Code.** The function's module and the local modules and packages it imports (read from the
|
|
179
|
+
source, never run) are sent with the job. Add data files with `include=["data/*.json"]`. Inside
|
|
180
|
+
the job, `gridlinegpu` is a small stand-in, so the module imports in any image; only the
|
|
181
|
+
function runs, never `local_entrypoint` or `if __name__ == "__main__"` code.
|
|
182
|
+
- **Arguments and return values** are JSON data plus `gl.File` arguments. There is no pickle.
|
|
183
|
+
Each `gl.File` is uploaded as an input file (at most 512 MiB). The return value is at most
|
|
184
|
+
8 MiB. Larger results are files written under `gl.output_dir()` and declared in `outputs`
|
|
185
|
+
(at most 128 MiB each).
|
|
186
|
+
- **Options:**
|
|
187
|
+
- `gpu="any:<N>GB"` sets the minimum GPU memory (default 8 GB).
|
|
188
|
+
- `models={"base": gl.Model.hf(repo, revision)}` is fetched by the platform and found at
|
|
189
|
+
`gl.model_path("base")`.
|
|
190
|
+
- `kind="training"` marks a training job.
|
|
191
|
+
- `timeout=` is a local limit, enforced best-effort while the SDK waits; past it the job is
|
|
192
|
+
cancelled.
|
|
193
|
+
- **Not yet available:** named GPU types, `max_price`, `capacity` and built images. Each
|
|
194
|
+
raises when used.
|
|
195
|
+
|
|
196
|
+
**Many calls: `.map()` and `.starmap()`.** These return a `MapRun`; iterating it submits the calls
|
|
197
|
+
and yields their values. Nothing is submitted before that.
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
for value in transcribe.map([gl.File(path) for path in sorted(Path("audio").glob("*.flac"))],
|
|
201
|
+
kwargs={"language": "en"}):
|
|
202
|
+
print(value)
|
|
203
|
+
results = list(transcribe.starmap([(gl.File("a.flac"), "en"), (gl.File("b.flac"), "de")],
|
|
204
|
+
max_spend_usd=5.0, return_exceptions=True))
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
A map runs one of two ways:
|
|
208
|
+
|
|
209
|
+
- **Warm pool.** With `max_spend_usd` (and `warm=None`, the default) or `warm=True`, a worker
|
|
210
|
+
pool of 1 to `max_workers` (at most 4) GPU workers runs the calls. The module loads once per
|
|
211
|
+
worker.
|
|
212
|
+
- Each call is one item, delivered at least once, with exactly one result committed.
|
|
213
|
+
- The function's `timeout` is the item timeout (60 to 3,600 seconds).
|
|
214
|
+
- A pooled function declares at most 7 outputs, each a file name directly in
|
|
215
|
+
`gl.output_dir()`.
|
|
216
|
+
- An account without pools falls back to chunks, unless `warm=True`.
|
|
217
|
+
- **Chunks of ordinary jobs.** `per_job` calls per job (default 16; 1 when the function
|
|
218
|
+
declares outputs), at most `max_running` jobs at once (default 2, the account's
|
|
219
|
+
running-jobs limit). A call that raises is recorded and the job runs its next call.
|
|
220
|
+
|
|
221
|
+
Common to both:
|
|
222
|
+
|
|
223
|
+
- Values come back in input order (`order="completed"` yields each as it finishes).
|
|
224
|
+
- `@app.function(retries=N)` (0 to 4) gives each failed call N more attempts.
|
|
225
|
+
- A call that still fails raises `gl.FunctionError` (`function_raised`, the job's failure code,
|
|
226
|
+
or `item_dead`). With `return_exceptions=True` it is yielded instead.
|
|
227
|
+
- Each job's or item's files land under `gridline-outputs/<function>/<job or pool id>/`.
|
|
228
|
+
|
|
229
|
+
Inside a job, a decorated function is the plain function: call other functions with
|
|
230
|
+
`.local()`. `.remote()` and `.map()` are refused there, because the job has no network.
|
|
231
|
+
|
|
232
|
+
**Fine-tuning in one call.** `gl.finetune.lora()` and `gl.finetune.qlora()` submit one training job on
|
|
233
|
+
`gridline/hf-finetune-2.5@r1`. The job runs Gridline's recipe, trl `SFTTrainer` with a peft LoRA:
|
|
234
|
+
bf16 for LoRA, 4-bit NF4 for QLoRA. The platform fetches the base model at its pinned revision;
|
|
235
|
+
the dataset is a JSON-lines file whose rows have a `text` field:
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
run = gl.finetune.qlora(gl.Model.hf("HuggingFaceTB/SmolLM2-135M", revision="93efa2f097d58c2a74874c7e644dbc9b0cee75a2"),
|
|
239
|
+
"data/train.jsonl", eval_dataset="data/eval.jsonl", epochs=2, rank=16)
|
|
240
|
+
metrics = run.get(outputs_dir="adapters/smol") # adapter_model.safetensors, adapter_config.json, metrics.json
|
|
241
|
+
print(metrics["eval_loss_before"], metrics["eval_loss_after"])
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Options: `epochs`, `learning_rate`, `rank`, `alpha`, `dropout`, `max_seq_length`,
|
|
245
|
+
`batch_size`, `max_steps`, `target_modules` and `gpu` (default `"any:16GB"`). The adapter is at
|
|
246
|
+
most 128 MiB. Full fine-tunes (all weights) are not offered yet.
|
|
247
|
+
|
|
248
|
+
**From the command line.** Running submits paid jobs, so it needs `--apply`:
|
|
249
|
+
|
|
250
|
+
```sh
|
|
251
|
+
gridline app show transcribe_app.py # each function's generated YAML; nothing is sent
|
|
252
|
+
gridline run --apply transcribe_app.py --folder audio # the local_entrypoint, arguments typed from it
|
|
253
|
+
gridline run --apply transcribe_app.py::transcribe --audio a.flac --language en
|
|
254
|
+
gridline run --apply --detach transcribe_app.py::transcribe --audio a.flac # print the job id and exit
|
|
255
|
+
gridline pools status <pool id> # also: items, close, cancel --apply
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`gridline run` imports the file the way `python app.py` would. Each `--name value` is converted
|
|
259
|
+
by the parameter's annotation or default: `int`, `float`, `bool`, `str`, or `gl.File` for a path.
|
|
260
|
+
- **Failures:** a failed job raises `gl.FunctionError` with the job's failure code. An uncertain
|
|
261
|
+
submission raises `SubmissionOutcomeUnknown`, as `run()` does; continue it with
|
|
262
|
+
`transcribe.resume(error.submission_id)`.
|
|
263
|
+
|
|
264
|
+
## Many files: batches of ordinary jobs
|
|
265
|
+
|
|
266
|
+
`client.run_batch()` (0.1.10 or newer) runs one workload over many files, at most `per_job` files
|
|
267
|
+
(1 to 64) per job.
|
|
268
|
+
Each chunk is an ordinary `run()`, with the same admission, funding, routing, isolation and
|
|
269
|
+
billing as any job, and nothing new on the server. The workload declares one input per slot,
|
|
270
|
+
`item_00` to `item_<per_job - 1>` (`gridlinegpu.batch_slots(per_job)` lists the names). Give each
|
|
271
|
+
slot a default `source`, such as a small placeholder file, and its own `inputs/` path:
|
|
272
|
+
|
|
273
|
+
```yaml
|
|
274
|
+
task:
|
|
275
|
+
# ...
|
|
276
|
+
command: [python, transcribe.py, --count, {parameter: count}]
|
|
277
|
+
inputs:
|
|
278
|
+
item_00: {source: audio/empty.flac, path: inputs/item_00.flac}
|
|
279
|
+
item_01: {source: audio/empty.flac, path: inputs/item_01.flac}
|
|
280
|
+
# ... through item_31 for per_job=32
|
|
281
|
+
parameters:
|
|
282
|
+
count: {type: integer, default: 0}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
from pathlib import Path
|
|
287
|
+
|
|
288
|
+
run = client.run_batch("transcribe.yaml", sorted(Path("audio").glob("*.flac")), per_job=32,
|
|
289
|
+
count_parameter="count")
|
|
290
|
+
for done in run.results(): # one BatchResult per job, as each one finishes
|
|
291
|
+
if done.succeeded:
|
|
292
|
+
done.job.outputs(f"results/{done.index:04d}")
|
|
293
|
+
else:
|
|
294
|
+
print(done.index, done.files, done.result.failure_code)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
- **Nothing is submitted until `results()` is iterated.** At most `max_running` jobs (default 2,
|
|
298
|
+
the per-account running-jobs default) run at once.
|
|
299
|
+
- **When the account is at its running-jobs limit,** the chunk waits and the same submission is
|
|
300
|
+
resumed once a job finishes.
|
|
301
|
+
- **A short last chunk** leaves its unused slots at their default. Use `count_parameter` to tell
|
|
302
|
+
the job how many real files it got (an `integer` parameter bound in `command`).
|
|
303
|
+
- **Limits:** files are project-relative paths, at most 512 MiB each and 1 GiB per job, as for any
|
|
304
|
+
input.
|
|
305
|
+
- **Cost:** every job pays its own start-up (pod start, image, model load). Choose `per_job` so
|
|
306
|
+
each job's work is well above that.
|
|
307
|
+
- **Errors:** an error other than the running-jobs limit is raised as `run()` raises it.
|
|
308
|
+
- `run.jobs` lists what was submitted, and `run.remaining` lists what was not.
|
|
309
|
+
- An error with a `submission_id` is resumed with `client.resume()`, never run again.
|
|
310
|
+
- **Without a client:** `gridlinegpu.batch(files, per_job)` returns the input maps alone, one per
|
|
311
|
+
job, to pass as `run(..., inputs=...)`.
|
|
312
|
+
|
|
313
|
+
## Worker pools (beta)
|
|
314
|
+
|
|
315
|
+
A worker pool runs one workload over a stream of files on 1 to 4 warm GPU workers. The model
|
|
316
|
+
loads once per worker instead of once per file. Pools are in beta: the account must be enabled
|
|
317
|
+
for them (`pool_owner_not_allowed` otherwise). `client.pool()` and `gridlinegpu.worker` need
|
|
318
|
+
0.1.10 or newer.
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
from pathlib import Path
|
|
322
|
+
|
|
323
|
+
pool = client.pool("transcribe.yaml", max_workers=2, max_spend_usd=5.0, unit_name="audio_second")
|
|
324
|
+
pool.put_many(sorted(Path("audio").glob("*.flac"))) # enqueue any time while the pool is open
|
|
325
|
+
pool.close() # no more items: workers drain, then stop
|
|
326
|
+
for item in pool.results(directory="results"): # each item once it is finished; done
|
|
327
|
+
print(item.key, item.state, item.last_error_detail) # items' outputs land in results/<key>/
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
`pool.outputs(key, directory)` downloads one done item's outputs. Each file is checked against its
|
|
331
|
+
listed size and SHA-256 before it appears, and an existing identical file is kept. Outputs are
|
|
332
|
+
kept for 7 days after the item finished (`pool_item_output_expired` after that).
|
|
333
|
+
|
|
334
|
+
The workload's command runs once per worker and takes items with `gridlinegpu.worker`:
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
import gridlinegpu.worker
|
|
338
|
+
|
|
339
|
+
model = load_model()
|
|
340
|
+
for item in gridlinegpu.worker.items():
|
|
341
|
+
text = model.transcribe(item.input_path)
|
|
342
|
+
(item.output_dir / "transcript.json").write_text(text)
|
|
343
|
+
item.done() # or item.fail("why"); an exception hands the item back failed
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
What a pool is:
|
|
347
|
+
|
|
348
|
+
- **Bounded.** A pool has hard limits: `max_duration_seconds` (default 3,600, at most 24 hours),
|
|
349
|
+
`max_items` (at most 5,000), `max_spend_usd` and `max_workers` (at most 4). Each worker is an
|
|
350
|
+
ordinary 1-GPU job with the same routing, isolation, holds and settlement as `run()`.
|
|
351
|
+
- **Scaling.** Gridline starts workers while queued work justifies them and stops a worker after
|
|
352
|
+
`idle_seconds` (default 120) without items. An idle pool costs nothing; enqueue again to
|
|
353
|
+
restart it.
|
|
354
|
+
- **Billing.** Billing is pass-through: you pay the workers' GPU time, with no pool fee.
|
|
355
|
+
`status()["money"]` shows what is held and settled. The per-item cost is informational: each
|
|
356
|
+
worker's settled cost is shared by busy time, and cold start and idle time are shown
|
|
357
|
+
separately.
|
|
358
|
+
- **Items.** Each item has one input file (at most 512 MiB). Uploads go in batches of at most 64
|
|
359
|
+
files and 1 GiB. An item has at most 8 output files, 128 MiB each and 256 MiB in all.
|
|
360
|
+
- **Retries.** A failed item is retried after a backoff, up to `max_attempts` (default 3, at
|
|
361
|
+
most 5), then kept as `dead` with its last error. `pool.retry(key)` requeues it once. A lost
|
|
362
|
+
machine requeues an item without using an attempt.
|
|
363
|
+
- **Keys.** A key defaults to the file's SHA-256, so the same file is enqueued once. Pass `key=`
|
|
364
|
+
to name items yourself.
|
|
365
|
+
- **At least once.** An item can run twice, for example when a worker is lost after finishing
|
|
366
|
+
it or a lease expires. Only one result is ever committed, so make your work idempotent.
|
|
367
|
+
Transcription and most batch inference already are.
|
|
368
|
+
- **Shared process.** Items of one pool share a warm process inside a worker. They are not
|
|
369
|
+
isolated from each other, only from other accounts.
|
|
370
|
+
- **No network.** As with `run()`, your code has no network. Items arrive as local files over a
|
|
371
|
+
Unix socket that Gridline's supervisor serves.
|
|
372
|
+
|
|
373
|
+
If `pool()` ends with `submission_pending` (uncertain), call `client.resume_pool(submission_id)`.
|
|
374
|
+
It resends the same request and returns the same pool. `client.pool_handle(pool_id)` reattaches
|
|
375
|
+
to a pool from another process.
|
|
376
|
+
|
|
377
|
+
|
|
154
378
|
## YAML routing workloads
|
|
155
379
|
|
|
156
380
|
Submit one YAML workload through Gridline's authenticated API. The Python client stages
|
|
@@ -158,7 +382,7 @@ files referenced by YAML, returns a route for review, then launches only that ex
|
|
|
158
382
|
fresh plan when you supply a cost ceiling and idempotency key.
|
|
159
383
|
|
|
160
384
|
```sh
|
|
161
|
-
python -m pip install 'gridlinegpu>=0.
|
|
385
|
+
python -m pip install 'gridlinegpu>=0.2.0,<0.3'
|
|
162
386
|
```
|
|
163
387
|
|
|
164
388
|
Run browser setup once. It creates a 30-day scoped token in a private file, attempts to
|
|
@@ -272,7 +496,7 @@ step in the SDK. Request access first (see the quickstart above); `client.access
|
|
|
272
496
|
`gridline access` show where your request stands.
|
|
273
497
|
|
|
274
498
|
```sh
|
|
275
|
-
python -m pip install 'gridlinegpu[source-contract]>=0.
|
|
499
|
+
python -m pip install 'gridlinegpu[source-contract]>=0.2.0,<0.3'
|
|
276
500
|
gridline setup --apply # once: saves a private, scoped token file
|
|
277
501
|
gridline access # "approved" before your first run
|
|
278
502
|
```
|
|
@@ -395,7 +619,7 @@ Under `hardened_user_separation` each process has an 8 GiB data-memory limit, so
|
|
|
395
619
|
base model straight to the GPU (`device_map` or 4-bit loading) rather than building it in
|
|
396
620
|
CPU memory. While a job runs, about $1.78 of credits is held per window of about an hour;
|
|
397
621
|
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
|
|
622
|
+
fine-tunes that return merged weights need larger outputs and are not yet offered.
|
|
399
623
|
|
|
400
624
|
The rest of this section describes how source runs behave.
|
|
401
625
|
|
|
@@ -10,13 +10,13 @@ Linux.
|
|
|
10
10
|
1. **Sign up** at https://gridlinegpu.com/signup and verify your email.
|
|
11
11
|
2. **Request access** at https://gridlinegpu.com/get-started: accept the partner beta terms and
|
|
12
12
|
describe your use case in a few lines.
|
|
13
|
-
3. **Wait for approval.** Gridline
|
|
14
|
-
credit
|
|
15
|
-
credit.
|
|
13
|
+
3. **Wait for approval.** Gridline checks each request; approval grants your account beta
|
|
14
|
+
credit, and the get-started page shows the status. There is no payment step: jobs draw on
|
|
15
|
+
that credit.
|
|
16
16
|
4. **Install** the SDK with the source extra (Python 3.10 or newer):
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
python -m pip install 'gridlinegpu[source-contract]>=0.
|
|
19
|
+
python -m pip install 'gridlinegpu[source-contract]>=0.2.0,<0.3'
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
5. **Create a token** once. Browser approval saves a private, scoped token file; it is never
|
|
@@ -133,6 +133,230 @@ compiler. Without it, the YAML commands and `client.access()` still work and sou
|
|
|
133
133
|
`source_dependencies_missing` with the install line. The "Source runs (beta)" section below has the
|
|
134
134
|
limits, costs and failure codes.
|
|
135
135
|
|
|
136
|
+
## Functions: `App`, `@app.function`, `.remote()` and `.spawn()` (preview)
|
|
137
|
+
|
|
138
|
+
Decorate a Python function instead of writing YAML. Each call becomes one ordinary source job,
|
|
139
|
+
with the same access, credit holds, limits, isolation and billing as `run()`:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import gridlinegpu as gl
|
|
143
|
+
|
|
144
|
+
app = gl.App("transcribe")
|
|
145
|
+
image = gl.Image.curated("gridline/hf-torch-2.5@r1")
|
|
146
|
+
|
|
147
|
+
@app.function(image=image, gpu="any:16GB", outputs={"transcript": "transcript.txt"})
|
|
148
|
+
def transcribe(audio: gl.File, language: str = "en") -> dict:
|
|
149
|
+
text = run_model(audio.path, language)
|
|
150
|
+
(gl.output_dir() / "transcript.txt").write_text(text)
|
|
151
|
+
return {"chars": len(text)}
|
|
152
|
+
|
|
153
|
+
print(transcribe.remote(gl.File("audio/a.flac"))) # waits; files land in gridline-outputs/
|
|
154
|
+
call = transcribe.spawn(gl.File("audio/b.flac")) # returns once the job is admitted
|
|
155
|
+
print(call.job_id, call.get())
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
How the parts work:
|
|
159
|
+
|
|
160
|
+
- **Code.** The function's module and the local modules and packages it imports (read from the
|
|
161
|
+
source, never run) are sent with the job. Add data files with `include=["data/*.json"]`. Inside
|
|
162
|
+
the job, `gridlinegpu` is a small stand-in, so the module imports in any image; only the
|
|
163
|
+
function runs, never `local_entrypoint` or `if __name__ == "__main__"` code.
|
|
164
|
+
- **Arguments and return values** are JSON data plus `gl.File` arguments. There is no pickle.
|
|
165
|
+
Each `gl.File` is uploaded as an input file (at most 512 MiB). The return value is at most
|
|
166
|
+
8 MiB. Larger results are files written under `gl.output_dir()` and declared in `outputs`
|
|
167
|
+
(at most 128 MiB each).
|
|
168
|
+
- **Options:**
|
|
169
|
+
- `gpu="any:<N>GB"` sets the minimum GPU memory (default 8 GB).
|
|
170
|
+
- `models={"base": gl.Model.hf(repo, revision)}` is fetched by the platform and found at
|
|
171
|
+
`gl.model_path("base")`.
|
|
172
|
+
- `kind="training"` marks a training job.
|
|
173
|
+
- `timeout=` is a local limit, enforced best-effort while the SDK waits; past it the job is
|
|
174
|
+
cancelled.
|
|
175
|
+
- **Not yet available:** named GPU types, `max_price`, `capacity` and built images. Each
|
|
176
|
+
raises when used.
|
|
177
|
+
|
|
178
|
+
**Many calls: `.map()` and `.starmap()`.** These return a `MapRun`; iterating it submits the calls
|
|
179
|
+
and yields their values. Nothing is submitted before that.
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
for value in transcribe.map([gl.File(path) for path in sorted(Path("audio").glob("*.flac"))],
|
|
183
|
+
kwargs={"language": "en"}):
|
|
184
|
+
print(value)
|
|
185
|
+
results = list(transcribe.starmap([(gl.File("a.flac"), "en"), (gl.File("b.flac"), "de")],
|
|
186
|
+
max_spend_usd=5.0, return_exceptions=True))
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
A map runs one of two ways:
|
|
190
|
+
|
|
191
|
+
- **Warm pool.** With `max_spend_usd` (and `warm=None`, the default) or `warm=True`, a worker
|
|
192
|
+
pool of 1 to `max_workers` (at most 4) GPU workers runs the calls. The module loads once per
|
|
193
|
+
worker.
|
|
194
|
+
- Each call is one item, delivered at least once, with exactly one result committed.
|
|
195
|
+
- The function's `timeout` is the item timeout (60 to 3,600 seconds).
|
|
196
|
+
- A pooled function declares at most 7 outputs, each a file name directly in
|
|
197
|
+
`gl.output_dir()`.
|
|
198
|
+
- An account without pools falls back to chunks, unless `warm=True`.
|
|
199
|
+
- **Chunks of ordinary jobs.** `per_job` calls per job (default 16; 1 when the function
|
|
200
|
+
declares outputs), at most `max_running` jobs at once (default 2, the account's
|
|
201
|
+
running-jobs limit). A call that raises is recorded and the job runs its next call.
|
|
202
|
+
|
|
203
|
+
Common to both:
|
|
204
|
+
|
|
205
|
+
- Values come back in input order (`order="completed"` yields each as it finishes).
|
|
206
|
+
- `@app.function(retries=N)` (0 to 4) gives each failed call N more attempts.
|
|
207
|
+
- A call that still fails raises `gl.FunctionError` (`function_raised`, the job's failure code,
|
|
208
|
+
or `item_dead`). With `return_exceptions=True` it is yielded instead.
|
|
209
|
+
- Each job's or item's files land under `gridline-outputs/<function>/<job or pool id>/`.
|
|
210
|
+
|
|
211
|
+
Inside a job, a decorated function is the plain function: call other functions with
|
|
212
|
+
`.local()`. `.remote()` and `.map()` are refused there, because the job has no network.
|
|
213
|
+
|
|
214
|
+
**Fine-tuning in one call.** `gl.finetune.lora()` and `gl.finetune.qlora()` submit one training job on
|
|
215
|
+
`gridline/hf-finetune-2.5@r1`. The job runs Gridline's recipe, trl `SFTTrainer` with a peft LoRA:
|
|
216
|
+
bf16 for LoRA, 4-bit NF4 for QLoRA. The platform fetches the base model at its pinned revision;
|
|
217
|
+
the dataset is a JSON-lines file whose rows have a `text` field:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
run = gl.finetune.qlora(gl.Model.hf("HuggingFaceTB/SmolLM2-135M", revision="93efa2f097d58c2a74874c7e644dbc9b0cee75a2"),
|
|
221
|
+
"data/train.jsonl", eval_dataset="data/eval.jsonl", epochs=2, rank=16)
|
|
222
|
+
metrics = run.get(outputs_dir="adapters/smol") # adapter_model.safetensors, adapter_config.json, metrics.json
|
|
223
|
+
print(metrics["eval_loss_before"], metrics["eval_loss_after"])
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Options: `epochs`, `learning_rate`, `rank`, `alpha`, `dropout`, `max_seq_length`,
|
|
227
|
+
`batch_size`, `max_steps`, `target_modules` and `gpu` (default `"any:16GB"`). The adapter is at
|
|
228
|
+
most 128 MiB. Full fine-tunes (all weights) are not offered yet.
|
|
229
|
+
|
|
230
|
+
**From the command line.** Running submits paid jobs, so it needs `--apply`:
|
|
231
|
+
|
|
232
|
+
```sh
|
|
233
|
+
gridline app show transcribe_app.py # each function's generated YAML; nothing is sent
|
|
234
|
+
gridline run --apply transcribe_app.py --folder audio # the local_entrypoint, arguments typed from it
|
|
235
|
+
gridline run --apply transcribe_app.py::transcribe --audio a.flac --language en
|
|
236
|
+
gridline run --apply --detach transcribe_app.py::transcribe --audio a.flac # print the job id and exit
|
|
237
|
+
gridline pools status <pool id> # also: items, close, cancel --apply
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`gridline run` imports the file the way `python app.py` would. Each `--name value` is converted
|
|
241
|
+
by the parameter's annotation or default: `int`, `float`, `bool`, `str`, or `gl.File` for a path.
|
|
242
|
+
- **Failures:** a failed job raises `gl.FunctionError` with the job's failure code. An uncertain
|
|
243
|
+
submission raises `SubmissionOutcomeUnknown`, as `run()` does; continue it with
|
|
244
|
+
`transcribe.resume(error.submission_id)`.
|
|
245
|
+
|
|
246
|
+
## Many files: batches of ordinary jobs
|
|
247
|
+
|
|
248
|
+
`client.run_batch()` (0.1.10 or newer) runs one workload over many files, at most `per_job` files
|
|
249
|
+
(1 to 64) per job.
|
|
250
|
+
Each chunk is an ordinary `run()`, with the same admission, funding, routing, isolation and
|
|
251
|
+
billing as any job, and nothing new on the server. The workload declares one input per slot,
|
|
252
|
+
`item_00` to `item_<per_job - 1>` (`gridlinegpu.batch_slots(per_job)` lists the names). Give each
|
|
253
|
+
slot a default `source`, such as a small placeholder file, and its own `inputs/` path:
|
|
254
|
+
|
|
255
|
+
```yaml
|
|
256
|
+
task:
|
|
257
|
+
# ...
|
|
258
|
+
command: [python, transcribe.py, --count, {parameter: count}]
|
|
259
|
+
inputs:
|
|
260
|
+
item_00: {source: audio/empty.flac, path: inputs/item_00.flac}
|
|
261
|
+
item_01: {source: audio/empty.flac, path: inputs/item_01.flac}
|
|
262
|
+
# ... through item_31 for per_job=32
|
|
263
|
+
parameters:
|
|
264
|
+
count: {type: integer, default: 0}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
from pathlib import Path
|
|
269
|
+
|
|
270
|
+
run = client.run_batch("transcribe.yaml", sorted(Path("audio").glob("*.flac")), per_job=32,
|
|
271
|
+
count_parameter="count")
|
|
272
|
+
for done in run.results(): # one BatchResult per job, as each one finishes
|
|
273
|
+
if done.succeeded:
|
|
274
|
+
done.job.outputs(f"results/{done.index:04d}")
|
|
275
|
+
else:
|
|
276
|
+
print(done.index, done.files, done.result.failure_code)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
- **Nothing is submitted until `results()` is iterated.** At most `max_running` jobs (default 2,
|
|
280
|
+
the per-account running-jobs default) run at once.
|
|
281
|
+
- **When the account is at its running-jobs limit,** the chunk waits and the same submission is
|
|
282
|
+
resumed once a job finishes.
|
|
283
|
+
- **A short last chunk** leaves its unused slots at their default. Use `count_parameter` to tell
|
|
284
|
+
the job how many real files it got (an `integer` parameter bound in `command`).
|
|
285
|
+
- **Limits:** files are project-relative paths, at most 512 MiB each and 1 GiB per job, as for any
|
|
286
|
+
input.
|
|
287
|
+
- **Cost:** every job pays its own start-up (pod start, image, model load). Choose `per_job` so
|
|
288
|
+
each job's work is well above that.
|
|
289
|
+
- **Errors:** an error other than the running-jobs limit is raised as `run()` raises it.
|
|
290
|
+
- `run.jobs` lists what was submitted, and `run.remaining` lists what was not.
|
|
291
|
+
- An error with a `submission_id` is resumed with `client.resume()`, never run again.
|
|
292
|
+
- **Without a client:** `gridlinegpu.batch(files, per_job)` returns the input maps alone, one per
|
|
293
|
+
job, to pass as `run(..., inputs=...)`.
|
|
294
|
+
|
|
295
|
+
## Worker pools (beta)
|
|
296
|
+
|
|
297
|
+
A worker pool runs one workload over a stream of files on 1 to 4 warm GPU workers. The model
|
|
298
|
+
loads once per worker instead of once per file. Pools are in beta: the account must be enabled
|
|
299
|
+
for them (`pool_owner_not_allowed` otherwise). `client.pool()` and `gridlinegpu.worker` need
|
|
300
|
+
0.1.10 or newer.
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
from pathlib import Path
|
|
304
|
+
|
|
305
|
+
pool = client.pool("transcribe.yaml", max_workers=2, max_spend_usd=5.0, unit_name="audio_second")
|
|
306
|
+
pool.put_many(sorted(Path("audio").glob("*.flac"))) # enqueue any time while the pool is open
|
|
307
|
+
pool.close() # no more items: workers drain, then stop
|
|
308
|
+
for item in pool.results(directory="results"): # each item once it is finished; done
|
|
309
|
+
print(item.key, item.state, item.last_error_detail) # items' outputs land in results/<key>/
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
`pool.outputs(key, directory)` downloads one done item's outputs. Each file is checked against its
|
|
313
|
+
listed size and SHA-256 before it appears, and an existing identical file is kept. Outputs are
|
|
314
|
+
kept for 7 days after the item finished (`pool_item_output_expired` after that).
|
|
315
|
+
|
|
316
|
+
The workload's command runs once per worker and takes items with `gridlinegpu.worker`:
|
|
317
|
+
|
|
318
|
+
```python
|
|
319
|
+
import gridlinegpu.worker
|
|
320
|
+
|
|
321
|
+
model = load_model()
|
|
322
|
+
for item in gridlinegpu.worker.items():
|
|
323
|
+
text = model.transcribe(item.input_path)
|
|
324
|
+
(item.output_dir / "transcript.json").write_text(text)
|
|
325
|
+
item.done() # or item.fail("why"); an exception hands the item back failed
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
What a pool is:
|
|
329
|
+
|
|
330
|
+
- **Bounded.** A pool has hard limits: `max_duration_seconds` (default 3,600, at most 24 hours),
|
|
331
|
+
`max_items` (at most 5,000), `max_spend_usd` and `max_workers` (at most 4). Each worker is an
|
|
332
|
+
ordinary 1-GPU job with the same routing, isolation, holds and settlement as `run()`.
|
|
333
|
+
- **Scaling.** Gridline starts workers while queued work justifies them and stops a worker after
|
|
334
|
+
`idle_seconds` (default 120) without items. An idle pool costs nothing; enqueue again to
|
|
335
|
+
restart it.
|
|
336
|
+
- **Billing.** Billing is pass-through: you pay the workers' GPU time, with no pool fee.
|
|
337
|
+
`status()["money"]` shows what is held and settled. The per-item cost is informational: each
|
|
338
|
+
worker's settled cost is shared by busy time, and cold start and idle time are shown
|
|
339
|
+
separately.
|
|
340
|
+
- **Items.** Each item has one input file (at most 512 MiB). Uploads go in batches of at most 64
|
|
341
|
+
files and 1 GiB. An item has at most 8 output files, 128 MiB each and 256 MiB in all.
|
|
342
|
+
- **Retries.** A failed item is retried after a backoff, up to `max_attempts` (default 3, at
|
|
343
|
+
most 5), then kept as `dead` with its last error. `pool.retry(key)` requeues it once. A lost
|
|
344
|
+
machine requeues an item without using an attempt.
|
|
345
|
+
- **Keys.** A key defaults to the file's SHA-256, so the same file is enqueued once. Pass `key=`
|
|
346
|
+
to name items yourself.
|
|
347
|
+
- **At least once.** An item can run twice, for example when a worker is lost after finishing
|
|
348
|
+
it or a lease expires. Only one result is ever committed, so make your work idempotent.
|
|
349
|
+
Transcription and most batch inference already are.
|
|
350
|
+
- **Shared process.** Items of one pool share a warm process inside a worker. They are not
|
|
351
|
+
isolated from each other, only from other accounts.
|
|
352
|
+
- **No network.** As with `run()`, your code has no network. Items arrive as local files over a
|
|
353
|
+
Unix socket that Gridline's supervisor serves.
|
|
354
|
+
|
|
355
|
+
If `pool()` ends with `submission_pending` (uncertain), call `client.resume_pool(submission_id)`.
|
|
356
|
+
It resends the same request and returns the same pool. `client.pool_handle(pool_id)` reattaches
|
|
357
|
+
to a pool from another process.
|
|
358
|
+
|
|
359
|
+
|
|
136
360
|
## YAML routing workloads
|
|
137
361
|
|
|
138
362
|
Submit one YAML workload through Gridline's authenticated API. The Python client stages
|
|
@@ -140,7 +364,7 @@ files referenced by YAML, returns a route for review, then launches only that ex
|
|
|
140
364
|
fresh plan when you supply a cost ceiling and idempotency key.
|
|
141
365
|
|
|
142
366
|
```sh
|
|
143
|
-
python -m pip install 'gridlinegpu>=0.
|
|
367
|
+
python -m pip install 'gridlinegpu>=0.2.0,<0.3'
|
|
144
368
|
```
|
|
145
369
|
|
|
146
370
|
Run browser setup once. It creates a 30-day scoped token in a private file, attempts to
|
|
@@ -254,7 +478,7 @@ step in the SDK. Request access first (see the quickstart above); `client.access
|
|
|
254
478
|
`gridline access` show where your request stands.
|
|
255
479
|
|
|
256
480
|
```sh
|
|
257
|
-
python -m pip install 'gridlinegpu[source-contract]>=0.
|
|
481
|
+
python -m pip install 'gridlinegpu[source-contract]>=0.2.0,<0.3'
|
|
258
482
|
gridline setup --apply # once: saves a private, scoped token file
|
|
259
483
|
gridline access # "approved" before your first run
|
|
260
484
|
```
|
|
@@ -377,7 +601,7 @@ Under `hardened_user_separation` each process has an 8 GiB data-memory limit, so
|
|
|
377
601
|
base model straight to the GPU (`device_map` or 4-bit loading) rather than building it in
|
|
378
602
|
CPU memory. While a job runs, about $1.78 of credits is held per window of about an hour;
|
|
379
603
|
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
|
|
604
|
+
fine-tunes that return merged weights need larger outputs and are not yet offered.
|
|
381
605
|
|
|
382
606
|
The rest of this section describes how source runs behave.
|
|
383
607
|
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
"""Gridline YAML workload client."""
|
|
2
2
|
|
|
3
|
+
from ._batch import BatchResult, BatchRun, batch
|
|
4
|
+
from ._batch import slot_names as batch_slots
|
|
5
|
+
from .app import (App, File, Function, FunctionCall, FunctionError, Image, MapRun, Model, Output, model_path,
|
|
6
|
+
output_dir)
|
|
3
7
|
from .client import GridlineClient, PlannedWorkload
|
|
4
8
|
from .errors import (
|
|
5
9
|
GridlineError, LaunchOutcomeUnknown, OutputsIncomplete, ReceiptPersistenceError, SubmissionOutcomeUnknown,
|
|
6
10
|
WaitTimeout, WorkloadValidationError,
|
|
7
11
|
)
|
|
12
|
+
from . import finetune # noqa: E402 - after app's names, which its job module imports
|
|
8
13
|
|
|
9
14
|
__all__ = [
|
|
10
|
-
"
|
|
11
|
-
"
|
|
15
|
+
"App", "BatchResult", "BatchRun", "File", "Function", "FunctionCall", "FunctionError", "GridlineClient",
|
|
16
|
+
"GridlineError", "Image", "LaunchOutcomeUnknown", "MapRun", "Model", "Output", "OutputsIncomplete", "PlannedWorkload",
|
|
17
|
+
"ReceiptPersistenceError", "SubmissionOutcomeUnknown", "WaitTimeout", "WorkloadValidationError", "batch",
|
|
18
|
+
"batch_slots", "finetune", "model_path", "output_dir",
|
|
12
19
|
]
|
|
13
20
|
|
|
14
21
|
|