gridlinegpu 0.1.3__tar.gz → 0.1.5__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.3 → gridlinegpu-0.1.5}/PKG-INFO +44 -3
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/README.md +43 -2
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/pyproject.toml +1 -1
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/src/gridlinegpu/cli.py +20 -2
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/src/gridlinegpu/client.py +325 -26
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/src/gridlinegpu.egg-info/PKG-INFO +44 -3
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/tests/test_cli.py +39 -0
- gridlinegpu-0.1.5/tests/test_client.py +613 -0
- gridlinegpu-0.1.3/tests/test_client.py +0 -159
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/setup.cfg +0 -0
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/src/gridlinegpu/__init__.py +0 -0
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/src/gridlinegpu/__main__.py +0 -0
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/src/gridlinegpu/cli_auth.py +0 -0
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/src/gridlinegpu/errors.py +0 -0
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/src/gridlinegpu.egg-info/SOURCES.txt +0 -0
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/src/gridlinegpu.egg-info/dependency_links.txt +0 -0
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/src/gridlinegpu.egg-info/entry_points.txt +0 -0
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/src/gridlinegpu.egg-info/requires.txt +0 -0
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/src/gridlinegpu.egg-info/top_level.txt +0 -0
- {gridlinegpu-0.1.3 → gridlinegpu-0.1.5}/tests/test_cli_auth.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.5
|
|
4
4
|
Summary: Python client for Gridline YAML workload routing
|
|
5
5
|
Author: Gridline GPU
|
|
6
6
|
License: Proprietary
|
|
@@ -23,7 +23,7 @@ the `gridline` command in the same Python environment on macOS and Linux.
|
|
|
23
23
|
## Install
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
python -m pip install 'gridlinegpu>=0.1.
|
|
26
|
+
python -m pip install 'gridlinegpu>=0.1.4,<0.2'
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Run browser setup once. It creates a 30-day scoped token in a private file, attempts to
|
|
@@ -36,12 +36,16 @@ gridline setup --apply
|
|
|
36
36
|
|
|
37
37
|
Use a YAML file beside its referenced inputs. Task, model, engine, providers, output,
|
|
38
38
|
budget, and deadline belong in YAML rather than Python arguments.
|
|
39
|
-
|
|
39
|
+
Server-advertised artifact limits govern each submission. Newer Gridline deployments
|
|
40
|
+
use direct, signed resumable Storage uploads for large local inputs; older deployments
|
|
41
|
+
may still cap inputs at 40 MiB and outputs at 8 MiB.
|
|
40
42
|
|
|
41
43
|
```python
|
|
42
44
|
from gridlinegpu import GridlineClient
|
|
43
45
|
|
|
44
46
|
client = GridlineClient()
|
|
47
|
+
search = client.search("workload.yaml")
|
|
48
|
+
print(search["coverage"], search["offers"]) # Catalog facts may be unknown; coverage may be partial.
|
|
45
49
|
plan = client.plan("workload.yaml")
|
|
46
50
|
print(plan.review) # Check selected GPU, exclusions, estimate, and maximum.
|
|
47
51
|
|
|
@@ -56,6 +60,7 @@ print(client.outputs(job_id, "results"))
|
|
|
56
60
|
The same installed package supports a terminal flow:
|
|
57
61
|
|
|
58
62
|
```sh
|
|
63
|
+
gridline workload search workload.yaml
|
|
59
64
|
gridline workload validate --file workload.yaml
|
|
60
65
|
gridline workload plan --file workload.yaml
|
|
61
66
|
gridline workload run --file workload.yaml --apply --max-cost-usd 5 \
|
|
@@ -79,6 +84,13 @@ approval; the Python CLI never stores a privileged refresh token.
|
|
|
79
84
|
After revoking a token, remove its local file before running setup again; setup never
|
|
80
85
|
overwrites an existing file.
|
|
81
86
|
|
|
87
|
+
`plan()` validates YAML and checks advertised task readiness before reserving or
|
|
88
|
+
uploading inputs. An explicitly unavailable managed ASR/LLM runtime, minimal managed
|
|
89
|
+
model resolver, or custom finite path raises `GridlineError` with code
|
|
90
|
+
`planning_unavailable`. Qualify the custom container and enable its launch path, or
|
|
91
|
+
use available managed runtime/full YAML settings as indicated by the error. Missing
|
|
92
|
+
task readiness fields on older servers preserve existing behavior. Custom endpoint
|
|
93
|
+
support remains a server planning decision; finite readiness does not describe it.
|
|
82
94
|
`plan()` stages input bytes but does not allocate a GPU. `launch()` never silently
|
|
83
95
|
changes a plan or retries an ambiguous paid request. If launch outcome is unknown,
|
|
84
96
|
inspect `LaunchOutcomeUnknown.workload_id` with the same idempotency key; do not use a
|
|
@@ -86,3 +98,32 @@ new key. `outputs()` verifies byte count and SHA-256 before creating private fil
|
|
|
86
98
|
Cancellation requests stop; cleanup and final provider billing can remain pending.
|
|
87
99
|
For finite jobs, `wait()` returns when outputs are complete, even if cleanup and billing
|
|
88
100
|
leave `outcome` pending. Check `status()` later for final settlement.
|
|
101
|
+
|
|
102
|
+
`search()` reads only YAML and connected-provider catalogs. It does not inspect or
|
|
103
|
+
upload referenced inputs, resolve a model or image, create a workload, or allocate a
|
|
104
|
+
GPU. `availability()["searchAvailable"]` is separate from planning and execution
|
|
105
|
+
readiness. Missing price, VRAM, capacity, or topology facts remain unknown. Partial
|
|
106
|
+
catalog coverage and unsupported execution remain visible; a search result is not a
|
|
107
|
+
launch quote. `scopeStatus: "empty_scope"` with `catalogCompleteness: "not_applicable"`
|
|
108
|
+
means no accounts were searched. Servers with catalog search disabled return a
|
|
109
|
+
search availability error. The CLI prints the same JSON response and also accepts
|
|
110
|
+
`gridline workload search --file workload.yaml`.
|
|
111
|
+
|
|
112
|
+
When worker progress is enabled on the hosted API/control service and supported by the
|
|
113
|
+
deployed worker image, `status()` also returns optional `progress`:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
status = client.status(job_id)
|
|
117
|
+
progress = status.get("progress")
|
|
118
|
+
if progress:
|
|
119
|
+
for event in progress["events"]:
|
|
120
|
+
print(event["stage"], event["observedAt"])
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Events are bounded, ordered worker reports for `progress["attemptId"]` and its fence.
|
|
124
|
+
Stages include input staging, model preparation/loading, inference start/completion,
|
|
125
|
+
and output upload. `observedAt` is the server's receipt time. Poll no faster than every
|
|
126
|
+
five seconds; deduplicate by attempt/fence/stage. Missing progress or skipped stages
|
|
127
|
+
mean unavailable evidence, not failure or an inferred stage. Inference completion
|
|
128
|
+
does not imply stored outputs, job success, cleanup, or settled billing. `wait()` keeps
|
|
129
|
+
its existing completion behavior and does not stream progress callbacks.
|
|
@@ -8,7 +8,7 @@ the `gridline` command in the same Python environment on macOS and Linux.
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
|
-
python -m pip install 'gridlinegpu>=0.1.
|
|
11
|
+
python -m pip install 'gridlinegpu>=0.1.4,<0.2'
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Run browser setup once. It creates a 30-day scoped token in a private file, attempts to
|
|
@@ -21,12 +21,16 @@ gridline setup --apply
|
|
|
21
21
|
|
|
22
22
|
Use a YAML file beside its referenced inputs. Task, model, engine, providers, output,
|
|
23
23
|
budget, and deadline belong in YAML rather than Python arguments.
|
|
24
|
-
|
|
24
|
+
Server-advertised artifact limits govern each submission. Newer Gridline deployments
|
|
25
|
+
use direct, signed resumable Storage uploads for large local inputs; older deployments
|
|
26
|
+
may still cap inputs at 40 MiB and outputs at 8 MiB.
|
|
25
27
|
|
|
26
28
|
```python
|
|
27
29
|
from gridlinegpu import GridlineClient
|
|
28
30
|
|
|
29
31
|
client = GridlineClient()
|
|
32
|
+
search = client.search("workload.yaml")
|
|
33
|
+
print(search["coverage"], search["offers"]) # Catalog facts may be unknown; coverage may be partial.
|
|
30
34
|
plan = client.plan("workload.yaml")
|
|
31
35
|
print(plan.review) # Check selected GPU, exclusions, estimate, and maximum.
|
|
32
36
|
|
|
@@ -41,6 +45,7 @@ print(client.outputs(job_id, "results"))
|
|
|
41
45
|
The same installed package supports a terminal flow:
|
|
42
46
|
|
|
43
47
|
```sh
|
|
48
|
+
gridline workload search workload.yaml
|
|
44
49
|
gridline workload validate --file workload.yaml
|
|
45
50
|
gridline workload plan --file workload.yaml
|
|
46
51
|
gridline workload run --file workload.yaml --apply --max-cost-usd 5 \
|
|
@@ -64,6 +69,13 @@ approval; the Python CLI never stores a privileged refresh token.
|
|
|
64
69
|
After revoking a token, remove its local file before running setup again; setup never
|
|
65
70
|
overwrites an existing file.
|
|
66
71
|
|
|
72
|
+
`plan()` validates YAML and checks advertised task readiness before reserving or
|
|
73
|
+
uploading inputs. An explicitly unavailable managed ASR/LLM runtime, minimal managed
|
|
74
|
+
model resolver, or custom finite path raises `GridlineError` with code
|
|
75
|
+
`planning_unavailable`. Qualify the custom container and enable its launch path, or
|
|
76
|
+
use available managed runtime/full YAML settings as indicated by the error. Missing
|
|
77
|
+
task readiness fields on older servers preserve existing behavior. Custom endpoint
|
|
78
|
+
support remains a server planning decision; finite readiness does not describe it.
|
|
67
79
|
`plan()` stages input bytes but does not allocate a GPU. `launch()` never silently
|
|
68
80
|
changes a plan or retries an ambiguous paid request. If launch outcome is unknown,
|
|
69
81
|
inspect `LaunchOutcomeUnknown.workload_id` with the same idempotency key; do not use a
|
|
@@ -71,3 +83,32 @@ new key. `outputs()` verifies byte count and SHA-256 before creating private fil
|
|
|
71
83
|
Cancellation requests stop; cleanup and final provider billing can remain pending.
|
|
72
84
|
For finite jobs, `wait()` returns when outputs are complete, even if cleanup and billing
|
|
73
85
|
leave `outcome` pending. Check `status()` later for final settlement.
|
|
86
|
+
|
|
87
|
+
`search()` reads only YAML and connected-provider catalogs. It does not inspect or
|
|
88
|
+
upload referenced inputs, resolve a model or image, create a workload, or allocate a
|
|
89
|
+
GPU. `availability()["searchAvailable"]` is separate from planning and execution
|
|
90
|
+
readiness. Missing price, VRAM, capacity, or topology facts remain unknown. Partial
|
|
91
|
+
catalog coverage and unsupported execution remain visible; a search result is not a
|
|
92
|
+
launch quote. `scopeStatus: "empty_scope"` with `catalogCompleteness: "not_applicable"`
|
|
93
|
+
means no accounts were searched. Servers with catalog search disabled return a
|
|
94
|
+
search availability error. The CLI prints the same JSON response and also accepts
|
|
95
|
+
`gridline workload search --file workload.yaml`.
|
|
96
|
+
|
|
97
|
+
When worker progress is enabled on the hosted API/control service and supported by the
|
|
98
|
+
deployed worker image, `status()` also returns optional `progress`:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
status = client.status(job_id)
|
|
102
|
+
progress = status.get("progress")
|
|
103
|
+
if progress:
|
|
104
|
+
for event in progress["events"]:
|
|
105
|
+
print(event["stage"], event["observedAt"])
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Events are bounded, ordered worker reports for `progress["attemptId"]` and its fence.
|
|
109
|
+
Stages include input staging, model preparation/loading, inference start/completion,
|
|
110
|
+
and output upload. `observedAt` is the server's receipt time. Poll no faster than every
|
|
111
|
+
five seconds; deduplicate by attempt/fence/stage. Missing progress or skipped stages
|
|
112
|
+
mean unavailable evidence, not failure or an inferred stage. Inference completion
|
|
113
|
+
does not imply stored outputs, job success, cleanup, or settled billing. `wait()` keeps
|
|
114
|
+
its existing completion behavior and does not stream progress callbacks.
|
|
@@ -49,8 +49,11 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
49
49
|
revoke.add_argument("token_id")
|
|
50
50
|
revoke.add_argument("--apply", action="store_true")
|
|
51
51
|
|
|
52
|
-
workload = commands.add_parser("workload", help="
|
|
52
|
+
workload = commands.add_parser("workload", help="Search catalogs, plan, launch, and inspect YAML jobs")
|
|
53
53
|
actions = workload.add_subparsers(dest="action", required=True)
|
|
54
|
+
search = actions.add_parser("search", help="Search connected-provider catalogs without staging inputs")
|
|
55
|
+
search.add_argument("yaml_path", type=Path, nargs="?")
|
|
56
|
+
search.add_argument("--file", type=Path)
|
|
54
57
|
for name, help_text in [
|
|
55
58
|
("validate", "Validate YAML without allocating a GPU"),
|
|
56
59
|
("plan", "Stage inputs and review a route without allocating a GPU"),
|
|
@@ -77,7 +80,7 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
77
80
|
if name == "cancel":
|
|
78
81
|
action.add_argument("--apply", action="store_true")
|
|
79
82
|
action.add_argument("--yes", action="store_true")
|
|
80
|
-
actions.add_parser("availability", help="Show account planning and execution gates")
|
|
83
|
+
actions.add_parser("availability", help="Show account search, planning, and execution gates")
|
|
81
84
|
return parser
|
|
82
85
|
|
|
83
86
|
|
|
@@ -85,6 +88,15 @@ def _emit(value: Any, output: TextIO) -> None:
|
|
|
85
88
|
print(json.dumps(value, indent=2, sort_keys=True, default=str), file=output)
|
|
86
89
|
|
|
87
90
|
|
|
91
|
+
def _warn_unmeasured_duration(review: dict[str, Any], errors: TextIO) -> None:
|
|
92
|
+
quote = review.get("result", {}).get("quote", {})
|
|
93
|
+
selected = quote.get("selected", {}) if isinstance(quote, dict) else {}
|
|
94
|
+
duration = selected.get("expectedDurationMs", {}) if isinstance(selected, dict) else {}
|
|
95
|
+
if isinstance(duration, dict) and duration.get("source") == "customer_declared":
|
|
96
|
+
print("Gridline: task-cost ranking uses customer-declared duration, not measured "
|
|
97
|
+
"GPU-specific performance; estimate is not guaranteed.", file=errors)
|
|
98
|
+
|
|
99
|
+
|
|
88
100
|
def _confirm(message: str, *, yes: bool, input_stream: TextIO,
|
|
89
101
|
output: TextIO) -> None:
|
|
90
102
|
if yes:
|
|
@@ -136,10 +148,15 @@ def main(argv: Sequence[str] | None = None, *, input_stream: TextIO = sys.stdin,
|
|
|
136
148
|
action = args.action
|
|
137
149
|
if action == "availability":
|
|
138
150
|
result = client.availability()
|
|
151
|
+
elif action == "search":
|
|
152
|
+
if (args.yaml_path is None) == (args.file is None):
|
|
153
|
+
raise ValueError("workload search requires YAML or --file YAML, exactly once")
|
|
154
|
+
result = client.search(args.yaml_path or args.file)
|
|
139
155
|
elif action == "validate":
|
|
140
156
|
result = client.validate(args.file)
|
|
141
157
|
elif action == "plan":
|
|
142
158
|
result = client.plan(args.file).review
|
|
159
|
+
_warn_unmeasured_duration(result, errors)
|
|
143
160
|
elif action == "run":
|
|
144
161
|
if not args.apply:
|
|
145
162
|
raise GridlineError("Paid launch requires --apply", code="apply_required")
|
|
@@ -151,6 +168,7 @@ def main(argv: Sequence[str] | None = None, *, input_stream: TextIO = sys.stdin,
|
|
|
151
168
|
raise ValueError("--idempotency-key must contain 1–128 safe characters")
|
|
152
169
|
planned = client.plan(args.file)
|
|
153
170
|
_emit(planned.review, output)
|
|
171
|
+
_warn_unmeasured_duration(planned.review, errors)
|
|
154
172
|
if planned.quote is None:
|
|
155
173
|
raise GridlineError("Router abstained; inspect plan coverage", code="plan_abstained")
|
|
156
174
|
_confirm(f"Launch with approved ceiling ${amount}?", yes=args.yes,
|