dstack 0.19.11rc2__py3-none-any.whl → 0.19.12__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 (32) hide show
  1. dstack/_internal/cli/commands/offer.py +2 -0
  2. dstack/_internal/cli/services/configurators/run.py +43 -42
  3. dstack/_internal/cli/utils/run.py +10 -26
  4. dstack/_internal/core/backends/template/configurator.py.jinja +1 -6
  5. dstack/_internal/core/backends/template/models.py.jinja +4 -0
  6. dstack/_internal/core/models/configurations.py +1 -1
  7. dstack/_internal/core/models/fleets.py +6 -1
  8. dstack/_internal/core/models/profiles.py +43 -3
  9. dstack/_internal/core/models/repos/local.py +19 -13
  10. dstack/_internal/core/models/runs.py +78 -45
  11. dstack/_internal/server/background/tasks/process_running_jobs.py +47 -12
  12. dstack/_internal/server/background/tasks/process_runs.py +14 -1
  13. dstack/_internal/server/services/fleets.py +2 -2
  14. dstack/_internal/server/services/gateways/__init__.py +1 -1
  15. dstack/_internal/server/services/plugins.py +3 -2
  16. dstack/_internal/server/services/runner/client.py +4 -1
  17. dstack/_internal/server/services/runs.py +2 -2
  18. dstack/_internal/server/services/volumes.py +1 -1
  19. dstack/_internal/server/statics/index.html +1 -1
  20. dstack/_internal/server/statics/{main-5b9786c955b42bf93581.js → main-b0e80f8e26a168c129e9.js} +72 -25
  21. dstack/_internal/server/statics/{main-5b9786c955b42bf93581.js.map → main-b0e80f8e26a168c129e9.js.map} +1 -1
  22. dstack/_internal/server/testing/common.py +2 -1
  23. dstack/_internal/utils/common.py +4 -0
  24. dstack/api/server/_fleets.py +5 -1
  25. dstack/api/server/_runs.py +8 -0
  26. dstack/version.py +1 -1
  27. {dstack-0.19.11rc2.dist-info → dstack-0.19.12.dist-info}/METADATA +2 -1
  28. {dstack-0.19.11rc2.dist-info → dstack-0.19.12.dist-info}/RECORD +31 -32
  29. dstack/_internal/utils/ignore.py +0 -92
  30. {dstack-0.19.11rc2.dist-info → dstack-0.19.12.dist-info}/WHEEL +0 -0
  31. {dstack-0.19.11rc2.dist-info → dstack-0.19.12.dist-info}/entry_points.txt +0 -0
  32. {dstack-0.19.11rc2.dist-info → dstack-0.19.12.dist-info}/licenses/LICENSE.md +0 -0
@@ -18,6 +18,7 @@ from dstack._internal.core.models.instances import (
18
18
  SSHConnectionParams,
19
19
  )
20
20
  from dstack._internal.core.models.metrics import Metric
21
+ from dstack._internal.core.models.profiles import StartupOrder
21
22
  from dstack._internal.core.models.repos import RemoteRepoCreds
