Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FluidStack][API] Update FluidStack to new API #3799

Merged
merged 3 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 2 additions & 3 deletions docs/source/getting-started/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -301,13 +301,12 @@ RunPod
Fluidstack
~~~~~~~~~~~~~~~~~~

`Fluidstack <https://fluidstack.io/>`__ is a cloud provider offering low-cost GPUs. To configure Fluidstack access, go to the `Home <https://console.fluidstack.io/>`__ page on your Fluidstack console to generate an API key and then add the :code:`API key` to :code:`~/.fluidstack/api_key` and the :code:`API token` to :code:`~/.fluidstack/api_token`:

`Fluidstack <https://fluidstack.io/>`__ is a cloud provider offering low-cost GPUs. To configure Fluidstack access, go to the `Home <https://dashboard.fluidstack.io/>`__ page on your Fluidstack console to generate an API key and then add the :code:`API key` to :code:`~/.fluidstack/api_key` :
.. code-block:: shell

mkdir -p ~/.fluidstack
echo "your_api_key_here" > ~/.fluidstack/api_key
echo "your_api_token_here" > ~/.fluidstack/api_token



Cudo Compute
Expand Down
59 changes: 22 additions & 37 deletions sky/clouds/fluidstack.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@

_CREDENTIAL_FILES = [
# credential files for FluidStack,
fluidstack_utils.FLUIDSTACK_API_KEY_PATH,
fluidstack_utils.FLUIDSTACK_API_TOKEN_PATH,
fluidstack_utils.FLUIDSTACK_API_KEY_PATH
]
if typing.TYPE_CHECKING:
# Renaming to avoid shadowing variables.
Expand Down Expand Up @@ -189,20 +188,12 @@ def make_deploy_resources_variables(
custom_resources = json.dumps(acc_dict, separators=(',', ':'))
else:
custom_resources = None
cuda_installation_commands = """
sudo wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-keyring_1.1-1_all.deb -O /usr/local/cuda-keyring_1.1-1_all.deb;
sudo dpkg -i /usr/local/cuda-keyring_1.1-1_all.deb;
sudo apt-get update;
sudo apt-get -y install cuda-toolkit-12-3;
sudo apt-get install -y cuda-drivers;
sudo apt-get install -y python3-pip;
nvidia-smi || sudo reboot;"""

return {
'instance_type': resources.instance_type,
'custom_resources': custom_resources,
'region': region.name,
'fluidstack_username': self.default_username(region.name),
'cuda_installation_commands': cuda_installation_commands,
'fluidstack_username': 'ubuntu',
}

def _get_feasible_launchable_resources(
Expand Down Expand Up @@ -270,17 +261,26 @@ def check_credentials(cls) -> Tuple[bool, Optional[str]]:
try:
assert os.path.exists(
os.path.expanduser(fluidstack_utils.FLUIDSTACK_API_KEY_PATH))
assert os.path.exists(
os.path.expanduser(fluidstack_utils.FLUIDSTACK_API_TOKEN_PATH))

with open(os.path.expanduser(
fluidstack_utils.FLUIDSTACK_API_KEY_PATH),
encoding='UTF-8') as f:
api_key = f.read().strip()
if not api_key.startswith('api_key'):
return False, ('Invalid FluidStack API key format. '
'To configure credentials, go to:\n '
' https://dashboard.fluidstack.io \n '
'to obtain an API key, '
'then add save the contents '
'to ~/.fluidstack/api_key \n')
except AssertionError:
return False, (
'Failed to access FluidStack Cloud'
' with credentials. '
'To configure credentials, go to:\n '
' https://console.fluidstack.io \n '
'to obtain an API key and API Token, '
'then add save the contents '
'to ~/.fluidstack/api_key and ~/.fluidstack/api_token \n')
return False, ('Failed to access FluidStack Cloud'
' with credentials. '
'To configure credentials, go to:\n '
' https://dashboard.fluidstack.io \n '
'to obtain an API key, '
'then add save the contents '
'to ~/.fluidstack/api_key \n')
mjibril marked this conversation as resolved.
Show resolved Hide resolved
except requests.exceptions.ConnectionError:
return False, ('Failed to verify FluidStack Cloud credentials. '
'Check your network connection '
Expand All @@ -303,21 +303,6 @@ def validate_region_zone(self, region: Optional[str], zone: Optional[str]):
zone,
clouds='fluidstack')

@classmethod
def default_username(cls, region: str) -> str:
return {
'norway_2_eu': 'ubuntu',
'calgary_1_canada': 'ubuntu',
'norway_3_eu': 'ubuntu',
'norway_4_eu': 'ubuntu',
'india_2': 'root',
'nevada_1_usa': 'fsuser',
'generic_1_canada': 'ubuntu',
'iceland_1_eu': 'ubuntu',
'new_york_1_usa': 'fsuser',
'illinois_1_usa': 'fsuser'
}.get(region, 'ubuntu')

@classmethod
def query_status(
cls,
Expand Down
218 changes: 175 additions & 43 deletions sky/clouds/service_catalog/data_fetchers/fetch_fluidstack.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,140 @@

import requests

ENDPOINT = 'https://api.fluidstack.io/v1/plans'
ENDPOINT = 'https://platform.fluidstack.io/list_available_configurations'
DEFAULT_FLUIDSTACK_API_KEY_PATH = os.path.expanduser('~/.fluidstack/api_key')
DEFAULT_FLUIDSTACK_API_TOKEN_PATH = os.path.expanduser(
'~/.fluidstack/api_token')

plan_vcpus_memory = [{
'gpu_type': 'RTX_A6000_48GB',
'gpu_count': 2,
'min_cpu_count': 12,
'min_memory': 110.0
}, {
'gpu_type': 'RTX_A6000_48GB',
'gpu_count': 4,
'min_cpu_count': 24,
'min_memory': 220.0
}, {
'gpu_type': 'A100_NVLINK_80GB',
'gpu_count': 8,
'min_cpu_count': 252,
'min_memory': 960.0
}, {
'gpu_type': 'H100_PCIE_80GB',
'gpu_count': 8,
'min_cpu_count': 252,
'min_memory': 1440.0
}, {
'gpu_type': 'RTX_A4000_16GB',
'gpu_count': 2,
'min_cpu_count': 12,
'min_memory': 48.0
}, {
'gpu_type': 'H100_PCIE_80GB',
'gpu_count': 2,
'min_cpu_count': 60,
'min_memory': 360.0
}, {
'gpu_type': 'RTX_A6000_48GB',
'gpu_count': 8,
'min_cpu_count': 252,
'min_memory': 464.0
}, {
'gpu_type': 'H100_NVLINK_80GB',
'gpu_count': 8,
'min_cpu_count': 252,
'min_memory': 1440.0
}, {
'gpu_type': 'H100_PCIE_80GB',
'gpu_count': 1,
'min_cpu_count': 28,
'min_memory': 180.0
}, {
'gpu_type': 'RTX_A5000_24GB',
'gpu_count': 1,
'min_cpu_count': 8,
'min_memory': 30.0
}, {
'gpu_type': 'RTX_A5000_24GB',
'gpu_count': 2,
'min_cpu_count': 16,
'min_memory': 60.0
}, {
'gpu_type': 'L40_48GB',
'gpu_count': 2,
'min_cpu_count': 64,
'min_memory': 120.0
}, {
'gpu_type': 'RTX_A4000_16GB',
'gpu_count': 8,
'min_cpu_count': 48,
'min_memory': 192.0
}, {
'gpu_type': 'RTX_A4000_16GB',
'gpu_count': 1,
'min_cpu_count': 6,
'min_memory': 24.0
}, {
'gpu_type': 'RTX_A4000_16GB',
'gpu_count': 4,
'min_cpu_count': 24,
'min_memory': 96.0
}, {
'gpu_type': 'A100_PCIE_80GB',
'gpu_count': 4,
'min_cpu_count': 124,
'min_memory': 480.0
}, {
'gpu_type': 'H100_PCIE_80GB',
'gpu_count': 4,
'min_cpu_count': 124,
'min_memory': 720.0
}, {
'gpu_type': 'L40_48GB',
'gpu_count': 8,
'min_cpu_count': 252,
'min_memory': 480.0
}, {
'gpu_type': 'RTX_A5000_24GB',
'gpu_count': 8,
'min_cpu_count': 64,
'min_memory': 240.0
}, {
'gpu_type': 'L40_48GB',
'gpu_count': 1,
'min_cpu_count': 32,
'min_memory': 60.0
}, {
'gpu_type': 'RTX_A6000_48GB',
'gpu_count': 1,
'min_cpu_count': 6,
'min_memory': 55.0
}, {
'gpu_type': 'L40_48GB',
'gpu_count': 4,
'min_cpu_count': 126,
'min_memory': 240.0
}, {
'gpu_type': 'A100_PCIE_80GB',
'gpu_count': 1,
'min_cpu_count': 28,
'min_memory': 120.0
}, {
'gpu_type': 'A100_PCIE_80GB',
'gpu_count': 8,
'min_cpu_count': 252,
'min_memory': 1440.0
}, {
'gpu_type': 'A100_PCIE_80GB',
'gpu_count': 2,
'min_cpu_count': 60,
'min_memory': 240.0
}, {
'gpu_type': 'RTX_A5000_24GB',
'gpu_count': 4,
'min_cpu_count': 32,
'min_memory': 120.0
}]

GPU_MAP = {
'H100_PCIE_80GB': 'H100',
Expand Down Expand Up @@ -47,19 +177,15 @@ def get_regions(plans: List) -> dict:
regions = {}
for plan in plans:
for region in plan.get('regions', []):
regions[region['id']] = region['id']
regions[region] = region
return regions


def create_catalog(output_dir: str) -> None:
response = requests.get(ENDPOINT)
with open(DEFAULT_FLUIDSTACK_API_KEY_PATH, 'r', encoding='UTF-8') as f:
api_key = f.read().strip()
response = requests.get(ENDPOINT, headers={'api-key': api_key})
plans = response.json()
#plans = [plan for plan in plans if len(plan['regions']) > 0]
plans = [
plan for plan in plans if plan['minimum_commitment'] == 'hourly' and
plan['type'] in ['preconfigured'] and
plan['gpu_type'] not in ['NO GPU', 'RTX_3080_10GB', 'RTX_3090_24GB']
]

with open(os.path.join(output_dir, 'vms.csv'), mode='w',
encoding='utf-8') as f:
Expand All @@ -81,39 +207,45 @@ def create_catalog(output_dir: str) -> None:
except KeyError:
#print(f'Could not map {plan["gpu_type"]}')
continue
gpu_memory = int(
str(plan['configuration']['gpu_memory']).replace('GB',
'')) * 1024
gpu_cnt = int(plan['configuration']['gpu_count'])
vcpus = float(plan['configuration']['core_count'])
mem = float(plan['configuration']['ram'])
price = float(plan['price']['hourly']) * gpu_cnt
gpuinfo = {
'Gpus': [{
'Name': gpu,
'Manufacturer': 'NVIDIA',
'Count': gpu_cnt,
'MemoryInfo': {
'SizeInMiB': int(gpu_memory)
},
}],
'TotalGpuMemoryInMiB': int(gpu_memory * gpu_cnt),
}
gpuinfo = json.dumps(gpuinfo).replace('"', "'") # pylint: disable=invalid-string-quote
for r in plan.get('regions', []):
if r['id'] == 'india_2':
for gpu_cnt in plan['gpu_counts']:
gpu_memory = float(plan['gpu_type'].split('_')[-1].replace(
'GB', '')) * 1024
try:
vcpus_mem = [
x for x in plan_vcpus_memory
if x['gpu_type'] == plan['gpu_type'] and
x['gpu_count'] == gpu_cnt
][0]
vcpus = vcpus_mem['min_cpu_count']
mem = vcpus_mem['min_memory']
except IndexError:
continue
writer.writerow([
plan['plan_id'],
gpu,
gpu_cnt,
vcpus,
mem,
price,
r['id'],
gpuinfo,
'',
])
price = float(plan['price_per_gpu_hr']) * gpu_cnt
gpuinfo = {
'Gpus': [{
'Name': gpu,
'Manufacturer': 'NVIDIA',
'Count': gpu_cnt,
'MemoryInfo': {
'SizeInMiB': int(gpu_memory)
},
}],
'TotalGpuMemoryInMiB': int(gpu_memory * gpu_cnt),
}
gpuinfo = json.dumps(gpuinfo).replace('"', "'") # pylint: disable=invalid-string-quote
instance_type = f'{plan["gpu_type"]}::{gpu_cnt}'
for region in plan.get('regions', []):
writer.writerow([
instance_type,
gpu,
gpu_cnt,
vcpus,
mem,
price,
region,
gpuinfo,
'',
])


if __name__ == '__main__':
Expand Down
Loading
Loading