dstack 0.19.26__py3-none-any.whl → 0.19.28__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 (93) hide show
  1. dstack/_internal/cli/commands/__init__.py +11 -8
  2. dstack/_internal/cli/commands/apply.py +6 -3
  3. dstack/_internal/cli/commands/completion.py +3 -1
  4. dstack/_internal/cli/commands/config.py +1 -0
  5. dstack/_internal/cli/commands/init.py +4 -4
  6. dstack/_internal/cli/commands/offer.py +1 -1
  7. dstack/_internal/cli/commands/project.py +1 -0
  8. dstack/_internal/cli/commands/server.py +2 -2
  9. dstack/_internal/cli/main.py +1 -1
  10. dstack/_internal/cli/services/configurators/base.py +2 -4
  11. dstack/_internal/cli/services/configurators/fleet.py +4 -5
  12. dstack/_internal/cli/services/configurators/gateway.py +3 -5
  13. dstack/_internal/cli/services/configurators/run.py +165 -43
  14. dstack/_internal/cli/services/configurators/volume.py +3 -5
  15. dstack/_internal/cli/services/repos.py +1 -18
  16. dstack/_internal/core/backends/amddevcloud/__init__.py +1 -0
  17. dstack/_internal/core/backends/amddevcloud/backend.py +16 -0
  18. dstack/_internal/core/backends/amddevcloud/compute.py +5 -0
  19. dstack/_internal/core/backends/amddevcloud/configurator.py +29 -0
  20. dstack/_internal/core/backends/aws/compute.py +6 -1
  21. dstack/_internal/core/backends/base/compute.py +33 -5
  22. dstack/_internal/core/backends/base/offers.py +2 -0
  23. dstack/_internal/core/backends/configurators.py +15 -0
  24. dstack/_internal/core/backends/digitalocean/__init__.py +1 -0
  25. dstack/_internal/core/backends/digitalocean/backend.py +16 -0
  26. dstack/_internal/core/backends/digitalocean/compute.py +5 -0
  27. dstack/_internal/core/backends/digitalocean/configurator.py +31 -0
  28. dstack/_internal/core/backends/digitalocean_base/__init__.py +1 -0
  29. dstack/_internal/core/backends/digitalocean_base/api_client.py +104 -0
  30. dstack/_internal/core/backends/digitalocean_base/backend.py +5 -0
  31. dstack/_internal/core/backends/digitalocean_base/compute.py +173 -0
  32. dstack/_internal/core/backends/digitalocean_base/configurator.py +57 -0
  33. dstack/_internal/core/backends/digitalocean_base/models.py +43 -0
  34. dstack/_internal/core/backends/gcp/compute.py +32 -8
  35. dstack/_internal/core/backends/hotaisle/api_client.py +25 -33
  36. dstack/_internal/core/backends/hotaisle/compute.py +1 -6
  37. dstack/_internal/core/backends/models.py +7 -0
  38. dstack/_internal/core/backends/nebius/compute.py +0 -7
  39. dstack/_internal/core/backends/oci/compute.py +4 -5
  40. dstack/_internal/core/backends/vultr/compute.py +1 -5
  41. dstack/_internal/core/compatibility/fleets.py +5 -0
  42. dstack/_internal/core/compatibility/runs.py +10 -1
  43. dstack/_internal/core/models/backends/base.py +5 -1
  44. dstack/_internal/core/models/common.py +67 -43
  45. dstack/_internal/core/models/configurations.py +109 -69
  46. dstack/_internal/core/models/files.py +1 -1
  47. dstack/_internal/core/models/fleets.py +115 -25
  48. dstack/_internal/core/models/instances.py +5 -5
  49. dstack/_internal/core/models/profiles.py +66 -47
  50. dstack/_internal/core/models/repos/remote.py +21 -16
  51. dstack/_internal/core/models/resources.py +69 -65
  52. dstack/_internal/core/models/runs.py +41 -14
  53. dstack/_internal/core/services/repos.py +85 -80
  54. dstack/_internal/server/app.py +5 -0
  55. dstack/_internal/server/background/tasks/process_fleets.py +117 -13
  56. dstack/_internal/server/background/tasks/process_instances.py +12 -71
  57. dstack/_internal/server/background/tasks/process_running_jobs.py +2 -0
  58. dstack/_internal/server/background/tasks/process_runs.py +2 -0
  59. dstack/_internal/server/background/tasks/process_submitted_jobs.py +48 -16
  60. dstack/_internal/server/migrations/versions/2498ab323443_add_fleetmodel_consolidation_attempt_.py +44 -0
  61. dstack/_internal/server/models.py +11 -7
  62. dstack/_internal/server/schemas/gateways.py +10 -9
  63. dstack/_internal/server/schemas/runner.py +1 -0
  64. dstack/_internal/server/services/backends/handlers.py +2 -0
  65. dstack/_internal/server/services/docker.py +8 -7
  66. dstack/_internal/server/services/fleets.py +23 -25
  67. dstack/_internal/server/services/instances.py +3 -3
  68. dstack/_internal/server/services/jobs/configurators/base.py +46 -6
  69. dstack/_internal/server/services/jobs/configurators/dev.py +4 -4
  70. dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +3 -5
  71. dstack/_internal/server/services/jobs/configurators/extensions/vscode.py +4 -6
  72. dstack/_internal/server/services/jobs/configurators/service.py +0 -3
  73. dstack/_internal/server/services/jobs/configurators/task.py +0 -3
  74. dstack/_internal/server/services/projects.py +52 -1
  75. dstack/_internal/server/services/runs.py +16 -0
  76. dstack/_internal/server/settings.py +46 -0
  77. dstack/_internal/server/statics/index.html +1 -1
  78. dstack/_internal/server/statics/{main-aec4762350e34d6fbff9.css → main-5e0d56245c4bd241ec27.css} +1 -1
  79. dstack/_internal/server/statics/{main-d151b300fcac3933213d.js → main-a2a16772fbf11a14d191.js} +1215 -998
  80. dstack/_internal/server/statics/{main-d151b300fcac3933213d.js.map → main-a2a16772fbf11a14d191.js.map} +1 -1
  81. dstack/_internal/server/testing/common.py +6 -3
  82. dstack/_internal/utils/env.py +85 -11
  83. dstack/_internal/utils/path.py +8 -1
  84. dstack/_internal/utils/ssh.py +7 -0
  85. dstack/api/_public/repos.py +41 -6
  86. dstack/api/_public/runs.py +14 -1
  87. dstack/version.py +1 -1
  88. {dstack-0.19.26.dist-info → dstack-0.19.28.dist-info}/METADATA +2 -2
  89. {dstack-0.19.26.dist-info → dstack-0.19.28.dist-info}/RECORD +92 -78
  90. dstack/_internal/server/statics/static/media/github.1f7102513534c83a9d8d735d2b8c12a2.svg +0 -3
  91. {dstack-0.19.26.dist-info → dstack-0.19.28.dist-info}/WHEEL +0 -0
  92. {dstack-0.19.26.dist-info → dstack-0.19.28.dist-info}/entry_points.txt +0 -0
  93. {dstack-0.19.26.dist-info → dstack-0.19.28.dist-info}/licenses/LICENSE.md +0 -0