22
23
  from dstack._internal.core.models.runs import (
23
24
  ClusterInfo,
@@ -184,18 +185,10 @@ async def _process_running_job(session: AsyncSession, job_model: JobModel):
184
185
  if job_provisioning_data.hostname is None:
185
186
  await _wait_for_instance_provisioning_data(job_model=job_model)
186
187
  else:
187
- # Wait until all other jobs in the replica have IPs assigned.
188
- # This is needed to ensure cluster_info has all IPs set.
189
- for other_job in run.jobs:
190
- if (
191
- other_job.job_spec.replica_num == job.job_spec.replica_num
192
- and other_job.job_submissions[-1].status == JobStatus.PROVISIONING
193
- and other_job.job_submissions[-1].job_provisioning_data is not None
194
- and other_job.job_submissions[-1].job_provisioning_data.hostname is None
195
- ):
196
- job_model.last_processed_at = common_utils.get_current_datetime()
197
- await session.commit()
198
- return
188
+ if _should_wait_for_other_nodes(run, job, job_model):
189
+ job_model.last_processed_at = common_utils.get_current_datetime()
190
+ await session.commit()
191
+ return
199
192
 
200
193
  # fails are acceptable until timeout is exceeded
201
194
  if job_provisioning_data.dockerized:
@@ -406,6 +399,48 @@ async def _wait_for_instance_provisioning_data(job_model: JobModel):
406
399
  job_model.job_provisioning_data = job_model.instance.job_provisioning_data
407
400
 
408
401
 
402
+ def _should_wait_for_other_nodes(run: Run, job: Job, job_model: JobModel) -> bool:
403
+ for other_job in run.jobs:
404
+ if (
405
+ other_job.job_spec.replica_num == job.job_spec.replica_num
406
+ and other_job.job_submissions[-1].status == JobStatus.PROVISIONING
407
+ and other_job.job_submissions[-1].job_provisioning_data is not None
408
+ and other_job.job_submissions[-1].job_provisioning_data.hostname is None
409
+ ):
410
+ logger.debug(
411
+ "%s: waiting for other job to have IP assigned",
412
+ fmt(job_model),
413
+ )
414
+ return True
415
+ master_job = find_job(run.jobs, job.job_spec.replica_num, 0)
416
+ if (
417
+ job.job_spec.job_num != 0
418
+ and run.run_spec.merged_profile.startup_order == StartupOrder.MASTER_FIRST
419
+ and master_job.job_submissions[-1].status != JobStatus.RUNNING
420
+ ):
421
+ logger.debug(
422
+ "%s: waiting for master job to become running",
423
+ fmt(job_model),
424
+ )
425
+ return True
426
+ if (
427
+ job.job_spec.job_num == 0
428
+ and run.run_spec.merged_profile.startup_order == StartupOrder.WORKERS_FIRST
429
+ ):
430
+ for other_job in run.jobs:
431
+ if (
432
+ other_job.job_spec.replica_num == job.job_spec.replica_num
433
+ and other_job.job_spec.job_num != job.job_spec.job_num
434
+ and other_job.job_submissions[-1].status != JobStatus.RUNNING
435
+ ):
436
+ logger.debug(
437
+ "%s: waiting for worker job to become running",
438
+ fmt(job_model),
439
+ )
440
+ return True
441
+ return False
442
+
443
+
409
444
  @runner_ssh_tunnel(ports=[DSTACK_SHIM_HTTP_PORT], retries=1)
410
445
  def _process_provisioning_with_shim(
411
446
  ports: Dict[int, int],
@@ -10,7 +10,7 @@ from sqlalchemy.orm import joinedload, selectinload
10
10
  import dstack._internal.server.services.gateways as gateways
11
11
  import dstack._internal.server.services.services.autoscalers as autoscalers
12
12
  from dstack._internal.core.errors import ServerError
13
- from dstack._internal.core.models.profiles import RetryEvent
13
+ from dstack._internal.core.models.profiles import RetryEvent, StopCriteria
14
14
  from dstack._internal.core.models.runs import (
15
15
  Job,
16
16
  JobStatus,
@@ -313,6 +313,10 @@ async def _process_active_run(session: AsyncSession, run_model: RunModel):
313
313
  termination_reason = RunTerminationReason.RETRY_LIMIT_EXCEEDED
314
314
  else:
315
315
  raise ValueError(f"Unexpected termination reason {run_termination_reasons}")
316
+ elif _should_stop_on_master_done(run):
317
+ new_status = RunStatus.TERMINATING
318
+ # ALL_JOBS_DONE is used for all DONE reasons including master-done
319
+ termination_reason = RunTerminationReason.ALL_JOBS_DONE
316
320
  elif RunStatus.RUNNING in run_statuses:
317
321
  new_status = RunStatus.RUNNING
318
322
  elif RunStatus.PROVISIONING in run_statuses:
@@ -434,3 +438,12 @@ def _can_retry_single_job(run_spec: RunSpec) -> bool:
434
438
  # We could make partial retry in some multi-node cases.
435
439
  # E.g. restarting a worker node, independent jobs.
436
440
  return False
441
+
442
+
443
+ def _should_stop_on_master_done(run: Run) -> bool:
444
+ if run.run_spec.merged_profile.stop_criteria != StopCriteria.MASTER_DONE:
445
+ return False
446
+ for job in run.jobs:
447
+ if job.job_spec.job_num == 0 and job.job_submissions[-1].status == JobStatus.DONE:
448
+ return True
449
+ return False
@@ -237,7 +237,7 @@ async def get_plan(
237
237
  ) -> FleetPlan:
238
238
  # Spec must be copied by parsing to calculate merged_profile
239
239
  effective_spec = FleetSpec.parse_obj(spec.dict())
240
- effective_spec = apply_plugin_policies(
240
+ effective_spec = await apply_plugin_policies(
241
241
  user=user.name,
242
242
  project=project.name,
243
243
  spec=effective_spec,
@@ -342,7 +342,7 @@ async def create_fleet(
342
342
  spec: FleetSpec,
343
343
  ) -> Fleet:
344
344
  # Spec must be copied by parsing to calculate merged_profile
345
- spec = apply_plugin_policies(
345
+ spec = await apply_plugin_policies(
346
346
  user=user.name,
347
347
  project=project.name,
348
348
  spec=spec,
@@ -140,7 +140,7 @@ async def create_gateway(
140
140
  project: ProjectModel,
141
141
  configuration: GatewayConfiguration,
142
142
  ) -> Gateway:
143
- spec = apply_plugin_policies(
143
+ spec = await apply_plugin_policies(
144
144
  user=user.name,
145
145
  project=project.name,
146
146
  # Create pseudo spec until the gateway API is updated to accept spec
@@ -5,6 +5,7 @@ from typing import Dict
5
5
  from backports.entry_points_selectable import entry_points # backport for Python 3.9
6
6
 
7
7
  from dstack._internal.core.errors import ServerClientError
8
+ from dstack._internal.utils.common import run_async
8
9
  from dstack._internal.utils.logging import get_logger
9
10
  from dstack.plugins import ApplyPolicy, ApplySpec, Plugin
10
11
 
@@ -91,11 +92,11 @@ def load_plugins(enabled_plugins: list[str]):
91
92
  logger.warning("Enabled plugins not found: %s", plugins_to_load)
92
93
 
93
94
 
94
- def apply_plugin_policies(user: str, project: str, spec: ApplySpec) -> ApplySpec:
95
+ async def apply_plugin_policies(user: str, project: str, spec: ApplySpec) -> ApplySpec:
95
96
  policies = _get_apply_policies()
96
97
  for policy in policies:
97
98
  try:
98
- spec = policy.on_apply(user=user, project=project, spec=spec)
99
+ spec = await run_async(policy.on_apply, user=user, project=project, spec=spec)
99
100
  except ValueError as e:
100
101
  msg = None
101
102
  if len(e.args) > 0:
@@ -32,6 +32,7 @@ from dstack._internal.utils.common import get_or_error
32
32
  from dstack._internal.utils.logging import get_logger
33
33
 
34
34
  REQUEST_TIMEOUT = 9
35
+ UPLOAD_CODE_REQUEST_TIMEOUT = 60
35
36
 
36
37
  logger = get_logger(__name__)
37
38
 
@@ -109,7 +110,9 @@ class RunnerClient:
109
110
  resp.raise_for_status()
110
111
 
111
112
  def upload_code(self, file: Union[BinaryIO, bytes]):
112
- resp = requests.post(self._url("/api/upload_code"), data=file, timeout=REQUEST_TIMEOUT)
113
+ resp = requests.post(
114
+ self._url("/api/upload_code"), data=file, timeout=UPLOAD_CODE_REQUEST_TIMEOUT
115
+ )
113
116
  resp.raise_for_status()
114
117
 
115
118
  def run_job(self):
@@ -283,7 +283,7 @@ async def get_plan(
283
283
  ) -> RunPlan:
284
284
  # Spec must be copied by parsing to calculate merged_profile
285
285
  effective_run_spec = RunSpec.parse_obj(run_spec.dict())
286
- effective_run_spec = apply_plugin_policies(
286
+ effective_run_spec = await apply_plugin_policies(
287
287
  user=user.name,
288
288
  project=project.name,
289
289
  spec=effective_run_spec,
@@ -382,7 +382,7 @@ async def apply_plan(
382
382
  force: bool,
383
383
  ) -> Run:
384
384
  run_spec = plan.run_spec
385
- run_spec = apply_plugin_policies(
385
+ run_spec = await apply_plugin_policies(
386
386
  user=user.name,
387
387
  project=project.name,
388
388
  spec=run_spec,
@@ -205,7 +205,7 @@ async def create_volume(
205
205
  user: UserModel,
206
206
  configuration: VolumeConfiguration,
207
207
  ) -> Volume:
208
- spec = apply_plugin_policies(
208
+ spec = await apply_plugin_policies(
209
209
  user=user.name,
210
210
  project=project.name,
211
211
  # Create pseudo spec until the volume API is updated to accept spec
@@ -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-5b9786c955b42bf93581.js"></script><link href="/main-8f9c66f404e9c7e7e020.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></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-b0e80f8e26a168c129e9.js"></script><link href="/main-8f9c66f404e9c7e7e020.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></body></html>