Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
65b340b
Add GCP image workflow
r4victor Jan 26, 2023
8d16dc8
Move runners.py to core
r4victor Jan 30, 2023
9ec5c8e
Fix cli commands import
r4victor Jan 30, 2023
1820fde
Turn off gcp_image workflow on push
r4victor Jan 30, 2023
dc691e2
GCP WIP
r4victor Jan 30, 2023
2f960bc
Implement job stopping and deleting
r4victor Feb 1, 2023
38f1fb6
Merge branch 'master' into issue_1_gcp
r4victor Feb 8, 2023
92457b6
Merge branch 'master' into issue_1_gcp
r4victor Feb 8, 2023
a6931d7
Update gcp backend after refactoring
r4victor Feb 8, 2023
7b414d6
Merge branch 'master' into issue_1_gcp
r4victor Feb 13, 2023
0ec273e
Refactor gcp to use common backend logic
r4victor Feb 13, 2023
dbf55ce
Implement secrets for gcp
r4victor Feb 13, 2023
8f079db
Merge branch 'master' into issue_1_gcp
r4victor Feb 15, 2023
0a31282
Implement artifacts for gcp
r4victor Feb 15, 2023
d329d41
Merge branch 'master' into issue_1_gcp
r4victor Feb 16, 2023
274b78a
Implement gcp poll logs
r4victor Feb 16, 2023
687e09b
Merge branch 'master' into issue_1_gcp
r4victor Feb 16, 2023
b70e6cf
Setup basic config
r4victor Feb 16, 2023
c2aae21
Merge branch 'master' into issue_1_gcp
r4victor Feb 20, 2023
1ebb76d
Implement gcp storage for runner
r4victor Feb 21, 2023
0519279
Implement gcp secrets for runner
r4victor Feb 21, 2023
8f7e932
Merge branch 'master' into issue_1_gcp
r4victor Feb 22, 2023
cce3e41
Fix logs after merge
r4victor Feb 22, 2023
8283805
Return gcp instance status for deleted instances
r4victor Feb 22, 2023
4e1e5f1
Implement gcp compute for runner
r4victor Feb 22, 2023
df2733a
Implement gcp logging for runner
r4victor Feb 22, 2023
05cd913
Implement metadata for gcp storage
r4victor Feb 22, 2023
69df2cb
Implement metadata for gcp storage in runner
r4victor Feb 22, 2023
556fa24
Add job helper methods
r4victor Feb 22, 2023
ab10986
Merge branch 'master' into issue_1_gcp
r4victor Feb 22, 2023
97247c6
Fix removeprefix after merge
r4victor Feb 22, 2023
6c91d9b
Implement GCPBackend methods in runner: Init, Job, UpdateState, Check…
r4victor Feb 22, 2023
0e8ff04
Implement Secrets() for gcp backend in runner
r4victor Feb 23, 2023
b41c818
Implement basic gcp support in runner
r4victor Feb 23, 2023
771db5d
Fix job filepath
r4victor Feb 23, 2023
92ca454
Do not wait for gcp instance to terminate
r4victor Feb 23, 2023
8c19460
Clean up gcp cli
r4victor Feb 23, 2023
ec94134
Implement basic gcp support with local runner
r4victor Feb 23, 2023
c72fc0d
Fix log.Error() call
r4victor Feb 23, 2023
b83322d
Implement gcp artifacter upload
r4victor Feb 23, 2023
191588d
Implement gcp artifacter download
r4victor Feb 24, 2023
15dee4a
Fix gcp logs and storage concurrency issues
r4victor Feb 24, 2023
a022749
Read gcp config file in runner
r4victor Feb 27, 2023
b33350a
Run build workflow
r4victor Feb 27, 2023
db004d9
Run main build workflow
r4victor Feb 27, 2023
67af930
Add gcloud requirements to setup.py
r4victor Feb 27, 2023
f311327
Run manual_gcp_image_stgn workflow
r4victor Feb 27, 2023
4822c01
Handle file renaming to the same name in gcp storage
r4victor Feb 27, 2023
74e6fe8
Make runner work on gcp
r4victor Feb 27, 2023
8a0f521
Run manual_gcp_image_stgn workflow
r4victor Feb 27, 2023
99bb4bb
Create gcp firewall rule
r4victor Feb 27, 2023
dbd5c63
Switch to gcp auth via service account
r4victor Feb 27, 2023
1a44299
Configure gcp via cli
r4victor Feb 28, 2023
35babaf
Remove google-api-python-client from dependencies
r4victor Feb 28, 2023
f01f7cc
Store gcp credentials file in config.yaml
r4victor Feb 28, 2023
59b8d7a
Test gcp images release build
r4victor Feb 28, 2023
fb9a006
Test gcp images staging build
r4victor Feb 28, 2023
7844c13
Test gcp images release build 2
r4victor Feb 28, 2023
647b883
Test gcp images staging build 2
r4victor Feb 28, 2023
21d5edf
Add nocuda prefix to image without cuda
r4victor Feb 28, 2023
bf84ef8
Use configured remote backend as default remote
r4victor Feb 28, 2023
ccd7837
Support interruptible isntances for gcp
r4victor Feb 28, 2023
281e97f
Choose gcp machine type
r4victor Mar 1, 2023
7fb2c58
Support gcp gpu instances
r4victor Mar 1, 2023
6cb882a
Warn about mount artifacts not supported on gcp
r4victor Mar 1, 2023
fc15b7f
Set ssd size to 100gb
r4victor Mar 1, 2023
6a07555
Merge branch 'master' into issue_1_gcp
r4victor Mar 2, 2023
67a1f86
Remove duplicated aiosqlite from requirements
r4victor Mar 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/manual_gcp_image_stgn.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Manual GCP image build for STGN

