|
1 | | -from __future__ import annotations |
2 | | - |
| 1 | +import logging |
| 2 | +import math |
3 | 3 | from enum import Enum |
4 | 4 | from typing import TYPE_CHECKING, Dict, List, Optional, Union |
5 | 5 |
|
|
12 | 12 |
|
13 | 13 | from pydantic import BaseModel, RootModel |
14 | 14 |
|
| 15 | +logger = logging.getLogger(__name__) |
| 16 | + |
15 | 17 |
|
16 | 18 | class PricingEntity(str, Enum): |
17 | 19 | STORAGE = "storage" |
@@ -46,18 +48,117 @@ class ComputeUnit(BaseModel): |
46 | 48 | disk_mib: int |
47 | 49 |
|
48 | 50 |
|
| 51 | +class TierComputedSpec(ComputeUnit): |
| 52 | + ... |
| 53 | + gpu_model: Optional[str] |
| 54 | + vram: Optional[int] |
| 55 | + |
| 56 | + |
49 | 57 | class Tier(BaseModel): |
50 | 58 | id: str |
51 | 59 | compute_units: int |
52 | 60 | vram: Optional[int] = None |
53 | 61 | model: Optional[str] = None |
54 | 62 |
|
| 63 | + def extract_tier_id(self) -> str: |
| 64 | + return self.id.split("-", 1)[-1] |
| 65 | + |
55 | 66 |
|
56 | 67 | class PricingPerEntity(BaseModel): |
57 | 68 | price: Dict[str, Union[Price, Decimal]] |
58 | 69 | compute_unit: Optional[ComputeUnit] = None |
59 | 70 | tiers: Optional[List[Tier]] = None |
60 | 71 |
|
| 72 | + def _get_nb_compute_units( |
| 73 | + self, |
| 74 | + vcpus: int = 1, |
| 75 | + memory_mib: int = 2048, |
| 76 | + ) -> Optional[int]: |
| 77 | + if self.compute_unit: |
| 78 | + memory = math.ceil(memory_mib / self.compute_unit.memory_mib) |
| 79 | + nb_compute = vcpus if vcpus >= memory else memory |
| 80 | + return nb_compute |
| 81 | + return None |
| 82 | + |
| 83 | + def get_closest_tier( |
| 84 | + self, |
| 85 | + vcpus: Optional[int] = None, |
| 86 | + memory_mib: Optional[int] = None, |
| 87 | + compute_unit: Optional[int] = None, |
| 88 | + ): |
| 89 | + """Get Closest tier for Program / Instance""" |
| 90 | + |
| 91 | + # We Calculate Compute Unit requested based on vcpus and memory |
| 92 | + computed_cu = None |
| 93 | + if vcpus is not None and memory_mib is not None: |
| 94 | + computed_cu = self._get_nb_compute_units(vcpus=vcpus, memory_mib=memory_mib) |
| 95 | + elif vcpus is not None and self.compute_unit is not None: |
| 96 | + computed_cu = self._get_nb_compute_units( |
| 97 | + vcpus=vcpus, memory_mib=self.compute_unit.memory_mib |
| 98 | + ) |
| 99 | + elif memory_mib is not None and self.compute_unit is not None: |
| 100 | + computed_cu = self._get_nb_compute_units( |
| 101 | + vcpus=self.compute_unit.vcpus, memory_mib=memory_mib |
| 102 | + ) |
| 103 | + |
| 104 | + # Case where Vcpus or memory is given but also a number of CU (case on aleph-client) |
| 105 | + cu: Optional[int] = None |
| 106 | + if computed_cu is not None and compute_unit is not None: |
| 107 | + if computed_cu != compute_unit: |
| 108 | + logger.warning( |
| 109 | + f"Mismatch in compute units: from CPU/RAM={computed_cu}, given={compute_unit}. " |
| 110 | + f"Choosing {max(computed_cu, compute_unit)}." |
| 111 | + ) |
| 112 | + cu = max(computed_cu, compute_unit) # We trust the bigger trier |
| 113 | + else: |
| 114 | + cu = compute_unit if compute_unit is not None else computed_cu |
| 115 | + |
| 116 | + # now tier found |
| 117 | + if cu is None: |
| 118 | + return None |
| 119 | + |
| 120 | + # With CU available, choose the closest one |
| 121 | + candidates = self.tiers |
| 122 | + if candidates is None: |
| 123 | + return None |
| 124 | + |
| 125 | + best_tier = min( |
| 126 | + candidates, |
| 127 | + key=lambda t: (abs(t.compute_units - cu), -t.compute_units), |
| 128 | + ) |
| 129 | + return best_tier |
| 130 | + |
| 131 | + def get_services_specs( |
| 132 | + self, |
| 133 | + tier: Tier, |
| 134 | + ) -> TierComputedSpec: |
| 135 | + """ |
| 136 | + Calculate ammount of vram / cpu / disk | + gpu model / vram if it GPU instance |
| 137 | + """ |
| 138 | + if self.compute_unit is None: |
| 139 | + raise ValueError("ComputeUnit is required to get service specs") |
| 140 | + |
| 141 | + cpu = tier.compute_units * self.compute_unit.vcpus |
| 142 | + memory_mib = tier.compute_units * self.compute_unit.memory_mib |
| 143 | + disk = ( |
| 144 | + tier.compute_units * self.compute_unit.disk_mib |
| 145 | + ) # Min value disk can be increased |
| 146 | + |
| 147 | + # Gpu Specs |
| 148 | + gpu = None |
| 149 | + vram = None |
| 150 | + if tier.model and tier.vram: |
| 151 | + gpu = tier.model |
| 152 | + vram = tier.vram |
| 153 | + |
| 154 | + return TierComputedSpec( |
| 155 | + vcpus=cpu, |
| 156 | + memory_mib=memory_mib, |
| 157 | + disk_mib=disk, |
| 158 | + gpu_model=gpu, |
| 159 | + vram=vram, |
| 160 | + ) |
| 161 | + |
61 | 162 |
|
62 | 163 | class PricingModel(RootModel[Dict[PricingEntity, PricingPerEntity]]): |
63 | 164 | def __iter__(self): |
|
0 commit comments