dstack 0.19.25rc1__py3-none-any.whl → 0.19.27__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/commands/__init__.py +2 -2
- dstack/_internal/cli/commands/apply.py +3 -61
- dstack/_internal/cli/commands/attach.py +1 -1
- dstack/_internal/cli/commands/completion.py +1 -1
- dstack/_internal/cli/commands/delete.py +2 -2
- dstack/_internal/cli/commands/fleet.py +1 -1
- dstack/_internal/cli/commands/gateway.py +2 -2
- dstack/_internal/cli/commands/init.py +56 -24
- dstack/_internal/cli/commands/logs.py +1 -1
- dstack/_internal/cli/commands/metrics.py +1 -1
- dstack/_internal/cli/commands/offer.py +45 -7
- dstack/_internal/cli/commands/project.py +2 -2
- dstack/_internal/cli/commands/secrets.py +2 -2
- dstack/_internal/cli/commands/server.py +1 -1
- dstack/_internal/cli/commands/stop.py +1 -1
- dstack/_internal/cli/commands/volume.py +1 -1
- dstack/_internal/cli/main.py +2 -2
- dstack/_internal/cli/services/completion.py +2 -2
- dstack/_internal/cli/services/configurators/__init__.py +6 -2
- dstack/_internal/cli/services/configurators/base.py +6 -7
- dstack/_internal/cli/services/configurators/fleet.py +1 -3
- dstack/_internal/cli/services/configurators/gateway.py +2 -4
- dstack/_internal/cli/services/configurators/run.py +293 -58
- dstack/_internal/cli/services/configurators/volume.py +2 -4
- dstack/_internal/cli/services/profile.py +1 -1
- dstack/_internal/cli/services/repos.py +35 -48
- dstack/_internal/core/backends/amddevcloud/__init__.py +1 -0
- dstack/_internal/core/backends/amddevcloud/backend.py +16 -0
- dstack/_internal/core/backends/amddevcloud/compute.py +5 -0
- dstack/_internal/core/backends/amddevcloud/configurator.py +29 -0
- dstack/_internal/core/backends/aws/compute.py +6 -1
- dstack/_internal/core/backends/aws/configurator.py +11 -7
- dstack/_internal/core/backends/azure/configurator.py +11 -7
- dstack/_internal/core/backends/base/compute.py +33 -5
- dstack/_internal/core/backends/base/configurator.py +25 -13
- dstack/_internal/core/backends/base/offers.py +2 -0
- dstack/_internal/core/backends/cloudrift/configurator.py +13 -7
- dstack/_internal/core/backends/configurators.py +15 -0
- dstack/_internal/core/backends/cudo/configurator.py +11 -7
- dstack/_internal/core/backends/datacrunch/compute.py +5 -1
- dstack/_internal/core/backends/datacrunch/configurator.py +13 -7
- dstack/_internal/core/backends/digitalocean/__init__.py +1 -0
- dstack/_internal/core/backends/digitalocean/backend.py +16 -0
- dstack/_internal/core/backends/digitalocean/compute.py +5 -0
- dstack/_internal/core/backends/digitalocean/configurator.py +31 -0
- dstack/_internal/core/backends/digitalocean_base/__init__.py +1 -0
- dstack/_internal/core/backends/digitalocean_base/api_client.py +104 -0
- dstack/_internal/core/backends/digitalocean_base/backend.py +5 -0
- dstack/_internal/core/backends/digitalocean_base/compute.py +173 -0
- dstack/_internal/core/backends/digitalocean_base/configurator.py +57 -0
- dstack/_internal/core/backends/digitalocean_base/models.py +43 -0
- dstack/_internal/core/backends/gcp/compute.py +32 -8
- dstack/_internal/core/backends/gcp/configurator.py +11 -7
- dstack/_internal/core/backends/hotaisle/api_client.py +25 -33
- dstack/_internal/core/backends/hotaisle/compute.py +1 -6
- dstack/_internal/core/backends/hotaisle/configurator.py +13 -7
- dstack/_internal/core/backends/kubernetes/configurator.py +13 -7
- dstack/_internal/core/backends/lambdalabs/configurator.py +11 -7
- dstack/_internal/core/backends/models.py +7 -0
- dstack/_internal/core/backends/nebius/compute.py +1 -8
- dstack/_internal/core/backends/nebius/configurator.py +11 -7
- dstack/_internal/core/backends/nebius/resources.py +21 -11
- dstack/_internal/core/backends/oci/compute.py +4 -5
- dstack/_internal/core/backends/oci/configurator.py +11 -7
- dstack/_internal/core/backends/runpod/configurator.py +11 -7
- dstack/_internal/core/backends/template/configurator.py.jinja +11 -7
- dstack/_internal/core/backends/tensordock/configurator.py +13 -7
- dstack/_internal/core/backends/vastai/configurator.py +11 -7
- dstack/_internal/core/backends/vultr/compute.py +1 -5
- dstack/_internal/core/backends/vultr/configurator.py +11 -4
- dstack/_internal/core/compatibility/fleets.py +5 -0
- dstack/_internal/core/compatibility/gpus.py +13 -0
- dstack/_internal/core/compatibility/runs.py +9 -1
- dstack/_internal/core/models/backends/base.py +5 -1
- dstack/_internal/core/models/common.py +3 -3
- dstack/_internal/core/models/configurations.py +191 -32
- dstack/_internal/core/models/files.py +1 -1
- dstack/_internal/core/models/fleets.py +80 -3
- dstack/_internal/core/models/profiles.py +41 -11
- dstack/_internal/core/models/resources.py +46 -42
- dstack/_internal/core/models/runs.py +28 -5
- dstack/_internal/core/services/configs/__init__.py +6 -3
- dstack/_internal/core/services/profiles.py +2 -2
- dstack/_internal/core/services/repos.py +86 -79
- dstack/_internal/core/services/ssh/ports.py +1 -1
- dstack/_internal/proxy/lib/deps.py +6 -2
- dstack/_internal/server/app.py +22 -17
- dstack/_internal/server/background/tasks/process_fleets.py +109 -13
- dstack/_internal/server/background/tasks/process_gateways.py +4 -1
- dstack/_internal/server/background/tasks/process_instances.py +22 -73
- dstack/_internal/server/background/tasks/process_probes.py +1 -1
- dstack/_internal/server/background/tasks/process_running_jobs.py +12 -4
- dstack/_internal/server/background/tasks/process_runs.py +3 -1
- dstack/_internal/server/background/tasks/process_submitted_jobs.py +67 -44
- dstack/_internal/server/background/tasks/process_terminating_jobs.py +2 -2
- dstack/_internal/server/background/tasks/process_volumes.py +1 -1
- dstack/_internal/server/db.py +8 -4
- dstack/_internal/server/migrations/versions/2498ab323443_add_fleetmodel_consolidation_attempt_.py +44 -0
- dstack/_internal/server/models.py +6 -2
- dstack/_internal/server/routers/gpus.py +1 -6
- dstack/_internal/server/schemas/runner.py +11 -0
- dstack/_internal/server/services/backends/__init__.py +14 -8
- dstack/_internal/server/services/backends/handlers.py +6 -1
- dstack/_internal/server/services/docker.py +5 -5
- dstack/_internal/server/services/fleets.py +37 -38
- dstack/_internal/server/services/gateways/__init__.py +2 -0
- dstack/_internal/server/services/gateways/client.py +5 -2
- dstack/_internal/server/services/gateways/connection.py +1 -1
- dstack/_internal/server/services/gpus.py +50 -49
- dstack/_internal/server/services/instances.py +44 -4
- dstack/_internal/server/services/jobs/__init__.py +15 -4
- dstack/_internal/server/services/jobs/configurators/base.py +53 -17
- dstack/_internal/server/services/jobs/configurators/dev.py +9 -4
- dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +6 -8
- dstack/_internal/server/services/jobs/configurators/extensions/vscode.py +7 -9
- dstack/_internal/server/services/jobs/configurators/service.py +1 -3
- dstack/_internal/server/services/jobs/configurators/task.py +3 -3
- dstack/_internal/server/services/locking.py +5 -5
- dstack/_internal/server/services/logging.py +10 -2
- dstack/_internal/server/services/logs/__init__.py +8 -6
- dstack/_internal/server/services/logs/aws.py +330 -327
- dstack/_internal/server/services/logs/filelog.py +7 -6
- dstack/_internal/server/services/logs/gcp.py +141 -139
- dstack/_internal/server/services/plugins.py +1 -1
- dstack/_internal/server/services/projects.py +2 -5
- dstack/_internal/server/services/proxy/repo.py +5 -1
- dstack/_internal/server/services/requirements/__init__.py +0 -0
- dstack/_internal/server/services/requirements/combine.py +259 -0
- dstack/_internal/server/services/runner/client.py +7 -0
- dstack/_internal/server/services/runs.py +17 -1
- dstack/_internal/server/services/services/__init__.py +8 -2
- dstack/_internal/server/services/services/autoscalers.py +2 -0
- dstack/_internal/server/services/ssh.py +2 -1
- dstack/_internal/server/services/storage/__init__.py +5 -6
- dstack/_internal/server/services/storage/gcs.py +49 -49
- dstack/_internal/server/services/storage/s3.py +52 -52
- dstack/_internal/server/statics/index.html +1 -1
- dstack/_internal/server/statics/{main-d151b300fcac3933213d.js → main-4eecc75fbe64067eb1bc.js} +1146 -899
- dstack/_internal/server/statics/{main-d151b300fcac3933213d.js.map → main-4eecc75fbe64067eb1bc.js.map} +1 -1
- dstack/_internal/server/statics/{main-aec4762350e34d6fbff9.css → main-56191c63d516fd0041c4.css} +1 -1
- dstack/_internal/server/testing/common.py +7 -4
- dstack/_internal/server/utils/logging.py +3 -3
- dstack/_internal/server/utils/provisioning.py +3 -3
- dstack/_internal/utils/json_schema.py +3 -1
- dstack/_internal/utils/path.py +8 -1
- dstack/_internal/utils/ssh.py +7 -0
- dstack/_internal/utils/typing.py +14 -0
- dstack/api/_public/repos.py +62 -8
- dstack/api/_public/runs.py +19 -8
- dstack/api/server/__init__.py +17 -19
- dstack/api/server/_gpus.py +2 -1
- dstack/api/server/_group.py +4 -3
- dstack/api/server/_repos.py +20 -3
- dstack/plugins/builtin/rest_plugin/_plugin.py +1 -0
- dstack/version.py +1 -1
- {dstack-0.19.25rc1.dist-info → dstack-0.19.27.dist-info}/METADATA +2 -2
- {dstack-0.19.25rc1.dist-info → dstack-0.19.27.dist-info}/RECORD +160 -142
- dstack/api/huggingface/__init__.py +0 -73
- {dstack-0.19.25rc1.dist-info → dstack-0.19.27.dist-info}/WHEEL +0 -0
- {dstack-0.19.25rc1.dist-info → dstack-0.19.27.dist-info}/entry_points.txt +0 -0
- {dstack-0.19.25rc1.dist-info → dstack-0.19.27.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -26,6 +26,7 @@ from dstack._internal.server.schemas.runner import (
|
|
|
26
26
|
ShimVolumeInfo,
|
|
27
27
|
SubmitBody,
|
|
28
28
|
TaskInfoResponse,
|
|
29
|
+
TaskListResponse,
|
|
29
30
|
TaskSubmitRequest,
|
|
30
31
|
TaskTerminateRequest,
|
|
31
32
|
)
|
|
@@ -245,6 +246,12 @@ class ShimClient:
|
|
|
245
246
|
self._raise_for_status(resp)
|
|
246
247
|
return self._response(InstanceHealthResponse, resp)
|
|
247
248
|
|
|
249
|
+
def list_tasks(self) -> TaskListResponse:
|
|
250
|
+
if not self.is_api_v2_supported():
|
|
251
|
+
raise ShimAPIVersionError()
|
|
252
|
+
resp = self._request("GET", "/api/tasks", raise_for_status=True)
|
|
253
|
+
return self._response(TaskListResponse, resp)
|
|
254
|
+
|
|
248
255
|
def get_task(self, task_id: "_TaskID") -> TaskInfoResponse:
|
|
249
256
|
if not self.is_api_v2_supported():
|
|
250
257
|
raise ShimAPIVersionError()
|
|
@@ -43,6 +43,7 @@ from dstack._internal.core.models.runs import (
|
|
|
43
43
|
JobTerminationReason,
|
|
44
44
|
ProbeSpec,
|
|
45
45
|
Run,
|
|
46
|
+
RunFleet,
|
|
46
47
|
RunPlan,
|
|
47
48
|
RunSpec,
|
|
48
49
|
RunStatus,
|
|
@@ -58,6 +59,7 @@ from dstack._internal.core.services.diff import diff_models
|
|
|
58
59
|
from dstack._internal.server import settings
|
|
59
60
|
from dstack._internal.server.db import get_db
|
|
60
61
|
from dstack._internal.server.models import (
|
|
62
|
+
FleetModel,
|
|
61
63
|
JobModel,
|
|
62
64
|
ProbeModel,
|
|
63
65
|
ProjectModel,
|
|
@@ -227,6 +229,7 @@ async def list_projects_run_models(
|
|
|
227
229
|
select(RunModel)
|
|
228
230
|
.where(*filters)
|
|
229
231
|
.options(joinedload(RunModel.user).load_only(UserModel.name))
|
|
232
|
+
.options(joinedload(RunModel.fleet).load_only(FleetModel.id, FleetModel.name))
|
|
230
233
|
.options(selectinload(RunModel.jobs).joinedload(JobModel.probes))
|
|
231
234
|
.order_by(*order_by)
|
|
232
235
|
.limit(limit)
|
|
@@ -269,6 +272,7 @@ async def get_run_by_name(
|
|
|
269
272
|
RunModel.deleted == False,
|
|
270
273
|
)
|
|
271
274
|
.options(joinedload(RunModel.user))
|
|
275
|
+
.options(joinedload(RunModel.fleet).load_only(FleetModel.id, FleetModel.name))
|
|
272
276
|
.options(selectinload(RunModel.jobs).joinedload(JobModel.probes))
|
|
273
277
|
)
|
|
274
278
|
run_model = res.scalar()
|
|
@@ -289,6 +293,7 @@ async def get_run_by_id(
|
|
|
289
293
|
RunModel.id == run_id,
|
|
290
294
|
)
|
|
291
295
|
.options(joinedload(RunModel.user))
|
|
296
|
+
.options(joinedload(RunModel.fleet).load_only(FleetModel.id, FleetModel.name))
|
|
292
297
|
.options(selectinload(RunModel.jobs).joinedload(JobModel.probes))
|
|
293
298
|
)
|
|
294
299
|
run_model = res.scalar()
|
|
@@ -529,7 +534,7 @@ async def submit_run(
|
|
|
529
534
|
initial_status = RunStatus.PENDING
|
|
530
535
|
initial_replicas = 0
|
|
531
536
|
elif run_spec.configuration.type == "service":
|
|
532
|
-
initial_replicas = run_spec.configuration.replicas.min
|
|
537
|
+
initial_replicas = run_spec.configuration.replicas.min or 0
|
|
533
538
|
|
|
534
539
|
run_model = RunModel(
|
|
535
540
|
id=uuid.uuid4(),
|
|
@@ -709,10 +714,12 @@ def run_model_to_run(
|
|
|
709
714
|
|
|
710
715
|
status_message = _get_run_status_message(run_model)
|
|
711
716
|
error = _get_run_error(run_model)
|
|
717
|
+
fleet = _get_run_fleet(run_model)
|
|
712
718
|
run = Run(
|
|
713
719
|
id=run_model.id,
|
|
714
720
|
project_name=run_model.project.name,
|
|
715
721
|
user=run_model.user.name,
|
|
722
|
+
fleet=fleet,
|
|
716
723
|
submitted_at=run_model.submitted_at,
|
|
717
724
|
last_processed_at=run_model.last_processed_at,
|
|
718
725
|
status=run_model.status,
|
|
@@ -821,6 +828,15 @@ def _get_run_error(run_model: RunModel) -> Optional[str]:
|
|
|
821
828
|
return run_model.termination_reason.to_error()
|
|
822
829
|
|
|
823
830
|
|
|
831
|
+
def _get_run_fleet(run_model: RunModel) -> Optional[RunFleet]:
|
|
832
|
+
if run_model.fleet is None:
|
|
833
|
+
return None
|
|
834
|
+
return RunFleet(
|
|
835
|
+
id=run_model.fleet.id,
|
|
836
|
+
name=run_model.fleet.name,
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
|
|
824
840
|
async def _get_pool_offers(
|
|
825
841
|
session: AsyncSession,
|
|
826
842
|
project: ProjectModel,
|
|
@@ -5,7 +5,6 @@ Application logic related to `type: service` runs.
|
|
|
5
5
|
import uuid
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from typing import Optional
|
|
8
|
-
from urllib.parse import urlparse
|
|
9
8
|
|
|
10
9
|
import httpx
|
|
11
10
|
from sqlalchemy import select
|
|
@@ -73,6 +72,8 @@ async def register_service(session: AsyncSession, run_model: RunModel, run_spec:
|
|
|
73
72
|
async def _register_service_in_gateway(
|
|
74
73
|
session: AsyncSession, run_model: RunModel, run_spec: RunSpec, gateway: GatewayModel
|
|
75
74
|
) -> ServiceSpec:
|
|
75
|
+
assert run_spec.configuration.type == "service"
|
|
76
|
+
|
|
76
77
|
if gateway.gateway_compute is None:
|
|
77
78
|
raise ServerClientError("Gateway has no instance associated with it")
|
|
78
79
|
|
|
@@ -100,6 +101,9 @@ async def _register_service_in_gateway(
|
|
|
100
101
|
model_url=f"{gateway_protocol}://gateway.{wildcard_domain}",
|
|
101
102
|
)
|
|
102
103
|
|
|
104
|
+
domain = service_spec.get_domain()
|
|
105
|
+
assert domain is not None
|
|
106
|
+
|
|
103
107
|
conn = await get_or_add_gateway_connection(session, gateway.id)
|
|
104
108
|
try:
|
|
105
109
|
logger.debug("%s: registering service as %s", fmt(run_model), service_spec.url)
|
|
@@ -107,7 +111,7 @@ async def _register_service_in_gateway(
|
|
|
107
111
|
await client.register_service(
|
|
108
112
|
project=run_model.project.name,
|
|
109
113
|
run_name=run_model.run_name,
|
|
110
|
-
domain=
|
|
114
|
+
domain=domain,
|
|
111
115
|
service_https=service_https,
|
|
112
116
|
gateway_https=gateway_https,
|
|
113
117
|
auth=run_spec.configuration.auth,
|
|
@@ -127,6 +131,7 @@ async def _register_service_in_gateway(
|
|
|
127
131
|
|
|
128
132
|
|
|
129
133
|
def _register_service_in_server(run_model: RunModel, run_spec: RunSpec) -> ServiceSpec:
|
|
134
|
+
assert run_spec.configuration.type == "service"
|
|
130
135
|
if run_spec.configuration.https != SERVICE_HTTPS_DEFAULT:
|
|
131
136
|
# Note: if the user sets `https: <default-value>`, it will be ignored silently
|
|
132
137
|
# TODO: in 0.19, make `https` Optional to be able to tell if it was set or omitted
|
|
@@ -270,6 +275,7 @@ async def unregister_replica(session: AsyncSession, job_model: JobModel):
|
|
|
270
275
|
|
|
271
276
|
|
|
272
277
|
def _get_service_https(run_spec: RunSpec, configuration: GatewayConfiguration) -> bool:
|
|
278
|
+
assert run_spec.configuration.type == "service"
|
|
273
279
|
if not run_spec.configuration.https:
|
|
274
280
|
return False
|
|
275
281
|
if configuration.certificate is not None and configuration.certificate.type == "acm":
|
|
@@ -120,6 +120,8 @@ class RPSAutoscaler(BaseServiceScaler):
|
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
def get_service_scaler(conf: ServiceConfiguration) -> BaseServiceScaler:
|
|
123
|
+
assert conf.replicas.min is not None
|
|
124
|
+
assert conf.replicas.max is not None
|
|
123
125
|
if conf.scaling is None:
|
|
124
126
|
return ManualScaler(
|
|
125
127
|
min_replicas=conf.replicas.min,
|
|
@@ -20,10 +20,11 @@ def container_ssh_tunnel(
|
|
|
20
20
|
"""
|
|
21
21
|
Build SSHTunnel for connecting to the container running the specified job.
|
|
22
22
|
"""
|
|
23
|
-
|
|
24
23
|
jpd: JobProvisioningData = JobProvisioningData.__response__.parse_raw(
|
|
25
24
|
job.job_provisioning_data
|
|
26
25
|
)
|
|
26
|
+
assert jpd.hostname is not None
|
|
27
|
+
assert jpd.ssh_port is not None
|
|
27
28
|
if not jpd.dockerized:
|
|
28
29
|
ssh_destination = f"{jpd.username}@{jpd.hostname}"
|
|
29
30
|
ssh_port = jpd.ssh_port
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
3
|
from dstack._internal.server import settings
|
|
4
|
+
from dstack._internal.server.services.storage import gcs, s3
|
|
4
5
|
from dstack._internal.server.services.storage.base import BaseStorage
|
|
5
|
-
from dstack._internal.server.services.storage.gcs import GCS_AVAILABLE, GCSStorage
|
|
6
|
-
from dstack._internal.server.services.storage.s3 import BOTO_AVAILABLE, S3Storage
|
|
7
6
|
|
|
8
7
|
_default_storage = None
|
|
9
8
|
|
|
@@ -20,16 +19,16 @@ def init_default_storage():
|
|
|
20
19
|
)
|
|
21
20
|
|
|
22
21
|
if settings.SERVER_S3_BUCKET:
|
|
23
|
-
if not BOTO_AVAILABLE:
|
|
22
|
+
if not s3.BOTO_AVAILABLE:
|
|
24
23
|
raise ValueError("AWS dependencies are not installed")
|
|
25
|
-
_default_storage = S3Storage(
|
|
24
|
+
_default_storage = s3.S3Storage(
|
|
26
25
|
bucket=settings.SERVER_S3_BUCKET,
|
|
27
26
|
region=settings.SERVER_S3_BUCKET_REGION,
|
|
28
27
|
)
|
|
29
28
|
elif settings.SERVER_GCS_BUCKET:
|
|
30
|
-
if not GCS_AVAILABLE:
|
|
29
|
+
if not gcs.GCS_AVAILABLE:
|
|
31
30
|
raise ValueError("GCS dependencies are not installed")
|
|
32
|
-
_default_storage = GCSStorage(
|
|
31
|
+
_default_storage = gcs.GCSStorage(
|
|
33
32
|
bucket=settings.SERVER_GCS_BUCKET,
|
|
34
33
|
)
|
|
35
34
|
|
|
@@ -8,59 +8,59 @@ try:
|
|
|
8
8
|
from google.cloud.exceptions import NotFound
|
|
9
9
|
except ImportError:
|
|
10
10
|
GCS_AVAILABLE = False
|
|
11
|
+
else:
|
|
11
12
|
|
|
13
|
+
class GCSStorage(BaseStorage):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
bucket: str,
|
|
17
|
+
):
|
|
18
|
+
self._client = storage.Client()
|
|
19
|
+
self._bucket = self._client.bucket(bucket)
|
|
12
20
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
def upload_code(
|
|
22
|
+
self,
|
|
23
|
+
project_id: str,
|
|
24
|
+
repo_id: str,
|
|
25
|
+
code_hash: str,
|
|
26
|
+
blob: bytes,
|
|
27
|
+
):
|
|
28
|
+
key = self._get_code_key(project_id, repo_id, code_hash)
|
|
29
|
+
self._upload(key, blob)
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
self._upload(key, blob)
|
|
31
|
+
def get_code(
|
|
32
|
+
self,
|
|
33
|
+
project_id: str,
|
|
34
|
+
repo_id: str,
|
|
35
|
+
code_hash: str,
|
|
36
|
+
) -> Optional[bytes]:
|
|
37
|
+
key = self._get_code_key(project_id, repo_id, code_hash)
|
|
38
|
+
return self._get(key)
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
def upload_archive(
|
|
41
|
+
self,
|
|
42
|
+
user_id: str,
|
|
43
|
+
archive_hash: str,
|
|
44
|
+
blob: bytes,
|
|
45
|
+
):
|
|
46
|
+
key = self._get_archive_key(user_id, archive_hash)
|
|
47
|
+
self._upload(key, blob)
|
|
39
48
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
self._upload(key, blob)
|
|
49
|
+
def get_archive(
|
|
50
|
+
self,
|
|
51
|
+
user_id: str,
|
|
52
|
+
archive_hash: str,
|
|
53
|
+
) -> Optional[bytes]:
|
|
54
|
+
key = self._get_archive_key(user_id, archive_hash)
|
|
55
|
+
return self._get(key)
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
archive_hash: str,
|
|
53
|
-
) -> Optional[bytes]:
|
|
54
|
-
key = self._get_archive_key(user_id, archive_hash)
|
|
55
|
-
return self._get(key)
|
|
57
|
+
def _upload(self, key: str, blob: bytes):
|
|
58
|
+
blob_obj = self._bucket.blob(key)
|
|
59
|
+
blob_obj.upload_from_string(blob)
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
blob = self._bucket.blob(key)
|
|
64
|
-
except NotFound:
|
|
65
|
-
return None
|
|
66
|
-
return blob.download_as_bytes()
|
|
61
|
+
def _get(self, key: str) -> Optional[bytes]:
|
|
62
|
+
try:
|
|
63
|
+
blob = self._bucket.blob(key)
|
|
64
|
+
except NotFound:
|
|
65
|
+
return None
|
|
66
|
+
return blob.download_as_bytes()
|
|
@@ -8,62 +8,62 @@ try:
|
|
|
8
8
|
from boto3 import Session
|
|
9
9
|
except ImportError:
|
|
10
10
|
BOTO_AVAILABLE = False
|
|
11
|
+
else:
|
|
11
12
|
|
|
13
|
+
class S3Storage(BaseStorage):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
bucket: str,
|
|
17
|
+
region: Optional[str] = None,
|
|
18
|
+
):
|
|
19
|
+
self._session = Session()
|
|
20
|
+
self._client = self._session.client("s3", region_name=region)
|
|
21
|
+
self.bucket = bucket
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
def upload_code(
|
|
24
|
+
self,
|
|
25
|
+
project_id: str,
|
|
26
|
+
repo_id: str,
|
|
27
|
+
code_hash: str,
|
|
28
|
+
blob: bytes,
|
|
29
|
+
):
|
|
30
|
+
key = self._get_code_key(project_id, repo_id, code_hash)
|
|
31
|
+
self._upload(key, blob)
|
|
22
32
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
self._upload(key, blob)
|
|
33
|
+
def get_code(
|
|
34
|
+
self,
|
|
35
|
+
project_id: str,
|
|
36
|
+
repo_id: str,
|
|
37
|
+
code_hash: str,
|
|
38
|
+
) -> Optional[bytes]:
|
|
39
|
+
key = self._get_code_key(project_id, repo_id, code_hash)
|
|
40
|
+
return self._get(key)
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
def upload_archive(
|
|
43
|
+
self,
|
|
44
|
+
user_id: str,
|
|
45
|
+
archive_hash: str,
|
|
46
|
+
blob: bytes,
|
|
47
|
+
):
|
|
48
|
+
key = self._get_archive_key(user_id, archive_hash)
|
|
49
|
+
self._upload(key, blob)
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
self._upload(key, blob)
|
|
51
|
+
def get_archive(
|
|
52
|
+
self,
|
|
53
|
+
user_id: str,
|
|
54
|
+
archive_hash: str,
|
|
55
|
+
) -> Optional[bytes]:
|
|
56
|
+
key = self._get_archive_key(user_id, archive_hash)
|
|
57
|
+
return self._get(key)
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
user_id: str,
|
|
54
|
-
archive_hash: str,
|
|
55
|
-
) -> Optional[bytes]:
|
|
56
|
-
key = self._get_archive_key(user_id, archive_hash)
|
|
57
|
-
return self._get(key)
|
|
59
|
+
def _upload(self, key: str, blob: bytes):
|
|
60
|
+
self._client.put_object(Bucket=self.bucket, Key=key, Body=blob)
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return None
|
|
68
|
-
raise e
|
|
69
|
-
return response["Body"].read()
|
|
62
|
+
def _get(self, key: str) -> Optional[bytes]:
|
|
63
|
+
try:
|
|
64
|
+
response = self._client.get_object(Bucket=self.bucket, Key=key)
|
|
65
|
+
except botocore.exceptions.ClientError as e:
|
|
66
|
+
if e.response["Error"]["Code"] == "NoSuchKey":
|
|
67
|
+
return None
|
|
68
|
+
raise e
|
|
69
|
+
return response["Body"].read()
|
|
@@ -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-d151b300fcac3933213d.js"></script><link href="/main-aec4762350e34d6fbff9.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 src="https://widget.kapa.ai/kapa-widget.bundle.js" data-website-id="1c028b8e-609e-40e3-b792-f3939a461850" 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-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" async></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-4eecc75fbe64067eb1bc.js"></script><link href="/main-56191c63d516fd0041c4.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-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>
|