@@ -14,8 +14,16 @@ from dstack._internal.core.backends.dstack.models import (
14
14
  from dstack._internal.core.backends.models import BackendInfo
15
15
  from dstack._internal.core.errors import ForbiddenError, ResourceExistsError, ServerClientError
16
16
  from dstack._internal.core.models.projects import Member, MemberPermissions, Project
17
+ from dstack._internal.core.models.runs import RunStatus
17
18
  from dstack._internal.core.models.users import GlobalRole, ProjectRole
18
- from dstack._internal.server.models import MemberModel, ProjectModel, UserModel
19
+ from dstack._internal.server.models import (
20
+ FleetModel,
21
+ MemberModel,
22
+ ProjectModel,
23
+ RunModel,
24
+ UserModel,
25
+ VolumeModel,
26
+ )
19
27
  from dstack._internal.server.schemas.projects import MemberSetting
20
28
  from dstack._internal.server.services import users
21
29
  from dstack._internal.server.services.backends import (
@@ -178,6 +186,19 @@ async def delete_projects(
178
186
  raise ForbiddenError()
179
187
  if all(name in projects_names for name in user_project_names):
180
188
  raise ServerClientError("Cannot delete the only project")
189
+
190
+ res = await session.execute(
191
+ select(ProjectModel.id).where(ProjectModel.name.in_(projects_names))
192
+ )
193
+ project_ids = res.scalars().all()
194
+ if len(project_ids) != len(projects_names):
195
+ raise ServerClientError("Failed to delete non-existent projects")
196
+
197
+ for project_id in project_ids:
198
+ # FIXME: The checks are not under lock,
199
+ # so there can be dangling active resources due to race conditions.
200
+ await _check_project_has_active_resources(session=session, project_id=project_id)
201
+
181
202
  timestamp = str(int(get_current_datetime().timestamp()))
182
203
  new_project_name = "_deleted_" + timestamp + ProjectModel.name
183
204
  await session.execute(
@@ -614,6 +635,36 @@ def _is_project_admin(
614
635
  return False
615
636
 
616
637
 
638
+ async def _check_project_has_active_resources(session: AsyncSession, project_id: uuid.UUID):
639
+ res = await session.execute(
640
+ select(RunModel.run_name).where(
641
+ RunModel.project_id == project_id,
642
+ RunModel.status.not_in(RunStatus.finished_statuses()),
643
+ )
644
+ )
645
+ run_names = list(res.scalars().all())
646
+ if len(run_names) > 0:
647
+ raise ServerClientError(f"Failed to delete project with active runs: {run_names}")
648
+ res = await session.execute(
649
+ select(FleetModel.name).where(
650
+ FleetModel.project_id == project_id,
651
+ FleetModel.deleted.is_(False),
652
+ )
653
+ )
654
+ fleet_names = list(res.scalars().all())
655
+ if len(fleet_names) > 0:
656
+ raise ServerClientError(f"Failed to delete project with active fleets: {fleet_names}")
657
+ res = await session.execute(
658
+ select(VolumeModel.name).where(
659
+ VolumeModel.project_id == project_id,
660
+ VolumeModel.deleted.is_(False),
661
+ )
662
+ )
663
+ volume_names = list(res.scalars().all())
664
+ if len(volume_names) > 0:
665
+ raise ServerClientError(f"Failed to delete project with active volumes: {volume_names}")
666
+
667
+
617
668
  async def remove_project_members(
618
669
  session: AsyncSession,
619
670
  user: UserModel,
@@ -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()
@@ -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,
@@ -4,8 +4,14 @@ Environment variables read by the dstack server. Documented in reference/environ
4
4
 
5
5
  import os
6
6
  import warnings
7
+ from enum import Enum
7
8
  from pathlib import Path
8
9
 
10
+ from dstack._internal.utils.env import environ
11
+ from dstack._internal.utils.logging import get_logger
12
+
13
+ logger = get_logger(__name__)
14
+
9
15
  DSTACK_DIR_PATH = Path("~/.dstack/").expanduser()
10
16
 
11
17
  SERVER_DIR_PATH = Path(os.getenv("DSTACK_SERVER_DIR", DSTACK_DIR_PATH / "server"))
@@ -136,3 +142,43 @@ UPDATE_DEFAULT_PROJECT = os.getenv("DSTACK_UPDATE_DEFAULT_PROJECT") is not None
136
142
  DO_NOT_UPDATE_DEFAULT_PROJECT = os.getenv("DSTACK_DO_NOT_UPDATE_DEFAULT_PROJECT") is not None
137
143
  SKIP_GATEWAY_UPDATE = os.getenv("DSTACK_SKIP_GATEWAY_UPDATE") is not None
138
144
  ENABLE_PROMETHEUS_METRICS = os.getenv("DSTACK_ENABLE_PROMETHEUS_METRICS") is not None
145
+
146
+
147
+ class JobNetworkMode(Enum):
148
+ # "host" for multinode runs only, "bridge" otherwise. Opt-in new defaut
149
+ HOST_FOR_MULTINODE_ONLY = 1
150
+ # "bridge" if the job occupies only a part of the instance, "host" otherswise. Current default
151
+ HOST_WHEN_POSSIBLE = 2
152
+ # Always "bridge", even for multinode runs. Same as legacy DSTACK_FORCE_BRIDGE_NETWORK=true
153
+ FORCED_BRIDGE = 3
154
+
155
+
156
+ def _get_job_network_mode() -> JobNetworkMode:
157
+ # Current default
158
+ mode = JobNetworkMode.HOST_WHEN_POSSIBLE
159
+ bridge_var = "DSTACK_FORCE_BRIDGE_NETWORK"
160
+ force_bridge = environ.get_bool(bridge_var)
161
+ mode_var = "DSTACK_SERVER_JOB_NETWORK_MODE"
162
+ mode_from_env = environ.get_enum(mode_var, JobNetworkMode, value_type=int)
163
+ if mode_from_env is not None:
164
+ if force_bridge is not None:
165
+ logger.warning(
166
+ f"{bridge_var} is deprecated since 0.19.27 and ignored when {mode_var} is set"
167
+ )
168
+ return mode_from_env
169
+ if force_bridge is not None:
170
+ if force_bridge:
171
+ mode = JobNetworkMode.FORCED_BRIDGE
172
+ logger.warning(
173
+ (
174
+ f"{bridge_var} is deprecated since 0.19.27."
175
+ f" Set {mode_var} to {mode.value} and remove {bridge_var}"
176
+ )
177
+ )
178
+ else:
179
+ logger.warning(f"{bridge_var} is deprecated since 0.19.27. Remove {bridge_var}")
180
+ return mode
181
+
182
+
183
+ JOB_NETWORK_MODE = _get_job_network_mode()
184
+ del _get_job_network_mode
@@ -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 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>
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-a2a16772fbf11a14d191.js"></script><link href="/main-5e0d56245c4bd241ec27.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>