lightning-sdk 0.2.3__py3-none-any.whl → 0.2.5__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 +1 -1
- lightning_sdk/ai_hub.py +10 -17
- lightning_sdk/api/ai_hub_api.py +20 -3
- lightning_sdk/api/studio_api.py +0 -8
- lightning_sdk/cli/serve.py +139 -22
- lightning_sdk/deployment/deployment.py +32 -4
- lightning_sdk/lightning_cloud/openapi/__init__.py +49 -1
- lightning_sdk/lightning_cloud/openapi/api/__init__.py +4 -0
- lightning_sdk/lightning_cloud/openapi/api/cloud_space_environment_template_service_api.py +537 -0
- lightning_sdk/lightning_cloud/openapi/api/cluster_service_api.py +10 -6
- lightning_sdk/lightning_cloud/openapi/api/lit_dataset_service_api.py +1973 -0
- lightning_sdk/lightning_cloud/openapi/api/lit_registry_service_api.py +5 -1
- lightning_sdk/lightning_cloud/openapi/api/models_store_api.py +20 -20
- lightning_sdk/lightning_cloud/openapi/api/pipeline_templates_service_api.py +339 -0
- lightning_sdk/lightning_cloud/openapi/api/pipelines_service_api.py +5 -1
- lightning_sdk/lightning_cloud/openapi/api/schedules_service_api.py +573 -0
- lightning_sdk/lightning_cloud/openapi/api/slurm_jobs_user_service_api.py +202 -0
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +45 -1
- lightning_sdk/lightning_cloud/openapi/models/cluster_id_capacityblock_body.py +15 -15
- lightning_sdk/lightning_cloud/openapi/models/cluster_id_slurmusers_body.py +201 -0
- lightning_sdk/lightning_cloud/openapi/models/dataset_id_versions_body.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/dataset_id_visibility_body.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/environmenttemplates_id_body.py +227 -0
- lightning_sdk/lightning_cloud/openapi/models/externalv1_cloud_space_instance_status.py +69 -69
- lightning_sdk/lightning_cloud/openapi/models/litdatasets_dataset_id_body.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/pipelines_id_body.py +69 -17
- lightning_sdk/lightning_cloud/openapi/models/pipelinetemplates_id_body.py +331 -0
- lightning_sdk/lightning_cloud/openapi/models/project_id_litdatasets_body.py +227 -0
- lightning_sdk/lightning_cloud/openapi/models/project_id_pipelines_body.py +17 -17
- lightning_sdk/lightning_cloud/openapi/models/project_id_schedules_body.py +201 -0
- lightning_sdk/lightning_cloud/openapi/models/schedules_id_body.py +383 -0
- lightning_sdk/lightning_cloud/openapi/models/slurm_jobs_body.py +15 -15
- lightning_sdk/lightning_cloud/openapi/models/upload_id_complete_body1.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/upload_id_parts_body1.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_agent_job.py +124 -20
- lightning_sdk/lightning_cloud/openapi/models/v1_assistant_model_status.py +2 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_provider.py +1 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_environment_template.py +253 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_spec.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_complete_lit_dataset_multi_part_upload_response.py +97 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_complete_lit_dataset_upload_response.py +97 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_create_cloud_space_environment_template_request.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_create_lit_dataset_multi_part_upload_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_create_organization_request.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_create_pipeline_template_request.py +383 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_create_project_request.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/{v1_pipeline_schedule.py → v1_delete_cloud_space_environment_template_response.py} +32 -32
- lightning_sdk/lightning_cloud/openapi/models/v1_delete_lit_dataset_response.py +97 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_delete_lit_dataset_version_response.py +97 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_delete_schedule_response.py +175 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_get_cloud_space_size_response.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_get_lit_dataset_file_upload_urls_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_get_lit_dataset_files_url_response.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_get_user_response.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_instance_overprovisioning_spec.py +79 -27
- lightning_sdk/lightning_cloud/openapi/models/v1_job_spec.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_list_cloud_space_environment_templates_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_list_lit_dataset_versions_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_list_lit_datasets_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_list_schedules_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_list_slurm_cluster_users_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_lit_dataset.py +539 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_lit_dataset_file.py +175 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_lit_dataset_version_archive.py +435 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_lit_registry_project.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_lit_repository.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_magic_link_login_request.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_managed_model.py +107 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_pipeline.py +69 -17
- lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_parameter.py +435 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_parameter_placement.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_parameter_placement_type.py +106 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_parameter_type.py +106 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_template.py +513 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_template_visibility_type.py +105 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_project_settings.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_schedule.py +435 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_schedule_resource_type.py +103 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_slurm_cluster_user.py +227 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_slurm_job.py +58 -6
- lightning_sdk/lightning_cloud/openapi/models/v1_slurm_node.py +31 -291
- lightning_sdk/lightning_cloud/openapi/models/v1_update_lit_dataset_visibility_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_update_user_request.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +157 -105
- lightning_sdk/lightning_cloud/openapi/models/v1_validate_managed_endpoint_response.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_voltage_park_direct_v1.py +203 -0
- lightning_sdk/lightning_cloud/openapi/models/version_default_body.py +29 -29
- lightning_sdk/lightning_cloud/openapi/models/version_default_body1.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/version_uploads_body1.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/versions_version_body1.py +123 -0
- lightning_sdk/lightning_cloud/rest_client.py +2 -0
- lightning_sdk/lit_container.py +8 -1
- lightning_sdk/mmt/mmt.py +1 -18
- lightning_sdk/mmt/v1.py +1 -28
- lightning_sdk/models.py +15 -6
- lightning_sdk/pipeline/pipeline.py +2 -2
- lightning_sdk/pipeline/types.py +28 -2
- lightning_sdk/pipeline/utils.py +1 -1
- lightning_sdk/plugin.py +0 -6
- lightning_sdk/serve.py +55 -22
- lightning_sdk/utils/resolve.py +1 -0
- {lightning_sdk-0.2.3.dist-info → lightning_sdk-0.2.5.dist-info}/METADATA +1 -1
- {lightning_sdk-0.2.3.dist-info → lightning_sdk-0.2.5.dist-info}/RECORD +110 -62
- {lightning_sdk-0.2.3.dist-info → lightning_sdk-0.2.5.dist-info}/WHEEL +1 -1
- {lightning_sdk-0.2.3.dist-info → lightning_sdk-0.2.5.dist-info}/LICENSE +0 -0
- {lightning_sdk-0.2.3.dist-info → lightning_sdk-0.2.5.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-0.2.3.dist-info → lightning_sdk-0.2.5.dist-info}/top_level.txt +0 -0
lightning_sdk/__init__.py
CHANGED
lightning_sdk/ai_hub.py
CHANGED
|
@@ -3,6 +3,7 @@ from urllib.parse import quote
|
|
|
3
3
|
|
|
4
4
|
from lightning_sdk.api import AIHubApi
|
|
5
5
|
from lightning_sdk.api.utils import _get_cloud_url
|
|
6
|
+
from lightning_sdk.lightning_cloud.openapi.models import V1Deployment
|
|
6
7
|
from lightning_sdk.user import User
|
|
7
8
|
from lightning_sdk.utils.resolve import _resolve_teamspace
|
|
8
9
|
|
|
@@ -109,7 +110,8 @@ class AIHub:
|
|
|
109
110
|
org: Optional[Union[str, "Organization"]] = None,
|
|
110
111
|
user: Optional[Union[str, "User"]] = None,
|
|
111
112
|
machine: Optional[Union[str, "Machine"]] = None,
|
|
112
|
-
|
|
113
|
+
quantity: Optional[int] = None,
|
|
114
|
+
) -> V1Deployment:
|
|
113
115
|
"""Deploy an API from the AI Hub.
|
|
114
116
|
|
|
115
117
|
Example:
|
|
@@ -126,14 +128,14 @@ class AIHub:
|
|
|
126
128
|
api_arguments: Additional API argument, such as model name, or batch size.
|
|
127
129
|
name: Name for the deployed API. Defaults to None.
|
|
128
130
|
cloud_account: The cloud account where you want to run the template, such as "lightning-public-prod".
|
|
129
|
-
Defaults to None.
|
|
131
|
+
Defaults to None, which will use the cloud account of the teamspace.
|
|
130
132
|
teamspace: The team or group for deployment. Defaults to None.
|
|
131
133
|
org: The organization for deployment. Don't pass user with this. Defaults to None.
|
|
132
134
|
user: The user for deployment. Don't pass org with this. Defaults to None.
|
|
133
135
|
machine: The machine to run the deployment on. Defaults to the first option set in the AI Hub template.
|
|
134
136
|
|
|
135
137
|
Returns:
|
|
136
|
-
A
|
|
138
|
+
A V1Deployment object containing the name of the deployed API,
|
|
137
139
|
the URL to access it, and whether it is interruptible.
|
|
138
140
|
|
|
139
141
|
Raises:
|
|
@@ -157,6 +159,7 @@ class AIHub:
|
|
|
157
159
|
name=name,
|
|
158
160
|
api_arguments=api_arguments,
|
|
159
161
|
machine=machine,
|
|
162
|
+
quantity=quantity,
|
|
160
163
|
)
|
|
161
164
|
|
|
162
165
|
url = (
|
|
@@ -169,16 +172,9 @@ class AIHub:
|
|
|
169
172
|
|
|
170
173
|
print("Deployment available at:", url)
|
|
171
174
|
|
|
172
|
-
return
|
|
173
|
-
"id": deployment.id,
|
|
174
|
-
"name": deployment.name,
|
|
175
|
-
"deployment_url": url,
|
|
176
|
-
"api_endpoint": deployment.status.urls[0],
|
|
177
|
-
"interruptible": deployment.spec.spot,
|
|
178
|
-
"teamspace id": teamspace_id,
|
|
179
|
-
}
|
|
175
|
+
return deployment
|
|
180
176
|
|
|
181
|
-
def delete_deployment(self, deployment:
|
|
177
|
+
def delete_deployment(self, deployment: V1Deployment) -> None:
|
|
182
178
|
"""Delete a deployment from the AI Hub.
|
|
183
179
|
|
|
184
180
|
Example:
|
|
@@ -188,9 +184,6 @@ class AIHub:
|
|
|
188
184
|
hub.delete_deployment(deployment)
|
|
189
185
|
|
|
190
186
|
Args:
|
|
191
|
-
deployment: The deployment
|
|
187
|
+
deployment: The deployment object returned by the run method.
|
|
192
188
|
"""
|
|
193
|
-
|
|
194
|
-
raise ValueError("Deployment dictionary must contain 'teamspace id' and 'id' keys.")
|
|
195
|
-
|
|
196
|
-
self._api.delete_api(deployment["id"], deployment["teamspace id"])
|
|
189
|
+
self._api.delete_api(deployment.id, deployment.project_id)
|
lightning_sdk/api/ai_hub_api.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import traceback
|
|
2
|
+
import warnings
|
|
2
3
|
from typing import Dict, List, Optional, Tuple, Union
|
|
3
4
|
|
|
4
5
|
import backoff
|
|
@@ -95,15 +96,17 @@ class AIHubApi:
|
|
|
95
96
|
|
|
96
97
|
if p.type == V1DeploymentTemplateParameterType.CHECKBOX and p.checkbox:
|
|
97
98
|
api_arguments[p.name] = (
|
|
98
|
-
(p.checkbox.true_value or "
|
|
99
|
-
if p.checkbox.is_checked
|
|
100
|
-
else (p.checkbox.false_value or "False")
|
|
99
|
+
(p.checkbox.true_value or "") if p.checkbox.is_checked else (p.checkbox.false_value or "")
|
|
101
100
|
)
|
|
102
101
|
|
|
103
102
|
for p in parameters:
|
|
104
103
|
name = p.name
|
|
105
104
|
pattern = f"${{{name}}}"
|
|
106
105
|
if name in api_arguments:
|
|
106
|
+
if p.type == V1DeploymentTemplateParameterType.CHECKBOX and p.checkbox:
|
|
107
|
+
api_arguments[p.name] = (
|
|
108
|
+
(p.checkbox.true_value or "") if api_arguments[name] is True else (p.checkbox.false_value or "")
|
|
109
|
+
)
|
|
107
110
|
AIHubApi._update_parameters(job, p.placements, pattern, api_arguments[name])
|
|
108
111
|
elif not p.required:
|
|
109
112
|
AIHubApi._update_parameters(job, p.placements, pattern, "")
|
|
@@ -120,6 +123,7 @@ class AIHubApi:
|
|
|
120
123
|
name: Optional[str],
|
|
121
124
|
api_arguments: Dict[str, str],
|
|
122
125
|
machine: Optional[Union[str, Machine]],
|
|
126
|
+
quantity: Optional[int],
|
|
123
127
|
) -> V1Deployment:
|
|
124
128
|
template = self._client.deployment_templates_service_get_deployment_template(template_id)
|
|
125
129
|
name = name or template.name
|
|
@@ -139,6 +143,19 @@ class AIHubApi:
|
|
|
139
143
|
apply_change(template.spec_v2.job, "instance_name", machine)
|
|
140
144
|
apply_change(template.spec_v2.job, "instance_type", machine)
|
|
141
145
|
|
|
146
|
+
if quantity != template.spec_v2.job.quantity:
|
|
147
|
+
# If the quantity is different from the published template quantity, override it with warnging
|
|
148
|
+
warnings.warn(
|
|
149
|
+
"Overriding the quantity of the template with the provided quantity. "
|
|
150
|
+
"This may result in unexpected behavior. "
|
|
151
|
+
f"Please verify the template (https://lightning.ai/lightning-ai/ai-hub/{template_id}) "
|
|
152
|
+
"and asscoiated parameters before running."
|
|
153
|
+
)
|
|
154
|
+
apply_change(template.spec_v2.job, "quantity", quantity)
|
|
155
|
+
|
|
156
|
+
# Override the cluster_id with the cloud_account if it is provided
|
|
157
|
+
if len(cloud_account) > 0:
|
|
158
|
+
apply_change(template.spec_v2.job, "cluster_id", cloud_account)
|
|
142
159
|
return self._client.jobs_service_create_deployment(
|
|
143
160
|
project_id=project_id,
|
|
144
161
|
body=CreateDeploymentRequestDefinesASpecForTheJobThatAllowsForAutoscalingJobs(
|
lightning_sdk/api/studio_api.py
CHANGED
|
@@ -140,11 +140,6 @@ class StudioApi:
|
|
|
140
140
|
startup_status = self.get_studio_status(studio_id, teamspace_id).in_use.startup_status
|
|
141
141
|
return startup_status and startup_status.top_up_restore_finished
|
|
142
142
|
|
|
143
|
-
@backoff.on_exception(backoff.expo, AttributeError, max_tries=10)
|
|
144
|
-
def _check_code_status_sync_in_progress(self, studio_id: str, teamspace_id: str) -> bool:
|
|
145
|
-
"""Retries checking the sync_in_progress value of the code status when there's an AttributeError."""
|
|
146
|
-
return self.get_studio_status(studio_id, teamspace_id).in_use.sync_in_progress
|
|
147
|
-
|
|
148
143
|
def start_studio(
|
|
149
144
|
self, studio_id: str, teamspace_id: str, machine: Union[Machine, str], interruptible: False
|
|
150
145
|
) -> None:
|
|
@@ -165,9 +160,6 @@ class StudioApi:
|
|
|
165
160
|
studio_id,
|
|
166
161
|
)
|
|
167
162
|
|
|
168
|
-
while self._check_code_status_sync_in_progress(studio_id, teamspace_id):
|
|
169
|
-
time.sleep(1)
|
|
170
|
-
|
|
171
163
|
while True:
|
|
172
164
|
if self._check_code_status_top_up_restore_finished(studio_id, teamspace_id):
|
|
173
165
|
break
|
lightning_sdk/cli/serve.py
CHANGED
|
@@ -4,17 +4,19 @@ from pathlib import Path
|
|
|
4
4
|
from typing import Optional, Union
|
|
5
5
|
|
|
6
6
|
import click
|
|
7
|
-
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 import Machine, Teamspace
|
|
12
12
|
from lightning_sdk.api.deployment_api import DeploymentApi
|
|
13
13
|
from lightning_sdk.api.lit_container_api import LitContainerApi
|
|
14
14
|
from lightning_sdk.cli.exceptions import StudioCliError
|
|
15
15
|
from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
|
|
16
16
|
from lightning_sdk.serve import _LitServeDeployer
|
|
17
17
|
|
|
18
|
+
_MACHINE_VALUES = tuple([machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine)])
|
|
19
|
+
|
|
18
20
|
|
|
19
21
|
@click.group("serve")
|
|
20
22
|
def serve() -> None:
|
|
@@ -24,11 +26,10 @@ def serve() -> None:
|
|
|
24
26
|
lightning serve api server.py # serve locally
|
|
25
27
|
|
|
26
28
|
Example:
|
|
27
|
-
lightning serve api server.py --cloud # deploy to the cloud
|
|
29
|
+
lightning serve api server.py --cloud --name litserve-api # deploy to the cloud
|
|
28
30
|
|
|
29
31
|
You can deploy the API to the cloud by running `lightning serve api server.py --cloud`.
|
|
30
|
-
This will
|
|
31
|
-
Deploying to the cloud requires pre-login to the docker registry.
|
|
32
|
+
This will build a docker container for the server.py script and deploy it to the Lightning AI platform.
|
|
32
33
|
"""
|
|
33
34
|
|
|
34
35
|
|
|
@@ -48,7 +49,6 @@ def serve() -> None:
|
|
|
48
49
|
flag_value=True,
|
|
49
50
|
help="Deploy the model to the Lightning AI platform",
|
|
50
51
|
)
|
|
51
|
-
@click.option("--gpu", is_flag=True, default=False, flag_value=True, help="Use GPU for serving")
|
|
52
52
|
@click.option("--name", default=None, help="Name of the deployed API (e.g., 'classification-api', 'Llama-api')")
|
|
53
53
|
@click.option(
|
|
54
54
|
"--non-interactive",
|
|
@@ -58,17 +58,88 @@ def serve() -> None:
|
|
|
58
58
|
flag_value=True,
|
|
59
59
|
help="Do not prompt for confirmation",
|
|
60
60
|
)
|
|
61
|
+
@click.option(
|
|
62
|
+
"--machine",
|
|
63
|
+
default="CPU",
|
|
64
|
+
show_default=True,
|
|
65
|
+
type=click.Choice(_MACHINE_VALUES),
|
|
66
|
+
help="The machine type to deploy the API on.",
|
|
67
|
+
)
|
|
68
|
+
@click.option(
|
|
69
|
+
"--interruptible",
|
|
70
|
+
is_flag=True,
|
|
71
|
+
default=False,
|
|
72
|
+
flag_value=True,
|
|
73
|
+
help="Whether the machine should be interruptible (spot) or not.",
|
|
74
|
+
)
|
|
75
|
+
@click.option(
|
|
76
|
+
"--teamspace",
|
|
77
|
+
default=None,
|
|
78
|
+
help="The teamspace the deployment should be associated with. Defaults to the current teamspace.",
|
|
79
|
+
)
|
|
80
|
+
@click.option(
|
|
81
|
+
"--org",
|
|
82
|
+
default=None,
|
|
83
|
+
help="The organization owning the teamspace (if any). Defaults to the current organization.",
|
|
84
|
+
)
|
|
85
|
+
@click.option("--user", default=None, help="The user owning the teamspace (if any). Defaults to the current user.")
|
|
86
|
+
@click.option(
|
|
87
|
+
"--cloud-account",
|
|
88
|
+
"--cloud_account",
|
|
89
|
+
default=None,
|
|
90
|
+
help=(
|
|
91
|
+
"The cloud account to run the deployment on. "
|
|
92
|
+
"Defaults to the studio cloud account if running with studio compute env. "
|
|
93
|
+
"If not provided will fall back to the teamspaces default cloud account."
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
@click.option("--port", default=8000, help="The port to expose the API on.")
|
|
97
|
+
@click.option("--min_replica", "--min-replica", default=0, help="Number of replicas to start with.")
|
|
98
|
+
@click.option("--max_replica", "--max-replica", default=1, help="Number of replicas to scale up to.")
|
|
99
|
+
@click.option("--replicas", "--replicas", default=1, help="Deployment will start with this many replicas.")
|
|
100
|
+
@click.option(
|
|
101
|
+
"--no_credentials",
|
|
102
|
+
"--no-credentials",
|
|
103
|
+
is_flag=True,
|
|
104
|
+
default=False,
|
|
105
|
+
flag_value=True,
|
|
106
|
+
help="Whether to include credentials in the deployment.",
|
|
107
|
+
)
|
|
61
108
|
def api(
|
|
62
109
|
script_path: str,
|
|
63
110
|
easy: bool,
|
|
64
111
|
cloud: bool,
|
|
65
|
-
gpu: bool,
|
|
66
112
|
name: str,
|
|
67
113
|
non_interactive: bool,
|
|
114
|
+
machine: str,
|
|
115
|
+
interruptible: bool,
|
|
116
|
+
teamspace: Optional[str],
|
|
117
|
+
org: Optional[str],
|
|
118
|
+
user: Optional[str],
|
|
119
|
+
cloud_account: Optional[str],
|
|
120
|
+
port: Optional[int],
|
|
121
|
+
min_replica: Optional[int],
|
|
122
|
+
max_replica: Optional[int],
|
|
123
|
+
replicas: Optional[int],
|
|
124
|
+
no_credentials: Optional[bool],
|
|
68
125
|
) -> None:
|
|
69
126
|
"""Deploy a LitServe model script."""
|
|
70
127
|
return api_impl(
|
|
71
|
-
script_path=script_path,
|
|
128
|
+
script_path=script_path,
|
|
129
|
+
easy=easy,
|
|
130
|
+
cloud=cloud,
|
|
131
|
+
repository=name,
|
|
132
|
+
non_interactive=non_interactive,
|
|
133
|
+
machine=machine,
|
|
134
|
+
interruptible=interruptible,
|
|
135
|
+
teamspace=teamspace,
|
|
136
|
+
org=org,
|
|
137
|
+
user=user,
|
|
138
|
+
cloud_account=cloud_account,
|
|
139
|
+
port=port,
|
|
140
|
+
min_replica=min_replica,
|
|
141
|
+
max_replica=max_replica,
|
|
142
|
+
include_credentials=not no_credentials,
|
|
72
143
|
)
|
|
73
144
|
|
|
74
145
|
|
|
@@ -76,10 +147,20 @@ def api_impl(
|
|
|
76
147
|
script_path: Union[str, Path],
|
|
77
148
|
easy: bool = False,
|
|
78
149
|
cloud: bool = False,
|
|
79
|
-
gpu: bool = False,
|
|
80
150
|
repository: [str] = None,
|
|
81
151
|
tag: Optional[str] = None,
|
|
82
152
|
non_interactive: bool = False,
|
|
153
|
+
machine: str = "CPU",
|
|
154
|
+
interruptible: bool = False,
|
|
155
|
+
teamspace: Optional[str] = None,
|
|
156
|
+
org: Optional[str] = None,
|
|
157
|
+
user: Optional[str] = None,
|
|
158
|
+
cloud_account: Optional[str] = None,
|
|
159
|
+
port: Optional[int] = 8000,
|
|
160
|
+
min_replica: Optional[int] = 0,
|
|
161
|
+
max_replica: Optional[int] = 1,
|
|
162
|
+
replicas: Optional[int] = 1,
|
|
163
|
+
include_credentials: Optional[bool] = True,
|
|
83
164
|
) -> None:
|
|
84
165
|
"""Deploy a LitServe model script."""
|
|
85
166
|
console = Console()
|
|
@@ -93,8 +174,24 @@ def api_impl(
|
|
|
93
174
|
|
|
94
175
|
if cloud:
|
|
95
176
|
repository = repository or "litserve-model"
|
|
177
|
+
machine = Machine.from_str(machine)
|
|
96
178
|
return _handle_cloud(
|
|
97
|
-
script_path,
|
|
179
|
+
script_path,
|
|
180
|
+
console,
|
|
181
|
+
repository=repository,
|
|
182
|
+
tag=tag,
|
|
183
|
+
non_interactive=non_interactive,
|
|
184
|
+
machine=machine,
|
|
185
|
+
interruptible=interruptible,
|
|
186
|
+
teamspace=teamspace,
|
|
187
|
+
org=org,
|
|
188
|
+
user=user,
|
|
189
|
+
cloud_account=cloud_account,
|
|
190
|
+
port=port,
|
|
191
|
+
min_replica=min_replica,
|
|
192
|
+
max_replica=max_replica,
|
|
193
|
+
replicas=replicas,
|
|
194
|
+
include_credentials=include_credentials,
|
|
98
195
|
)
|
|
99
196
|
|
|
100
197
|
try:
|
|
@@ -111,22 +208,30 @@ def api_impl(
|
|
|
111
208
|
def _handle_cloud(
|
|
112
209
|
script_path: Union[str, Path],
|
|
113
210
|
console: Console,
|
|
114
|
-
gpu: bool,
|
|
115
211
|
repository: str = "litserve-model",
|
|
116
212
|
tag: Optional[str] = None,
|
|
117
|
-
teamspace: Optional[str] = None,
|
|
118
213
|
non_interactive: bool = False,
|
|
214
|
+
machine: Machine = "CPU",
|
|
215
|
+
interruptible: bool = False,
|
|
216
|
+
teamspace: Optional[str] = None,
|
|
217
|
+
org: Optional[str] = None,
|
|
218
|
+
user: Optional[str] = None,
|
|
219
|
+
cloud_account: Optional[str] = None,
|
|
220
|
+
port: Optional[int] = 8000,
|
|
221
|
+
min_replica: Optional[int] = 0,
|
|
222
|
+
max_replica: Optional[int] = 1,
|
|
223
|
+
replicas: Optional[int] = 1,
|
|
224
|
+
include_credentials: Optional[bool] = True,
|
|
119
225
|
) -> None:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
except docker.errors.DockerException as e:
|
|
126
|
-
raise StudioCliError(f"Failed to connect to Docker daemon: {e!s}. Is Docker running?") from None
|
|
226
|
+
if teamspace is None:
|
|
227
|
+
menu = _TeamspacesMenu()
|
|
228
|
+
resolved_teamspace = menu._resolve_teamspace(teamspace)
|
|
229
|
+
else:
|
|
230
|
+
resolved_teamspace = Teamspace(name=teamspace, org=org, user=user)
|
|
127
231
|
|
|
232
|
+
port = port or 8000
|
|
128
233
|
ls_deployer = _LitServeDeployer()
|
|
129
|
-
path = ls_deployer.dockerize_api(script_path, port=
|
|
234
|
+
path = ls_deployer.dockerize_api(script_path, port=port, gpu=not machine.is_cpu(), tag=tag, print_success=False)
|
|
130
235
|
console.clear()
|
|
131
236
|
if non_interactive:
|
|
132
237
|
console.print("[italic]non-interactive[/italic] mode enabled, skipping confirmation prompts", style="blue")
|
|
@@ -142,7 +247,8 @@ def _handle_cloud(
|
|
|
142
247
|
lit_cr = LitContainerApi()
|
|
143
248
|
deployment_name = os.path.basename(repository)
|
|
144
249
|
|
|
145
|
-
|
|
250
|
+
ls_deployer.authenticate()
|
|
251
|
+
if DeploymentApi().get_deployment_by_name(deployment_name, resolved_teamspace.id):
|
|
146
252
|
raise StudioCliError(f"Deployment {deployment_name} already exists. Please choose a different name.") from None
|
|
147
253
|
|
|
148
254
|
with Progress(
|
|
@@ -154,7 +260,7 @@ def _handle_cloud(
|
|
|
154
260
|
) as progress:
|
|
155
261
|
try:
|
|
156
262
|
ls_deployer.build_container(path, repository, tag, console, progress)
|
|
157
|
-
push_status = ls_deployer.push_container(repository, tag,
|
|
263
|
+
push_status = ls_deployer.push_container(repository, tag, resolved_teamspace, lit_cr, progress)
|
|
158
264
|
except Exception as e:
|
|
159
265
|
console.print(f"❌ Deployment failed: {e}", style="red")
|
|
160
266
|
return
|
|
@@ -163,6 +269,17 @@ def _handle_cloud(
|
|
|
163
269
|
repository = push_status.get("repository")
|
|
164
270
|
|
|
165
271
|
deployment_status = ls_deployer._run_on_cloud(
|
|
166
|
-
deployment_name=deployment_name,
|
|
272
|
+
deployment_name=deployment_name,
|
|
273
|
+
image=repository,
|
|
274
|
+
teamspace=resolved_teamspace,
|
|
275
|
+
metric=None,
|
|
276
|
+
machine=machine,
|
|
277
|
+
spot=interruptible,
|
|
278
|
+
cloud_account=cloud_account,
|
|
279
|
+
port=port,
|
|
280
|
+
min_replica=min_replica,
|
|
281
|
+
max_replica=max_replica,
|
|
282
|
+
replicas=replicas,
|
|
283
|
+
include_credentials=include_credentials,
|
|
167
284
|
)
|
|
168
285
|
console.print(f"🚀 Deployment started, access at [i]{deployment_status.get('url')}[/i]")
|
|
@@ -32,7 +32,7 @@ from lightning_sdk.organization import Organization
|
|
|
32
32
|
from lightning_sdk.services.utilities import _get_cluster
|
|
33
33
|
from lightning_sdk.teamspace import Teamspace
|
|
34
34
|
from lightning_sdk.user import User
|
|
35
|
-
from lightning_sdk.utils.resolve import _resolve_deprecated_cluster, _resolve_teamspace, _resolve_user
|
|
35
|
+
from lightning_sdk.utils.resolve import _resolve_deprecated_cluster, _resolve_org, _resolve_teamspace, _resolve_user
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class Deployment:
|
|
@@ -74,6 +74,7 @@ class Deployment:
|
|
|
74
74
|
|
|
75
75
|
self._name = name
|
|
76
76
|
self._user = _resolve_user(self._user or user)
|
|
77
|
+
self._org = _resolve_org(org)
|
|
77
78
|
|
|
78
79
|
self._teamspace = _resolve_teamspace(
|
|
79
80
|
teamspace=teamspace,
|
|
@@ -96,6 +97,8 @@ class Deployment:
|
|
|
96
97
|
self._name = deployment.name
|
|
97
98
|
self._is_created = True
|
|
98
99
|
self._deployment = deployment
|
|
100
|
+
else:
|
|
101
|
+
self._deployment = None
|
|
99
102
|
|
|
100
103
|
def start(
|
|
101
104
|
self,
|
|
@@ -235,6 +238,13 @@ class Deployment:
|
|
|
235
238
|
"""All the deployment replicas will be stopped and all their traffic blocked."""
|
|
236
239
|
self._deployment = self._deployment_api.stop(self._deployment)
|
|
237
240
|
|
|
241
|
+
@property
|
|
242
|
+
def name(self) -> Optional[str]:
|
|
243
|
+
if self._deployment:
|
|
244
|
+
self._deployment = self._deployment_api.get_deployment_by_name(self._name, self._teamspace.id)
|
|
245
|
+
self._name = self._deployment.name
|
|
246
|
+
return self._name
|
|
247
|
+
|
|
238
248
|
@property
|
|
239
249
|
def replicas(self) -> Optional[int]:
|
|
240
250
|
"""The default number of replicas the release starts with."""
|
|
@@ -248,7 +258,7 @@ class Deployment:
|
|
|
248
258
|
"""The minimum number of replicas."""
|
|
249
259
|
if self._deployment:
|
|
250
260
|
self._deployment = self._deployment_api.get_deployment_by_name(self._name, self._teamspace.id)
|
|
251
|
-
return self._deployment.autoscaling.min_replicas
|
|
261
|
+
return int(self._deployment.autoscaling.min_replicas)
|
|
252
262
|
return None
|
|
253
263
|
|
|
254
264
|
@property
|
|
@@ -256,7 +266,7 @@ class Deployment:
|
|
|
256
266
|
"""The maximum number of replicas."""
|
|
257
267
|
if self._deployment:
|
|
258
268
|
self._deployment = self._deployment_api.get_deployment_by_name(self._name, self._teamspace.id)
|
|
259
|
-
return self._deployment.autoscaling.max_replicas
|
|
269
|
+
return int(self._deployment.autoscaling.max_replicas)
|
|
260
270
|
return None
|
|
261
271
|
|
|
262
272
|
@property
|
|
@@ -371,9 +381,13 @@ class Deployment:
|
|
|
371
381
|
return self._deployment.spec.include_credentials
|
|
372
382
|
return None
|
|
373
383
|
|
|
384
|
+
@property
|
|
385
|
+
def org(self) -> Optional[Organization]:
|
|
386
|
+
return self._org
|
|
387
|
+
|
|
374
388
|
@property
|
|
375
389
|
def user(self) -> Optional[User]:
|
|
376
|
-
"""The
|
|
390
|
+
"""The user of the deployment."""
|
|
377
391
|
return self._user
|
|
378
392
|
|
|
379
393
|
@property
|
|
@@ -385,6 +399,20 @@ class Deployment:
|
|
|
385
399
|
def is_started(self) -> bool:
|
|
386
400
|
return self._is_created
|
|
387
401
|
|
|
402
|
+
@property
|
|
403
|
+
def is_stopped(self) -> Optional[bool]:
|
|
404
|
+
if self._deployment:
|
|
405
|
+
self._deployment = self._deployment_api.get_deployment_by_name(self._name, self._teamspace.id)
|
|
406
|
+
return self._deployment.autoscaling.max_replicas == 0
|
|
407
|
+
return None
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def image(self) -> Optional[str]:
|
|
411
|
+
if self._deployment:
|
|
412
|
+
self._deployment = self._deployment_api.get_deployment_by_name(self._name, self._teamspace.id)
|
|
413
|
+
return self._deployment.spec.image
|
|
414
|
+
return None
|
|
415
|
+
|
|
388
416
|
@property
|
|
389
417
|
def _session(self) -> Any:
|
|
390
418
|
if self._request_session is None:
|