lightning-sdk 0.1.58__py3-none-any.whl → 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.
- lightning_sdk/__init__.py +5 -3
- lightning_sdk/api/deployment_api.py +23 -11
- lightning_sdk/api/job_api.py +42 -7
- lightning_sdk/api/lit_container_api.py +23 -3
- lightning_sdk/api/mmt_api.py +46 -8
- lightning_sdk/api/pipeline_api.py +50 -0
- lightning_sdk/api/teamspace_api.py +2 -2
- lightning_sdk/api/utils.py +15 -5
- lightning_sdk/cli/ai_hub.py +30 -65
- lightning_sdk/cli/coloring.py +60 -0
- lightning_sdk/cli/configure.py +25 -40
- lightning_sdk/cli/connect.py +7 -20
- lightning_sdk/cli/create.py +83 -0
- lightning_sdk/cli/delete.py +72 -75
- lightning_sdk/cli/docker.py +22 -0
- lightning_sdk/cli/download.py +78 -113
- lightning_sdk/cli/entrypoint.py +44 -65
- lightning_sdk/cli/generate.py +28 -43
- lightning_sdk/cli/inspect.py +22 -50
- lightning_sdk/cli/list.py +281 -222
- lightning_sdk/cli/mmts_menu.py +1 -1
- lightning_sdk/cli/open.py +62 -0
- lightning_sdk/cli/run.py +430 -263
- lightning_sdk/cli/serve.py +128 -191
- lightning_sdk/cli/start.py +55 -36
- lightning_sdk/cli/stop.py +97 -55
- lightning_sdk/cli/switch.py +53 -36
- lightning_sdk/cli/upload.py +318 -255
- lightning_sdk/deployment/__init__.py +2 -0
- lightning_sdk/deployment/deployment.py +33 -8
- lightning_sdk/lightning_cloud/openapi/__init__.py +23 -0
- lightning_sdk/lightning_cloud/openapi/api/__init__.py +1 -0
- lightning_sdk/lightning_cloud/openapi/api/assistants_service_api.py +10 -6
- lightning_sdk/lightning_cloud/openapi/api/jobs_service_api.py +355 -4
- lightning_sdk/lightning_cloud/openapi/api/lit_logger_service_api.py +4 -4
- lightning_sdk/lightning_cloud/openapi/api/lit_registry_service_api.py +14 -2
- lightning_sdk/lightning_cloud/openapi/api/pipelines_service_api.py +674 -0
- lightning_sdk/lightning_cloud/openapi/api/storage_service_api.py +303 -4
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +22 -0
- lightning_sdk/lightning_cloud/openapi/models/agents_id_body.py +17 -69
- lightning_sdk/lightning_cloud/openapi/models/cluster_id_capacityreservations_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/create.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/create_deployment_request_defines_a_spec_for_the_job_that_allows_for_autoscaling_jobs.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/deployments_id_body.py +105 -1
- lightning_sdk/lightning_cloud/openapi/models/id_visibility_body1.py +1 -27
- lightning_sdk/lightning_cloud/openapi/models/id_visibility_body2.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/org_id_memberships_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +157 -1
- lightning_sdk/lightning_cloud/openapi/models/pipelines_id_body.py +461 -0
- lightning_sdk/lightning_cloud/openapi/models/project_id_pipelines_body.py +227 -0
- lightning_sdk/lightning_cloud/openapi/models/projects_id_body.py +157 -1
- lightning_sdk/lightning_cloud/openapi/models/slurm_jobs_body.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/uploads_upload_id_body.py +1 -27
- lightning_sdk/lightning_cloud/openapi/models/uploads_upload_id_body1.py +175 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_agent_job.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_assistant.py +17 -69
- lightning_sdk/lightning_cloud/openapi/models/v1_capacity_block_offering.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_artifact_event_type.py +1 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_accelerator.py +131 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_capacity_reservation.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_security_options.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_complete_upload_temporary_artifact_request.py +175 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_create_deployment_request.py +461 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_create_deployment_template_request.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_create_job_request.py +201 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_create_managed_endpoint_response.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_create_multi_machine_job_request.py +253 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_data_connection.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_delete_pipeline_response.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment.py +105 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment_details.py +175 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment_template.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_filestore_data_connection.py +201 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_filesystem_job.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_filesystem_mmt.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_find_capacity_block_offering_response.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_job.py +133 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_job_artifacts_type.py +103 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_job_spec.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_job_timing.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_list_pipelines_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_lit_registry_artifact.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_lit_repository.py +29 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_managed_model.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_multi_machine_job.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_multi_machine_job_state.py +2 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +209 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_pipeline.py +513 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_schedule.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_step.py +253 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_step_status.py +331 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_step_type.py +104 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_project_settings.py +157 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_restart_timing.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_rule_resource.py +1 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_shared_filesystem.py +201 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_slurm_job.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_update_job_visibility_response.py +97 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_upload_temporary_artifact_request.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +95 -355
- lightning_sdk/lightning_cloud/openapi/models/validate.py +27 -1
- lightning_sdk/lightning_cloud/rest_client.py +4 -2
- lightning_sdk/machine.py +25 -1
- lightning_sdk/models.py +18 -12
- lightning_sdk/pipeline/__init__.py +4 -0
- lightning_sdk/pipeline/pipeline.py +109 -0
- lightning_sdk/pipeline/types.py +268 -0
- lightning_sdk/pipeline/utils.py +69 -0
- lightning_sdk/plugin.py +9 -10
- lightning_sdk/serve.py +134 -0
- lightning_sdk/services/utilities.py +2 -2
- lightning_sdk/studio.py +5 -1
- lightning_sdk/teamspace.py +1 -1
- lightning_sdk/utils/resolve.py +12 -1
- {lightning_sdk-0.1.58.dist-info → lightning_sdk-0.2.1.dist-info}/METADATA +6 -8
- {lightning_sdk-0.1.58.dist-info → lightning_sdk-0.2.1.dist-info}/RECORD +120 -88
- lightning_sdk/cli/legacy.py +0 -135
- {lightning_sdk-0.1.58.dist-info → lightning_sdk-0.2.1.dist-info}/LICENSE +0 -0
- {lightning_sdk-0.1.58.dist-info → lightning_sdk-0.2.1.dist-info}/WHEEL +0 -0
- {lightning_sdk-0.1.58.dist-info → lightning_sdk-0.2.1.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-0.1.58.dist-info → lightning_sdk-0.2.1.dist-info}/top_level.txt +0 -0
lightning_sdk/cli/serve.py
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import subprocess
|
|
3
|
-
import warnings
|
|
4
2
|
from pathlib import Path
|
|
5
3
|
from typing import Optional, Union
|
|
6
4
|
|
|
5
|
+
import click
|
|
7
6
|
import docker
|
|
8
7
|
from rich.console import Console
|
|
9
8
|
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
|
|
10
9
|
from rich.prompt import Confirm
|
|
11
10
|
|
|
11
|
+
from lightning_sdk.api.lit_container_api import LitContainerApi
|
|
12
|
+
from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
|
|
13
|
+
from lightning_sdk.serve import _LitServeDeployer
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
|
|
16
|
+
@click.group("serve")
|
|
17
|
+
def serve() -> None:
|
|
14
18
|
"""Serve a LitServe model.
|
|
15
19
|
|
|
16
20
|
Example:
|
|
17
21
|
lightning serve api server.py # serve locally
|
|
22
|
+
|
|
23
|
+
Example:
|
|
18
24
|
lightning serve api server.py --cloud # deploy to the cloud
|
|
19
25
|
|
|
20
26
|
You can deploy the API to the cloud by running `lightning serve api server.py --cloud`.
|
|
@@ -22,192 +28,123 @@ class _LitServe:
|
|
|
22
28
|
Deploying to the cloud requires pre-login to the docker registry.
|
|
23
29
|
"""
|
|
24
30
|
|
|
25
|
-
def api(
|
|
26
|
-
self,
|
|
27
|
-
script_path: Union[str, Path],
|
|
28
|
-
easy: bool = False,
|
|
29
|
-
cloud: bool = False,
|
|
30
|
-
repository: Optional[str] = None,
|
|
31
|
-
non_interactive: bool = False,
|
|
32
|
-
) -> None:
|
|
33
|
-
"""Deploy a LitServe model script.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
script_path: Path to the script to serve
|
|
37
|
-
easy: If True, generates a client for the model
|
|
38
|
-
cloud: If True, deploy the model to the Lightning Studio
|
|
39
|
-
repository: Optional Docker repository name (e.g., 'username/model-name')
|
|
40
|
-
non_interactive: If True, do not prompt for confirmation
|
|
41
|
-
Raises:
|
|
42
|
-
FileNotFoundError: If script_path doesn't exist
|
|
43
|
-
ImportError: If litserve is not installed
|
|
44
|
-
subprocess.CalledProcessError: If the script fails to run
|
|
45
|
-
IOError: If client.py generation fails
|
|
46
|
-
"""
|
|
47
|
-
console = Console()
|
|
48
|
-
script_path = Path(script_path)
|
|
49
|
-
if not script_path.exists():
|
|
50
|
-
raise FileNotFoundError(f"Script not found: {script_path}")
|
|
51
|
-
if not script_path.is_file():
|
|
52
|
-
raise ValueError(f"Path is not a file: {script_path}")
|
|
53
|
-
|
|
54
|
-
try:
|
|
55
|
-
from litserve.python_client import client_template
|
|
56
|
-
except ImportError:
|
|
57
|
-
raise ImportError(
|
|
58
|
-
"litserve is not installed. Please install it with `pip install lightning_sdk[serve]`"
|
|
59
|
-
) from None
|
|
60
|
-
|
|
61
|
-
if easy:
|
|
62
|
-
client_path = Path("client.py")
|
|
63
|
-
if client_path.exists():
|
|
64
|
-
console.print("Skipping client generation: client.py already exists", style="blue")
|
|
65
|
-
else:
|
|
66
|
-
try:
|
|
67
|
-
client_path.write_text(client_template)
|
|
68
|
-
console.print("✅ Client generated at client.py", style="bold green")
|
|
69
|
-
except OSError as e:
|
|
70
|
-
raise OSError(f"Failed to generate client.py: {e!s}") from None
|
|
71
|
-
|
|
72
|
-
if cloud:
|
|
73
|
-
tag = repository if repository else "litserve-model"
|
|
74
|
-
return self._handle_cloud(script_path, console, tag=tag, non_interactive=non_interactive)
|
|
75
|
-
|
|
76
|
-
try:
|
|
77
|
-
subprocess.run(
|
|
78
|
-
["python", str(script_path)],
|
|
79
|
-
check=True,
|
|
80
|
-
text=True,
|
|
81
|
-
)
|
|
82
|
-
except subprocess.CalledProcessError as e:
|
|
83
|
-
error_msg = f"Script execution failed with exit code {e.returncode}\nstdout: {e.stdout}\nstderr: {e.stderr}"
|
|
84
|
-
raise RuntimeError(error_msg) from None
|
|
85
|
-
|
|
86
|
-
def _handle_cloud(
|
|
87
|
-
self,
|
|
88
|
-
script_path: Union[str, Path],
|
|
89
|
-
console: Console,
|
|
90
|
-
tag: str = "litserve-model",
|
|
91
|
-
non_interactive: bool = False,
|
|
92
|
-
) -> None:
|
|
93
|
-
try:
|
|
94
|
-
client = docker.from_env()
|
|
95
|
-
client.ping()
|
|
96
|
-
except docker.errors.DockerException as e:
|
|
97
|
-
raise RuntimeError(f"Failed to connect to Docker daemon: {e!s}. Is Docker running?") from None
|
|
98
|
-
|
|
99
|
-
dockerizer = _Docker()
|
|
100
|
-
path = dockerizer.api(script_path, port=8000, gpu=False, tag=tag)
|
|
101
|
-
|
|
102
|
-
console.clear()
|
|
103
|
-
if non_interactive:
|
|
104
|
-
console.print("[italic]non-interactive[/italic] mode enabled, skipping confirmation prompts", style="blue")
|
|
105
|
-
|
|
106
|
-
console.print(f"\nPlease review the Dockerfile at [u]{path}[/u] and make sure it is correct.", style="bold")
|
|
107
|
-
correct_dockerfile = True if non_interactive else Confirm.ask("Is the Dockerfile correct?", default=True)
|
|
108
|
-
if not correct_dockerfile:
|
|
109
|
-
console.print("Please fix the Dockerfile and try again.", style="red")
|
|
110
|
-
return
|
|
111
|
-
|
|
112
|
-
with Progress(
|
|
113
|
-
SpinnerColumn(),
|
|
114
|
-
TextColumn("[progress.description]{task.description}"),
|
|
115
|
-
TimeElapsedColumn(),
|
|
116
|
-
console=console,
|
|
117
|
-
transient=False,
|
|
118
|
-
) as progress:
|
|
119
|
-
build_task = progress.add_task("Building Docker image", total=None)
|
|
120
|
-
build_status = client.api.build(
|
|
121
|
-
path=os.path.dirname(path), dockerfile=path, tag=tag, decode=True, quiet=False
|
|
122
|
-
)
|
|
123
|
-
for line in build_status:
|
|
124
|
-
if "error" in line:
|
|
125
|
-
progress.stop()
|
|
126
|
-
console.print(f"\n[red]{line}[/red]")
|
|
127
|
-
return
|
|
128
|
-
if "stream" in line and line["stream"].strip():
|
|
129
|
-
console.print(line["stream"].strip(), style="bright_black")
|
|
130
|
-
progress.update(build_task, description="Building Docker image")
|
|
131
|
-
|
|
132
|
-
progress.update(build_task, description="[green]Build completed![/green]")
|
|
133
|
-
|
|
134
|
-
push_task = progress.add_task("Pushing to registry", total=None)
|
|
135
|
-
console.print("\nPushing image...", style="bold blue")
|
|
136
|
-
push_status = client.api.push(tag, stream=True, decode=True)
|
|
137
|
-
for line in push_status:
|
|
138
|
-
if "error" in line:
|
|
139
|
-
progress.stop()
|
|
140
|
-
console.print(f"\n[red]{line}[/red]")
|
|
141
|
-
return
|
|
142
|
-
if "status" in line:
|
|
143
|
-
console.print(line["status"], style="bright_black")
|
|
144
|
-
progress.update(push_task, description="Pushing to registry")
|
|
145
|
-
|
|
146
|
-
progress.update(push_task, description="[green]Push completed![/green]")
|
|
147
|
-
|
|
148
|
-
console.print(f"\n✅ Image pushed to {tag}", style="bold green")
|
|
149
|
-
console.print(
|
|
150
|
-
"Soon you will be able to deploy this model to the Lightning Studio!",
|
|
151
|
-
)
|
|
152
|
-
# TODO: Deploy to the cloud
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
class _Docker:
|
|
156
|
-
"""Generate a Dockerfile for a LitServe model."""
|
|
157
|
-
|
|
158
|
-
def api(self, server_filename: str, port: int = 8000, gpu: bool = False, tag: str = "litserve-model") -> str:
|
|
159
|
-
"""Generate a Dockerfile for the given server code.
|
|
160
|
-
|
|
161
|
-
Args:
|
|
162
|
-
server_filename: The path to the server file. Example sever.py or app.py.
|
|
163
|
-
port: The port to expose in the Docker container.
|
|
164
|
-
gpu: Whether to use a GPU-enabled Docker image.
|
|
165
|
-
tag: Docker image tag to use in examples.
|
|
166
|
-
"""
|
|
167
|
-
import litserve as ls
|
|
168
|
-
from litserve import docker_builder
|
|
169
|
-
|
|
170
|
-
requirements = ""
|
|
171
|
-
if os.path.exists("requirements.txt"):
|
|
172
|
-
requirements = "-r requirements.txt"
|
|
173
|
-
else:
|
|
174
|
-
warnings.warn(
|
|
175
|
-
f"requirements.txt not found at {os.getcwd()}. "
|
|
176
|
-
f"Make sure to install the required packages in the Dockerfile.",
|
|
177
|
-
UserWarning,
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
current_dir = Path.cwd()
|
|
181
|
-
if not (current_dir / server_filename).is_file():
|
|
182
|
-
raise FileNotFoundError(f"Server file `{server_filename}` must be in the current directory: {os.getcwd()}")
|
|
183
|
-
|
|
184
|
-
version = ls.__version__
|
|
185
|
-
if gpu:
|
|
186
|
-
run_cmd = f"docker run --gpus all -p {port}:{port} {tag}:latest"
|
|
187
|
-
docker_template = docker_builder.CUDA_DOCKER_TEMPLATE
|
|
188
|
-
else:
|
|
189
|
-
run_cmd = f"docker run -p {port}:{port} {tag}:latest"
|
|
190
|
-
docker_template = docker_builder.DOCKERFILE_TEMPLATE
|
|
191
|
-
dockerfile_content = docker_template.format(
|
|
192
|
-
server_filename=server_filename,
|
|
193
|
-
port=port,
|
|
194
|
-
version=version,
|
|
195
|
-
requirements=requirements,
|
|
196
|
-
)
|
|
197
|
-
with open("Dockerfile", "w") as f:
|
|
198
|
-
f.write(dockerfile_content)
|
|
199
31
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
32
|
+
@serve.command("api")
|
|
33
|
+
@click.argument("script-path", type=click.Path(exists=True))
|
|
34
|
+
@click.option(
|
|
35
|
+
"--easy",
|
|
36
|
+
is_flag=True,
|
|
37
|
+
default=False,
|
|
38
|
+
flag_value=True,
|
|
39
|
+
help="Generate a client for the model",
|
|
40
|
+
)
|
|
41
|
+
@click.option(
|
|
42
|
+
"--cloud",
|
|
43
|
+
is_flag=True,
|
|
44
|
+
default=False,
|
|
45
|
+
flag_value=True,
|
|
46
|
+
help="Deploy the model to the Lightning AI platform",
|
|
47
|
+
)
|
|
48
|
+
@click.option("--gpu", is_flag=True, default=False, flag_value=True, help="Use GPU for serving")
|
|
49
|
+
@click.option("--repository", default=None, help="Docker repository name (e.g., 'username/model-name')")
|
|
50
|
+
@click.option(
|
|
51
|
+
"--non-interactive",
|
|
52
|
+
"--non_interactive",
|
|
53
|
+
is_flag=True,
|
|
54
|
+
default=False,
|
|
55
|
+
flag_value=True,
|
|
56
|
+
help="Do not prompt for confirmation",
|
|
57
|
+
)
|
|
58
|
+
def api(
|
|
59
|
+
script_path: str,
|
|
60
|
+
easy: bool,
|
|
61
|
+
cloud: bool,
|
|
62
|
+
gpu: bool,
|
|
63
|
+
repository: str,
|
|
64
|
+
non_interactive: bool,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Deploy a LitServe model script."""
|
|
67
|
+
return api_impl(
|
|
68
|
+
script_path=script_path, easy=easy, cloud=cloud, gpu=gpu, repository=repository, non_interactive=non_interactive
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def api_impl(
|
|
73
|
+
script_path: Union[str, Path],
|
|
74
|
+
easy: bool = False,
|
|
75
|
+
cloud: bool = False,
|
|
76
|
+
gpu: bool = False,
|
|
77
|
+
repository: Optional[str] = None,
|
|
78
|
+
non_interactive: bool = False,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Deploy a LitServe model script."""
|
|
81
|
+
console = Console()
|
|
82
|
+
script_path = Path(script_path)
|
|
83
|
+
if not script_path.exists():
|
|
84
|
+
raise FileNotFoundError(f"Script not found: {script_path}")
|
|
85
|
+
if not script_path.is_file():
|
|
86
|
+
raise ValueError(f"Path is not a file: {script_path}")
|
|
87
|
+
|
|
88
|
+
ls_deployer = _LitServeDeployer()
|
|
89
|
+
ls_deployer.generate_client() if easy else None
|
|
90
|
+
|
|
91
|
+
if cloud:
|
|
92
|
+
tag = repository if repository else "litserve-model"
|
|
93
|
+
return _handle_cloud(script_path, console, gpu=gpu, tag=tag, non_interactive=non_interactive)
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
subprocess.run(
|
|
97
|
+
["python", str(script_path)],
|
|
98
|
+
check=True,
|
|
99
|
+
text=True,
|
|
100
|
+
)
|
|
101
|
+
except subprocess.CalledProcessError as e:
|
|
102
|
+
error_msg = f"Script execution failed with exit code {e.returncode}\nstdout: {e.stdout}\nstderr: {e.stderr}"
|
|
103
|
+
raise RuntimeError(error_msg) from None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _handle_cloud(
|
|
107
|
+
script_path: Union[str, Path],
|
|
108
|
+
console: Console,
|
|
109
|
+
gpu: bool,
|
|
110
|
+
repository: str = "litserve-model",
|
|
111
|
+
tag: Optional[str] = None,
|
|
112
|
+
teamspace: Optional[str] = None,
|
|
113
|
+
non_interactive: bool = False,
|
|
114
|
+
) -> None:
|
|
115
|
+
try:
|
|
116
|
+
client = docker.from_env()
|
|
117
|
+
client.ping()
|
|
118
|
+
except docker.errors.DockerException as e:
|
|
119
|
+
raise RuntimeError(f"Failed to connect to Docker daemon: {e!s}. Is Docker running?") from None
|
|
120
|
+
|
|
121
|
+
ls_deployer = _LitServeDeployer()
|
|
122
|
+
path = ls_deployer.dockerize_api(script_path, port=8000, gpu=gpu, tag=tag)
|
|
123
|
+
console.clear()
|
|
124
|
+
if non_interactive:
|
|
125
|
+
console.print("[italic]non-interactive[/italic] mode enabled, skipping confirmation prompts", style="blue")
|
|
126
|
+
|
|
127
|
+
console.print(f"\nPlease review the Dockerfile at [u]{path}[/u] and make sure it is correct.", style="bold")
|
|
128
|
+
correct_dockerfile = True if non_interactive else Confirm.ask("Is the Dockerfile correct?", default=True)
|
|
129
|
+
if not correct_dockerfile:
|
|
130
|
+
console.print("Please fix the Dockerfile and try again.", style="red")
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
tag = tag if tag else "latest"
|
|
134
|
+
|
|
135
|
+
lit_cr = LitContainerApi()
|
|
136
|
+
menu = _TeamspacesMenu()
|
|
137
|
+
teamspace = menu._resolve_teamspace(teamspace)
|
|
138
|
+
with Progress(
|
|
139
|
+
SpinnerColumn(),
|
|
140
|
+
TextColumn("[progress.description]{task.description}"),
|
|
141
|
+
TimeElapsedColumn(),
|
|
142
|
+
console=console,
|
|
143
|
+
transient=False,
|
|
144
|
+
) as progress:
|
|
145
|
+
ls_deployer._build_container(path, repository, tag, console, progress)
|
|
146
|
+
ls_deployer._push_container(repository, tag, teamspace, lit_cr, progress)
|
|
147
|
+
console.print(f"\n✅ Image pushed to {tag}", style="bold green")
|
|
148
|
+
console.print(
|
|
149
|
+
"Soon you will be able to deploy this model to the Lightning Studio!",
|
|
150
|
+
)
|
lightning_sdk/cli/start.py
CHANGED
|
@@ -1,43 +1,62 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
|
+
import click
|
|
4
|
+
|
|
3
5
|
from lightning_sdk import Machine, Studio
|
|
6
|
+
from lightning_sdk.lightning_cloud.openapi.rest import ApiException
|
|
7
|
+
|
|
8
|
+
_MACHINE_VALUES = tuple([machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine)])
|
|
4
9
|
|
|
5
10
|
|
|
6
|
-
|
|
11
|
+
@click.group("start")
|
|
12
|
+
def start() -> None:
|
|
7
13
|
"""Start resources on the Lightning AI platform."""
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
15
|
+
|
|
16
|
+
@start.command("studio")
|
|
17
|
+
@click.argument(
|
|
18
|
+
"name",
|
|
19
|
+
)
|
|
20
|
+
@click.option(
|
|
21
|
+
"--teamspace",
|
|
22
|
+
default=None,
|
|
23
|
+
help=(
|
|
24
|
+
"The teamspace the studio is part of. "
|
|
25
|
+
"Should be of format <OWNER>/<TEAMSPACE_NAME>. "
|
|
26
|
+
"If not specified, tries to infer from the environment (e.g. when run from within a Studio.)"
|
|
27
|
+
),
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--machine",
|
|
31
|
+
default="CPU",
|
|
32
|
+
show_default=True,
|
|
33
|
+
type=click.Choice(_MACHINE_VALUES),
|
|
34
|
+
help="The machine type to start the studio on.",
|
|
35
|
+
)
|
|
36
|
+
def studio(name: str, teamspace: Optional[str] = None, machine: str = "CPU") -> None:
|
|
37
|
+
"""Start a studio on a given machine.
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
lightning start studio NAME
|
|
41
|
+
|
|
42
|
+
NAME: the name of the studio to start
|
|
43
|
+
"""
|
|
44
|
+
if teamspace is not None:
|
|
45
|
+
ts_splits = teamspace.split("/")
|
|
46
|
+
if len(ts_splits) != 2:
|
|
47
|
+
raise ValueError(f"Teamspace should be of format <OWNER>/<TEAMSPACE_NAME> but got {teamspace}")
|
|
48
|
+
owner, teamspace = ts_splits
|
|
49
|
+
else:
|
|
50
|
+
owner, teamspace = None, None
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
studio = Studio(name=name, teamspace=teamspace, org=owner, user=None, create_ok=False)
|
|
54
|
+
except (RuntimeError, ValueError, ApiException):
|
|
55
|
+
studio = Studio(name=name, teamspace=teamspace, org=None, user=owner, create_ok=False)
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
resolved_machine = getattr(Machine, machine.upper(), Machine(machine, machine))
|
|
59
|
+
except KeyError:
|
|
60
|
+
resolved_machine = machine
|
|
61
|
+
|
|
62
|
+
studio.start(resolved_machine)
|
lightning_sdk/cli/stop.py
CHANGED
|
@@ -1,65 +1,107 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
|
+
import click
|
|
3
4
|
from rich.console import Console
|
|
4
5
|
|
|
5
6
|
from lightning_sdk.cli.job_and_mmt_action import _JobAndMMTAction
|
|
7
|
+
from lightning_sdk.lightning_cloud.openapi.rest import ApiException
|
|
6
8
|
from lightning_sdk.studio import Studio
|
|
7
9
|
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
@click.group("stop")
|
|
12
|
+
def stop() -> None:
|
|
10
13
|
"""Stop resources on the Lightning AI platform."""
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
""
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
15
|
+
|
|
16
|
+
@stop.command("job")
|
|
17
|
+
@click.argument(
|
|
18
|
+
"name",
|
|
19
|
+
)
|
|
20
|
+
@click.option(
|
|
21
|
+
"--teamspace",
|
|
22
|
+
default=None,
|
|
23
|
+
help=(
|
|
24
|
+
"the name of the teamspace the job lives in. "
|
|
25
|
+
"Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace). "
|
|
26
|
+
"If not specified can be selected interactively."
|
|
27
|
+
),
|
|
28
|
+
)
|
|
29
|
+
def job(name: str, teamspace: Optional[str] = None) -> None:
|
|
30
|
+
"""Stop a job.
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
lightning stop job NAME
|
|
34
|
+
|
|
35
|
+
NAME: the name of the job to stop.
|
|
36
|
+
"""
|
|
37
|
+
menu = _JobAndMMTAction()
|
|
38
|
+
job = menu.job(name=name, teamspace=teamspace)
|
|
39
|
+
|
|
40
|
+
job.stop()
|
|
41
|
+
Console().print(f"Successfully stopped {job.name}!")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@stop.command("mmt")
|
|
45
|
+
@click.argument(
|
|
46
|
+
"name",
|
|
47
|
+
)
|
|
48
|
+
@click.option(
|
|
49
|
+
"--teamspace",
|
|
50
|
+
default=None,
|
|
51
|
+
help=(
|
|
52
|
+
"the name of the teamspace the multi-machine job lives in. "
|
|
53
|
+
"Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace). "
|
|
54
|
+
"If not specified can be selected interactively."
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
def mmt(name: str, teamspace: Optional[str] = None) -> None:
|
|
58
|
+
"""Stop a multi-machine job.
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
lightning stop mmt NAME
|
|
62
|
+
|
|
63
|
+
NAME: the name of the multi-machine job to stop.
|
|
64
|
+
"""
|
|
65
|
+
menu = _JobAndMMTAction()
|
|
66
|
+
mmt = menu.mmt(name=name, teamspace=teamspace)
|
|
67
|
+
|
|
68
|
+
mmt.stop()
|
|
69
|
+
Console().print(f"Successfully stopped {mmt.name}!")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@stop.command("studio")
|
|
73
|
+
@click.argument(
|
|
74
|
+
"name",
|
|
75
|
+
)
|
|
76
|
+
@click.option(
|
|
77
|
+
"--teamspace",
|
|
78
|
+
default=None,
|
|
79
|
+
help=(
|
|
80
|
+
"the name of the teamspace the studio lives in. "
|
|
81
|
+
"Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace). "
|
|
82
|
+
"If not specified can be selected interactively."
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
def studio(name: str, teamspace: Optional[str] = None) -> None:
|
|
86
|
+
"""Stop a running studio.
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
lightning stop studio NAME
|
|
90
|
+
|
|
91
|
+
NAME: the name of the studio to stop.
|
|
92
|
+
"""
|
|
93
|
+
if teamspace is not None:
|
|
94
|
+
ts_splits = teamspace.split("/")
|
|
95
|
+
if len(ts_splits) != 2:
|
|
96
|
+
raise ValueError(f"Teamspace should be of format <OWNER>/<TEAMSPACE_NAME> but got {teamspace}")
|
|
97
|
+
owner, teamspace = ts_splits
|
|
98
|
+
else:
|
|
99
|
+
owner, teamspace = None, None
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
studio = Studio(name=name, teamspace=teamspace, org=owner, user=None, create_ok=False)
|
|
103
|
+
except (RuntimeError, ValueError, ApiException):
|
|
104
|
+
studio = Studio(name=name, teamspace=teamspace, org=None, user=owner, create_ok=False)
|
|
105
|
+
|
|
106
|
+
studio.stop()
|
|
107
|
+
Console().print("Studio successfully stopped")
|