Skip to content
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
2 changes: 1 addition & 1 deletion src/dstack/_internal/core/models/fleets.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ class InstanceGroupParams(CoreModel):
resources: Annotated[
Optional[ResourcesSpec],
Field(description="The resources requirements"),
] = ResourcesSpec()
] = None

blocks: Annotated[
Union[Literal["auto"], int],
Expand Down
10 changes: 5 additions & 5 deletions src/dstack/_internal/core/models/instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from dstack._internal.core.models.envs import Env
from dstack._internal.core.models.health import HealthStatus
from dstack._internal.core.models.volumes import Volume
from dstack._internal.utils.common import pretty_resources
from dstack._internal.utils.common import format_mib_as_gb, pretty_resources
from dstack._internal.utils.logging import get_logger

logger = get_logger(__name__)
Expand Down Expand Up @@ -135,7 +135,7 @@ def _pretty_format(
"gpu_count": len(gpus),
}
if gpu.memory_mib > 0:
gpu_resources["gpu_memory"] = f"{gpu.memory_mib / 1024:.0f}GB"
gpu_resources["gpu_memory"] = format_mib_as_gb(gpu.memory_mib)
output = pretty_resources(**gpu_resources)
if include_spot and spot:
output += " (spot)"
Expand All @@ -146,15 +146,15 @@ def _pretty_format(
resources["cpus"] = cpus
resources["cpu_arch"] = cpu_arch
if memory_mib > 0:
resources["memory"] = f"{memory_mib / 1024:.0f}GB"
resources["memory"] = format_mib_as_gb(memory_mib)
if disk_size_mib > 0:
resources["disk_size"] = f"{disk_size_mib / 1024:.0f}GB"
resources["disk_size"] = format_mib_as_gb(disk_size_mib)
if gpus:
gpu = gpus[0]
resources["gpu_name"] = gpu.name
resources["gpu_count"] = len(gpus)
if gpu.memory_mib > 0:
resources["gpu_memory"] = f"{gpu.memory_mib / 1024:.0f}GB"
resources["gpu_memory"] = format_mib_as_gb(gpu.memory_mib)
output = pretty_resources(**resources)
if include_spot and spot:
output += " (spot)"
Expand Down
10 changes: 10 additions & 0 deletions src/dstack/_internal/core/models/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,16 @@ class ResourcesSpec(generate_dual_core_model(ResourcesSpecConfig)):
"""`gpu` is optional for backward compatibility."""
disk: Annotated[Optional[DiskSpec], Field(description="The disk resources")] = DEFAULT_DISK

@classmethod
def unconstrained(cls) -> "ResourcesSpec":
"""ResourcesSpec with no meaningful minimum constraints."""
return cls(
cpu=CPUSpec(count=Range[int](min=1, max=None)),
memory=Range[Memory](min=Memory.parse("0"), max=None),
gpu=DEFAULT_GPU_SPEC,
disk=None,
)

def pretty_format(self) -> str:
# TODO: Remove in 0.20. Use self.cpu directly
cpu = parse_obj_as(CPUSpec, self.cpu)
Expand Down
5 changes: 4 additions & 1 deletion src/dstack/_internal/server/services/fleets.py
Original file line number Diff line number Diff line change
Expand Up @@ -937,8 +937,11 @@ def is_cloud_cluster(fleet_model: FleetModel) -> bool:

def get_fleet_requirements(fleet_spec: FleetSpec) -> Requirements:
profile = fleet_spec.merged_profile
resources = fleet_spec.configuration.resources
if resources is None:
resources = ResourcesSpec.unconstrained()
requirements = Requirements(
resources=fleet_spec.configuration.resources or ResourcesSpec(),
resources=resources,
max_price=profile.max_price,
spot=get_policy_map(profile.spot_policy, default=SpotPolicy.ONDEMAND),
reservation=fleet_spec.configuration.reservation,
Expand Down
5 changes: 5 additions & 0 deletions src/dstack/_internal/utils/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ def pretty_date(time: datetime) -> str:
return str(years) + " years ago"


def format_mib_as_gb(mib: int) -> str:
"""Format a MiB value as a human-readable GB string, e.g. 512 → '0.5GB', 8192 → '8GB'."""
return f"{round(mib / 1024, 1):g}GB"


def pretty_resources(
*,
cpu_arch: Optional[Any] = None,
Expand Down
45 changes: 3 additions & 42 deletions src/tests/_internal/server/routers/test_fleets.py
Original file line number Diff line number Diff line change
Expand Up @@ -936,20 +936,7 @@ async def test_creates_fleet(self, test_db, session: AsyncSession, client: Async
"placement": None,
"env": {},
"ssh_config": None,
"resources": {
"cpu": {"min": 2, "max": None},
"memory": {"min": 8.0, "max": None},
"shm_size": None,
"gpu": {
"vendor": None,
"name": None,
"count": {"min": 0, "max": None},
"memory": None,
"total_memory": None,
"compute_capability": None,
},
"disk": {"size": {"min": 100.0, "max": None}},
},
"resources": None,
"backends": None,
"regions": None,
"availability_zones": None,
Expand Down Expand Up @@ -1067,20 +1054,7 @@ async def test_creates_ssh_fleet(self, test_db, session: AsyncSession, client: A
},
"nodes": None,
"placement": None,
"resources": {
"cpu": {"min": 2, "max": None},
"memory": {"min": 8.0, "max": None},
"shm_size": None,
"gpu": {
"vendor": None,
"name": None,
"count": {"min": 0, "max": None},
"memory": None,
"total_memory": None,
"compute_capability": None,
},
"disk": {"size": {"min": 100.0, "max": None}},
},
"resources": None,
"backends": None,
"regions": None,
"availability_zones": None,
Expand Down Expand Up @@ -1297,20 +1271,7 @@ async def test_updates_ssh_fleet(self, test_db, session: AsyncSession, client: A
},
"nodes": None,
"placement": None,
"resources": {
"cpu": {"min": 2, "max": None},
"memory": {"min": 8.0, "max": None},
"shm_size": None,
"gpu": {
"vendor": None,
"name": None,
"count": {"min": 0, "max": None},
"memory": None,
"total_memory": None,
"compute_capability": None,
},
"disk": {"size": {"min": 100.0, "max": None}},
},
"resources": None,
"backends": None,
"regions": None,
"availability_zones": None,
Expand Down
23 changes: 23 additions & 0 deletions src/tests/_internal/server/services/requirements/test_combine.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,29 @@ def test_combines_requirements(
== expected_requirements
)

def test_unconstrained_fleet_resources_pass_through_run_requirements(self):
unconstrained_fleet = Requirements(
resources=ResourcesSpec.unconstrained(),
)
run = Requirements(
resources=ResourcesSpec(
cpu=CPUSpec(count=Range(min=2, max=None)),
memory=Range(min=Memory.parse("2GB"), max=None),
gpu=GPUSpec(count=Range(min=1, max=None)),
disk=DiskSpec(size=Range(min=Memory.parse("50GB"), max=None)),
),
)
result = combine_fleet_and_run_requirements(unconstrained_fleet, run)
assert result is not None
combined_cpu = result.resources.cpu
assert isinstance(combined_cpu, CPUSpec)
assert combined_cpu.count.min == 2
assert result.resources.memory.min == Memory.parse("2GB")
assert result.resources.gpu is not None
assert result.resources.gpu.count.min == 1
assert result.resources.disk is not None
assert result.resources.disk.size.min == Memory.parse("50GB")


class TestIntersectLists:
def test_both_none_returns_none(self):
Expand Down
Loading