on:
workflow_dispatch:

jobs:
gcp-images-build:
defaults:
run:
working-directory: runner
strategy:
matrix:
variant: [ gcp-image, gcp-image-cuda ]
include:
- variant: gcp-image
image_prefix: stgn-dstack-nocuda
- variant: gcp-image-cuda
image_prefix: stgn-dstack-cuda
runs-on: ubuntu-latest
permissions:
contents: 'read'
id-token: 'write'
steps:
- uses: actions/checkout@v3
- name: 'Authenticate to Google Cloud'
uses: 'google-github-actions/auth@v1'
with:
workload_identity_provider: 'projects/531508670106/locations/global/workloadIdentityPools/github-identity-pool/providers/github-id-provider'
service_account: 'github-actions@dstack.iam.gserviceaccount.com'
create_credentials_file: true
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v1'
- name: Download packer
run: |
wget https://releases.hashicorp.com/packer/1.8.0/packer_1.8.0_linux_amd64.zip
unzip packer_1.8.0_linux_amd64.zip
chmod +x packer
cp -R ami/packer/* .
- name: Run packer
run: |
VERSION=${{ github.run_number }}
./packer build -var build_id=$VERSION ${{ matrix.variant }}.json
- name: Publish images
run: |
VERSION=${{ github.run_number }}
gcloud compute images add-iam-policy-binding ${{ matrix.image_prefix }}-$VERSION --member='allAuthenticatedUsers' --role='roles/compute.imageUser'
43 changes: 43 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,49 @@ jobs:
run: |
VERSION=${GITHUB_REF#refs/tags/}
./packer build -var build_id=$VERSION -var build_prefix='' -var-file=prod-var.json ${{ matrix.variants }}.json

gcp-images-build:
defaults:
run:
working-directory: runner
strategy:
matrix:
variant: [ gcp-image, gcp-image-cuda ]
include:
- variant: gcp-image
image_prefix: dstack-nocuda
- variant: gcp-image-cuda
image_prefix: dstack-cuda
runs-on: ubuntu-latest
permissions:
contents: 'read'
id-token: 'write'
steps:
- uses: actions/checkout@v3
- name: 'Authenticate to Google Cloud'
uses: 'google-github-actions/auth@v1'
with:
workload_identity_provider: 'projects/531508670106/locations/global/workloadIdentityPools/github-identity-pool/providers/github-id-provider'
service_account: 'github-actions@dstack.iam.gserviceaccount.com'
create_credentials_file: true
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v1'
- name: Download packer
run: |
wget https://releases.hashicorp.com/packer/1.8.0/packer_1.8.0_linux_amd64.zip
unzip packer_1.8.0_linux_amd64.zip
chmod +x packer
cp -R ami/packer/* .
- name: Run packer
run: |
VERSION=${GITHUB_REF#refs/tags/}
VERSION=${VERSION//./-}
./packer build -var build_id=$VERSION -var build_prefix='' -var dstack_stage=PROD ${{ matrix.variant }}.json
- name: Publish image
run: |
VERSION=${GITHUB_REF#refs/tags/}
VERSION=${VERSION//./-}
gcloud compute images add-iam-policy-binding ${{ matrix.image_prefix }}-$VERSION --member='allAuthenticatedUsers' --role='roles/compute.imageUser'

update-get-dstack-tag:
runs-on: ubuntu-latest
Expand Down
17 changes: 11 additions & 6 deletions cli/dstack/api/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

from dstack import version
from dstack.backend.aws import AwsBackend
from dstack.backend.base import Backend, RemoteBackend
from dstack.backend.base import Backend, BackendType, RemoteBackend
from dstack.backend.gcp import GCPBackend
from dstack.backend.hub import HubBackend
from dstack.backend.local import LocalBackend

DEFAULT_REMOTE = "aws"
DEFAULT = "local"

backends_classes = [AwsBackend, LocalBackend]
backends_classes = [AwsBackend, GCPBackend, LocalBackend]
if not version.__is_release__:
backends_classes.append(HubBackend)

Expand All @@ -28,6 +26,10 @@ def list_backends(all_backend: bool = False) -> List[Backend]:
return l


def list_remote_backends() -> List[RemoteBackend]:
return [b for b in list_backends() if b.type is BackendType.REMOTE]


def dict_backends(all_backend: bool = False) -> Dict[str, Backend]:
return {backend.name: backend for backend in list_backends(all_backend=all_backend)}

Expand All @@ -37,7 +39,10 @@ def get_backend_by_name(name: str) -> Optional[Backend]:


def get_current_remote_backend() -> Optional[RemoteBackend]:
return get_backend_by_name(DEFAULT_REMOTE)
remote_backends = list_remote_backends()
if len(remote_backends) == 0:
return None
return remote_backends[0]


def get_local_backend() -> LocalBackend:
Expand Down
6 changes: 3 additions & 3 deletions cli/dstack/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

from dstack import version
from dstack.backend.aws.config import AWSConfig
from dstack.backend.gcp.config import GCPConfigurator
from dstack.backend.hub.config import HUBConfig
from dstack.core.config import BackendConfig

configs_classes = [AWSConfig]
configs_classes = [AWSConfig, GCPConfigurator]
if not version.__is_release__:
configs_classes.append(HUBConfig)

Expand All @@ -19,7 +20,6 @@ def dict_config() -> Dict[str, BackendConfig]:
configs = [cls() for cls in configs_classes]
names = {}
for config in configs:
if config.configured:
names[config.name] = config
names[config.name] = config

return names
8 changes: 4 additions & 4 deletions cli/dstack/backend/aws/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ def get_request_head(self, job: Job, request_id: Optional[str]) -> RequestHead:
)

def get_instance_type(self, job: Job) -> Optional[InstanceType]:
return runners._get_instance_type(
return runners.get_instance_type(
ec2_client=self.ec2_client,
requirements=job.requirements,
)

def run_instance(self, job: Job, instance_type: InstanceType) -> str:
return runners._run_instance_retry(
return runners.run_instance_retry(
ec2_client=self.ec2_client,
iam_client=self.iam_client,
bucket_name=self.bucket_name,
Expand All @@ -52,13 +52,13 @@ def run_instance(self, job: Job, instance_type: InstanceType) -> str:
)

def terminate_instance(self, request_id: str):
runners._terminate_instance(
runners.terminate_instance(
ec2_client=self.ec2_client,
request_id=request_id,
)

def cancel_spot_request(self, request_id: str):
runners._cancel_spot_request(
runners.cancel_spot_request(
ec2_client=self.ec2_client,
request_id=request_id,
)
2 changes: 0 additions & 2 deletions cli/dstack/backend/aws/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@
class AWSConfig(BackendConfig):
NAME = "aws"

_configured = True

bucket_name = None
region_name = None
profile_name = None
Expand Down
102 changes: 15 additions & 87 deletions cli/dstack/backend/aws/runners.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import json
import time
from functools import cmp_to_key, reduce
from functools import reduce
from typing import List, Optional, Tuple

import yaml
from botocore.client import BaseClient

from dstack import version
from dstack.backend.base.compute import choose_instance_type
from dstack.core.instance import InstanceType
from dstack.core.job import Job, JobStatus, Requirements
from dstack.core.repo import RepoAddress
from dstack.core.request import RequestHead, RequestStatus
from dstack.core.runners import Gpu, Resources, Runner
from dstack.core.runners import Gpu, Resources

CREATE_INSTANCE_RETRY_RATE_SECS = 3


def get_instance_type(
ec2_client: BaseClient, requirements: Optional[Requirements]
) -> Optional[InstanceType]:
instance_types = _get_instance_types(ec2_client)
return choose_instance_type(instance_types, requirements)


def _get_instance_types(ec2_client: BaseClient) -> List[InstanceType]:
response = None
instance_types = []
Expand Down Expand Up @@ -54,86 +61,7 @@ def _get_instance_types(ec2_client: BaseClient) -> List[InstanceType]:
),
)
)

def compare(i1, i2):
r1_gpu_total_memory_mib = sum(map(lambda g: g.memory_mib, i1.resources.gpus or []))
r2_gpu_total_memory_mib = sum(map(lambda g: g.memory_mib, i2.resources.gpus or []))
if r1_gpu_total_memory_mib < r2_gpu_total_memory_mib:
return -1
elif r1_gpu_total_memory_mib > r2_gpu_total_memory_mib:
return 1
if i1.resources.cpus < i2.resources.cpus:
return -1
elif i1.resources.cpus > i2.resources.cpus:
return 1
if i1.resources.memory_mib < i2.resources.memory_mib:
return -1
elif i1.resources.memory_mib > i2.resources.memory_mib:
return 1
return 0

return sorted(instance_types, key=cmp_to_key(compare))


def _matches(resources: Resources, requirements: Optional[Requirements]) -> bool:
if not requirements:
return True
if requirements.cpus and requirements.cpus > resources.cpus:
return False
if requirements.memory_mib and requirements.memory_mib > resources.memory_mib:
return False
if requirements.gpus:
gpu_count = requirements.gpus.count or 1
if gpu_count > len(resources.gpus or []):
return False
if requirements.gpus.name and gpu_count > len(
list(filter(lambda gpu: gpu.name == requirements.gpus.name, resources.gpus or []))
):
return False
if requirements.gpus.memory_mib and gpu_count > len(
list(
filter(
lambda gpu: gpu.memory_mib >= requirements.gpus.memory_mib,
resources.gpus or [],
)
)
):
return False
if requirements.interruptible and not resources.interruptible:
return False
return True


def _get_instance_type(
ec2_client: BaseClient, requirements: Optional[Requirements]
) -> Optional[InstanceType]:
instance_types = _get_instance_types(ec2_client)

instance_type = next(
(
instance_type
for instance_type in instance_types
if _matches(instance_type.resources, requirements)
),
None,
)
interruptible = False
if requirements and requirements.interruptible:
interruptible = True
return (
InstanceType(
instance_name=instance_type.instance_name,
resources=Resources(
cpus=instance_type.resources.cpus,
memory_mib=instance_type.resources.memory_mib,
gpus=instance_type.resources.gpus,
interruptible=interruptible,
local=False,
),
)
if instance_type
else None
)
return instance_types


def _get_security_group_id(ec2_client: BaseClient, bucket_name: str, subnet_id: Optional[str]):
Expand Down Expand Up @@ -491,7 +419,7 @@ def _run_instance(
return request_id


def _run_instance_retry(
def run_instance_retry(
ec2_client: BaseClient,
iam_client: BaseClient,
bucket_name: str,
Expand Down Expand Up @@ -525,7 +453,7 @@ def _run_instance_retry(
):
if attempts > 0:
time.sleep(CREATE_INSTANCE_RETRY_RATE_SECS)
return _run_instance_retry(
return run_instance_retry(
ec2_client,
iam_client,
bucket_name,
Expand All @@ -544,7 +472,7 @@ def _run_instance_retry(
raise e


def _cancel_spot_request(ec2_client: BaseClient, request_id: str):
def cancel_spot_request(ec2_client: BaseClient, request_id: str):
ec2_client.cancel_spot_instance_requests(SpotInstanceRequestIds=[request_id])
response = ec2_client.describe_instances(
Filters=[
Expand All @@ -557,7 +485,7 @@ def _cancel_spot_request(ec2_client: BaseClient, request_id: str):
)


def _terminate_instance(ec2_client: BaseClient, request_id: str):
def terminate_instance(ec2_client: BaseClient, request_id: str):
try:
ec2_client.terminate_instances(InstanceIds=[request_id])
except Exception as e:
Expand Down
Loading