From 7d5c8ebf7d9ee4252c621090469d0f82f5eb2897 Mon Sep 17 00:00:00 2001 From: pyon12 Date: Thu, 20 Nov 2025 20:24:09 +0000 Subject: [PATCH 1/5] feat: update for gpu splitting --- lium/cli/init/auth.py | 7 +++++-- lium/cli/ls/display.py | 2 +- lium/cli/plugin_example.py | 3 ++- lium/cli/ps/display.py | 8 +++----- lium/cli/rm/display.py | 12 ++++++------ lium/cli/up/actions.py | 4 +++- lium/cli/up/command.py | 9 +++++---- lium/cli/up/validation.py | 4 ++-- lium/cli/utils.py | 1 + lium/sdk/client.py | 12 +++++++++++- lium/sdk/models.py | 3 +++ 11 files changed, 42 insertions(+), 23 deletions(-) diff --git a/lium/cli/init/auth.py b/lium/cli/init/auth.py index eea0af3..5dd6ae4 100644 --- a/lium/cli/init/auth.py +++ b/lium/cli/init/auth.py @@ -2,6 +2,9 @@ from typing import Optional from lium.cli import ui +from lium.sdk.config import Config + +config = Config.load() class quiet_fds: """Redirect stdout/stderr to /dev/null (silences child processes).""" @@ -18,7 +21,7 @@ def __exit__(self, *_): self._null.close() def init_auth(): - url = "https://lium.io/api/cli-auth/init" + url = f"{config.base_url}/cli-auth/init" resp = requests.post(url, json={"callback_url": "http://localhost:8080/auth/callback"}, headers={"Content-Type": "application/json"}, @@ -28,7 +31,7 @@ def init_auth(): return resp.json()["browser_url"], resp.json()["session_id"] def poll_auth(session_id, max_attempts=6, interval=5) -> Optional[str]: # 30 seconds timeout (6 * 5) - url = f"https://lium.io/api/cli-auth/poll/{session_id}" + url = f"{config.base_url}/cli-auth/poll/{session_id}" for _ in range(max_attempts): try: resp = requests.get(url, timeout=5) diff --git a/lium/cli/ls/display.py b/lium/cli/ls/display.py index 874e331..5f9773d 100644 --- a/lium/cli/ls/display.py +++ b/lium/cli/ls/display.py @@ -22,7 +22,7 @@ def _mid_ellipsize(s: str, width: int = 28) -> str: def _cfg(exe: ExecutorInfo) -> str: """Format GPU configuration string.""" - return f"{exe.gpu_count}×{exe.gpu_type}" + return f"{exe.available_gpu_count}×{exe.gpu_type}" def _country_name(loc: Optional[Dict]) -> str: diff --git a/lium/cli/plugin_example.py b/lium/cli/plugin_example.py index ae306a3..c07c985 100644 --- a/lium/cli/plugin_example.py +++ b/lium/cli/plugin_example.py @@ -82,7 +82,7 @@ def compose_up(ctx, file: str, detach: bool): # Find suitable executor gpu_type = model_config.get('gpu_type') - gpu_count = model_config.get('gpu_count', 1) + gpu_count = model_config.get('gpu_count', None) template_id = model_config.get('template_id') # Get available executors @@ -96,6 +96,7 @@ def compose_up(ctx, file: str, detach: bool): pod = lium.up( executor=executor.id, name=model_name, + gpu_count=gpu_count, template=template_id, ) diff --git a/lium/cli/ps/display.py b/lium/cli/ps/display.py index 1322a58..7df4ed0 100644 --- a/lium/cli/ps/display.py +++ b/lium/cli/ps/display.py @@ -112,13 +112,11 @@ def build_pods_table(pods: List[PodInfo], short: bool = False) -> tuple[Table | for pod in pods: executor = pod.executor if executor: - config = f"{executor.gpu_count}×{executor.gpu_type}" if executor.gpu_count > 1 else executor.gpu_type - price_str = f"${executor.price_per_hour:.2f}" - price_per_hour = executor.price_per_hour + config = f"{pod.gpu_count}×{executor.gpu_type}" if pod.gpu_count > 1 else executor.gpu_type + price_str = f"${pod.price:.2f}" else: config = "—" price_str = "—" - price_per_hour = None status_color = console.pod_status_color(pod.status) status_text = f"[{status_color}]{pod.status.upper()}[/]" @@ -132,7 +130,7 @@ def build_pods_table(pods: List[PodInfo], short: bool = False) -> tuple[Table | config, console.get_styled(template_name, 'info'), price_str, - _format_cost(pod.created_at, price_per_hour), + _format_cost(pod.created_at, pod.price), _format_uptime(pod.created_at), ] diff --git a/lium/cli/rm/display.py b/lium/cli/rm/display.py index 65a275b..390b63b 100644 --- a/lium/cli/rm/display.py +++ b/lium/cli/rm/display.py @@ -15,7 +15,7 @@ def calculate_pod_cost(pod: PodInfo) -> float: Returns: Total cost in dollars """ - if not pod.executor or not pod.executor.price_per_hour or not pod.created_at: + if not pod.price or not pod.created_at: return 0.0 try: @@ -28,7 +28,7 @@ def calculate_pod_cost(pod: PodInfo) -> float: now_utc = datetime.now(timezone.utc) hours = (now_utc - dt_created).total_seconds() / 3600 - return hours * pod.executor.price_per_hour + return hours * pod.price except Exception: return 0.0 @@ -48,8 +48,8 @@ def format_pods_for_removal(pods: List[PodInfo], show_cost: bool = True) -> str: for pod in pods: price_info = "" - if pod.executor and pod.executor.price_per_hour: - price_info = f" (${pod.executor.price_per_hour:.2f}/h)" + if pod.price: + price_info = f" (${pod.price:.2f}/h)" if show_cost: total_cost += calculate_pod_cost(pod) @@ -75,8 +75,8 @@ def format_pods_for_scheduled_removal(pods: List[PodInfo], termination_time: dat for pod in pods: price_info = "" - if pod.executor and pod.executor.price_per_hour: - price_info = f" (${pod.executor.price_per_hour:.2f}/h)" + if pod.price: + price_info = f" (${pod.price:.2f}/h)" lines.append(f" {pod.huid} - {pod.status}{price_info}") # Add scheduled time info diff --git a/lium/cli/up/actions.py b/lium/cli/up/actions.py index 215279c..a959245 100644 --- a/lium/cli/up/actions.py +++ b/lium/cli/up/actions.py @@ -44,7 +44,7 @@ def execute(self, ctx: dict) -> ActionResult: executors = lium.ls(gpu_type=gpu) if count: - executors = [e for e in executors if e.gpu_count == count] + executors = [e for e in executors if e.available_gpu_count >= count] if country: executors = [ e for e in executors @@ -128,6 +128,7 @@ def execute(self, ctx: dict) -> ActionResult: lium: Lium = ctx["lium"] executor: ExecutorInfo = ctx["executor"] template: Template = ctx["template"] + gpu_count: Optional[int] = ctx.get("gpu_count") name: Optional[str] = ctx.get("name") volume_id: Optional[str] = ctx.get("volume_id") ports: Optional[int] = ctx.get("ports") @@ -139,6 +140,7 @@ def execute(self, ctx: dict) -> ActionResult: pod_info = lium.up( executor_id=executor.id, name=name, + gpu_count=gpu_count, template_id=template.id if template else None, volume_id=volume_id, ports=ports, diff --git a/lium/cli/up/command.py b/lium/cli/up/command.py index 0f79130..2229356 100644 --- a/lium/cli/up/command.py +++ b/lium/cli/up/command.py @@ -1,7 +1,7 @@ from typing import Optional import click -from lium.sdk import Lium +from lium.sdk import Lium, ExecutorInfo from lium.cli import ui from lium.cli.utils import handle_errors, ensure_config from lium.cli.completion import get_gpu_completions @@ -105,13 +105,13 @@ def up_command( ui.error(result.error) return - executor = result.data["executor"] + executor: ExecutorInfo = result.data["executor"] if not yes: confirm_msg = ( f"Acquire pod on {executor.huid} " - f"({executor.gpu_count}×{executor.gpu_type}) " - f"at ${executor.price_per_hour:.2f}/h?" + f"({count or executor.available_gpu_count}×{executor.gpu_type}) " + f"at ${(executor.price_per_gpu_hour * (count or executor.available_gpu_count)):.2f}/h?" ) if not ui.confirm(confirm_msg): return @@ -152,6 +152,7 @@ def up_command( "lium": lium, "executor": executor, "template": template, + "gpu_count": count or executor.available_gpu_count, "name": name, "volume_id": volume_id, "ports": ports diff --git a/lium/cli/up/validation.py b/lium/cli/up/validation.py index 8fe987f..7f50da6 100644 --- a/lium/cli/up/validation.py +++ b/lium/cli/up/validation.py @@ -3,8 +3,8 @@ def validate(executor_id: str | None, gpu: str | None, count: int | None, country: str | None, ttl: str | None, until: str | None) -> tuple[bool, str]: """Validate up command inputs.""" - if executor_id and (gpu or count or country): - return False, "Cannot use filters (--gpu, --count, --country) when specifying an executor ID" + if executor_id and (gpu or country): + return False, "Cannot use filters (--gpu, --country) when specifying an executor ID" if not executor_id and not (gpu or count or country): return False, "Must provide either EXECUTOR_ID or filters (--gpu, --count, --country)" diff --git a/lium/cli/utils.py b/lium/cli/utils.py index 8e10c9b..9f278dc 100644 --- a/lium/cli/utils.py +++ b/lium/cli/utils.py @@ -400,6 +400,7 @@ def store_executor_selection(executors: List[ExecutorInfo]) -> None: 'huid': executor.huid, 'gpu_type': executor.gpu_type, 'gpu_count': executor.gpu_count, + 'available_gpu_count': executor.available_gpu_count, 'price_per_hour': executor.price_per_hour, 'location': executor.location.get('country', 'Unknown') if executor.location else 'Unknown' }) diff --git a/lium/sdk/client.py b/lium/sdk/client.py index d3f1160..dd1b22c 100644 --- a/lium/sdk/client.py +++ b/lium/sdk/client.py @@ -122,6 +122,7 @@ def _dict_to_executor_info(self, executor_dict: Dict) -> Optional[ExecutorInfo]: specs = executor_dict.get("specs", {}) gpu_info = specs.get("gpu", {}) gpu_count = gpu_info.get("count", 1) + available_gpu_count = executor_dict.get("available_gpu_count", 1) # Extract GPU type from machine_name or specs machine_name = executor_dict.get("machine_name", "") @@ -136,6 +137,7 @@ def _dict_to_executor_info(self, executor_dict: Dict) -> Optional[ExecutorInfo]: gpu_type = extract_gpu_type(gpu_name) price_per_hour = executor_dict.get("price_per_hour", 0) + price_per_gpu = executor_dict.get("price_per_gpu", 0) return ExecutorInfo( id=executor_dict.get("id", ""), @@ -143,8 +145,9 @@ def _dict_to_executor_info(self, executor_dict: Dict) -> Optional[ExecutorInfo]: machine_name=machine_name, gpu_type=gpu_type, gpu_count=gpu_count, + available_gpu_count=available_gpu_count, price_per_hour=price_per_hour, - price_per_gpu_hour=price_per_hour / max(1, gpu_count), + price_per_gpu_hour=price_per_gpu, location=executor_dict.get("location", {}), specs=specs, status=executor_dict.get("status", "unknown"), @@ -157,6 +160,7 @@ def up( *, executor_id: str, name: Optional[str] = None, + gpu_count: Optional[int] = None, template_id: Optional[str] = None, volume_id: Optional[str] = None, ports: Optional[int] = None, @@ -189,6 +193,7 @@ def up( payload = { "pod_name": name, + "gpu_count": gpu_count, "template_id": template_id, "volume_id": volume_id, "user_public_key": ssh_material, @@ -279,6 +284,8 @@ def ps(self) -> List[PodInfo]: name=d.get("pod_name", ""), status=d.get("status", "unknown"), huid=generate_huid(d.get("id", "")), + gpu_count=int(d.get("gpu_count", 0)), + price=d.get("price", 0.0), ssh_cmd=d.get("ssh_connect_cmd"), ports=d.get("ports_mapping", {}), created_at=d.get("created_at", ""), @@ -814,6 +821,8 @@ def switch_template(self, pod: PodInfo, *, template_id: str) -> PodInfo: name=response.get("pod_name", pod.name), status=response.get("status", "PENDING"), huid=pod.huid, # Keep the original HUID + gpu_count=int(response.get("gpu_count", 0)), + price=response.get("price", 0.0), ssh_cmd=response.get("ssh_connect_cmd"), ports=response.get("ports_mapping", {}), created_at=response.get("created_at", ""), @@ -824,6 +833,7 @@ def switch_template(self, pod: PodInfo, *, template_id: str) -> PodInfo: machine_name="", gpu_type=response.get("gpu_name", ""), gpu_count=int(response.get("gpu_count", 0) or 0), + available_gpu_count=0, price_per_hour=0.0, price_per_gpu_hour=0.0, location={}, diff --git a/lium/sdk/models.py b/lium/sdk/models.py index 2e60b18..8348eed 100644 --- a/lium/sdk/models.py +++ b/lium/sdk/models.py @@ -12,6 +12,7 @@ class ExecutorInfo: machine_name: str gpu_type: str gpu_count: int + available_gpu_count: int price_per_hour: float price_per_gpu_hour: float location: Dict @@ -42,6 +43,8 @@ class PodInfo: ports: Dict created_at: str updated_at: str + gpu_count: int + price: float executor: Optional[ExecutorInfo] template: Dict removal_scheduled_at: Optional[str] From 755a209c2c6a864b5b8399fc1980f77a0db6efc5 Mon Sep 17 00:00:00 2001 From: pyon12 Date: Fri, 21 Nov 2025 15:08:38 +0000 Subject: [PATCH 2/5] feat: make gpu_count required when renting a pod --- lium/cli/plugin_example.py | 2 +- lium/cli/up/command.py | 4 ++-- lium/sdk/client.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lium/cli/plugin_example.py b/lium/cli/plugin_example.py index c07c985..03899e4 100644 --- a/lium/cli/plugin_example.py +++ b/lium/cli/plugin_example.py @@ -86,7 +86,7 @@ def compose_up(ctx, file: str, detach: bool): template_id = model_config.get('template_id') # Get available executors - executors = lium.ls(gpu_type=gpu_type) + executors = lium.ls(gpu_type=gpu_type, gpu_count=gpu_count) if not executors: click.echo(f"No executors available for {gpu_type}", err=True) continue diff --git a/lium/cli/up/command.py b/lium/cli/up/command.py index 2229356..0fbba32 100644 --- a/lium/cli/up/command.py +++ b/lium/cli/up/command.py @@ -25,7 +25,7 @@ @click.option("--volume", "-v", help="Volume spec: 'id:' or 'new:name=[,desc=]'") @click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt") @click.option("--gpu", help="Filter executors by GPU type (e.g., H200, A6000)", shell_complete=get_gpu_completions) -@click.option("--count", "-c", type=int, help="Number of GPUs per pod") +@click.option("--count", "-c", type=int, required=True, help="Number of GPUs per pod") @click.option("--country", help="Filter executors by ISO country code (e.g., US, FR)") @click.option("--ports", "-p", type=int, help="Minimum number of available ports required") @click.option("--ttl", help="Auto-terminate after duration (e.g., 6h, 45m, 2d)") @@ -39,7 +39,7 @@ def up_command( volume: Optional[str], yes: bool, gpu: Optional[str], - count: Optional[int], + count: int, country: Optional[str], ports: Optional[int], ttl: Optional[str], diff --git a/lium/sdk/client.py b/lium/sdk/client.py index dd1b22c..906454c 100644 --- a/lium/sdk/client.py +++ b/lium/sdk/client.py @@ -255,7 +255,7 @@ def ls( params["machine_names"] = gpu_type if gpu_count: params["gpu_count_gte"] = gpu_count - params["gpu_count_lte"] = gpu_count + # params["gpu_count_lte"] = gpu_count if lat is not None and lon is not None: params["lat"] = lat params["lon"] = lon From 34def94f65bf46e3773e3c5f51ecab4663ac716d Mon Sep 17 00:00:00 2001 From: pyon12 Date: Mon, 24 Nov 2025 09:13:25 +0000 Subject: [PATCH 3/5] fix: check executor based on available_gpu_count and count requirement in lium up command --- lium/cli/up/actions.py | 3 +++ lium/cli/up/command.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lium/cli/up/actions.py b/lium/cli/up/actions.py index a959245..22b522c 100644 --- a/lium/cli/up/actions.py +++ b/lium/cli/up/actions.py @@ -32,6 +32,9 @@ def execute(self, ctx: dict) -> ActionResult: executor = lium.get_executor(executor_id) if not executor: return ActionResult(ok=False, data={}, error=f"Executor '{executor_id}' not found") + + if count and count > executor.available_gpu_count: + return ActionResult(ok=False, data={}, error=f"Executor {executor.huid} has insufficient GPUs (available: {executor.available_gpu_count}, required: {count})") if ports and (not executor.available_port_count or executor.available_port_count < ports): available = executor.available_port_count or 0 diff --git a/lium/cli/up/command.py b/lium/cli/up/command.py index 0fbba32..2229356 100644 --- a/lium/cli/up/command.py +++ b/lium/cli/up/command.py @@ -25,7 +25,7 @@ @click.option("--volume", "-v", help="Volume spec: 'id:' or 'new:name=[,desc=]'") @click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt") @click.option("--gpu", help="Filter executors by GPU type (e.g., H200, A6000)", shell_complete=get_gpu_completions) -@click.option("--count", "-c", type=int, required=True, help="Number of GPUs per pod") +@click.option("--count", "-c", type=int, help="Number of GPUs per pod") @click.option("--country", help="Filter executors by ISO country code (e.g., US, FR)") @click.option("--ports", "-p", type=int, help="Minimum number of available ports required") @click.option("--ttl", help="Auto-terminate after duration (e.g., 6h, 45m, 2d)") @@ -39,7 +39,7 @@ def up_command( volume: Optional[str], yes: bool, gpu: Optional[str], - count: int, + count: Optional[int], country: Optional[str], ports: Optional[int], ttl: Optional[str], From 68c6f929dd8b8f77bd314fe800555901d9854564 Mon Sep 17 00:00:00 2001 From: pyon12 Date: Mon, 8 Dec 2025 18:07:13 +0000 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20DAH-1614=20-=20update=20Executors?= =?UTF-8?q?=20=20(2=20shown,=20=E2=98=85=201=20optimal)=20=20=20=20=20=20?= =?UTF-8?q?=20Id=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20Config=20=20=20=20=20=20=20=20GPU?= =?UTF-8?q?=20Splitting=20=20=20$/GPU=C2=B7h=20=20Location=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20VRAM=20(Gb)=20=20?= =?UTF-8?q?=20=20RAM=20(Gb)=20=20=20=20Disk=20(Gb)=20=20=20Upload=20(Mbps)?= =?UTF-8?q?=20=20=20Download=20(Mbps)=20=20Ports=20=20=20=201=20=20?= =?UTF-8?q?=E2=98=85=20cosmic-comet-5a=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=201=C3=97A4000=20=20=20=20=20=20=20=E2=80=94=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=200.12=20=20United=20State?= =?UTF-8?q?s=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2015=20=20=20=20=20=20=20=202052=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?9673=20=20=20=20=20=20=20=20=20=20=20=20=20471=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20861=20=201714=20=20=20=202=20=20?= =?UTF-8?q?=20=20cosmic-matrix-65=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?4=C3=97A4000=20=20=20=20=20=20=20Min=20GPUs:=202=20=20=20=20=20?= =?UTF-8?q?=20=20=200.12=20=20United=20Kingdom=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2015=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2084=20=20=20=20=20=20=20=20=20=20400=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20525=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20337=20=201717?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tip: lium up # e.g. lium up 1 command and add gpu splitting info --- lium/cli/ls/display.py | 5 ++++- lium/cli/utils.py | 9 +++++++-- lium/sdk/client.py | 8 ++------ lium/sdk/models.py | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lium/cli/ls/display.py b/lium/cli/ls/display.py index 5f9773d..5ebd369 100644 --- a/lium/cli/ls/display.py +++ b/lium/cli/ls/display.py @@ -117,8 +117,9 @@ def _sort_key_factory(name: str) -> Callable[[ExecutorInfo], Any]: def _add_table_columns(t: Table) -> None: """Add columns to the table with fixed widths.""" t.add_column("", justify="right", width=3, no_wrap=True, style="dim") - t.add_column("Id", justify="left", ratio=8, min_width=24, overflow="fold") + t.add_column("Id", justify="left", ratio=8, min_width=18, overflow="fold") t.add_column("Config", justify="left", width=12, no_wrap=True) + t.add_column("GPU Splitting", justify="left", min_width=12, no_wrap=True) t.add_column("$/GPU·h", justify="right", width=8, no_wrap=True) t.add_column("Location", justify="left", ratio=4, min_width=10, overflow="fold") t.add_column("VRAM (Gb)", justify="right", width=11, no_wrap=True) @@ -201,11 +202,13 @@ def build_executors_table( huid = _mid_ellipsize(exe.huid) huid += " (DinD)" if exe.docker_in_docker else "" huid_display = f"{console.get_styled('★', 'success')} {console.get_styled(huid, 'id')}" if is_pareto else f" {console.get_styled(huid, 'id')}" + gpu_splitting_display = f"Min GPUs: {exe.min_gpu_count_for_rental}" if exe.min_gpu_count_for_rental else "—" table.add_row( str(idx), huid_display, _cfg(exe), + gpu_splitting_display, console.get_styled(_money(exe.price_per_gpu_hour), 'success'), _country_name(exe.location), s["VRAM"], diff --git a/lium/cli/utils.py b/lium/cli/utils.py index 9f278dc..69ced72 100644 --- a/lium/cli/utils.py +++ b/lium/cli/utils.py @@ -401,8 +401,13 @@ def store_executor_selection(executors: List[ExecutorInfo]) -> None: 'gpu_type': executor.gpu_type, 'gpu_count': executor.gpu_count, 'available_gpu_count': executor.available_gpu_count, - 'price_per_hour': executor.price_per_hour, - 'location': executor.location.get('country', 'Unknown') if executor.location else 'Unknown' + 'price_per_gpu_hour': executor.price_per_gpu_hour, + 'min_gpu_count_for_rental': executor.min_gpu_count_for_rental, + 'location': executor.location.get('country', 'Unknown') if executor.location else 'Unknown', + 'status': executor.status, + 'docker_in_docker': executor.docker_in_docker, + 'ip': executor.ip, + 'available_port_count': executor.available_port_count, }) # Store in config directory diff --git a/lium/sdk/client.py b/lium/sdk/client.py index 5e0309a..c2235ea 100644 --- a/lium/sdk/client.py +++ b/lium/sdk/client.py @@ -136,9 +136,6 @@ def _dict_to_executor_info(self, executor_dict: Dict) -> Optional[ExecutorInfo]: if gpu_name: gpu_type = extract_gpu_type(gpu_name) - price_per_hour = executor_dict.get("price_per_hour", 0) - price_per_gpu = executor_dict.get("price_per_gpu", 0) - return ExecutorInfo( id=executor_dict.get("id", ""), ip=executor_dict.get("executor_ip_address", ""), @@ -147,13 +144,13 @@ def _dict_to_executor_info(self, executor_dict: Dict) -> Optional[ExecutorInfo]: gpu_type=gpu_type, gpu_count=gpu_count, available_gpu_count=available_gpu_count, - price_per_hour=price_per_hour, - price_per_gpu_hour=price_per_gpu, + price_per_gpu_hour=executor_dict.get("price_per_gpu", 0), location=executor_dict.get("location", {}), specs=specs, status=executor_dict.get("status", "unknown"), docker_in_docker=specs.get("sysbox_runtime", False), available_port_count=specs.get("available_port_count"), + min_gpu_count_for_rental=executor_dict.get("min_gpu_count_for_rental", None), ) def up( @@ -920,7 +917,6 @@ def switch_template(self, pod: PodInfo, *, template_id: str) -> PodInfo: gpu_type=response.get("gpu_name", ""), gpu_count=int(response.get("gpu_count", 0) or 0), available_gpu_count=0, - price_per_hour=0.0, price_per_gpu_hour=0.0, location={}, specs={}, diff --git a/lium/sdk/models.py b/lium/sdk/models.py index fd91274..5c69e9c 100644 --- a/lium/sdk/models.py +++ b/lium/sdk/models.py @@ -13,7 +13,6 @@ class ExecutorInfo: gpu_type: str gpu_count: int available_gpu_count: int - price_per_hour: float price_per_gpu_hour: float location: Dict specs: Dict @@ -21,6 +20,7 @@ class ExecutorInfo: docker_in_docker: bool ip: str available_port_count: Optional[int] = None + min_gpu_count_for_rental: int | None = None @property def driver_version(self) -> str: From 0053b3cfaa72d108df9a1465851268b2c244d1ce Mon Sep 17 00:00:00 2001 From: pyon12 Date: Mon, 8 Dec 2025 18:57:22 +0000 Subject: [PATCH 5/5] feat: DAH-1614 - update lium up and handle gpu_count correctly --- lium/cli/up/actions.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lium/cli/up/actions.py b/lium/cli/up/actions.py index 54f8c26..322aae8 100644 --- a/lium/cli/up/actions.py +++ b/lium/cli/up/actions.py @@ -33,8 +33,15 @@ def execute(self, ctx: dict) -> ActionResult: if not executor: return ActionResult(ok=False, data={}, error=f"Executor '{executor_id}' not found") - if count and count > executor.available_gpu_count: - return ActionResult(ok=False, data={}, error=f"Executor {executor.huid} has insufficient GPUs (available: {executor.available_gpu_count}, required: {count})") + if count: + if count > executor.available_gpu_count: + return ActionResult(ok=False, data={}, error=f"Executor {executor.huid} has insufficient GPUs (available: {executor.available_gpu_count}, required: {count})") + if executor.min_gpu_count_for_rental: + if count < executor.min_gpu_count_for_rental: + return ActionResult(ok=False, data={}, error=f"Executor {executor.huid} requires at least {executor.min_gpu_count_for_rental} GPUs for rental.") + else: + if count < executor.available_gpu_count: + return ActionResult(ok=False, data={}, error=f"Executor {executor.huid} doesn't support gpu splitting.") if ports and (not executor.available_port_count or executor.available_port_count < ports): available = executor.available_port_count or 0 @@ -44,10 +51,13 @@ def execute(self, ctx: dict) -> ActionResult: error=f"Executor {executor.huid} has insufficient ports (available: {available}, required: {ports})" ) else: - executors = lium.ls(gpu_type=gpu) + executors = lium.ls(gpu_type=gpu, gpu_count=count) if count: - executors = [e for e in executors if e.available_gpu_count >= count] + executors = [ + e for e in executors + if (not e.min_gpu_count_for_rental and e.available_gpu_count == count) or (e.min_gpu_count_for_rental and e.min_gpu_count_for_rental <= count and e.available_gpu_count >= count) + ] if country: executors = [ e for e in executors