Skip to content

Commit efbb22a

Browse files
oobaboogapre-commit-ci[bot]danielhanchendanielhanchen
authored andcommitted
Studio: GPU memory configuration for GGUF models (unslothai#6414)
* Studio: GPU memory dropdown — llama.cpp --fit on and manual gpu-layers/cpu-moe * Studio: simplify GPU memory changes (reuse ParamSlider, GPU_LAYERS_ALL, loadedGpuMemoryFields helper) * Studio: GPU picker — choose which GPUs a GGUF model loads on (gpu_ids) * Studio: simplify GPU picker (share /api/system fetch, validate gpu_ids) * Studio: GPU picker review fixes (gate relative indices, no cross-model leak, validate, types) * Studio: group GPU controls under a collapsible GPU section * Studio: GPU feature review fixes (fix fit-ctx test, behavior-test the floor, comment accuracy) * Studio: make GPU a top-level settings section (not nested under Model) * Studio: flatten GPU controls into the Model section, group by GPU/context/generation * Studio: move GPU Memory to the bottom of Model with its dependent controls beneath it * Studio: move GPU Memory below Tensor Parallelism and GPUs below GPU Memory * Studio: tighten GPU Memory and GPU Layers tooltip copy * Studio: fix fit-mode context slider track-click, restore GPU Memory tooltip, shorten fit dropdown label * Studio: GPU Memory tooltip one mode per line, briefer * Studio: note HIP_VISIBLE_DEVICES (ROCm) in the GPUs picker tooltip * Studio: narrow the GPU Memory dropdown to fit the shortened label * Studio: use 'llama.cpp --fit' in the GPU Memory tooltip for consistency * Studio: allow Tensor Parallelism in Manual GPU mode * Studio: graduated MoE-on-CPU offload (--n-cpu-moe) replacing the all-or-nothing toggle * Studio: size the MoE-offload slider for staged (deferred-load) models * Studio: share one GGUF header walk for the context-length and MoE-count readers * Studio: size the GPU Layers slider for staged models (one staged-header read) * Studio: move Tensor Parallelism below the GPUs picker * Studio: GPU split (--tensor-split) per-GPU model share in Manual mode * Studio: tolerate whitespace in GPU split input, move it below GPU Layers * Studio: rename the GPU split control to "Split ratio" * Studio: Split ratio sends explicit even input; fix blank=free-VRAM (not even) copy * Studio: tighten llama.cpp --fit VRAM margin with --fit-target 512 * Studio: GPU memory review fixes (rollback re-baseline, single-GPU TP gate, accurate copy) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio: move Split ratio below MoE Layers on CPU * Studio: address PR review (fix GPU-info hydration race, share fit context-length across load paths) * Studio: address codex review (manual single-GPU TP guard, GPU-aware spec defaults in fit/manual, GGUF-only context/preference) * Studio: address codex review round 2 (gpu_present seed, single-GPU tensor-split guard, staged manual-knob reset, strip inherited offload flags) * Studio: address codex review round 3 (strip inherited --n-cpu-moe, CPU-fallback warning in Manual mode) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio: address codex review round 4 (preserve pinned fit context across a later Apply) * Studio: address codex review round 5 (honor GPU picker for diffusion GGUFs, clear fit pin on cross-model switch) * Studio: preserve the pending GPU Memory mode when staging a model * Studio: pin diffusion GPU device order and reset GPU-memory state for diffusion loads * Studio: address codex review round 6 (fit-Auto rollback context, preserve manual non-tensor split modes, persist GPU mode on load not select) * Studio: persist the applied GPU Memory mode, not the requested one (skip diffusion loads) * Studio: replace Manual-mode split-ratio field with per-GPU layer sliders * Studio: clarify per-GPU layer split hint for tensor-parallel mode * Studio: address codex review round 7 (allow GGUF gpu_ids past the legacy guard, replay GPU-memory fields on respawn) * Studio: address codex review round 8 (size the validate preflight like the load in fit mode, across both load paths) * Studio: skip the training-OOM guard for llama.cpp --fit GGUF loads (they spill to RAM) * Studio: drop the now-redundant compare-path validate sizing (the --fit guard skip makes it moot) * Studio: address codex review round 9 (keep the training guard for fit loads, forward gpu_ids to validate, strip inherited manual tensor-split) * Studio: address codex review round 10 (gate GPU-memory adoption on is_gguf, record manual knobs only in Manual mode) * Studio: handle diffusion GGUFs symmetrically in the GPU Memory controls (preserve the standing mode preference, hide the inapplicable mode/TP controls) * Studio: remember the GPU Memory settings per model * Studio: consolidate --fit mode and Manual mode into a single Manual mode * Studio: preserve the per-GPU layer split across GPU Layers changes * Studio: trim overly long GPU Memory comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * address GPU memory config review comments * trim redundant GPU memory tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Reconcile manual-mode TP drops with the unslothai#6659 drop-site invariants * Preserve quantized KV in manual --fit, charge GGUF companions in full, reconcile GPU pick on load * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Clear stale GPU baseline on non-GGUF loads so it can't read as dirty * Fix no-context-shift test for the conditional -c flag * Credit manual GPU-layer offload for cached HF GGUFs * Reset per-model load knobs on GGUF quant switch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Strip inherited tensor-split when manual ratio is cleared * Match auto-load validation to safetensors placement * Reset editable manual knobs after Auto GGUF loads * Record a single device for diffusion GPU picks * Reset per-model GPU knobs before applying saved settings * Address review comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Guard manual tensor splits and keep remembered context on auto-load * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Snapshot compare knobs, seed splits from free VRAM, flag zero-offload loads * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Exempt CPU-only loads from the guard floor and harden compare and reseed paths * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Reach full offload from the layers slider and charge extras drafters in the guard * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Warm the GPU device cache before pick reconciles and disable staged GPU controls * Align the training guard with inherited extras, spec mode, and compare targets * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Hide GPUs from companion-less zero-offload loads * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Size diffusion picks per device, own manual offload flags, reject XPU picks * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Drop tensor flags at zero layers and exempt CPU-pinned drafters * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Allowlist the zero-layer tensor parallel drop site * Keep validate and load guards on the same extras and refresh stale baselines * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Drop mismatched manual tensor splits before launch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Gate XPU picks on the real backend field and harden split and hydration paths * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Weight full GPUs as zero, clamp split shares, and refine the zero-layer mask gate * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Carry fit context across mode changes and align drafter and picker gates * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Catch variant switches, uncached diffusion repos, and text-only mmproj skips * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Check companions on the first device and size native and remote zero-layer loads * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Replace the training guard's precise VRAM modeling with a conservative bound * Baseline context pins on non-GGUF hydration and reprobe list-seeded staged GGUFs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Size manual splits by their largest share and preserve resolved context from Default * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Default-deny unsized required companions and price KV at the effective cache dtype * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Reserve MTP draft KV and MLA target-copy in the training guard * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Size tensor-parallel loads per device and show GPU controls for native GGUFs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Reserve MTP overhead for uncached remote GGUFs and the mmproj runtime factor * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Drop the training-coexistence VRAM estimation this PR added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Gate remembered load settings to GGUF picks * Lock the remaining load-time controls during a staged load * Clear the stale native-path token on compare loads * Drop a stale guard reference from the zero-offload masking comment * Seed GPU baselines from the rollback response and drop never-emitted offload flags * Match validate's training guard to load and keep the native reload token * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Trim verbose GPU-memory comments * Thread the variants header walk off the event loop, honor device pins on zero-offload, and hold staged GPU edits * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Honor manual placement and classify pinned zero-offload loads * Close diffusion admission and status hydration gaps * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Check the actual diffusion GPU during training * Align staged baselines and manual reload dedupe * Fix GGUF placement and rollback state * Harden manual GGUF placement boundaries * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Remove unused resolve_tensor_parallel import in llama_cpp.py The name is used only in llama_server_args.py, routes/inference.py, and tests, not in llama_cpp.py; the unused hoisted import trips the import-hoist verifier in the source-lint CI job. * Fix diffusion GPU dedup and training guard for non-numeric device tokens The diffusion runner drives only its single lowest device and the backend records that one device (self._gpu_ids = [sorted(gpu_ids)[0]]), but the reload dedupe compared it against the full requested list, so a multi-GPU pick that resolves to the same device forced a needless reload. Normalize the request the same way for a loaded diffusion model in both _already_in_target_state and the route _request_matches_loaded_settings. The chat-during-training coexistence guard called int() on the single-device token and hard-rejected when it could not parse. A non-numeric token (a CUDA UUID / MIG handle) now sizes against the whole visible pool like the GGUF guard instead of falsely blocking the load, and an empty token (a CPU-only runner such as a CPU diffusion GGUF) is allowed outright since it uses no GPU VRAM. * Tighten comments added by the GPU memory config changes * Harden GGUF placement from independent review: VRAM sizing, diffusion TP reset, tensor_split validation - Training coexistence guard: a single-device runner pinned through an unresolvable UUID/MIG token was sized against the aggregate visible-VRAM pool, so a load could pass on capacity it cannot use and then OOM active training. Size against the worst-case visible device (min free) instead, keeping the guard's documented default-deny contract. The empty-token (CPU-only runner) allow path is unchanged. - Diffusion startup: _start_diffusion_server now resets self._tensor_parallel to False alongside the other placement resets. A prior tensor-parallel chat load (process killed but not fully unload-reset) otherwise left /status misreporting tensor parallelism and made an identical diffusion re-Apply reload against the stale state. - tensor_split: reject negative / non-finite / all-zero splits up front. They were dropped at launch but still compared raw in the reload dedupe, so an identical Apply reloaded indefinitely. - Tests: the shared httpx stub was incomplete and, installed via setdefault before real httpx loaded, broke a combined pytest run (collection errors on httpx.Response). Import the real installed httpx instead. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: danielhanchen <unslothshared@gmail.com> Co-authored-by: danielhanchen <danielhanchen@gmail.com>
1 parent 8d13a6f commit efbb22a

31 files changed

Lines changed: 4352 additions & 397 deletions

studio/backend/core/inference/llama_cpp.py

Lines changed: 666 additions & 49 deletions
Large diffs are not rendered by default.

studio/backend/core/inference/llama_server_args.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,25 @@ def is_managed_flag(flag: str) -> bool:
186186
_TENSOR_SPLIT_FLAGS: frozenset[str] = frozenset({"-ts", "--tensor-split"})
187187
_SPLIT_SHADOWING_FLAGS: frozenset[str] = _SPLIT_MODE_FLAGS | _TENSOR_SPLIT_FLAGS
188188

189+
# GPU-offload flags. Stripped only when the GPU Memory mode owns offload
190+
# (manual emits --fit / --gpu-layers / --n-cpu-moe); in auto, a user's
191+
# inherited -ngl is respected (the offload_overridden path), so this group is
192+
# opt-in, not default. Layer flags are shared with llama_cpp's override
193+
# detection; the MoE flags are strip-only (manual's --n-cpu-moe slider owns them).
194+
_LAYER_OFFLOAD_FLAGS: frozenset[str] = frozenset(
195+
{"-ngl", "--gpu-layers", "--n-gpu-layers", "-fit", "--fit"}
196+
)
197+
_MOE_OFFLOAD_FLAGS: frozenset[str] = frozenset({"-ncmoe", "--n-cpu-moe", "-cmoe", "--cpu-moe"})
198+
_OFFLOAD_SHADOWING_FLAGS: frozenset[str] = _LAYER_OFFLOAD_FLAGS | _MOE_OFFLOAD_FLAGS
199+
189200
_SHADOWING_FLAGS: frozenset[str] = (
190201
_CONTEXT_FLAGS | _CACHE_FLAGS | _SPEC_FLAGS | _TEMPLATE_FLAGS | _SPLIT_SHADOWING_FLAGS
191202
)
192203

193204
# Shadowing flags that take no value -- strip the flag only, not the next token.
194-
_BOOLEAN_SHADOWING_FLAGS: frozenset[str] = frozenset({"--spec-default", "--jinja", "--no-jinja"})
205+
_BOOLEAN_SHADOWING_FLAGS: frozenset[str] = frozenset(
206+
{"--spec-default", "--jinja", "--no-jinja", "-cmoe", "--cpu-moe"}
207+
)
195208

196209

197210
def parse_ctx_override(args: Optional[Iterable[str]]) -> Optional[int]:
@@ -424,6 +437,8 @@ def strip_shadowing_flags(
424437
strip_spec: bool = True,
425438
strip_template: bool = True,
426439
strip_split_mode: bool = True,
440+
strip_tensor_split: bool = False,
441+
strip_offload: bool = False,
427442
) -> list[str]:
428443
"""Strip flags that shadow first-class Unsloth settings.
429444
@@ -432,6 +447,12 @@ def strip_shadowing_flags(
432447
(same for cache / spec / template / split-mode). Each ``strip_*``
433448
toggle controls one group; the route only strips groups whose
434449
first-class field the caller actually supplied.
450+
451+
``strip_split_mode`` removes both ``--split-mode`` and the coupled
452+
``--tensor-split`` (the Tensor Parallelism toggle owns the whole split).
453+
``strip_tensor_split`` removes ``--tensor-split`` *alone*, so manual mode can
454+
replace an inherited per-GPU ratio while leaving the user's ``--split-mode``
455+
row/none/layer choice intact.
435456
"""
436457
shadowing: set[str] = set()
437458
if strip_context:
@@ -444,6 +465,10 @@ def strip_shadowing_flags(
444465
shadowing |= _TEMPLATE_FLAGS
445466
if strip_split_mode:
446467
shadowing |= _SPLIT_SHADOWING_FLAGS
468+
if strip_tensor_split:
469+
shadowing |= _TENSOR_SPLIT_FLAGS
470+
if strip_offload:
471+
shadowing |= _OFFLOAD_SHADOWING_FLAGS
447472

448473
tokens = [str(a) for a in (args or [])]
449474
out: list[str] = []

studio/backend/main.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1167,9 +1167,23 @@ def _get_cached_system_gpu_info(logger) -> dict[str, Any]:
11671167
enriched_dev["vram_utilization_pct"] = util.get("vram_utilization_pct")
11681168
enriched_devices.append(enriched_dev)
11691169

1170+
# Whether GGUF loads accept an explicit gpu_ids pick: /load and
1171+
# /validate 400 picks on XPU hosts (no visibility mask speaks torch-xpu
1172+
# ordinals) and on Vulkan-only builds (--device pins ggml's own
1173+
# ordinals), so the picker must not offer them.
1174+
try:
1175+
from core.inference.llama_cpp import LlamaCppBackend
1176+
from utils.hardware import DeviceType, get_device
1177+
gpu_ids_supported = (
1178+
get_device() != DeviceType.XPU and not LlamaCppBackend._is_vulkan_backend()
1179+
)
1180+
except Exception as e:
1181+
logger.debug(f"Could not resolve gpu_ids support: {e}")
1182+
gpu_ids_supported = True
11701183
gpu_info = {
11711184
"available": visibility_info.get("available", False),
11721185
"devices": enriched_devices,
1186+
"gguf_gpu_ids_supported": gpu_ids_supported,
11731187
}
11741188
_system_gpu_cache = (time.monotonic(), gpu_info)
11751189
return gpu_info

studio/backend/models/inference.py

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def normalize_blank_chat_template_override(cls, value: Optional[str]) -> Optiona
6464
)
6565
gpu_ids: Optional[List[int]] = Field(
6666
None,
67-
description = "Physical GPU indices to use, for example [0, 1]. Omit or pass [] to use automatic selection. Explicit gpu_ids are unsupported when the parent CUDA_VISIBLE_DEVICES uses UUID/MIG entries. Not supported for GGUF models.",
67+
description = "Physical GPU indices to use, for example [0, 1]. Omit or pass [] to use automatic selection. Explicit gpu_ids are unsupported when the parent CUDA_VISIBLE_DEVICES uses UUID/MIG entries. For GGUF models the picked devices are pinned via CUDA/HIP_VISIBLE_DEVICES.",
6868
)
6969
speculative_type: Optional[str] = Field(
7070
None,
@@ -100,6 +100,66 @@ def normalize_blank_chat_template_override(cls, value: Optional[str]) -> Optiona
100100
"No effect on a single GPU. Ignored for non-GGUF models."
101101
),
102102
)
103+
gpu_memory_mode: Literal["auto", "manual"] = Field(
104+
"auto",
105+
description = (
106+
"GPU memory strategy for GGUF models. 'auto' (default): Unsloth "
107+
"selects GPUs and caps context to fit VRAM. 'manual': you own the "
108+
"offload. Leave gpu_layers at -1 (Auto) to hand memory management to "
109+
"llama.cpp's --fit (no device masking, no context auto-reduce, no "
110+
"gpu-layer/tensor-split planning); set gpu_layers >= 0 to pin layers "
111+
"and n_cpu_moe yourself (--fit off), with tensor_parallel still "
112+
"applying (split by free VRAM unless tensor_split is set, no planner). "
113+
"Ignored for non-GGUF."
114+
),
115+
)
116+
gpu_layers: int = Field(
117+
-1,
118+
ge = -1,
119+
description = (
120+
"Manual mode only: number of layers to offload to the GPU "
121+
"(--gpu-layers, with --fit off). A value >= the model's layer count "
122+
"offloads all of them. -1 = Auto: hand layer + context sizing to "
123+
"llama.cpp's --fit. Ignored unless gpu_memory_mode is 'manual'."
124+
),
125+
)
126+
n_cpu_moe: int = Field(
127+
0,
128+
ge = 0,
129+
description = (
130+
"Manual mode only: keep the first N MoE expert layers on the CPU "
131+
"(--n-cpu-moe) to save VRAM on MoE models. 0 = none, N = number of "
132+
"MoE layers offloaded (the backend offsets past any leading dense "
133+
"layers). Ignored unless gpu_memory_mode is 'manual' with gpu_layers >= 0."
134+
),
135+
)
136+
tensor_split: Optional[List[float]] = Field(
137+
None,
138+
description = (
139+
"Manual mode only: relative share of the model per GPU (--tensor-split), "
140+
"in the order of the GPUs in use, e.g. [2, 1] for 2:1. Omit it to let "
141+
"llama.cpp use its default, which splits by free VRAM. Any list given is "
142+
"passed through as-is, so send [1, 1] to force an even split. Ignored "
143+
"unless gpu_memory_mode is 'manual' with gpu_layers >= 0."
144+
),
145+
)
146+
147+
@field_validator("tensor_split")
148+
@classmethod
149+
def _reject_degenerate_tensor_split(cls, value: Optional[List[float]]) -> Optional[List[float]]:
150+
# A negative / non-finite / all-zero split is silently dropped at launch
151+
# (stored as None) yet still compared raw in the reload dedupe, so an
152+
# identical Apply reloads forever. Reject it up front; [] = no split.
153+
if not value:
154+
return value
155+
import math
156+
157+
if any((not math.isfinite(v)) or v < 0 for v in value):
158+
raise ValueError("tensor_split entries must be finite and non-negative")
159+
if sum(value) <= 0:
160+
raise ValueError("tensor_split must have a positive total")
161+
return value
162+
103163
llama_extra_args: Optional[List[str]] = Field(
104164
None,
105165
description = (
@@ -133,6 +193,14 @@ class ValidateModelRequest(BaseModel):
133193
max_seq_length: int = Field(0, ge = 0, le = 1048576)
134194
load_in_4bit: bool = Field(True)
135195
gpu_ids: Optional[List[int]] = Field(None)
196+
gpu_memory_mode: Literal["auto", "manual"] = Field(
197+
"auto",
198+
description = (
199+
"GGUF GPU-memory strategy intended for the follow-up load. Manual "
200+
"placement bypasses the training coexistence estimate: Auto layers "
201+
"delegate fitting to llama.cpp, while explicit layers are user-owned."
202+
),
203+
)
136204
include_context_length: bool = Field(
137205
False,
138206
description = "Also read the native context length from the local GGUF header. "
@@ -188,6 +256,16 @@ class ValidateModelResponse(BaseModel):
188256
description = "Native training context length, read from the GGUF header when the file "
189257
"is already downloaded locally; None for non-GGUF, gated, or not-yet-downloaded models.",
190258
)
259+
layer_count: Optional[int] = Field(
260+
None,
261+
description = "Total layer count (GGUF block_count), the manual gpu-layers ceiling, read "
262+
"from the header alongside context_length; None when not read.",
263+
)
264+
moe_layer_count: Optional[int] = Field(
265+
None,
266+
description = "MoE expert-layer count (the manual --n-cpu-moe ceiling), read from the GGUF "
267+
"header alongside context_length; 0 for dense models, None when not read.",
268+
)
191269
# Additive fields; the consuming consent dialog ships in a follow-up frontend PR.
192270
requires_transformers_upgrade: bool = Field(
193271
False,
@@ -333,6 +411,34 @@ class LoadResponse(BaseModel):
333411
False,
334412
description = "Whether tensor-parallel split (--split-mode tensor) is active.",
335413
)
414+
gpu_memory_mode: Literal["auto", "manual"] = Field(
415+
"auto",
416+
description = "Active GPU memory strategy ('auto' or 'manual').",
417+
)
418+
gpu_layers: int = Field(
419+
-1,
420+
description = "Manual mode: requested --gpu-layers value (-1 = Auto/--fit, or when not manual).",
421+
)
422+
n_cpu_moe: int = Field(
423+
0,
424+
description = "Manual mode: MoE expert layers pinned to CPU (--n-cpu-moe); 0 = none.",
425+
)
426+
tensor_split: Optional[List[float]] = Field(
427+
None,
428+
description = "Manual mode: relative model share per GPU (--tensor-split); None = default (split by free VRAM).",
429+
)
430+
n_layers: Optional[int] = Field(
431+
None,
432+
description = "Model's layer count (GGUF block_count), for the manual gpu-layers ceiling.",
433+
)
434+
n_moe_layers: int = Field(
435+
0,
436+
description = "Model's MoE expert-layer count (the n_cpu_moe ceiling); 0 if not an MoE model.",
437+
)
438+
gpu_ids: Optional[List[int]] = Field(
439+
None,
440+
description = "Physical GPU indices the model is pinned to, or None for automatic selection.",
441+
)
336442

337443

338444
class UnloadResponse(BaseModel):
@@ -461,6 +567,42 @@ class InferenceStatusResponse(BaseModel):
461567
False,
462568
description = "Whether tensor-parallel split (--split-mode tensor) is active.",
463569
)
570+
gpu_memory_mode: Literal["auto", "manual"] = Field(
571+
"auto",
572+
description = "Active GPU memory strategy ('auto' or 'manual').",
573+
)
574+
gpu_layers: int = Field(
575+
-1,
576+
description = "Manual mode: requested --gpu-layers value (-1 = Auto/--fit, or when not manual).",
577+
)
578+
n_cpu_moe: int = Field(
579+
0,
580+
description = "Manual mode: MoE expert layers pinned to CPU (--n-cpu-moe); 0 = none.",
581+
)
582+
tensor_split: Optional[List[float]] = Field(
583+
None,
584+
description = "Manual mode: relative model share per GPU (--tensor-split); None = default (split by free VRAM).",
585+
)
586+
requested_context_length: Optional[int] = Field(
587+
None,
588+
description = (
589+
"The n_ctx the active GGUF load was invoked with (0 = Auto). Lets the "
590+
"UI re-seed a Manual + Auto-layers context pin on hydration, where "
591+
"context_length only exposes the resolved value. None for non-GGUF."
592+
),
593+
)
594+
n_layers: Optional[int] = Field(
595+
None,
596+
description = "Model's layer count (GGUF block_count), for the manual gpu-layers ceiling.",
597+
)
598+
n_moe_layers: int = Field(
599+
0,
600+
description = "Model's MoE expert-layer count (the n_cpu_moe ceiling); 0 if not an MoE model.",
601+
)
602+
gpu_ids: Optional[List[int]] = Field(
603+
None,
604+
description = "Physical GPU indices the model is pinned to, or None for automatic selection.",
605+
)
464606
llama_cpp_supports_mtp: bool = Field(
465607
True,
466608
description = (

0 commit comments

Comments
 (0)