Skip to content

Commit 8a241c6

Browse files
authored
Add Nebius backend (#2463)
1 parent 3b35c90 commit 8a241c6

File tree

21 files changed

+998
-19
lines changed

21 files changed

+998
-19
lines changed

docs/docs/concepts/backends.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,56 @@ projects:
596596
597597
</div>
598598
599+
### Nebius
600+
601+
Log into your [Nebius AI Cloud :material-arrow-top-right-thin:{ .external }](https://console.eu.nebius.com/) account, navigate to Access, and select Service Accounts. Create a service account, add it to the editors group, and upload its authorized key.
602+
603+
Then configure the backend:
604+
605+
<div editor-title="~/.dstack/server/config.yml">
606+
607+
```yaml
608+
projects:
609+
- name: main
610+
backends:
611+
- type: nebius
612+
creds:
613+
type: service_account
614+
service_account_id: serviceaccount-e00dhnv9ftgb3cqmej
615+
public_key_id: publickey-e00ngaex668htswqy4
616+
private_key_file: ~/path/to/key.pem
617+
```
618+
619+
</div>
620+
621+
??? info "Configuring in the UI"
622+
If you are configuring the backend in the `dstack` UI, specify the contents of the private key file in `private_key_content`.
623+
624+
<div editor-title="~/.dstack/server/config.yml">
625+
626+
```yaml
627+
type: nebius
628+
creds:
629+
type: service_account
630+
service_account_id: serviceaccount-e00dhnv9ftgb3cqmej
631+
public_key_id: publickey-e00ngaex668htswqy4
632+
private_key_content: |
633+
-----BEGIN PRIVATE KEY-----
634+
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQChwQ5OOhy60N7m
635+
cPx/9M0oRUyJdRRv2nCALbdU/wSDOo8o5N7sP63zCaxXPeKwLNEzneMd/U0gWSv2
636+
[...]
637+
8y1qYDPKQ8LR+DPCUmyhM2I8t6673Vz3GrtEjkLhgQo/KqOVb3yiBFVfkA5Jov5s
638+
kO7y4T0ynsI8b6wlhCukQTLpIYJ5
639+
-----END PRIVATE KEY-----
640+
```
641+
642+
</div>
643+
644+
!!! info "Python version"
645+
Nebius is only supported if `dstack server` is running on Python 3.10 or higher.
646+
647+
648+
599649
### RunPod
600650

601651
Log into your [RunPod :material-arrow-top-right-thin:{ .external }](https://www.runpod.io/console/) console, click Settings in the sidebar, expand the `API Keys` section, and click

docs/docs/reference/server/config.yml.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ to configure [backends](../../concepts/backends.md) and other [sever-level setti
1515
overrides:
1616
show_root_heading: false
1717
backends:
18-
type: 'Union[AWSBackendConfigWithCreds, AzureBackendConfigWithCreds, GCPBackendConfigWithCreds, LambdaBackendConfigWithCreds, RunpodBackendConfigWithCreds, TensorDockBackendConfigWithCreds, VastAIBackendConfigWithCreds, KubernetesConfig]'
18+
type: 'Union[AWSBackendConfigWithCreds, AzureBackendConfigWithCreds, GCPBackendConfigWithCreds, LambdaBackendConfigWithCreds, NebiusBackendConfigWithCreds, RunpodBackendConfigWithCreds, TensorDockBackendConfigWithCreds, VastAIBackendConfigWithCreds, KubernetesConfig]'
1919

2020
#### `projects[n].backends` { #backends data-toc-label="backends" }
2121

@@ -143,6 +143,23 @@ to configure [backends](../../concepts/backends.md) and other [sever-level setti
143143
type:
144144
required: true
145145

146+
##### `projects[n].backends[type=nebius]` { #nebius data-toc-label="nebius" }
147+
148+
#SCHEMA# dstack._internal.core.backends.nebius.models.NebiusBackendFileConfigWithCreds
149+
overrides:
150+
show_root_heading: false
151+
type:
152+
required: true
153+
item_id_prefix: nebius-
154+
155+
###### `projects[n].backends[type=nebius].creds` { #nebius-creds data-toc-label="creds" }
156+
157+
#SCHEMA# dstack._internal.core.backends.nebius.models.NebiusServiceAccountFileCreds
158+
overrides:
159+
show_root_heading: false
160+
type:
161+
required: true
162+
146163
##### `projects[n].backends[type=runpod]` { #runpod data-toc-label="runpod" }
147164

148165
#SCHEMA# dstack._internal.core.backends.runpod.models.RunpodBackendConfigWithCreds

frontend/src/types/backend.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ declare type TBackendType = | "aws"
77
|"kubernetes"
88
|"lambda"
99
|"local"
10+
|"nebius"
1011
|"remote"
1112
|"oci"
1213
|"runpod"

setup.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def get_long_description():
4949
"python-multipart>=0.0.16",
5050
"filelock",
5151
"psutil",
52-
"gpuhunt>=0.1.0,<0.2.0",
52+
"gpuhunt>=0.1.1,<0.2.0",
5353
"argcomplete>=3.5.0",
5454
]
5555

@@ -110,10 +110,23 @@ def get_long_description():
110110

111111
LAMBDA_DEPS = AWS_DEPS
112112

113+
# Nebius requires Python 3.10. On 3.9:
114+
# `pip install dstack[nebius]` is expected to fail
115+
# `pip install dstack[all]` is expected to ignore Nebius
116+
NEBIUS_DEPS = ["nebius>=0.2.19,<0.3"]
117+
MAYBE_NEBIUS_DEPS = ['nebius>=0.2.19,<0.3; python_version>="3.10"']
118+
113119
OCI_DEPS = ["oci"]
114120

115121
ALL_DEPS = (
116-
SERVER_DEPS + AWS_DEPS + AZURE_DEPS + GCP_DEPS + DATACRUNCH_DEPS + KUBERNETES_DEPS + OCI_DEPS
122+
SERVER_DEPS
123+
+ AWS_DEPS
124+
+ AZURE_DEPS
125+
+ GCP_DEPS
126+
+ DATACRUNCH_DEPS
127+
+ KUBERNETES_DEPS
128+
+ MAYBE_NEBIUS_DEPS
129+
+ OCI_DEPS
117130
)
118131

119132
setup(
@@ -155,6 +168,7 @@ def get_long_description():
155168
"gcp": SERVER_DEPS + GCP_DEPS,
156169
"kubernetes": SERVER_DEPS + KUBERNETES_DEPS,
157170
"lambda": SERVER_DEPS + LAMBDA_DEPS,
171+
"nebius": SERVER_DEPS + NEBIUS_DEPS,
158172
"oci": SERVER_DEPS + OCI_DEPS,
159173
},
160174
classifiers=[

src/dstack/_internal/core/backends/base/compute.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ def terminate_instance(
9494
"""
9595
Terminates an instance by `instance_id`.
9696
If the instance does not exist, it should not raise errors but return silently.
97+
98+
Should return ASAP. If required to wait for some operation, raise `NotYetTerminated`.
99+
In this case, the method will be called again after a few seconds.
97100
"""
98101
pass
99102

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
from pathlib import Path
22

33

4-
def fill_data(values: dict):
5-
if values.get("data") is not None:
4+
def fill_data(values: dict, filename_field: str = "filename", data_field: str = "data") -> dict:
5+
if values.get(data_field) is not None:
66
return values
7-
if "filename" not in values:
8-
raise ValueError()
7+
if (filename := values.get(filename_field)) is None:
8+
raise ValueError(f"Either `{filename_field}` or `{data_field}` must be specified")
99
try:
10-
with open(Path(values["filename"]).expanduser()) as f:
11-
values["data"] = f.read()
10+
with open(Path(filename).expanduser()) as f:
11+
values[data_field] = f.read()
1212
except OSError:
13-
raise ValueError(f"No such file {values['filename']}")
13+
raise ValueError(f"No such file {filename}")
1414
return values

src/dstack/_internal/core/backends/configurators.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@
6363
except ImportError:
6464
pass
6565

66+
try:
67+
from dstack._internal.core.backends.nebius.configurator import (
68+
NebiusConfigurator,
69+
)
70+
71+
_CONFIGURATOR_CLASSES.append(NebiusConfigurator)
72+
except ImportError:
73+
pass
74+
6675
try:
6776
from dstack._internal.core.backends.oci.configurator import OCIConfigurator
6877

src/dstack/_internal/core/backends/models.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
LambdaBackendConfig,
3535
LambdaBackendConfigWithCreds,
3636
)
37+
from dstack._internal.core.backends.nebius.models import (
38+
NebiusBackendConfig,
39+
NebiusBackendConfigWithCreds,
40+
NebiusBackendFileConfigWithCreds,
41+
)
3742
from dstack._internal.core.backends.oci.models import (
3843
OCIBackendConfig,
3944
OCIBackendConfigWithCreds,
@@ -65,6 +70,7 @@
6570
GCPBackendConfig,
6671
KubernetesBackendConfig,
6772
LambdaBackendConfig,
73+
NebiusBackendConfig,
6874
OCIBackendConfig,
6975
RunpodBackendConfig,
7076
TensorDockBackendConfig,
@@ -86,6 +92,7 @@
8692
KubernetesBackendConfigWithCreds,
8793
LambdaBackendConfigWithCreds,
8894
OCIBackendConfigWithCreds,
95+
NebiusBackendConfigWithCreds,
8996
RunpodBackendConfigWithCreds,
9097
TensorDockBackendConfigWithCreds,
9198
VastAIBackendConfigWithCreds,
@@ -105,6 +112,7 @@
105112
KubernetesBackendFileConfigWithCreds,
106113
LambdaBackendConfigWithCreds,
107114
OCIBackendConfigWithCreds,
115+
NebiusBackendFileConfigWithCreds,
108116
RunpodBackendConfigWithCreds,
109117
TensorDockBackendConfigWithCreds,
110118
VastAIBackendConfigWithCreds,

src/dstack/_internal/core/backends/nebius/__init__.py

Whitespace-only changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from dstack._internal.core.backends.base.backend import Backend
2+
from dstack._internal.core.backends.nebius.compute import NebiusCompute
3+
from dstack._internal.core.backends.nebius.models import NebiusConfig
4+
from dstack._internal.core.models.backends.base import BackendType
5+
6+
7+
class NebiusBackend(Backend):
8+
TYPE = BackendType.NEBIUS
9+
COMPUTE_CLASS = NebiusCompute
10+
11+
def __init__(self, config: NebiusConfig):
12+
self.config = config
13+
self._compute = NebiusCompute(self.config)
14+
15+
def compute(self) -> NebiusCompute:
16+
return self._compute

0 commit comments

Comments
 (0)