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.

Files changed (161) hide show
  1. dstack/_internal/cli/commands/__init__.py +2 -2
  2. dstack/_internal/cli/commands/apply.py +3 -61
  3. dstack/_internal/cli/commands/attach.py +1 -1
  4. dstack/_internal/cli/commands/completion.py +1 -1
  5. dstack/_internal/cli/commands/delete.py +2 -2
  6. dstack/_internal/cli/commands/fleet.py +1 -1
  7. dstack/_internal/cli/commands/gateway.py +2 -2
  8. dstack/_internal/cli/commands/init.py +56 -24
  9. dstack/_internal/cli/commands/logs.py +1 -1
  10. dstack/_internal/cli/commands/metrics.py +1 -1
  11. dstack/_internal/cli/commands/offer.py +45 -7
  12. dstack/_internal/cli/commands/project.py +2 -2
  13. dstack/_internal/cli/commands/secrets.py +2 -2
  14. dstack/_internal/cli/commands/server.py +1 -1
  15. dstack/_internal/cli/commands/stop.py +1 -1
  16. dstack/_internal/cli/commands/volume.py +1 -1
  17. dstack/_internal/cli/main.py +2 -2
  18. dstack/_internal/cli/services/completion.py +2 -2
  19. dstack/_internal/cli/services/configurators/__init__.py +6 -2
  20. dstack/_internal/cli/services/configurators/base.py +6 -7
  21. dstack/_internal/cli/services/configurators/fleet.py +1 -3
  22. dstack/_internal/cli/services/configurators/gateway.py +2 -4
  23. dstack/_internal/cli/services/configurators/run.py +293 -58
  24. dstack/_internal/cli/services/configurators/volume.py +2 -4
  25. dstack/_internal/cli/services/profile.py +1 -1
  26. dstack/_internal/cli/services/repos.py +35 -48
  27. dstack/_internal/core/backends/amddevcloud/__init__.py +1 -0
  28. dstack/_internal/core/backends/amddevcloud/backend.py +16 -0
  29. dstack/_internal/core/backends/amddevcloud/compute.py +5 -0
  30. dstack/_internal/core/backends/amddevcloud/configurator.py +29 -0
  31. dstack/_internal/core/backends/aws/compute.py +6 -1
  32. dstack/_internal/core/backends/aws/configurator.py +11 -7
  33. dstack/_internal/core/backends/azure/configurator.py +11 -7
  34. dstack/_internal/core/backends/base/compute.py +33 -5
  35. dstack/_internal/core/backends/base/configurator.py +25 -13
  36. dstack/_internal/core/backends/base/offers.py +2 -0
  37. dstack/_internal/core/backends/cloudrift/configurator.py +13 -7
  38. dstack/_internal/core/backends/configurators.py +15 -0
  39. dstack/_internal/core/backends/cudo/configurator.py +11 -7
  40. dstack/_internal/core/backends/datacrunch/compute.py +5 -1
  41. dstack/_internal/core/backends/datacrunch/configurator.py +13 -7
  42. dstack/_internal/core/backends/digitalocean/__init__.py +1 -0
  43. dstack/_internal/core/backends/digitalocean/backend.py +16 -0
  44. dstack/_internal/core/backends/digitalocean/compute.py +5 -0
  45. dstack/_internal/core/backends/digitalocean/configurator.py +31 -0
  46. dstack/_internal/core/backends/digitalocean_base/__init__.py +1 -0
  47. dstack/_internal/core/backends/digitalocean_base/api_client.py +104 -0
  48. dstack/_internal/core/backends/digitalocean_base/backend.py +5 -0
  49. dstack/_internal/core/backends/digitalocean_base/compute.py +173 -0
  50. dstack/_internal/core/backends/digitalocean_base/configurator.py +57 -0
  51. dstack/_internal/core/backends/digitalocean_base/models.py +43 -0
  52. dstack/_internal/core/backends/gcp/compute.py +32 -8
  53. dstack/_internal/core/backends/gcp/configurator.py +11 -7
  54. dstack/_internal/core/backends/hotaisle/api_client.py +25 -33
  55. dstack/_internal/core/backends/hotaisle/compute.py +1 -6
  56. dstack/_internal/core/backends/hotaisle/configurator.py +13 -7
  57. dstack/_internal/core/backends/kubernetes/configurator.py +13 -7
  58. dstack/_internal/core/backends/lambdalabs/configurator.py +11 -7
  59. dstack/_internal/core/backends/models.py +7 -0
  60. dstack/_internal/core/backends/nebius/compute.py +1 -8
  61. dstack/_internal/core/backends/nebius/configurator.py +11 -7
  62. dstack/_internal/core/backends/nebius/resources.py +21 -11
  63. dstack/_internal/core/backends/oci/compute.py +4 -5
  64. dstack/_internal/core/backends/oci/configurator.py +11 -7
  65. dstack/_internal/core/backends/runpod/configurator.py +11 -7
  66. dstack/_internal/core/backends/template/configurator.py.jinja +11 -7
  67. dstack/_internal/core/backends/tensordock/configurator.py +13 -7
  68. dstack/_internal/core/backends/vastai/configurator.py +11 -7
  69. dstack/_internal/core/backends/vultr/compute.py +1 -5
  70. dstack/_internal/core/backends/vultr/configurator.py +11 -4
  71. dstack/_internal/core/compatibility/fleets.py +5 -0
  72. dstack/_internal/core/compatibility/gpus.py +13 -0
  73. dstack/_internal/core/compatibility/runs.py +9 -1
  74. dstack/_internal/core/models/backends/base.py +5 -1
  75. dstack/_internal/core/models/common.py +3 -3
  76. dstack/_internal/core/models/configurations.py +191 -32
  77. dstack/_internal/core/models/files.py +1 -1
  78. dstack/_internal/core/models/fleets.py +80 -3
  79. dstack/_internal/core/models/profiles.py +41 -11
  80. dstack/_internal/core/models/resources.py +46 -42
  81. dstack/_internal/core/models/runs.py +28 -5
  82. dstack/_internal/core/services/configs/__init__.py +6 -3
  83. dstack/_internal/core/services/profiles.py +2 -2
  84. dstack/_internal/core/services/repos.py +86 -79
  85. dstack/_internal/core/services/ssh/ports.py +1 -1
  86. dstack/_internal/proxy/lib/deps.py +6 -2
  87. dstack/_internal/server/app.py +22 -17
  88. dstack/_internal/server/background/tasks/process_fleets.py +109 -13
  89. dstack/_internal/server/background/tasks/process_gateways.py +4 -1
  90. dstack/_internal/server/background/tasks/process_instances.py +22 -73
  91. dstack/_internal/server/background/tasks/process_probes.py +1 -1
  92. dstack/_internal/server/background/tasks/process_running_jobs.py +12 -4
  93. dstack/_internal/server/background/tasks/process_runs.py +3 -1
  94. dstack/_internal/server/background/tasks/process_submitted_jobs.py +67 -44
  95. dstack/_internal/server/background/tasks/process_terminating_jobs.py +2 -2
  96. dstack/_internal/server/background/tasks/process_volumes.py +1 -1
  97. dstack/_internal/server/db.py +8 -4
  98. dstack/_internal/server/migrations/versions/2498ab323443_add_fleetmodel_consolidation_attempt_.py +44 -0
  99. dstack/_internal/server/models.py +6 -2
  100. dstack/_internal/server/routers/gpus.py +1 -6
  101. dstack/_internal/server/schemas/runner.py +11 -0
  102. dstack/_internal/server/services/backends/__init__.py +14 -8
  103. dstack/_internal/server/services/backends/handlers.py +6 -1
  104. dstack/_internal/server/services/docker.py +5 -5
  105. dstack/_internal/server/services/fleets.py +37 -38
  106. dstack/_internal/server/services/gateways/__init__.py +2 -0
  107. dstack/_internal/server/services/gateways/client.py +5 -2
  108. dstack/_internal/server/services/gateways/connection.py +1 -1
  109. dstack/_internal/server/services/gpus.py +50 -49
  110. dstack/_internal/server/services/instances.py +44 -4
  111. dstack/_internal/server/services/jobs/__init__.py +15 -4
  112. dstack/_internal/server/services/jobs/configurators/base.py +53 -17
  113. dstack/_internal/server/services/jobs/configurators/dev.py +9 -4
  114. dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +6 -8
  115. dstack/_internal/server/services/jobs/configurators/extensions/vscode.py +7 -9
  116. dstack/_internal/server/services/jobs/configurators/service.py +1 -3
  117. dstack/_internal/server/services/jobs/configurators/task.py +3 -3
  118. dstack/_internal/server/services/locking.py +5 -5
  119. dstack/_internal/server/services/logging.py +10 -2
  120. dstack/_internal/server/services/logs/__init__.py +8 -6
  121. dstack/_internal/server/services/logs/aws.py +330 -327
  122. dstack/_internal/server/services/logs/filelog.py +7 -6
  123. dstack/_internal/server/services/logs/gcp.py +141 -139
  124. dstack/_internal/server/services/plugins.py +1 -1
  125. dstack/_internal/server/services/projects.py +2 -5
  126. dstack/_internal/server/services/proxy/repo.py +5 -1
  127. dstack/_internal/server/services/requirements/__init__.py +0 -0
  128. dstack/_internal/server/services/requirements/combine.py +259 -0
  129. dstack/_internal/server/services/runner/client.py +7 -0
  130. dstack/_internal/server/services/runs.py +17 -1
  131. dstack/_internal/server/services/services/__init__.py +8 -2
  132. dstack/_internal/server/services/services/autoscalers.py +2 -0
  133. dstack/_internal/server/services/ssh.py +2 -1
  134. dstack/_internal/server/services/storage/__init__.py +5 -6
  135. dstack/_internal/server/services/storage/gcs.py +49 -49
  136. dstack/_internal/server/services/storage/s3.py +52 -52
  137. dstack/_internal/server/statics/index.html +1 -1
  138. dstack/_internal/server/statics/{main-d151b300fcac3933213d.js → main-4eecc75fbe64067eb1bc.js} +1146 -899
  139. dstack/_internal/server/statics/{main-d151b300fcac3933213d.js.map → main-4eecc75fbe64067eb1bc.js.map} +1 -1
  140. dstack/_internal/server/statics/{main-aec4762350e34d6fbff9.css → main-56191c63d516fd0041c4.css} +1 -1
  141. dstack/_internal/server/testing/common.py +7 -4
  142. dstack/_internal/server/utils/logging.py +3 -3
  143. dstack/_internal/server/utils/provisioning.py +3 -3
  144. dstack/_internal/utils/json_schema.py +3 -1
  145. dstack/_internal/utils/path.py +8 -1
  146. dstack/_internal/utils/ssh.py +7 -0
  147. dstack/_internal/utils/typing.py +14 -0
  148. dstack/api/_public/repos.py +62 -8
  149. dstack/api/_public/runs.py +19 -8
  150. dstack/api/server/__init__.py +17 -19
  151. dstack/api/server/_gpus.py +2 -1
  152. dstack/api/server/_group.py +4 -3
  153. dstack/api/server/_repos.py +20 -3
  154. dstack/plugins/builtin/rest_plugin/_plugin.py +1 -0
  155. dstack/version.py +1 -1
  156. {dstack-0.19.25rc1.dist-info → dstack-0.19.27.dist-info}/METADATA +2 -2
  157. {dstack-0.19.25rc1.dist-info → dstack-0.19.27.dist-info}/RECORD +160 -142
  158. dstack/api/huggingface/__init__.py +0 -73
  159. {dstack-0.19.25rc1.dist-info → dstack-0.19.27.dist-info}/WHEEL +0 -0
  160. {dstack-0.19.25rc1.dist-info → dstack-0.19.27.dist-info}/entry_points.txt +0 -0
  161. {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=urlparse(service_spec.url).hostname,
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
- class GCSStorage(BaseStorage):
14
- def __init__(
15
- self,
16
- bucket: str,
17
- ):
18
- self._client = storage.Client()
19
- self._bucket = self._client.bucket(bucket)
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
- 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)
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
- 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)
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
- 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)
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
- 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)
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
- def _upload(self, key: str, blob: bytes):
58
- blob_obj = self._bucket.blob(key)
59
- blob_obj.upload_from_string(blob)
60
-
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()
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
- 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
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
- 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)
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
- 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)
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
- 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)
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
- 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)
59
+ def _upload(self, key: str, blob: bytes):
60
+ self._client.put_object(Bucket=self.bucket, Key=key, Body=blob)
58
61
 
59
- def _upload(self, key: str, blob: bytes):
60
- self._client.put_object(Bucket=self.bucket, Key=key, Body=blob)
61
-
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()
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>