lyceum-cli 1.2.2__tar.gz → 1.2.3__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.
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/PKG-INFO +1 -1
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/_version.py +1 -1
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/inference/claude_code.py +1 -1
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/inference/infer.py +53 -10
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum_cli.egg-info/PKG-INFO +1 -1
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/pyproject.toml +5 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/tests/test_dedicated_deploy.py +26 -2
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/README.md +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/__main__.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/__init__.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/auth/__init__.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/auth/environment.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/auth/keys.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/auth/login.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/__init__.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/__init__.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/config.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/docker.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/docker_compose.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/gpu_selection.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/jobs.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/notebook.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/execution/python.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/inference/__init__.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/compute/inference/chat.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/orgs/__init__.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/orgs/management.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/storage/__init__.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/storage/block.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/storage/files.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/vms/__init__.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/external/vms/management.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/main.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/__init__.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/config.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/http.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/imports.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/orgs.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/run_options.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/streaming.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum/shared/ui.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum_cli.egg-info/SOURCES.txt +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum_cli.egg-info/dependency_links.txt +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum_cli.egg-info/entry_points.txt +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum_cli.egg-info/requires.txt +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/lyceum_cli.egg-info/top_level.txt +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/setup.cfg +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/tests/test_block_storage.py +0 -0
- {lyceum_cli-1.2.2 → lyceum_cli-1.2.3}/tests/test_orgs.py +0 -0
|
@@ -19,7 +19,7 @@ console = Console()
|
|
|
19
19
|
# or ~/.claude settings on this machine.
|
|
20
20
|
CLAUDE_PROFILE_DIR = Path.home() / ".lyceum" / "claude"
|
|
21
21
|
|
|
22
|
-
DEFAULT_MODEL = "moonshotai/kimi-k3"
|
|
22
|
+
DEFAULT_MODEL = "moonshotai/kimi-k3.1-pro"
|
|
23
23
|
DEFAULT_FAST_MODEL = "z-ai/glm-5.3-flash"
|
|
24
24
|
|
|
25
25
|
# Use explicit Lyceum IDs so the backend never has to guess a substitute.
|
|
@@ -74,7 +74,7 @@ def _is_dedicated_inference_hidden() -> bool:
|
|
|
74
74
|
|
|
75
75
|
def _fetch_dedicated_models() -> list[dict]:
|
|
76
76
|
"""Serverless models a dedicated deployment may run, on their default GPU shape."""
|
|
77
|
-
return api_request("GET", "/
|
|
77
|
+
return api_request("GET", "/external/inference/models", timeout=20.0).get("models", [])
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
def _format_gpus(gpu_type: str, gpu_count: int) -> str:
|
|
@@ -181,14 +181,27 @@ def deploy_model(
|
|
|
181
181
|
target_latency: float = typer.Option(None, "--target-latency", hidden=True),
|
|
182
182
|
stabilisation: int = typer.Option(None, "--stabilisation", hidden=True),
|
|
183
183
|
gpu_count: int = typer.Option(
|
|
184
|
-
None,
|
|
184
|
+
None,
|
|
185
|
+
"--gpu-count",
|
|
186
|
+
help="GPUs per replica, defaults per model (also sets --tensor-parallel-size)",
|
|
187
|
+
),
|
|
188
|
+
offload: bool = typer.Option(
|
|
189
|
+
None,
|
|
190
|
+
"--offload/--no-offload",
|
|
191
|
+
help=(
|
|
192
|
+
"Serve requests the deployment cannot from Lyceum's serverless capacity (default off). That may include a "
|
|
193
|
+
"third-party provider under Lyceum's data terms unless your org is restricted to Lyceum's own"
|
|
194
|
+
),
|
|
185
195
|
),
|
|
186
196
|
yes: bool = typer.Option(False, "--yes", "-y", help="Do not ask before creating"),
|
|
187
197
|
wait: bool = typer.Option(False, "--wait", "-w", help="Wait until deployment has healthy replicas"),
|
|
188
198
|
):
|
|
189
199
|
"""Deploy a serverless model on dedicated GPUs."""
|
|
190
200
|
models = _fetch_dedicated_models()
|
|
191
|
-
model = next(
|
|
201
|
+
model = next(
|
|
202
|
+
(m for m in models if model_id.lower() in (m["serving_name"].lower(), m["hf_model_id"].lower())),
|
|
203
|
+
None,
|
|
204
|
+
)
|
|
192
205
|
if model is None:
|
|
193
206
|
console.print(f"[red]{model_id} cannot be deployed on dedicated GPUs.[/red]")
|
|
194
207
|
console.print("[dim]See the models that can: lyceum infer models --dedicated[/dim]")
|
|
@@ -222,7 +235,10 @@ def deploy_model(
|
|
|
222
235
|
raise typer.Exit(0)
|
|
223
236
|
|
|
224
237
|
if hf_token is None and not yes and not (os.getenv("HF_TOKEN") or os.getenv("HUGGING_FACE_HUB_TOKEN")):
|
|
225
|
-
if typer.confirm(
|
|
238
|
+
if typer.confirm(
|
|
239
|
+
"No HF token detected. Provide one (required for gated models)?",
|
|
240
|
+
default=False,
|
|
241
|
+
):
|
|
226
242
|
hf_token = typer.prompt("HuggingFace Token", hide_input=True)
|
|
227
243
|
|
|
228
244
|
payload = {
|
|
@@ -233,6 +249,7 @@ def deploy_model(
|
|
|
233
249
|
overrides = {
|
|
234
250
|
"gpu_count": gpu_count,
|
|
235
251
|
"gpu_type": gpu_type,
|
|
252
|
+
"offload": offload,
|
|
236
253
|
"stabilisation_window": stabilisation,
|
|
237
254
|
"target_latency_p95_ms": target_latency,
|
|
238
255
|
"target_rps": target_rps,
|
|
@@ -241,7 +258,7 @@ def deploy_model(
|
|
|
241
258
|
if hf_token:
|
|
242
259
|
payload["hf_token"] = hf_token
|
|
243
260
|
|
|
244
|
-
data = api_request("POST", "/
|
|
261
|
+
data = api_request("POST", "/external/inference/create", json=payload)
|
|
245
262
|
deployment_id = data.get("deployment_id", "?")
|
|
246
263
|
|
|
247
264
|
console.print("\n[green]Deployment created![/green]")
|
|
@@ -261,6 +278,30 @@ def deploy_model(
|
|
|
261
278
|
_wait_for_healthy(deployment_id, timeout=model["ready_timeout_s"])
|
|
262
279
|
|
|
263
280
|
|
|
281
|
+
def _offload_label(enabled: bool | None) -> str:
|
|
282
|
+
return "enabled" if enabled else "disabled"
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@infer_app.command("offload", hidden=_is_dedicated_inference_hidden())
|
|
286
|
+
def set_offload(
|
|
287
|
+
deployment_id: str = typer.Argument(..., help="Deployment ID"),
|
|
288
|
+
enabled: bool = typer.Option(..., "--enable/--disable", help="Turn offloading on or off"),
|
|
289
|
+
):
|
|
290
|
+
"""Turn offloading on or off for a deployment.
|
|
291
|
+
|
|
292
|
+
Enabled, requests the deployment cannot serve go to Lyceum's serverless capacity,
|
|
293
|
+
which may include a third-party provider under Lyceum's data terms unless your
|
|
294
|
+
org is restricted to Lyceum's own.
|
|
295
|
+
"""
|
|
296
|
+
data = api_request(
|
|
297
|
+
"POST",
|
|
298
|
+
"/external/inference/offload",
|
|
299
|
+
json={"deployment_id": deployment_id, "offload": enabled},
|
|
300
|
+
)
|
|
301
|
+
console.print(f" Deployment ID: [cyan]{data.get('deployment_id')}[/cyan]")
|
|
302
|
+
console.print(f" Offload: [yellow]{_offload_label(data.get('offload'))}[/yellow]")
|
|
303
|
+
|
|
304
|
+
|
|
264
305
|
@infer_app.command("stop")
|
|
265
306
|
def stop_model(
|
|
266
307
|
deployment_id: str = typer.Argument(..., help="Deployment ID to stop"),
|
|
@@ -268,7 +309,7 @@ def stop_model(
|
|
|
268
309
|
"""Stop a deployed model and all its replicas."""
|
|
269
310
|
console.print(f"[dim]Stopping deployment {deployment_id[:12]}...[/dim]")
|
|
270
311
|
|
|
271
|
-
data = api_request("DELETE", "/
|
|
312
|
+
data = api_request("DELETE", "/external/inference/stop", json={"deployment_id": deployment_id})
|
|
272
313
|
console.print("[green]Deployment stopped![/green]")
|
|
273
314
|
console.print(f" Deployment ID: [cyan]{data.get('deployment_id')}[/cyan]")
|
|
274
315
|
console.print(f" Status: [yellow]{data.get('status')}[/yellow]")
|
|
@@ -283,7 +324,7 @@ def deployment_status(
|
|
|
283
324
|
"""Show detailed status of a deployment."""
|
|
284
325
|
dep = api_request(
|
|
285
326
|
"GET",
|
|
286
|
-
"/
|
|
327
|
+
"/external/inference/get",
|
|
287
328
|
params={"deployment_id": deployment_id, "include_terminated": str(include_terminated).lower()},
|
|
288
329
|
timeout=15.0,
|
|
289
330
|
)
|
|
@@ -307,6 +348,8 @@ def deployment_status(
|
|
|
307
348
|
console.print(f" Target p95: {dep['target_latency_p95_ms']} ms")
|
|
308
349
|
if dep.get("stabilisation_window") is not None:
|
|
309
350
|
console.print(f" Stabilisation: {dep['stabilisation_window']} s")
|
|
351
|
+
if dep.get("offload") is not None:
|
|
352
|
+
console.print(f" Offload: {_offload_label(dep['offload'])}")
|
|
310
353
|
|
|
311
354
|
replicas = dep.get("replicas", [])
|
|
312
355
|
if replicas:
|
|
@@ -359,7 +402,7 @@ def list_deployments(
|
|
|
359
402
|
"""List your dedicated deployments."""
|
|
360
403
|
data = api_request(
|
|
361
404
|
"GET",
|
|
362
|
-
"/
|
|
405
|
+
"/external/inference/list",
|
|
363
406
|
params={"include_terminated": str(include_terminated).lower()},
|
|
364
407
|
timeout=15.0,
|
|
365
408
|
)
|
|
@@ -425,7 +468,7 @@ def replica_logs(
|
|
|
425
468
|
replica_id: str = typer.Argument(..., help="Replica ID"),
|
|
426
469
|
):
|
|
427
470
|
"""Show last container logs for a failed replica."""
|
|
428
|
-
data = api_request("GET", f"/
|
|
471
|
+
data = api_request("GET", f"/external/inference/replica/{replica_id}/logs", timeout=15.0)
|
|
429
472
|
error_logs = data.get("error_logs")
|
|
430
473
|
if not error_logs:
|
|
431
474
|
console.print(f"[yellow]No logs available for replica {replica_id}[/yellow]")
|
|
@@ -445,7 +488,7 @@ def _wait_for_healthy(deployment_id: str, timeout: int = 600, poll_interval: int
|
|
|
445
488
|
while time.time() < deadline:
|
|
446
489
|
try:
|
|
447
490
|
response = httpx.get(
|
|
448
|
-
f"{config.base_url}/
|
|
491
|
+
f"{config.base_url}/external/inference/get",
|
|
449
492
|
headers=headers,
|
|
450
493
|
params={"deployment_id": deployment_id},
|
|
451
494
|
timeout=15.0,
|
|
@@ -29,11 +29,14 @@ def gateway(monkeypatch):
|
|
|
29
29
|
creates = []
|
|
30
30
|
|
|
31
31
|
def api_request(method, path, **kw):
|
|
32
|
-
if path == "/
|
|
32
|
+
if path == "/external/inference/models":
|
|
33
33
|
return {"models": [GLM52, FLASH]}
|
|
34
|
-
if path == "/
|
|
34
|
+
if path == "/external/inference/create":
|
|
35
35
|
creates.append(kw["json"])
|
|
36
36
|
return {"deployment_id": "d1", "status": "pending"}
|
|
37
|
+
if path == "/external/inference/offload":
|
|
38
|
+
creates.append(kw["json"])
|
|
39
|
+
return kw["json"]
|
|
37
40
|
raise AssertionError(path)
|
|
38
41
|
|
|
39
42
|
monkeypatch.setattr(infer, "api_request", api_request)
|
|
@@ -103,3 +106,24 @@ def test_wait_uses_the_model_ready_timeout(gateway, monkeypatch):
|
|
|
103
106
|
monkeypatch.setattr(infer, "_wait_for_healthy", lambda d, timeout: waited.append((d, timeout)))
|
|
104
107
|
assert _run("deploy", "deepseek/deepseek-v4-flash-0731", "--wait").exit_code == 0
|
|
105
108
|
assert waited == [("d1", 4800)]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_deploy_offload_is_sent_only_when_given(gateway):
|
|
112
|
+
assert _run("deploy", "deepseek/deepseek-v4-flash-0731").exit_code == 0
|
|
113
|
+
assert "offload" not in gateway[0]
|
|
114
|
+
assert _run("deploy", "deepseek/deepseek-v4-flash-0731", "--offload").exit_code == 0
|
|
115
|
+
assert gateway[1]["offload"] is True
|
|
116
|
+
assert _run("deploy", "deepseek/deepseek-v4-flash-0731", "--no-offload").exit_code == 0
|
|
117
|
+
assert gateway[2]["offload"] is False
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_offload_command_posts_the_switch(gateway):
|
|
121
|
+
result = _run("offload", "d1", "--enable")
|
|
122
|
+
assert result.exit_code == 0
|
|
123
|
+
assert "enabled" in result.output
|
|
124
|
+
assert _run("offload", "d1", "--disable").exit_code == 0
|
|
125
|
+
assert gateway == [
|
|
126
|
+
{"deployment_id": "d1", "offload": True},
|
|
127
|
+
{"deployment_id": "d1", "offload": False},
|
|
128
|
+
]
|
|
129
|
+
assert _run("offload", "d1").exit_code == 2
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|