infimal 0.2.1__py3-none-any.whl

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.
infimal/__init__.py ADDED
@@ -0,0 +1,154 @@
1
+ """infimal: snapshot-native serverless GPU endpoints.
2
+
3
+ ```python
4
+ from infimal import App, Latency, Scale
5
+
6
+ app = App("voice", perf=Latency(p95_ms=300), scale=Scale(max_replicas=4))
7
+
8
+ @app.setup
9
+ def load():
10
+ return load_model() # runs once, on a builder GPU, then frozen into a snapshot
11
+
12
+ @app.handler
13
+ def transcribe(model, request):
14
+ return model(request["audio"])
15
+ ```
16
+
17
+ `infimal apps deploy` gives you an HTTPS endpoint that scales to zero and comes back from that
18
+ snapshot instead of a cold load. You never name a GPU: declare a latency or throughput target, or a
19
+ tier, and the platform picks the silicon.
20
+
21
+ Calling a model that is already served is the client:
22
+
23
+ ```python
24
+ from infimal import Client
25
+
26
+ client = Client.from_env()
27
+ print(client.model("qwen3-8b").chat("Explain this code.").max_tokens(128).run().text)
28
+ ```
29
+
30
+ The client names (`Client`, `InfimalError`, `Job`, `Request`, `ChatResult` and the rest) are
31
+ imported on first use, not by `import infimal`: an App module is imported inside the serving
32
+ container by the harness, and that import must not pull in the HTTP stack.
33
+ """
34
+
35
+ from typing import TYPE_CHECKING, Any
36
+
37
+ from .app import App, SnapshotBoundaryError, discover
38
+ from .gateway import Credential, Flat, Gateway, Model, PerToken, Provider, Source, Wire
39
+ from .models import (
40
+ Batch,
41
+ Deployment,
42
+ Endpoint,
43
+ Engine,
44
+ Latency,
45
+ Reservation,
46
+ Scale,
47
+ Throughput,
48
+ Tier,
49
+ TrafficEnvelope,
50
+ )
51
+ from .ring import Frame, FrameKind, Ring, RingError, RingFull
52
+ from .runtime import (
53
+ is_build,
54
+ production_vram_bytes,
55
+ snapshot_vram_bytes,
56
+ snapshot_vram_fraction,
57
+ target_vram_bytes,
58
+ )
59
+
60
+ if TYPE_CHECKING:
61
+ from .client import Client, InfimalError, InfimalUnreachable
62
+ from .request import (
63
+ Artifact,
64
+ Capabilities,
65
+ ChatResult,
66
+ Job,
67
+ JobResult,
68
+ LoraSpec,
69
+ ModelHandle,
70
+ Request,
71
+ )
72
+
73
+ __version__ = "0.2.1"
74
+
75
+ #: The client surface, by the module that defines it. Loaded lazily (PEP 562) so that
76
+ #: `import infimal` inside a container never imports httpx. `GlowError` and `GlowUnreachable` are
77
+ #: the pre-rename names of the two errors, resolvable in 0.2 only and left out of `__all__`.
78
+ _LAZY = {
79
+ "Client": "client",
80
+ "InfimalError": "client",
81
+ "InfimalUnreachable": "client",
82
+ "GlowError": "client",
83
+ "GlowUnreachable": "client",
84
+ "ModelHandle": "request",
85
+ "Request": "request",
86
+ "ChatResult": "request",
87
+ "JobResult": "request",
88
+ "Job": "request",
89
+ "Artifact": "request",
90
+ "Capabilities": "request",
91
+ "LoraSpec": "request",
92
+ }
93
+
94
+
95
+ def __getattr__(name: str) -> Any:
96
+ module = _LAZY.get(name)
97
+ if module is None:
98
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
99
+ from importlib import import_module
100
+
101
+ value = getattr(import_module(f".{module}", __name__), name)
102
+ globals()[name] = value
103
+ return value
104
+
105
+
106
+ def __dir__() -> list[str]:
107
+ return sorted(set(globals()) | set(_LAZY))
108
+
109
+
110
+ __all__ = [
111
+ "App",
112
+ "Artifact",
113
+ "Batch",
114
+ "Capabilities",
115
+ "ChatResult",
116
+ "Client",
117
+ "Credential",
118
+ "Deployment",
119
+ "Endpoint",
120
+ "Engine",
121
+ "Flat",
122
+ "Frame",
123
+ "FrameKind",
124
+ "Gateway",
125
+ "InfimalError",
126
+ "InfimalUnreachable",
127
+ "Job",
128
+ "JobResult",
129
+ "Latency",
130
+ "LoraSpec",
131
+ "Model",
132
+ "ModelHandle",
133
+ "PerToken",
134
+ "Provider",
135
+ "Request",
136
+ "Reservation",
137
+ "Ring",
138
+ "RingError",
139
+ "RingFull",
140
+ "Scale",
141
+ "SnapshotBoundaryError",
142
+ "Source",
143
+ "Throughput",
144
+ "Tier",
145
+ "TrafficEnvelope",
146
+ "Wire",
147
+ "__version__",
148
+ "discover",
149
+ "is_build",
150
+ "production_vram_bytes",
151
+ "snapshot_vram_bytes",
152
+ "snapshot_vram_fraction",
153
+ "target_vram_bytes",
154
+ ]
infimal/_synth.py ADDED
@@ -0,0 +1,347 @@
1
+ """`python -m infimal._synth`: the SDK's half of `infimal apps plan`, `apply` and `deploy`.
2
+
3
+ Private. The native `infimal` binary is the one customer CLI (D361); an App's topology is Python, and
4
+ the only way to know what a module declares is to execute it. So the binary runs this module as a
5
+ subprocess: one JSON request on stdin, one JSON reply on stdout. Nothing else calls it, and its shape
6
+ is versioned by the `synth` field rather than promised to anyone else. The contract is written down
7
+ in `docs/sdk.md`, "The synth contract".
8
+
9
+ It never opens a connection and reads no credential. Everything that crosses the wire is the
10
+ binary's: it fetches the live app between the two operations, sends the deploy or the configuration
11
+ this module computed, and renders the answer. What stays here is what has to be Python — executing
12
+ the customer's module — and what is Python today and is reused rather than ported: the diff engine
13
+ (`infimal.plan`) and the deploy body (`infimal.client.deploy_body`).
14
+
15
+ Two operations:
16
+
17
+ {"synth": 1, "op": "declare", "module": "main", "app": null}
18
+ executes the module (from the working directory) and returns the App's declaration, the
19
+ source digest, a description to print, and the deploy request.
20
+
21
+ {"synth": 1, "op": "diff", "app": "voice", "declaration": {…}, "source_digest": "sha256:…",
22
+ "live": null | <GET /v1/apps/{app}>}
23
+ diffs a declaration against the live app without executing anything, and returns the plan,
24
+ its lines, and the request `apply` sends (null when there is nothing to change).
25
+
26
+ Two more, for `infimal gateway plan` and `apply` (`infimal.gateway`):
27
+
28
+ {"synth": 1, "op": "gateway.declare", "module": "gateway", "gateway": null}
29
+ executes the module and returns the Gateway's declaration, the credentials it names as
30
+ `{name, env}` (the binary reads the variables; this module never does), a summary, and the
31
+ `PUT /v1/gateway` request.
32
+ {"synth": 1, "op": "gateway.diff", "declaration": {…}, "live": null | <GET /v1/gateway>}
33
+ diffs the declaration against the live gateway and returns the plan, its lines, and the
34
+ request `apply` sends (null when nothing changed).
35
+
36
+ Every reply carries `"synth": 1` and `"ok"`; a failure is `{"ok": false, "error", "message",
37
+ "next"}`, the API's envelope, so the binary shows it the way it shows a refusal. The exit status
38
+ is 0 with a reply that is ok, 1 with a failure reply, and 2 when the request itself could not be
39
+ read. What the customer's module prints while it is imported goes to stderr: stdout is the reply
40
+ and nothing else.
41
+ """
42
+
43
+ from __future__ import annotations
44
+
45
+ import contextlib
46
+ import json
47
+ import os
48
+ import sys
49
+ from dataclasses import asdict
50
+ from typing import Any
51
+ from urllib.parse import quote
52
+
53
+ #: The contract's version. The binary refuses a reply that does not carry the one it speaks.
54
+ VERSION = 1
55
+
56
+
57
+ class Refusal(Exception):
58
+ """A failure with the envelope the binary prints: a code, a sentence and the next step."""
59
+
60
+ def __init__(self, error: str, message: str, next: str):
61
+ super().__init__(message)
62
+ self.error = error
63
+ self.message = message
64
+ self.next = next
65
+
66
+
67
+ def _path(*segments: str) -> str:
68
+ return "/" + "/".join(quote(str(s), safe="") for s in segments)
69
+
70
+
71
+ def _summary(deployment: Any) -> list[str]:
72
+ """What the module declares, in the lines `infimal apps plan` prints above the diff."""
73
+ lines = [f"app: {deployment.name}", f" tier {deployment.tier.value}"]
74
+ if deployment.perf is not None:
75
+ target = deployment.perf.model_dump()
76
+ target.pop("kind", None)
77
+ shown = ", ".join(f"{k}={v}" for k, v in target.items())
78
+ lines.append(f" perf {deployment.perf.kind}({shown})")
79
+ scale = deployment.scale
80
+ lines.append(
81
+ f" scale {scale.min_replicas}..{scale.max_replicas}, "
82
+ f"target queue {scale.target_queue_ms} ms"
83
+ )
84
+ lines.append(
85
+ f" residency park after {scale.idle_park_ms / 1000:.0f}s idle, "
86
+ f"checkpoint after {scale.parked_checkpoint_ms / 1000:.0f}s parked"
87
+ )
88
+ if deployment.batch.enabled:
89
+ lines.append(
90
+ f" batching up to {deployment.batch.max_size} in {deployment.batch.window_ms} ms"
91
+ )
92
+ if deployment.reservation is not None:
93
+ lines.append(
94
+ f" reservation snapshot {deployment.reservation.snapshot_gb:.0f} GB, "
95
+ f"production {deployment.reservation.production_gb:.0f} GB"
96
+ )
97
+ return lines
98
+
99
+
100
+ def _load(module: str, app_name: str | None) -> Any:
101
+ """Execute the customer's module from the working directory and return its App, validated."""
102
+ from .harness import load_app
103
+
104
+ sys.path.insert(0, os.getcwd())
105
+ try:
106
+ # The module's own output goes to stderr: stdout carries the reply and nothing else.
107
+ with contextlib.redirect_stdout(sys.stderr):
108
+ app = load_app(module, app_name)
109
+ except ModuleNotFoundError as exc:
110
+ if exc.name and (exc.name == module or module.startswith(exc.name + ".")):
111
+ raise Refusal(
112
+ "module_not_found",
113
+ f"no module named {module!r} in {os.getcwd()}",
114
+ "Run from the directory that contains it, and name it as an import path "
115
+ "(services.voice), not as a file (services/voice.py).",
116
+ ) from None
117
+ raise Refusal(
118
+ "module_failed",
119
+ f"importing {module!r} failed: ModuleNotFoundError: {exc}",
120
+ "Install the module's dependencies in the Python environment that runs the SDK.",
121
+ ) from None
122
+ except SystemExit as exc:
123
+ if not isinstance(exc.code, str):
124
+ raise Refusal(
125
+ "module_failed",
126
+ f"importing {module!r} exited with status {exc.code!r}",
127
+ "Remove the exit from the module's import path: planning executes it.",
128
+ ) from None
129
+ # `load_app` names what it did not find: no App at all, no App of that name, or several.
130
+ raise Refusal(
131
+ "app_not_found",
132
+ str(exc.code),
133
+ "Declare one infimal.App in the module, or pass --app NAME to choose one.",
134
+ ) from None
135
+ except Exception as exc:
136
+ raise Refusal(
137
+ "module_failed",
138
+ f"importing {module!r} failed: {type(exc).__name__}: {exc}",
139
+ "Fix the module: planning executes it, so its imports must be safe to run.",
140
+ ) from None
141
+ try:
142
+ app.validate()
143
+ except ValueError as exc:
144
+ raise Refusal("invalid_app", str(exc), "Fix the App declaration, then plan again.") from None
145
+ return app
146
+
147
+
148
+ def declare(request: dict[str, Any]) -> dict[str, Any]:
149
+ from .client import deploy_body
150
+ from .plan import source_digest
151
+
152
+ module = request.get("module") or "main"
153
+ app = _load(str(module), request.get("app") or None)
154
+ wire = app.deployment.to_wire()
155
+ digest = source_digest(os.getcwd())
156
+ body = deploy_body(wire, tier=wire["tier"], region=wire.get("region"), source_digest=digest)
157
+ return {
158
+ "app": app.name,
159
+ "declaration": wire,
160
+ "source_digest": digest,
161
+ "summary": _summary(app.deployment),
162
+ "deploy": {"method": "POST", "path": _path("v1", "apps", app.name, "deploy"), "body": body},
163
+ }
164
+
165
+
166
+ def plan_data(plan: Any) -> dict[str, Any]:
167
+ """The plan as JSON: the dataclass, and the three properties a script branches on."""
168
+ return {
169
+ **asdict(plan),
170
+ "kind": plan.kind.value,
171
+ "requires_rebuild": plan.requires_rebuild,
172
+ "keeps_snapshot": plan.keeps_snapshot,
173
+ "keeps_rate_lock": plan.keeps_rate_lock,
174
+ }
175
+
176
+
177
+ def diff(request: dict[str, Any]) -> dict[str, Any]:
178
+ from .client import deploy_body
179
+ from .plan import ChangeKind, diff_wire, render
180
+
181
+ app = request.get("app")
182
+ wire = request.get("declaration")
183
+ if not isinstance(app, str) or not app or not isinstance(wire, dict):
184
+ raise Refusal(
185
+ "invalid_request",
186
+ "diff needs the app name and the declaration that declare returned",
187
+ "Upgrade the infimal binary and the SDK together.",
188
+ )
189
+ live = request.get("live")
190
+ digest = request.get("source_digest")
191
+ plan = diff_wire(app, wire, live if isinstance(live, dict) else None, local_source_digest=digest)
192
+ apply: dict[str, Any] | None = None
193
+ if plan.kind is not ChangeKind.NOOP:
194
+ if plan.requires_rebuild:
195
+ body = deploy_body(wire, tier=wire.get("tier"), region=wire.get("region"),
196
+ source_digest=digest)
197
+ apply = {"method": "POST", "path": _path("v1", "apps", app, "deploy"), "body": body}
198
+ else:
199
+ # A configuration merges onto the saved row: provided fields replace, omitted ones are
200
+ # kept (D327). So a target or a region the module no longer declares is sent as an
201
+ # explicit null, which is the only way to clear it.
202
+ body = dict(wire)
203
+ for change in plan.changes:
204
+ if change.after is None and change.field in ("perf", "region"):
205
+ body["slo" if change.field == "perf" else "region"] = None
206
+ apply = {"method": "PUT", "path": _path("v1", "apps", app, "config"), "body": body}
207
+ return {"plan": plan_data(plan), "lines": render(plan), "apply": apply}
208
+
209
+
210
+ def _load_gateway(module: str, namespace: str | None) -> Any:
211
+ """Execute the customer's module and return the `Gateway` it declares, validated on construction."""
212
+ from importlib import import_module
213
+
214
+ from .gateway import discover
215
+
216
+ sys.path.insert(0, os.getcwd())
217
+ try:
218
+ with contextlib.redirect_stdout(sys.stderr):
219
+ loaded = import_module(module)
220
+ except ModuleNotFoundError as exc:
221
+ if exc.name and (exc.name == module or module.startswith(exc.name + ".")):
222
+ raise Refusal(
223
+ "module_not_found",
224
+ f"no module named {module!r} in {os.getcwd()}",
225
+ "Run from the directory that contains it, and name it as an import path "
226
+ "(gateway), not as a file (gateway.py).",
227
+ ) from None
228
+ raise Refusal(
229
+ "module_failed",
230
+ f"importing {module!r} failed: ModuleNotFoundError: {exc}",
231
+ "Install the module's dependencies in the Python environment that runs the SDK.",
232
+ ) from None
233
+ except Exception as exc:
234
+ # A pydantic refusal inside the module is the declaration being invalid, not the import.
235
+ name = type(exc).__name__
236
+ error = "invalid_gateway" if name == "ValidationError" else "module_failed"
237
+ raise Refusal(
238
+ error,
239
+ f"importing {module!r} failed: {name}: {exc}",
240
+ "Fix the Gateway declaration, then plan again." if error == "invalid_gateway"
241
+ else "Fix the module: planning executes it, so its imports must be safe to run.",
242
+ ) from None
243
+ try:
244
+ return discover(loaded, namespace)
245
+ except LookupError as exc:
246
+ raise Refusal(
247
+ "gateway_not_found",
248
+ str(exc),
249
+ "Declare one infimal.Gateway in the module, or pass --gateway NAMESPACE to choose one.",
250
+ ) from None
251
+
252
+
253
+ def gateway_declare(request: dict[str, Any]) -> dict[str, Any]:
254
+ """Execute the module and return the declaration, its credentials by variable name, and the apply request.
255
+
256
+ The declaration carries no credential value. `credentials` lists `{name, env}`; the binary reads
257
+ each variable and sends `{"name", "key"}` under `credentials` in the body, refusing before any
258
+ request when one is unset.
259
+ """
260
+ module = request.get("module") or "gateway"
261
+ gateway = _load_gateway(str(module), request.get("gateway") or None)
262
+ wire = gateway.to_wire()
263
+ return {
264
+ "gateway": gateway.namespace,
265
+ "declaration": wire,
266
+ "credentials": wire["credentials"],
267
+ "summary": gateway.summary(),
268
+ "apply": {"method": "PUT", "path": _path("v1", "gateway"), "body": wire},
269
+ }
270
+
271
+
272
+ def gateway_diff(request: dict[str, Any]) -> dict[str, Any]:
273
+ """Diff a declaration against `GET /v1/gateway` without executing anything."""
274
+ from .gateway import GatewayChangeKind, diff_wire, render
275
+
276
+ wire = request.get("declaration")
277
+ if not isinstance(wire, dict) or not isinstance(wire.get("namespace"), str):
278
+ raise Refusal(
279
+ "invalid_request",
280
+ "gateway.diff needs the declaration that gateway.declare returned",
281
+ "Upgrade the infimal binary and the SDK together.",
282
+ )
283
+ live = request.get("live")
284
+ plan = diff_wire(wire, live if isinstance(live, dict) else None)
285
+ apply: dict[str, Any] | None = None
286
+ if plan.kind is not GatewayChangeKind.NOOP:
287
+ apply = {"method": "PUT", "path": _path("v1", "gateway"), "body": wire}
288
+ return {
289
+ "plan": {
290
+ **asdict(plan),
291
+ "kind": plan.kind.value,
292
+ "changes": [asdict(c) for c in plan.changes],
293
+ },
294
+ "lines": render(plan),
295
+ "apply": apply,
296
+ }
297
+
298
+
299
+ OPERATIONS = {
300
+ "declare": declare,
301
+ "diff": diff,
302
+ "gateway.declare": gateway_declare,
303
+ "gateway.diff": gateway_diff,
304
+ }
305
+
306
+
307
+ def run(raw: str) -> tuple[dict[str, Any], int]:
308
+ """One request in, one reply and an exit status out."""
309
+ try:
310
+ request = json.loads(raw)
311
+ except json.JSONDecodeError as exc:
312
+ return _failure("invalid_request", f"the request is not JSON: {exc}"), 2
313
+ if not isinstance(request, dict) or request.get("synth") != VERSION:
314
+ return _failure(
315
+ "version_mismatch",
316
+ f"this SDK speaks synth {VERSION}; the request asked for {request.get('synth') if isinstance(request, dict) else None!r}",
317
+ ), 2
318
+ operation = OPERATIONS.get(str(request.get("op")))
319
+ if operation is None:
320
+ return _failure("invalid_request", f"unknown operation {request.get('op')!r}"), 2
321
+ try:
322
+ reply = operation(request)
323
+ except Refusal as exc:
324
+ return {"synth": VERSION, "ok": False, "error": exc.error, "message": exc.message,
325
+ "next": exc.next}, 1
326
+ return {"synth": VERSION, "ok": True, "op": request["op"], **reply}, 0
327
+
328
+
329
+ def _failure(error: str, message: str) -> dict[str, Any]:
330
+ return {
331
+ "synth": VERSION,
332
+ "ok": False,
333
+ "error": error,
334
+ "message": message,
335
+ "next": "Install the SDK that matches this infimal binary; they are released together.",
336
+ }
337
+
338
+
339
+ def main() -> int:
340
+ reply, status = run(sys.stdin.read())
341
+ sys.stdout.write(json.dumps(reply, default=str) + "\n")
342
+ sys.stdout.flush()
343
+ return status
344
+
345
+
346
+ if __name__ == "__main__":
347
+ raise SystemExit(main())