dstack 0.19.33__py3-none-any.whl → 0.19.35__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.
Potentially problematic release.
This version of dstack might be problematic. Click here for more details.
- dstack/_internal/cli/services/configurators/run.py +1 -1
- dstack/_internal/core/backends/base/compute.py +20 -1
- dstack/_internal/core/backends/base/models.py +10 -0
- dstack/_internal/core/backends/base/offers.py +4 -1
- dstack/_internal/core/backends/features.py +5 -0
- dstack/_internal/core/backends/gcp/compute.py +24 -9
- dstack/_internal/core/backends/gcp/models.py +4 -1
- dstack/_internal/core/backends/nebius/compute.py +28 -16
- dstack/_internal/core/backends/nebius/configurator.py +1 -1
- dstack/_internal/core/backends/nebius/models.py +4 -0
- dstack/_internal/core/backends/nebius/resources.py +41 -20
- dstack/_internal/core/backends/runpod/api_client.py +245 -59
- dstack/_internal/core/backends/runpod/compute.py +161 -14
- dstack/_internal/core/compatibility/runs.py +25 -4
- dstack/_internal/core/models/compute_groups.py +39 -0
- dstack/_internal/core/models/fleets.py +6 -1
- dstack/_internal/core/models/instances.py +2 -1
- dstack/_internal/core/models/profiles.py +3 -1
- dstack/_internal/core/models/runs.py +4 -0
- dstack/_internal/core/services/ssh/key_manager.py +56 -0
- dstack/_internal/server/app.py +14 -2
- dstack/_internal/server/background/__init__.py +7 -0
- dstack/_internal/server/background/tasks/process_compute_groups.py +164 -0
- dstack/_internal/server/background/tasks/process_instances.py +81 -49
- dstack/_internal/server/background/tasks/process_submitted_jobs.py +179 -84
- dstack/_internal/server/migrations/env.py +20 -2
- dstack/_internal/server/migrations/versions/7d1ec2b920ac_add_computegroupmodel.py +93 -0
- dstack/_internal/server/models.py +42 -0
- dstack/_internal/server/routers/metrics.py +6 -2
- dstack/_internal/server/routers/runs.py +15 -6
- dstack/_internal/server/routers/users.py +7 -0
- dstack/_internal/server/services/compute_groups.py +22 -0
- dstack/_internal/server/services/fleets.py +1 -0
- dstack/_internal/server/services/jobs/__init__.py +31 -9
- dstack/_internal/server/services/jobs/configurators/base.py +3 -2
- dstack/_internal/server/services/offers.py +1 -0
- dstack/_internal/server/services/requirements/combine.py +1 -0
- dstack/_internal/server/services/runs.py +21 -3
- dstack/_internal/server/services/users.py +3 -3
- dstack/_internal/server/statics/index.html +1 -1
- dstack/_internal/server/statics/{main-97c7e184573ca23f9fe4.js → main-e79754c136f1d8e4e7e6.js} +11 -11
- dstack/_internal/server/statics/{main-97c7e184573ca23f9fe4.js.map → main-e79754c136f1d8e4e7e6.js.map} +1 -1
- dstack/_internal/server/testing/common.py +55 -0
- dstack/_internal/server/utils/routers.py +18 -20
- dstack/_internal/settings.py +4 -1
- dstack/_internal/utils/version.py +22 -0
- dstack/api/_public/__init__.py +2 -2
- dstack/api/_public/runs.py +36 -39
- dstack/api/server/__init__.py +4 -0
- dstack/version.py +1 -1
- {dstack-0.19.33.dist-info → dstack-0.19.35.dist-info}/METADATA +4 -4
- {dstack-0.19.33.dist-info → dstack-0.19.35.dist-info}/RECORD +55 -50
- dstack/_internal/core/backends/nebius/fabrics.py +0 -49
- {dstack-0.19.33.dist-info → dstack-0.19.35.dist-info}/WHEEL +0 -0
- {dstack-0.19.33.dist-info → dstack-0.19.35.dist-info}/entry_points.txt +0 -0
- {dstack-0.19.33.dist-info → dstack-0.19.35.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from typing import List, Tuple
|
|
1
|
+
from typing import Annotated, List, Optional, Tuple, cast
|
|
2
2
|
|
|
3
|
-
from fastapi import APIRouter, Depends
|
|
3
|
+
from fastapi import APIRouter, Depends, Request
|
|
4
4
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
5
5
|
|
|
6
6
|
from dstack._internal.core.errors import ResourceNotExistsError
|
|
@@ -35,6 +35,11 @@ project_router = APIRouter(
|
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
def use_legacy_default_working_dir(request: Request) -> bool:
|
|
39
|
+
client_release = cast(Optional[tuple[int, ...]], request.state.client_release)
|
|
40
|
+
return client_release is not None and client_release < (0, 19, 27)
|
|
41
|
+
|
|
42
|
+
|
|
38
43
|
@root_router.post(
|
|
39
44
|
"/list",
|
|
40
45
|
response_model=List[Run],
|
|
@@ -103,8 +108,9 @@ async def get_run(
|
|
|
103
108
|
)
|
|
104
109
|
async def get_plan(
|
|
105
110
|
body: GetRunPlanRequest,
|
|
106
|
-
session: AsyncSession
|
|
107
|
-
user_project:
|
|
111
|
+
session: Annotated[AsyncSession, Depends(get_session)],
|
|
112
|
+
user_project: Annotated[tuple[UserModel, ProjectModel], Depends(ProjectMember())],
|
|
113
|
+
legacy_default_working_dir: Annotated[bool, Depends(use_legacy_default_working_dir)],
|
|
108
114
|
):
|
|
109
115
|
"""
|
|
110
116
|
Returns a run plan for the given run spec.
|
|
@@ -119,6 +125,7 @@ async def get_plan(
|
|
|
119
125
|
user=user,
|
|
120
126
|
run_spec=body.run_spec,
|
|
121
127
|
max_offers=body.max_offers,
|
|
128
|
+
legacy_default_working_dir=legacy_default_working_dir,
|
|
122
129
|
)
|
|
123
130
|
return CustomORJSONResponse(run_plan)
|
|
124
131
|
|
|
@@ -129,8 +136,9 @@ async def get_plan(
|
|
|
129
136
|
)
|
|
130
137
|
async def apply_plan(
|
|
131
138
|
body: ApplyRunPlanRequest,
|
|
132
|
-
session: AsyncSession
|
|
133
|
-
user_project:
|
|
139
|
+
session: Annotated[AsyncSession, Depends(get_session)],
|
|
140
|
+
user_project: Annotated[tuple[UserModel, ProjectModel], Depends(ProjectMember())],
|
|
141
|
+
legacy_default_working_dir: Annotated[bool, Depends(use_legacy_default_working_dir)],
|
|
134
142
|
):
|
|
135
143
|
"""
|
|
136
144
|
Creates a new run or updates an existing run.
|
|
@@ -148,6 +156,7 @@ async def apply_plan(
|
|
|
148
156
|
project=project,
|
|
149
157
|
plan=body.plan,
|
|
150
158
|
force=body.force,
|
|
159
|
+
legacy_default_working_dir=legacy_default_working_dir,
|
|
151
160
|
)
|
|
152
161
|
)
|
|
153
162
|
|
|
@@ -38,8 +38,15 @@ async def list_users(
|
|
|
38
38
|
|
|
39
39
|
@router.post("/get_my_user", response_model=UserWithCreds)
|
|
40
40
|
async def get_my_user(
|
|
41
|
+
session: AsyncSession = Depends(get_session),
|
|
41
42
|
user: UserModel = Depends(Authenticated()),
|
|
42
43
|
):
|
|
44
|
+
if user.ssh_private_key is None or user.ssh_public_key is None:
|
|
45
|
+
# Generate keys for pre-0.19.33 users
|
|
46
|
+
updated_user = await users.refresh_ssh_key(session=session, user=user, username=user.name)
|
|
47
|
+
if updated_user is None:
|
|
48
|
+
raise ResourceNotExistsError()
|
|
49
|
+
user = updated_user
|
|
43
50
|
return CustomORJSONResponse(users.user_model_to_user_with_creds(user))
|
|
44
51
|
|
|
45
52
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from dstack._internal.core.models.compute_groups import ComputeGroup, ComputeGroupProvisioningData
|
|
2
|
+
from dstack._internal.server.models import ComputeGroupModel
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def compute_group_model_to_compute_group(compute_group_model: ComputeGroupModel) -> ComputeGroup:
|
|
6
|
+
provisioning_data = get_compute_group_provisioning_data(compute_group_model)
|
|
7
|
+
return ComputeGroup(
|
|
8
|
+
id=compute_group_model.id,
|
|
9
|
+
project_name=compute_group_model.project.name,
|
|
10
|
+
status=compute_group_model.status,
|
|
11
|
+
name=provisioning_data.compute_group_name,
|
|
12
|
+
created_at=compute_group_model.created_at,
|
|
13
|
+
provisioning_data=provisioning_data,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_compute_group_provisioning_data(
|
|
18
|
+
compute_group_model: ComputeGroupModel,
|
|
19
|
+
) -> ComputeGroupProvisioningData:
|
|
20
|
+
return ComputeGroupProvisioningData.__response__.parse_raw(
|
|
21
|
+
compute_group_model.provisioning_data
|
|
22
|
+
)
|
|
@@ -650,6 +650,7 @@ def get_fleet_requirements(fleet_spec: FleetSpec) -> Requirements:
|
|
|
650
650
|
max_price=profile.max_price,
|
|
651
651
|
spot=get_policy_map(profile.spot_policy, default=SpotPolicy.ONDEMAND),
|
|
652
652
|
reservation=fleet_spec.configuration.reservation,
|
|
653
|
+
multinode=fleet_spec.configuration.placement == InstanceGroupPlacement.CLUSTER,
|
|
653
654
|
)
|
|
654
655
|
return requirements
|
|
655
656
|
|
|
@@ -96,20 +96,42 @@ def find_job(jobs: List[Job], replica_num: int, job_num: int) -> Job:
|
|
|
96
96
|
)
|
|
97
97
|
|
|
98
98
|
|
|
99
|
+
def find_jobs(
|
|
100
|
+
jobs: List[Job],
|
|
101
|
+
replica_num: Optional[int] = None,
|
|
102
|
+
job_num: Optional[int] = None,
|
|
103
|
+
) -> list[Job]:
|
|
104
|
+
res = jobs
|
|
105
|
+
if replica_num is not None:
|
|
106
|
+
res = [j for j in res if j.job_spec.replica_num == replica_num]
|
|
107
|
+
if job_num is not None:
|
|
108
|
+
res = [j for j in res if j.job_spec.job_num == job_num]
|
|
109
|
+
return res
|
|
110
|
+
|
|
111
|
+
|
|
99
112
|
async def get_run_job_model(
|
|
100
|
-
session: AsyncSession,
|
|
113
|
+
session: AsyncSession,
|
|
114
|
+
project: ProjectModel,
|
|
115
|
+
run_name: str,
|
|
116
|
+
run_id: Optional[UUID],
|
|
117
|
+
replica_num: int,
|
|
118
|
+
job_num: int,
|
|
101
119
|
) -> Optional[JobModel]:
|
|
120
|
+
filters = [
|
|
121
|
+
RunModel.project_id == project.id,
|
|
122
|
+
RunModel.run_name == run_name,
|
|
123
|
+
JobModel.replica_num == replica_num,
|
|
124
|
+
JobModel.job_num == job_num,
|
|
125
|
+
]
|
|
126
|
+
if run_id is not None:
|
|
127
|
+
filters.append(RunModel.id == run_id)
|
|
128
|
+
else:
|
|
129
|
+
# Assuming run_name is unique for non-deleted runs
|
|
130
|
+
filters.append(RunModel.deleted == False)
|
|
102
131
|
res = await session.execute(
|
|
103
132
|
select(JobModel)
|
|
104
133
|
.join(JobModel.run)
|
|
105
|
-
.where(
|
|
106
|
-
RunModel.project_id == project.id,
|
|
107
|
-
# assuming run_name is unique for non-deleted runs
|
|
108
|
-
RunModel.run_name == run_name,
|
|
109
|
-
RunModel.deleted == False,
|
|
110
|
-
JobModel.replica_num == replica_num,
|
|
111
|
-
JobModel.job_num == job_num,
|
|
112
|
-
)
|
|
134
|
+
.where(*filters)
|
|
113
135
|
.order_by(JobModel.submission_num.desc())
|
|
114
136
|
.limit(1)
|
|
115
137
|
)
|
|
@@ -161,7 +161,7 @@ class JobConfigurator(ABC):
|
|
|
161
161
|
stop_duration=self._stop_duration(),
|
|
162
162
|
utilization_policy=self._utilization_policy(),
|
|
163
163
|
registry_auth=self._registry_auth(),
|
|
164
|
-
requirements=self._requirements(),
|
|
164
|
+
requirements=self._requirements(jobs_per_replica),
|
|
165
165
|
retry=self._retry(),
|
|
166
166
|
working_dir=self._working_dir(),
|
|
167
167
|
volumes=self._volumes(job_num),
|
|
@@ -295,13 +295,14 @@ class JobConfigurator(ABC):
|
|
|
295
295
|
def _registry_auth(self) -> Optional[RegistryAuth]:
|
|
296
296
|
return self.run_spec.configuration.registry_auth
|
|
297
297
|
|
|
298
|
-
def _requirements(self) -> Requirements:
|
|
298
|
+
def _requirements(self, jobs_per_replica: int) -> Requirements:
|
|
299
299
|
spot_policy = self._spot_policy()
|
|
300
300
|
return Requirements(
|
|
301
301
|
resources=self.run_spec.configuration.resources,
|
|
302
302
|
max_price=self.run_spec.merged_profile.max_price,
|
|
303
303
|
spot=None if spot_policy == SpotPolicy.AUTO else (spot_policy == SpotPolicy.SPOT),
|
|
304
304
|
reservation=self.run_spec.merged_profile.reservation,
|
|
305
|
+
multinode=jobs_per_replica > 1,
|
|
305
306
|
)
|
|
306
307
|
|
|
307
308
|
def _retry(self) -> Optional[Retry]:
|
|
@@ -63,6 +63,7 @@ def combine_fleet_and_run_requirements(
|
|
|
63
63
|
reservation=_get_single_value_optional(
|
|
64
64
|
fleet_requirements.reservation, run_requirements.reservation
|
|
65
65
|
),
|
|
66
|
+
multinode=fleet_requirements.multinode or run_requirements.multinode,
|
|
66
67
|
)
|
|
67
68
|
except CombineError:
|
|
68
69
|
return None
|
|
@@ -34,6 +34,7 @@ from dstack._internal.core.models.profiles import (
|
|
|
34
34
|
)
|
|
35
35
|
from dstack._internal.core.models.repos.virtual import DEFAULT_VIRTUAL_REPO_ID, VirtualRunRepoData
|
|
36
36
|
from dstack._internal.core.models.runs import (
|
|
37
|
+
LEGACY_REPO_DIR,
|
|
37
38
|
ApplyRunPlanInput,
|
|
38
39
|
Job,
|
|
39
40
|
JobPlan,
|
|
@@ -308,6 +309,7 @@ async def get_plan(
|
|
|
308
309
|
user: UserModel,
|
|
309
310
|
run_spec: RunSpec,
|
|
310
311
|
max_offers: Optional[int],
|
|
312
|
+
legacy_default_working_dir: bool = False,
|
|
311
313
|
) -> RunPlan:
|
|
312
314
|
# Spec must be copied by parsing to calculate merged_profile
|
|
313
315
|
effective_run_spec = RunSpec.parse_obj(run_spec.dict())
|
|
@@ -317,7 +319,11 @@ async def get_plan(
|
|
|
317
319
|
spec=effective_run_spec,
|
|
318
320
|
)
|
|
319
321
|
effective_run_spec = RunSpec.parse_obj(effective_run_spec.dict())
|
|
320
|
-
_validate_run_spec_and_set_defaults(
|
|
322
|
+
_validate_run_spec_and_set_defaults(
|
|
323
|
+
user=user,
|
|
324
|
+
run_spec=effective_run_spec,
|
|
325
|
+
legacy_default_working_dir=legacy_default_working_dir,
|
|
326
|
+
)
|
|
321
327
|
|
|
322
328
|
profile = effective_run_spec.merged_profile
|
|
323
329
|
creation_policy = profile.creation_policy
|
|
@@ -413,6 +419,7 @@ async def apply_plan(
|
|
|
413
419
|
project: ProjectModel,
|
|
414
420
|
plan: ApplyRunPlanInput,
|
|
415
421
|
force: bool,
|
|
422
|
+
legacy_default_working_dir: bool = False,
|
|
416
423
|
) -> Run:
|
|
417
424
|
run_spec = plan.run_spec
|
|
418
425
|
run_spec = await apply_plugin_policies(
|
|
@@ -422,7 +429,9 @@ async def apply_plan(
|
|
|
422
429
|
)
|
|
423
430
|
# Spec must be copied by parsing to calculate merged_profile
|
|
424
431
|
run_spec = RunSpec.parse_obj(run_spec.dict())
|
|
425
|
-
_validate_run_spec_and_set_defaults(
|
|
432
|
+
_validate_run_spec_and_set_defaults(
|
|
433
|
+
user=user, run_spec=run_spec, legacy_default_working_dir=legacy_default_working_dir
|
|
434
|
+
)
|
|
426
435
|
if run_spec.run_name is None:
|
|
427
436
|
return await submit_run(
|
|
428
437
|
session=session,
|
|
@@ -600,6 +609,7 @@ def create_job_model_for_new_submission(
|
|
|
600
609
|
job_spec_data=job.job_spec.json(),
|
|
601
610
|
job_provisioning_data=None,
|
|
602
611
|
probes=[],
|
|
612
|
+
waiting_master_job=job.job_spec.job_num != 0,
|
|
603
613
|
)
|
|
604
614
|
|
|
605
615
|
|
|
@@ -715,6 +725,9 @@ def run_model_to_run(
|
|
|
715
725
|
status_message = _get_run_status_message(run_model)
|
|
716
726
|
error = _get_run_error(run_model)
|
|
717
727
|
fleet = _get_run_fleet(run_model)
|
|
728
|
+
next_triggered_at = None
|
|
729
|
+
if not run_model.status.is_finished():
|
|
730
|
+
next_triggered_at = _get_next_triggered_at(run_spec)
|
|
718
731
|
run = Run(
|
|
719
732
|
id=run_model.id,
|
|
720
733
|
project_name=run_model.project.name,
|
|
@@ -734,6 +747,7 @@ def run_model_to_run(
|
|
|
734
747
|
deployment_num=run_model.deployment_num,
|
|
735
748
|
error=error,
|
|
736
749
|
deleted=run_model.deleted,
|
|
750
|
+
next_triggered_at=next_triggered_at,
|
|
737
751
|
)
|
|
738
752
|
run.cost = _get_run_cost(run)
|
|
739
753
|
return run
|
|
@@ -981,7 +995,9 @@ def _get_job_submission_cost(job_submission: JobSubmission) -> float:
|
|
|
981
995
|
return job_submission.job_provisioning_data.price * duration_hours
|
|
982
996
|
|
|
983
997
|
|
|
984
|
-
def _validate_run_spec_and_set_defaults(
|
|
998
|
+
def _validate_run_spec_and_set_defaults(
|
|
999
|
+
user: UserModel, run_spec: RunSpec, legacy_default_working_dir: bool = False
|
|
1000
|
+
):
|
|
985
1001
|
# This function may set defaults for null run_spec values,
|
|
986
1002
|
# although most defaults are resolved when building job_spec
|
|
987
1003
|
# so that we can keep both the original user-supplied value (null in run_spec)
|
|
@@ -1036,6 +1052,8 @@ def _validate_run_spec_and_set_defaults(user: UserModel, run_spec: RunSpec):
|
|
|
1036
1052
|
run_spec.ssh_key_pub = user.ssh_public_key
|
|
1037
1053
|
else:
|
|
1038
1054
|
raise ServerClientError("ssh_key_pub must be set if the user has no ssh_public_key")
|
|
1055
|
+
if run_spec.configuration.working_dir is None and legacy_default_working_dir:
|
|
1056
|
+
run_spec.configuration.working_dir = LEGACY_REPO_DIR
|
|
1039
1057
|
|
|
1040
1058
|
|
|
1041
1059
|
_UPDATABLE_SPEC_FIELDS = ["configuration_path", "configuration"]
|
|
@@ -20,8 +20,8 @@ from dstack._internal.core.models.users import (
|
|
|
20
20
|
from dstack._internal.server.models import DecryptedString, UserModel
|
|
21
21
|
from dstack._internal.server.services.permissions import get_default_permissions
|
|
22
22
|
from dstack._internal.server.utils.routers import error_forbidden
|
|
23
|
+
from dstack._internal.utils import crypto
|
|
23
24
|
from dstack._internal.utils.common import run_async
|
|
24
|
-
from dstack._internal.utils.crypto import generate_rsa_key_pair_bytes
|
|
25
25
|
from dstack._internal.utils.logging import get_logger
|
|
26
26
|
|
|
27
27
|
logger = get_logger(__name__)
|
|
@@ -88,7 +88,7 @@ async def create_user(
|
|
|
88
88
|
raise ResourceExistsError()
|
|
89
89
|
if token is None:
|
|
90
90
|
token = str(uuid.uuid4())
|
|
91
|
-
private_bytes, public_bytes = await run_async(generate_rsa_key_pair_bytes, username)
|
|
91
|
+
private_bytes, public_bytes = await run_async(crypto.generate_rsa_key_pair_bytes, username)
|
|
92
92
|
user = UserModel(
|
|
93
93
|
id=uuid.uuid4(),
|
|
94
94
|
name=username,
|
|
@@ -135,7 +135,7 @@ async def refresh_ssh_key(
|
|
|
135
135
|
logger.debug("Refreshing SSH key for user [code]%s[/code]", username)
|
|
136
136
|
if user.global_role != GlobalRole.ADMIN and user.name != username:
|
|
137
137
|
raise error_forbidden()
|
|
138
|
-
private_bytes, public_bytes = await run_async(generate_rsa_key_pair_bytes, username)
|
|
138
|
+
private_bytes, public_bytes = await run_async(crypto.generate_rsa_key_pair_bytes, username)
|
|
139
139
|
await session.execute(
|
|
140
140
|
update(UserModel)
|
|
141
141
|
.where(UserModel.name == username)
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>dstack</title><meta name="description" content="Get GPUs at the best prices and availability from a wide range of providers. No cloud account of your own is required.
|
|
2
2
|
"/><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet"><meta name="og:title" content="dstack"><meta name="og:type" content="article"><meta name="og:image" content="/splash_thumbnail.png"><meta name="og:description" content="Get GPUs at the best prices and availability from a wide range of providers. No cloud account of your own is required.
|
|
3
|
-
"><link rel="icon" type="image/x-icon" href="/assets/favicon.ico"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon-48x48.png"><link rel="manifest" href="/assets/manifest.webmanifest"><meta name="mobile-web-app-capable" content="yes"><meta name="theme-color" content="#fff"><meta name="application-name" content="dstackai"><link rel="apple-touch-icon" sizes="57x57" href="/assets/apple-touch-icon-57x57.png"><link rel="apple-touch-icon" sizes="60x60" href="/assets/apple-touch-icon-60x60.png"><link rel="apple-touch-icon" sizes="72x72" href="/assets/apple-touch-icon-72x72.png"><link rel="apple-touch-icon" sizes="76x76" href="/assets/apple-touch-icon-76x76.png"><link rel="apple-touch-icon" sizes="114x114" href="/assets/apple-touch-icon-114x114.png"><link rel="apple-touch-icon" sizes="120x120" href="/assets/apple-touch-icon-120x120.png"><link rel="apple-touch-icon" sizes="144x144" href="/assets/apple-touch-icon-144x144.png"><link rel="apple-touch-icon" sizes="152x152" href="/assets/apple-touch-icon-152x152.png"><link rel="apple-touch-icon" sizes="167x167" href="/assets/apple-touch-icon-167x167.png"><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-180x180.png"><link rel="apple-touch-icon" sizes="1024x1024" href="/assets/apple-touch-icon-1024x1024.png"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="dstackai"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-640x1136.png"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1136x640.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-750x1334.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1334x750.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1125x2436.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2436x1125.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1170x2532.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2532x1170.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1179x2556.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2556x1179.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-828x1792.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1792x828.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2688.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2688x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2208.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2208x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1284x2778.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2778x1284.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1290x2796.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2796x1290.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1488x2266.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2266x1488.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1536x2048.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2048x1536.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1620x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1620.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1640x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1640.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2388.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2388x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2224.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2224x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-2048x2732.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2732x2048.png"><meta name="msapplication-TileColor" content="#fff"><meta name="msapplication-TileImage" content="/assets/mstile-144x144.png"><meta name="msapplication-config" content="/assets/browserconfig.xml"><link rel="yandex-tableau-widget" href="/assets/yandex-browser-manifest.json"><script defer="defer" src="/main-97c7e184573ca23f9fe4.js"></script><link href="/main-720ce3a11140daa480cc.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div class="b-page-header" id="header"></div><div id="root"></div><script async src="https://widget.kapa.ai/kapa-widget.bundle.js" data-website-id="11a9339d-20ce-4ddb-9ba3-1b6e29afe8eb" data-project-name="dstack" data-project-color="rgba(0, 0, 0, 0.87)" data-font-size-lg="0.78rem" data-button-hide="true" data-modal-image="/logo-notext.svg" data-modal-z-index="1100" data-modal-title="Ask me anything" data-font-family='metro-web, Metro, -apple-system, "system-ui", "Segoe UI", Roboto' data-project-logo="/assets/images/kapa.svg" data-modal-disclaimer="This is a custom LLM for dstack with access to Documentation, API references and GitHub issues. This feature is experimental - Give it a try!" data-user-analytics-fingerprint-enabled="true"></script></body></html>
|
|
3
|
+
"><link rel="icon" type="image/x-icon" href="/assets/favicon.ico"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon-48x48.png"><link rel="manifest" href="/assets/manifest.webmanifest"><meta name="mobile-web-app-capable" content="yes"><meta name="theme-color" content="#fff"><meta name="application-name" content="dstackai"><link rel="apple-touch-icon" sizes="57x57" href="/assets/apple-touch-icon-57x57.png"><link rel="apple-touch-icon" sizes="60x60" href="/assets/apple-touch-icon-60x60.png"><link rel="apple-touch-icon" sizes="72x72" href="/assets/apple-touch-icon-72x72.png"><link rel="apple-touch-icon" sizes="76x76" href="/assets/apple-touch-icon-76x76.png"><link rel="apple-touch-icon" sizes="114x114" href="/assets/apple-touch-icon-114x114.png"><link rel="apple-touch-icon" sizes="120x120" href="/assets/apple-touch-icon-120x120.png"><link rel="apple-touch-icon" sizes="144x144" href="/assets/apple-touch-icon-144x144.png"><link rel="apple-touch-icon" sizes="152x152" href="/assets/apple-touch-icon-152x152.png"><link rel="apple-touch-icon" sizes="167x167" href="/assets/apple-touch-icon-167x167.png"><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-180x180.png"><link rel="apple-touch-icon" sizes="1024x1024" href="/assets/apple-touch-icon-1024x1024.png"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="dstackai"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-640x1136.png"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1136x640.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-750x1334.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1334x750.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1125x2436.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2436x1125.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1170x2532.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2532x1170.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1179x2556.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2556x1179.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-828x1792.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1792x828.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2688.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2688x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2208.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2208x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1284x2778.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2778x1284.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1290x2796.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2796x1290.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1488x2266.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2266x1488.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1536x2048.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2048x1536.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1620x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1620.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1640x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1640.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2388.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2388x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2224.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2224x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-2048x2732.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2732x2048.png"><meta name="msapplication-TileColor" content="#fff"><meta name="msapplication-TileImage" content="/assets/mstile-144x144.png"><meta name="msapplication-config" content="/assets/browserconfig.xml"><link rel="yandex-tableau-widget" href="/assets/yandex-browser-manifest.json"><script defer="defer" src="/main-e79754c136f1d8e4e7e6.js"></script><link href="/main-720ce3a11140daa480cc.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div class="b-page-header" id="header"></div><div id="root"></div><script async src="https://widget.kapa.ai/kapa-widget.bundle.js" data-website-id="11a9339d-20ce-4ddb-9ba3-1b6e29afe8eb" data-project-name="dstack" data-project-color="rgba(0, 0, 0, 0.87)" data-font-size-lg="0.78rem" data-button-hide="true" data-modal-image="/logo-notext.svg" data-modal-z-index="1100" data-modal-title="Ask me anything" data-font-family='metro-web, Metro, -apple-system, "system-ui", "Segoe UI", Roboto' data-project-logo="/assets/images/kapa.svg" data-modal-disclaimer="This is a custom LLM for dstack with access to Documentation, API references and GitHub issues. This feature is experimental - Give it a try!" data-user-analytics-fingerprint-enabled="true"></script></body></html>
|