Skip to content

Commit 73bac33

Browse files
authored
Drop fleet default min resources (#3776)
* Drop fleet default min resources * Fix memory display for <1GB cases
1 parent 6b6cfb9 commit 73bac33

7 files changed

Lines changed: 51 additions & 49 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ class InstanceGroupParams(CoreModel):
248248
resources: Annotated[
249249
Optional[ResourcesSpec],
250250
Field(description="The resources requirements"),
251-
] = ResourcesSpec()
251+
] = None
252252

253253
blocks: Annotated[
254254
Union[Literal["auto"], int],

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from dstack._internal.core.models.envs import Env
1515
from dstack._internal.core.models.health import HealthStatus
1616
from dstack._internal.core.models.volumes import Volume
17-
from dstack._internal.utils.common import pretty_resources
17+
from dstack._internal.utils.common import format_mib_as_gb, pretty_resources
1818
from dstack._internal.utils.logging import get_logger
1919

2020
logger = get_logger(__name__)
@@ -135,7 +135,7 @@ def _pretty_format(
135135
"gpu_count": len(gpus),
136136
}
137137
if gpu.memory_mib > 0:
138-
gpu_resources["gpu_memory"] = f"{gpu.memory_mib / 1024:.0f}GB"
138+
gpu_resources["gpu_memory"] = format_mib_as_gb(gpu.memory_mib)
139139
output = pretty_resources(**gpu_resources)
140140
if include_spot and spot:
141141
output += " (spot)"
@@ -146,15 +146,15 @@ def _pretty_format(
146146
resources["cpus"] = cpus
147147
resources["cpu_arch"] = cpu_arch
148148
if memory_mib > 0:
149-
resources["memory"] = f"{memory_mib / 1024:.0f}GB"
149+
resources["memory"] = format_mib_as_gb(memory_mib)
150150
if disk_size_mib > 0:
151-
resources["disk_size"] = f"{disk_size_mib / 1024:.0f}GB"
151+
resources["disk_size"] = format_mib_as_gb(disk_size_mib)
152152
if gpus:
153153
gpu = gpus[0]
154154
resources["gpu_name"] = gpu.name
155155
resources["gpu_count"] = len(gpus)
156156
if gpu.memory_mib > 0:
157-
resources["gpu_memory"] = f"{gpu.memory_mib / 1024:.0f}GB"
157+
resources["gpu_memory"] = format_mib_as_gb(gpu.memory_mib)
158158
output = pretty_resources(**resources)
159159
if include_spot and spot:
160160
output += " (spot)"

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,16 @@ class ResourcesSpec(generate_dual_core_model(ResourcesSpecConfig)):
394394
"""`gpu` is optional for backward compatibility."""
395395
disk: Annotated[Optional[DiskSpec], Field(description="The disk resources")] = DEFAULT_DISK
396396

397+
@classmethod
398+
def unconstrained(cls) -> "ResourcesSpec":
399+
"""ResourcesSpec with no meaningful minimum constraints."""
400+
return cls(
401+
cpu=CPUSpec(count=Range[int](min=1, max=None)),
402+
memory=Range[Memory](min=Memory.parse("0"), max=None),
403+
gpu=DEFAULT_GPU_SPEC,
404+
disk=None,
405+
)
406+
397407
def pretty_format(self) -> str:
398408
# TODO: Remove in 0.20. Use self.cpu directly
399409
cpu = parse_obj_as(CPUSpec, self.cpu)

src/dstack/_internal/server/services/fleets.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -937,8 +937,11 @@ def is_cloud_cluster(fleet_model: FleetModel) -> bool:
937937

938938
def get_fleet_requirements(fleet_spec: FleetSpec) -> Requirements:
939939
profile = fleet_spec.merged_profile
940+
resources = fleet_spec.configuration.resources
941+
if resources is None:
942+
resources = ResourcesSpec.unconstrained()
940943
requirements = Requirements(
941-
resources=fleet_spec.configuration.resources or ResourcesSpec(),
944+
resources=resources,
942945
max_price=profile.max_price,
943946
spot=get_policy_map(profile.spot_policy, default=SpotPolicy.ONDEMAND),
944947
reservation=fleet_spec.configuration.reservation,

src/dstack/_internal/utils/common.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ def pretty_date(time: datetime) -> str:
112112
return str(years) + " years ago"
113113

114114

115+
def format_mib_as_gb(mib: int) -> str:
116+
"""Format a MiB value as a human-readable GB string, e.g. 512 → '0.5GB', 8192 → '8GB'."""
117+
return f"{round(mib / 1024, 1):g}GB"
118+
119+
115120
def pretty_resources(
116121
*,
117122
cpu_arch: Optional[Any] = None,

src/tests/_internal/server/routers/test_fleets.py

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -936,20 +936,7 @@ async def test_creates_fleet(self, test_db, session: AsyncSession, client: Async
936936
"placement": None,
937937
"env": {},
938938
"ssh_config": None,
939-
"resources": {
940-
"cpu": {"min": 2, "max": None},
941-
"memory": {"min": 8.0, "max": None},
942-
"shm_size": None,
943-
"gpu": {
944-
"vendor": None,
945-
"name": None,
946-
"count": {"min": 0, "max": None},
947-
"memory": None,
948-
"total_memory": None,
949-
"compute_capability": None,
950-
},
951-
"disk": {"size": {"min": 100.0, "max": None}},
952-
},
939+
"resources": None,
953940
"backends": None,
954941
"regions": None,
955942
"availability_zones": None,
@@ -1067,20 +1054,7 @@ async def test_creates_ssh_fleet(self, test_db, session: AsyncSession, client: A
10671054
},
10681055
"nodes": None,
10691056
"placement": None,
1070-
"resources": {
1071-
"cpu": {"min": 2, "max": None},
1072-
"memory": {"min": 8.0, "max": None},
1073-
"shm_size": None,
1074-
"gpu": {
1075-
"vendor": None,
1076-
"name": None,
1077-
"count": {"min": 0, "max": None},
1078-
"memory": None,
1079-
"total_memory": None,
1080-
"compute_capability": None,
1081-
},
1082-
"disk": {"size": {"min": 100.0, "max": None}},
1083-
},
1057+
"resources": None,
10841058
"backends": None,
10851059
"regions": None,
10861060
"availability_zones": None,
@@ -1297,20 +1271,7 @@ async def test_updates_ssh_fleet(self, test_db, session: AsyncSession, client: A
12971271
},
12981272
"nodes": None,
12991273
"placement": None,
1300-
"resources": {
1301-
"cpu": {"min": 2, "max": None},
1302-
"memory": {"min": 8.0, "max": None},
1303-
"shm_size": None,
1304-
"gpu": {
1305-
"vendor": None,
1306-
"name": None,
1307-
"count": {"min": 0, "max": None},
1308-
"memory": None,
1309-
"total_memory": None,
1310-
"compute_capability": None,
1311-
},
1312-
"disk": {"size": {"min": 100.0, "max": None}},
1313-
},
1274+
"resources": None,
13141275
"backends": None,
13151276
"regions": None,
13161277
"availability_zones": None,

src/tests/_internal/server/services/requirements/test_combine.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,29 @@ def test_combines_requirements(
164164
== expected_requirements
165165
)
166166

167+
def test_unconstrained_fleet_resources_pass_through_run_requirements(self):
168+
unconstrained_fleet = Requirements(
169+
resources=ResourcesSpec.unconstrained(),
170+
)
171+
run = Requirements(
172+
resources=ResourcesSpec(
173+
cpu=CPUSpec(count=Range(min=2, max=None)),
174+
memory=Range(min=Memory.parse("2GB"), max=None),
175+
gpu=GPUSpec(count=Range(min=1, max=None)),
176+
disk=DiskSpec(size=Range(min=Memory.parse("50GB"), max=None)),
177+
),
178+
)
179+
result = combine_fleet_and_run_requirements(unconstrained_fleet, run)
180+
assert result is not None
181+
combined_cpu = result.resources.cpu
182+
assert isinstance(combined_cpu, CPUSpec)
183+
assert combined_cpu.count.min == 2
184+
assert result.resources.memory.min == Memory.parse("2GB")
185+
assert result.resources.gpu is not None
186+
assert result.resources.gpu.count.min == 1
187+
assert result.resources.disk is not None
188+
assert result.resources.disk.size.min == Memory.parse("50GB")
189+
167190

168191
class TestIntersectLists:
169192
def test_both_none_returns_none(self):

0 commit comments

Comments
 (0)