Shorten verbose comments and docstrings without changing behavior. Remove
comments that just restate the next line, collapse multi-line notes to a
single line, and tighten internal helper docstrings. Keep license headers,
lint and type directives, URLs and provenance, commented-out code, and the
why / algorithm / numerical notes that genuinely aid understanding.
Comments and docstrings only: an AST signature check confirms no code,
signatures, imports, or string literals changed, and the package
byte-compiles cleanly.
#6394 converted SecurityHeadersMiddleware to pure ASGI but did not lock the
property in. Add three guards: it must not be a BaseHTTPMiddleware (which would
re-wrap streaming responses in an anyio task group and break is_disconnected),
it must forward the ASGI receive channel untouched, and a StreamingResponse that
polls is_disconnected must unwind cleanly on client disconnect with headers
still applied.
* Studio: pin CUDA_DEVICE_ORDER=PCI_BUS_ID and list GPUs at startup
On a mixed-GPU host, Studio could load a model onto a different physical
GPU than the one it selected. The free-VRAM probe numbers GPUs via
nvidia-smi (PCI-bus order), but CUDA defaults to FASTEST_FIRST ordering,
so a selected index written into CUDA_VISIBLE_DEVICES resolved to the
wrong card. Example: 5090 + RTX PRO 6000, the picker chose the emptier
RTX PRO 6000 (nvidia-smi index 1) but CUDA read index 1 as the 5090.
Pin CUDA_DEVICE_ORDER=PCI_BUS_ID at import (before any CUDA context is
created) in both the Studio entrypoint and the hardware module, so torch,
nvidia-smi, and CUDA_VISIBLE_DEVICES share one index space. setdefault
keeps an explicit user override intact. Child processes inherit it via
os.environ.
Also list every detected CUDA GPU with its index at startup instead of
naming only device 0, matching nvidia-smi -L and making the selected
index unambiguous on multi-GPU hosts.
* Studio: make CUDA_DEVICE_ORDER tests exercise module import and respect user override
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: guard full _print_cuda_device_list body and fix test PYTHONPATH trailing separator
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---------
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* Studio: serialize non-streaming responses once and pool the proxy client
Two safe latency wins on the OpenAI/Anthropic-compatible endpoints that leave
the streaming generation paths untouched (they keep Connection: close and
max_keepalive_connections=0 so a client disconnect still stops GPU decode).
1. Non-streaming responses used JSONResponse(content=model.model_dump()), which
builds a dict and then re-runs json.dumps. Serialize once with
model.model_dump_json() via a small _model_json_response helper. The body is
byte-identical (nulls preserved), about 3x faster to encode in a microbench.
2. The non-streaming completions and embeddings proxies built a fresh
httpx.AsyncClient per request. Route them through one pooled client
(core/inference/llama_http) closed on shutdown; streaming generation keeps
its own per-request close-only client. About 5x faster per call to the
local llama-server in a microbench.
The existing API-monitor tests for the non-streaming completions, embeddings
and passthrough paths now patch nonstreaming_client instead of httpx.AsyncClient
to match the pooled client, so they stay deterministic.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: make the pooled non-streaming client per event loop
Review follow-up on the shared httpx client. It was a single module-global
instance, which has two lifecycle problems the per-request client did not:
1. After aclose() in lifespan shutdown, nonstreaming_client() kept handing back
the closed client, so a second lifespan in the same process (repeated
TestClient, embedded restart) failed with "client has been closed".
2. An httpx client binds its transport to the loop it first runs on, so reuse
from another loop could raise "Event loop is closed".
Hold one client per running loop in a WeakKeyDictionary, recreate when missing
or closed, and close all on shutdown. Single-loop production is unchanged.
* [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>
* Studio: convert SecurityHeadersMiddleware to pure ASGI
SecurityHeadersMiddleware was the last BaseHTTPMiddleware in the global stack,
so every response (including SSE streams) was wrapped in an anyio stream that
penalizes streaming. Rewrite it as a pure-ASGI middleware that mutates the
response-start headers, mirroring the logging-middleware rewrite in #6337.
The header logic is unchanged: it uses MutableHeaders over the start message,
so the same get/del/setdefault calls apply (CSP nonce splice and strip,
X-Frame-Options skip on Colab and the artifact-preview frame, the baseline
nosniff/Referrer-Policy/Permissions-Policy/server headers). The existing
middleware tests cover it; added cases assert headers still apply to a
streaming response and that the artifact-preview path omits X-Frame-Options.
* Studio: harden ASGI header coercion in SecurityHeadersMiddleware
Review follow-up. MutableHeaders mutates its raw list in place, so if a server
sends http.response.start with tuple-valued or missing headers the mutation
would raise. Coerce to a list (defaulting to empty) before wrapping, then inject
the same security headers as before. Also drop a stray em dash in a comment.
* [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>
The Linux installer ordered its CUDA runtime-line attempts purely by torch's
reported CUDA major (preferred_runtime_line), so a Blackwell host running a
cu12x torch build hoisted cuda12 ahead of an available native cuda13 bundle.
This brings the Linux selector to parity with the existing Windows Blackwell
preference: on an sm_120 host, prefer the highest CUDA-major line that ships
a bundle covering every visible host SM, then fall back to the torch line.
Selection-time only. No external pin and no source build: in-release cuda13
bundles already cover sm_120, and the per-artifact SM filter still drops any
incapable bundle (cuda12-older / cuda13-older) and prevents fall-through to a
non-Blackwell build. The override is gated on _host_is_blackwell and only
reorders lines that are already detected and driver-compatible, so it never
forces cuda13 when its runtime libraries are absent or the driver is pre-13,
and non-Blackwell hosts keep the exact torch-preference behavior.
The runtime-line ranking only considers well-formed "cuda<major>" lines and
skips any malformed or future-format value (e.g. "cuda13.1") instead of
crashing the major sort, matching how the surrounding selector already
tolerates unknown lines.
Adds focused selection tests covering the override, the incapable-cuda13
skip, the cuda13-unavailable fallback, the non-Blackwell no-op, the
malformed-runtime_line skip, and cuda14 forward-compat.
* studio: set _stats_logger in kill-process test backend
#6377 added a self._stats_logger cleanup step to _kill_process's finally block.
test_kill_process_records_timestamp_on_actual_kill (added in #6400) builds the
backend via __new__, which bypasses __init__ where _stats_logger is set, so once
both landed on main the test raised AttributeError: 'LlamaCppBackend' object has
no attribute '_stats_logger'. Set _stats_logger on the hand-built backend,
mirroring __init__, so the kill path's finally has the attribute it expects.
* test: assert torchao override step on normal Linux, not overrides.txt
#6400 moved the torchao dependency override from a fixed pin in overrides.txt to
a torch-matched spec installed via --force-reinstall (_select_torchao_spec), and
turned overrides.txt into a comment-only pointer. It updated the Windows variant
(test_windows_only_includes_overrides) to check for --reinstall, but left
test_normal_linux_includes_overrides asserting overrides.txt is installed, which
no longer happens. Check for the override step (--reinstall) instead, matching
the Windows test.
* test(ui): tolerate ERR_ABORTED on /login re-login in shutdown step
The Shutdown step re-logs in after a CLI password rotation that revoked the prior
token. The SPA auth guard can client-side-redirect mid-navigation against the
stale token, aborting page.goto("/login") with net::ERR_ABORTED. It is a race
(passes on main most of the time). Resolve on domcontentloaded and tolerate the
abort, relying on the password-field wait that follows to confirm we reached
/login, matching the wait_until used by the other navigations in this file.
* Studio: stop the llama.cpp update banner flickering and show the download size
The banner animated in and out with a motion opacity + scale + translate
transition. That transform/opacity transition promotes a GPU compositing
layer whose first and last frame can flash for a moment on real displays,
which reads as a flicker on appear and again on dismiss/snooze. Drop the
animation and render the banner as a plain conditional mount: it appears
and leaves cleanly with nothing to flash.
Also surface the download size. update-status now reports the size of the
prebuilt that Update would fetch (the latest-release asset matching this
host's bundle), and the banner shows it as whole MB next to the no-restart
note, so the cost of the update is clear before clicking.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Show llama.cpp update size for upstream and source-build installs
update_download_size_bytes now accepts the upstream ggml-org ubuntu-/win-
asset suffixes and falls back to the marker's binary_repo, so the size
resolves for CPU/ROCm prebuilts (the fork publish repo only carries the
app-* and macOS bundles). The source-build update path now populates
update_size_bytes from the resolved asset, matching the marker path.
Both fail open to null. Adds regression tests for the upstream and
source-build size lookups and the route field round-trip.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---------
Co-authored-by: danielhanchen <michaelhan2050@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* Shim removed vllm.transformers_utils.tokenizer for older unsloth-zoo
vLLM >= 0.22 (PR vllm-project/vllm#35024) deleted
`vllm.transformers_utils.tokenizer`. Older unsloth-zoo
patch_vllm_lora_tokenizer() does an unguarded
`import vllm.transformers_utils.tokenizer`, crashing fast_inference with
`No module named 'vllm.transformers_utils.tokenizer'`.
Add fix_vllm_lora_tokenizer_module(): a meta path finder appended after
the real finders that provides a no-op stub module only when vLLM no
longer ships it. Registered in _gpu_init.py before vLLM is imported, so
users who upgrade unsloth but keep an older unsloth-zoo are protected.
Refs unslothai/unsloth#6385
* Shorten comments in fix_vllm_lora_tokenizer_module
---------
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
* unsloth connect
* harden error paths, fix codex oss_provider routing, tighten key cache perms
* Increase timeout for studio server lookup and enhance key caching logic
* openclaw/opencode/hermes to connect
* improvements
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* error handling for requested models not loaded
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* fix claude connect env under WSL
* [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: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: imagineer99 <samleejackson0@gmail.com>
Set ThemeProvider defaultTheme to "system" and enable system preference so the app follows the OS theme. In the theme store, apply the resolved theme to the document immediately on mount so the store is the single source of truth and avoids an initial light flash on fresh origins (e.g. empty localStorage). Minor comment and formatting tweaks in setTheme were also made.
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
* Studio: add 'Load on selection' toggle to configure load options before loading
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: seed staged speculative decoding from the standing default
* Studio: address PR review for load-on-selection staging
* Studio: handle direct GGUF staging and stale-stage edge cases from load-on-selection review
* Studio: cancel replaced staged downloads and keep staged pick on load failure
* Studio: centralize staged-download cancel and guard staged-load restore
* fix: address staged GGUF load review
* fix: honor staged GGUF load metadata
* fix: clarify load-on-selection tooltip
Keep the load-on-selection hint visually anchored to the control and make the on/off behavior explicit without changing the broader deferred-load flow.
* Studio: reset orphaned staged knobs on abandon and cap Max Tokens to staged context
* Studio: remove dead code and cancel staged download when loading a different model
* fix: surface staged model in run settings before deferred load
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: imagineer99 <samleejackson0@gmail.com>
* Studio: trim serving-log noise and surface llama-server engine stats
Studio prints one structured line per HTTP request, so the SPA's polling and
per-invalidation fan-out bury the lines that matter.
- Dedup identical successful GETs within a short window (default 300ms,
UNSLOTH_STUDIO_ACCESS_LOG_DEDUP_MS) so a burst logs once. The dedup key
includes the query string, so distinct query-driven GETs are not collapsed.
Runs after the response is sent, so it adds no request latency; mutations,
non-2xx, and loading polls are untouched.
- Collapse pure-liveness polls (/api/health, /api/auth/status,
/api/inference/status, /api/inference/monitor) to a longer heartbeat
(default 10s, UNSLOTH_STUDIO_ACCESS_LOG_POLL_DEDUP_MS). The API monitor
console polls /monitor every 1.5s while open.
- Translate llama-server's Prometheus /metrics into a periodic vLLM-style
engine_stats line (generation/prompt throughput and requests in flight) from
a daemon poller, gated on UNSLOTH_STUDIO_ENGINE_STATS. Throughput uses
llama-server's predicted_tokens_seconds / prompt_tokens_seconds gauges, with
a tokens_predicted_total / prompt_tokens_total counter-delta fallback; it does
not use n_decode_total (which counts llama_decode() calls, not tokens). No KV
field is emitted, since llama.cpp does not expose kv_cache_usage_ratio.
--metrics is added only when probe_server_capabilities reports the binary
supports it, so older/custom binaries still load. The poller keeps retrying
through transient scrape failures (stop() drives shutdown) and a malformed
sample cannot crash its thread.
- api_monitor.append_reply: once the preview cap is reached, skip the per-chunk
re-concat (avoids O(n^2) on long generations) while still recording the "..."
truncation marker for a reply that lands exactly on the cap.
- unsloth studio --verbose and unsloth studio run --verbose both restore every
per-request log; --verbose before a subcommand is rejected with guidance
(matching --secure / --parallel). run --verbose still forwards --log-verbose
to llama-server, preserving the pre-existing pass-through verbosity.
* [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>
* fix(hub): stop demoting cached gguf variants on mmproj or filename mismatch
- bug: a quant with its bytes on disk was marked not fully downloaded when the API-preferred main filenames did not match or the mmproj adapter was absent
- fix: fall back to the on-disk quant byte signal, the same one inventory uses for on-device, so a present quant is no longer demoted
- broaden mmproj detection to accept any mmproj-looking cached file, not only the API-preferred name
* feat(hub): full-page redesign with trending feed, search, and persisted state
- convert the hub to a full-page view with a new layout, model cards, and a sortable models table
- add a trending/latest/finetune feed with model and section deep-link params validated on the hub route
- add recent searches and rework model and dataset search with pagination and infinite scroll
- persist feed and token state through a dedicated store and persist-storage layer
* fix(hub): browse sorting, deep-link presets, dataset URLs, and persistence
- sort dropdown: drive HF-wide browse across all repos by the chosen sort, respecting format and capability filters
- section deep-link: apply the section preset (format and sort) on refresh and deep-link, not only on click
- dataset detail URL: persist the resource kind so refresh and share resolve datasets correctly
- gguf card: show a Loading state for hub-cache dir-path repos via repo-id match
- active-model CTA: New Chat now actually opens a fresh chat
- persist-storage: fix throttle keying with a Map and dedupe a duplicated util
- transport-toggle: drop redundant controlled-tooltip state
* Studio: polish hub redesign UI and unify segmented tabs
Refinements on top of the hub full-page redesign:
- Unify every segmented control (Discover/On Device, models/datasets,
Unsloth/All, recent/name/size, settings tabs, train dataset source,
profile shape, theme, OS toggle) on one filled-pill design.
- Hub list now loads in larger batches with a shorter fetch interval so
results fill in fast instead of dripping one row at a time.
- Disable remote avatar fetches in list rows and brighten the colored
initial fallbacks so they read clearly without network calls.
- Add a split master-detail view for model lists and make it the default.
- Left-align the split "Showing GGUF models" header with the rows below.
- Round the "Load more" footer box and tidy On Device stats layout.
- Show recent trainings on Recipes and Export, falling back to recent
chats when there is no training history.
* Studio: address hub review comments (filter warning + scrollMargin)
- DiscoverFetchMoreFooter only shows the "results may be hidden by your
filters" note when a filter is actually active, instead of always.
- Use the destructured scrollMargin prop directly in the row transform
rather than reaching into virtualizer.options.scrollMargin.
* Fix/adjust Hub metadata and deep links for PR #6349
* Studio: drop avatar ring in hub split view
The split master-pane rows (discover + on device) added a ring-1 around
the owner avatar that read as a shadow. Remove it so split-view avatars
match the flat avatars elsewhere; grid cards and the full list keep theirs.
* Studio: hub sort + scope as dropdown pills beside view tabs
Recent/Name/Size and Unsloth/All were segmented controls that dropped to
their own row in the narrow split pane. Make each a compact dropdown pill
(HubOptionMenu) that sits in the header actions slot next to the view-mode
tabs in every layout, so split view no longer needs a separate row.
* Studio: align hub list header with the view tabs and rows
- Vertically center the "On device" / "Showing GGUF models" title with the
dropdown pill and view-mode tabs (items-center instead of items-end), so a
short title no longer sits low against the taller tab row.
- Nudge the back chevron 2px further left (-ml-2) so its glyph edge lines up
with the start of the row hover below it.
* Studio: align back chevron tip with the row hover edge
The arrow glyph is inset ~6px inside its centered icon box, so an
edge-aligned button left the visible chevron sitting in from the column.
Pull the button out (-ml-3.5) so the chevron tip lands on the row hover's
left edge instead of floating to its right.
* Studio: unify every bare tick on the shared check mark
Point all plain checkmarks at the canonical @/lib/tick-icon tick (the one
already used in the chat composer and menus), so there is a single tick
across the app:
- Hub: model-inspector, hub-option-menu, path-info-button were importing
the stock hugeicons Tick02Icon; switch them to the shared icon.
- Chat / assistant-ui: artifact-surface, prompt-storage-dialog, reasoning,
tool-ui-python, tool-ui-terminal, tool-ui-code-execution, and the
tool-fallback status map used lucide CheckIcon; render the shared tick
via HugeiconsIcon instead (tool-fallback wraps it to fit its icon map).
The circular CheckmarkCircle success badges are intentionally left as-is.
No bare CheckIcon/stock Tick02Icon references remain; verified the tick
renders in every converted spot via typecheck + build.
* Studio: nudge back chevron 2px right
-ml-3.5 pushed the chevron a touch too far left; -ml-3 sits it just
inside the row hover edge, aligned with the avatars below.
* Studio: search base-model chips across all publishers
Clicking a Base model chip searches the Hub for the upstream repo, which
lives under another publisher (google, meta, etc.). It left ownerScope at
the default "unsloth", so the search hard-restricted to the Unsloth org and
could never surface the base model. Switch the scope to "all" for this action.
* Studio: label the safetensors list header "Safetensors"
The focused list heading showed "Showing Checkpoint ... models" while the
format dropdown labels the same checkpoint filter value "Safetensors". Match
the dropdown so the header reads "Showing Safetensors ... models".
* Studio: simplify the focused list heading to "Models"
Drop the format/capability composition (e.g. "Showing Safetensors Reasoning
models") so the focused list heading just reads "Models" (or "Datasets").
Search keeps its "Results for ..." label.
* Studio: drop the header refresh button to the text baseline
The refresh button sat at the heading's vertical centre. Nudge it down so
it lines up with the bottom of the title text instead.
* Studio: hide redundant "Back to Hub" in the split detail pane
In split view on large screens the master list sits beside the detail, so
the back button is redundant. Hide it there (lg) and reclaim the top space.
It stays on the small-screen overlay and the full-page detail, where the
list is hidden and back is the only way out.
* Studio: match the readme scroll fade to the left column
The detail pane relied on the sticky back-bar's fade, which is now hidden in
split view. Add the same hub-scroll-fade overlay the master list uses so the
readme fades consistently at the top when scrolled. The back-bar, when shown,
sits above and covers it.
* Studio: align Hub refresh button to the heading text bottom
* Studio: nudge Hub refresh button up to the heading text
* Studio: optically centre the HF token shield in its circle
* Studio: preview the first visible on-device row in split view
* Studio: calm the on-device row colour and fix size tooltip contrast
* Studio: fix Hub reset tab and clear search when opening a section
* Studio: fix Hub feed defaults, filter sync, and GGUF vision download state
* Studio: condense Hub redesign code comments
---------
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: shimmyshimmer <info@unsloth.ai>
Co-authored-by: Michael Han <107991372+shimmyshimmer@users.noreply.github.com>
Co-authored-by: wasimysaid <wasimysdev@gmail.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: danielhanchen <michaelhan2050@gmail.com>
* studio: select torchao version from the installed torch
The Studio installer pins CUDA torch to torch>=2.4,<2.11 and its driver
ladder selects the cu130 wheel index on recent NVIDIA drivers, so pip
resolves torch 2.10.0. overrides.txt hard-pinned torchao==0.14.0, whose
C++ extensions are built against torch 2.9.0, so torchao skipped its cpp
kernels ("Skipping import of cpp extensions due to incompatible torch
version 2.10.0+cu130 for torchao version 0.14.0") and fell back to the
slow Python path. Every CUDA index now tops out at torch 2.10.0, so this
hit most modern installs, not just cu130.
Pick the torchao version matching the torch actually installed in the
venv (table: pytorch/ao#2919): torch 2.10.x -> torchao 0.16.0, 2.11.x ->
torchao 0.17.0, otherwise the previous 0.14.0 (so torch <=2.9 is
unchanged). The installer reads torch.__version__ from the venv via a
cross-platform sys.executable probe (probe_torch_wheel_env is Linux-only)
and passes the computed spec positionally to the existing force-reinstall
override step; overrides.txt becomes a pointer to that logic. torchao's
Python API (Float8Tensor, used by unsloth/kernels/utils.py) imports
cleanly on 0.16.0/0.17.0, verified against torch 2.9.1.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: address review on torchao selection
- Clean the torch minor of pre-release/dev suffixes before parsing
(e.g. '2.10rc1' -> minor 10), matching wheel_utils.probe_torch_wheel_env.
- Pass _windows_hidden_subprocess_kwargs() to the torch-version probe so
it does not flash a console window on Windows (no-op elsewhere).
- Use _safe_print for the selection log line, consistent with the file's
other status output (safe on non-UTF-8 consoles).
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* Studio: surface the real reason a model fails validation
validate_model caught every error and returned a blank "Invalid model" while
logging the actual cause. Selecting a GGUF model without a built llama-server,
for example, raises a deliberately actionable RuntimeError ("llama-server binary
not found - cannot load GGUF models. Run setup.sh ...") that the user never saw,
leaving them with an unexplained "Invalid model".
Surface RuntimeError and ValueError messages (path-redacted, and wrapped with the
existing "not supported yet" hint where it applies) in the 400 detail, matching
what the native-path branch already does. Any other exception type stays generic
so an unexpected internal error never leaks its details to the client.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Test: accept positional args in the from_identifier mock
Make the mock robust to a future from_identifier signature that passes
positional arguments, per review feedback.
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* studio: reserve MTP draft VRAM in GGUF auto-fit
Auto-fit advertised a context (for example ~110k for the Qwen3.6-27B MTP
GGUF) that fit on paper but OOMed mid-generation or during tool calls once
MTP speculative decoding was active. The MTP draft path's VRAM was reserved
as a flat 5% of total VRAM, which tracks neither of the two real costs: the
MTP head keeps its own attention KV cache that grows with context, and the
speculative verification buffer grows with --spec-draft-n-max. On the
hybrid Mamba/attention Qwen3.6 models the main KV is small, so auto-fit
happily kept a near-native context while the draft path pushed the load
over budget at runtime.
Replace the flat fraction with a byte-accurate, context- and n_max-aware
reserve sized from GGUF dims: draft KV from nextn_predict_layers and the
attention dims at f16 (llama.cpp's MTP draft context uses f16 KV regardless
of the main cache type), plus a verify buffer per embedding-unit per draft
token. The reserve is evaluated per candidate context inside the fit binary
search and added to every pin/fit check, including the tensor-parallel
planner and its even-split decision. Coefficients were calibrated against
llama-server VRAM measurements on the Qwen3.6-27B MTP GGUF (RMS 14 MiB).
The flat fraction remains as a fallback when GGUF dims are unavailable, so
non-MTP loads are unchanged. The budget now also engages when the user wires
MTP through extra args (--spec-type draft-mtp, including chains), reads the
effective draft depth from --spec-draft-n-max or the legacy --draft-max with
extras taking precedence over the first-class field, reserves a separate
drafter's weights when supplied via --model-draft/--spec-draft-model/-md,
and mirrors _build_speculative_flags so it never reserves for MTP the launch
resolver will not emit (needs a head/drafter and a binary that supports
--spec-type mtp).
Adds tests/test_mtp_vram_budget.py.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: total-based VRAM budget + deterministic compute-graph buffer
Build on the byte-accurate MTP reserve with three changes that make the
GGUF auto-fit budget deterministic across architectures and recover usable
context, especially for MTP models on a single tight card.
1. Total-based budget. Cap GPU occupancy at a fraction of TOTAL VRAM rather
than a fraction of FREE VRAM, and raise the fraction from 0.90 to 0.95:
budget = free - (1 - 0.95) * total (per GPU, summed for a pool)
The reserve is now absolute (a fixed slice of the card) instead of
shrinking as the GPU fills, so a partly-used GPU keeps a constant cushion
for compute/CUDA/verify buffers instead of over-promising context and
spilling to CPU at runtime. _get_gpu_memory() reads memory.total alongside
memory.free; _fit_context_to_vram, _select_gpus and the load_model pool
loops thread the totals through. Multi-GPU layer-split pools
sum(free_i - 0.05*total_i); tensor mode reserves per device.
2. Deterministic compute-graph buffer. Replace the flat 5 GB/device tensor
reserve (a magic constant that over-reserved about 8x on a 27B model) with
_estimate_compute_buffer_bytes, sized from GGUF dims and the launch flags:
out = n_vocab * n_ubatch * 4 # vocab-width output buffer
act = 4 * n_embd * n_ubatch * 4 # activation scratch
pipeline_per_device = act + out * (n_parallel - 1)
tensor_per_device = 2*act + out * n_parallel
The buffer is context-independent and scales with --parallel (serving
slots), not with how the model is split across GPUs. It is now reserved in
BOTH multi-GPU paths (layer split folds one buffer into the pooled
footprint; tensor mode reserves it per device). The flat 5 GB stays only as
a fallback when vocab/embedding dims are unavailable. Calibrated against
llama-server measurements (parallel 1/2/4/8 give 36/492/1388/3220 MiB on a
single GPU; about 600 MiB/device tensor); the estimate is a small upper
bound.
3. GGUF parsing. Read vocab size (tokenizer tokens array length) and
feed_forward_length for the compute-buffer estimate.
Effect on the Qwen3.6-27B MTP Q6_K case (MTP on): a single 32 GB card at
about 31 GB free advertises f16 23k to 64k, q8_0 44k to 115k, q4_0 82k to
200k; 2x 24 GB tensor mode recovers the full 262k window for f16 (was about
134k). Validated on hardware: 1x 32 GB f16 at 64768 loads at 29.3 GB / 120
t/s; 2x 23 GB tensor f16 at 262144 loads at 22.2 GB/device / 98 t/s; both
within 0.4% of the estimate. Adds test_compute_buffer.py and updates the
KV/context-fit/MTP-budget tests for the 0.95 constant and the new budget.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: tighten comments in the VRAM auto-fit changes
Condense the docstrings and inline comments added by this PR (internal backend
helpers): drop restated-signature docstrings, fold multi-line block comments to
one or two lines, and remove notes that just repeat the code. No behavior change
(AST-verified comment/docstring-only via comment_tools.py); the backend test
suite is unchanged and green.
* studio: address review findings in the VRAM auto-fit budget
Five fixes from a parallel-reviewer pass on this PR; all confirmed against the
real functions and covered by new tests.
- Tensor mode now honors the total-based VRAM cap. _plan_tensor_parallel took
total_by_idx and budgets each GPU at free - (1-frac)*total, mirroring the
layer-split paths; previously it fit against raw free and could spend the 5%
safety cushion on a partly-used multi-GPU box (reproduced ~3.3 GB over).
- Draft K and V cache types are parsed and accounted independently. A one-sided
override (e.g. --cache-type-k-draft q4_0, V left f16) no longer applies the
small quant to both axes and under-reserves the f16 axis. The embedded-head
formula sizes per axis; the separate-drafter path uses the heavier type so it
never under-reserves.
- The compute-graph buffer honors a user --ubatch / --ubatch-size / -ub override
(parsed and threaded into every _estimate_compute_buffer_bytes call and the
tensor planner); it previously always assumed the 512 default, under-reserving
up to ~8x at --ubatch 4096.
- GPU ranking uses the usable budget (free - (1-frac)*total) instead of raw free
in _select_gpus and both auto-context subset loops, so a more-used large card
no longer outranks a less-used small card that has more usable room.
Adds regression tests for each (tensor total cap, ubatch reserve scaling, split
K/V no-under-reserve, --ubatch parser, usable-ranking GPU selection). Full
targeted backend suite green (321 passed).
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: gate tensor-parallel admission on usable VRAM budget
The tensor-parallel GPU admission filters still used raw free VRAM after
the total-based budget landed, an asymmetric fix: a partly-used large card
can clear the per-device compute-buffer reserve on raw free while its usable
budget (free - (1-frac)*total) does not, so the planner admitted it and the
even split could emit a near-zero weight slice for a GPU that should have
been excluded.
- _plan_tensor_parallel: admit GPUs by usable budget, not raw free (move the
_usable helper above the filter).
- load_model: admit the tensor set by _gpu_usable, and downgrade to layer
split when the pooled usable budget cannot hold weights plus per-device
compute buffers (the planner can only floor the context, not stop an
overcommitted launch).
Adds regression tests: planner drops a GPU whose usable budget is below the
reserve, and a source-level check that load_model admits on the usable
budget and carries the pooled-weight downgrade.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: size the MTP reserve for the user's overriding drafter
A user --model-draft passed in extra_args is appended last and wins at the
llama-server launch, but the VRAM budget preferred Studio's auto-detected
drafter (mtp_draft_path or extras), so a larger custom drafter was
under-reserved. Flip the precedence to extras-first, matching the draft-depth
(n_max) resolution two lines above. Adds a source-level regression test.
* studio: account for MTP reserve in tensor gate, restore 2-col GPU probe
Two issues found by re-review of the prior fix:
- The tensor-parallel capacity gate only checked the model weights against the
pooled budget, not the MTP reserve. A separate-drafter MTP load whose weights
fit but weights + drafter do not could still launch overcommitted in tensor
mode. Add the non-shrinkable MTP reserve (drafter weights + floor draft KV, or
the flat 2 GiB fallback when dims are unavailable) to the gate.
- The nvidia-smi probe was switched to a three-column query (index,free,total)
for the total-based budget but required exactly three columns, so a driver or
mock returning the legacy two-column "index,free" was dropped and the probe
fell through to the real GPUs. Accept two columns (total 0) and treat an
unknown total as the legacy free*fraction in _select_gpus.
Tests: tensor gate asserts the MTP term is included; _get_gpu_memory parses both
two- and three-column output; the existing two-column GPU-detection mocks pass
again.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: keep the VRAM cushion in tensor planning when GPU totals are unknown
_plan_tensor_parallel fell back to raw free VRAM when a GPU's total was
unavailable (a two-column nvidia-smi probe reporting total 0), while
_select_gpus and the load_model ranking both fall back to free*fraction. That
let tensor planning spend the 5% cushion the rest of the fit preserves and
over-advertise context in exactly that path. Align the fallback to
free*_CTX_FIT_VRAM_FRACTION. Updates the no-totals planner test expectations
(now free*frac) and adds a regression test that total 0 keeps the cushion.
* studio: honor LLAMA_ARG_* env overrides and HF draft flags in the VRAM budget
The budget parsed llama-server flags only from the request's extra_args, but the
child process inherits Studio's full environment (child_env_without_native_path_secret
copies os.environ), and llama-server honors LLAMA_ARG_* env vars for the same
options. So a service-level override the child acts on was invisible to the fit,
which could then advertise a context/GPU set that OOMs at load.
- _extra_args_n_ubatch: fall back to LLAMA_ARG_UBATCH (drives the compute buffer;
an unseen 4096 vs the 512 default under-reserves ~8x).
- _extra_args_mtp_draft_path: also recognize the HF draft-repo flags
(--spec-draft-hf/-hfd/-hfrd/--hf-repo-draft) and fall back to
LLAMA_ARG_SPEC_DRAFT_MODEL / LLAMA_ARG_SPEC_DRAFT_HF_REPO. An HF repo isn't a
local file so it can't be sized, but recognizing it routes to the flat reserve
instead of mis-sizing Studio's auto/embedded drafter.
- _extra_args_draft_cache_types: fall back to
LLAMA_ARG_SPEC_DRAFT_CACHE_TYPE_K/_V per axis.
CLI extra_args win over env (they are appended last at launch). Each parser takes
an injectable env for deterministic tests. Adds env-fallback and HF-flag tests.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: review polish - drop non-flag --ubatch, harden GPU probe, document buffer
Non-blocking items from a second review pass; no behavior change in the common path:
- _extra_args_n_ubatch: drop --ubatch; the binary only accepts --ubatch-size/-ub,
so parsing --ubatch implied support it does not have (it would over-reserve for a
launch that fails on the unknown flag).
- _get_gpu_memory: skip a malformed nvidia-smi line instead of letting one bad line
raise and drop the whole NVIDIA probe to the torch fallback.
- _estimate_compute_buffer_bytes: document that the per-slot output-buffer model
assumes a small n_outputs_max (chat decode); it would under-count for
embeddings / --logits-all / reranking, which Studio does not run on this path.
* studio: honor LLAMA_ARG_SPEC_TYPE when deciding the MTP reserve
_extra_args_requests_mtp only checked extra_args, but the child inherits Studio's
env and llama-server honors LLAMA_ARG_SPEC_TYPE. So a service-level
LLAMA_ARG_SPEC_TYPE=draft-mtp would run MTP while the fit skipped the draft
reserve and could advertise a context/GPU set that OOMs at load. Recognize the
env value (CLI still wins). Completes the env-override coverage alongside ubatch,
draft model, and draft cache types. Adds an env regression test.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: reserve VRAM for non-MTP model-based draft modes too
The draft reserve only engaged for MTP. A user passing a non-MTP model-based
draft mode (--spec-type draft-simple / draft-eagle3) with a --model-draft loads
a separate draft model whose weights + KV consume GPU memory, but the fit
reserved nothing and could OOM at load. Engage the existing drafter reserve for
those modes when extras (or LLAMA_ARG_SPEC_TYPE) name a drafter; ngram-* load no
model and are unaffected. Purely additive (reserves where there was none).
Adds parser + gate tests.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: floor quantized embedded MTP draft KV at f16; fix two test issues
Address PR review feedback (three findings):
1. Quantized embedded MTP draft KV was underpriced. The embedded head is a
single draft layer, so llama.cpp cannot amortize quantized-KV overhead over
many layers the way the main model does: a quantized draft KV (e.g.
--spec-draft-type-k q4_0) actually fits LESS context than f16, not more
(ggml-org/llama.cpp#24102, where a collaborator recommends f16 for the draft
KV). Pricing q4_0 at 0.5625 of an element (~28% of f16) under-reserved, so a
quantized override could advertise a context that shrinks or OOMs at load.
Floor the embedded draft KV bytes-per-element at f16 (quantized types priced
as f16, f32 still its full 4 bytes). The separate multi-layer drafter, where
quantization does amortize, keeps the user's real type.
2. test_load_model_reserves_for_non_mtp_draft_modes asserted an exact one-line
source substring that pre-commit black wrapped across lines, breaking CI.
Strip whitespace before matching so the check survives any line-wrapping.
3. test_compute_buffer.py installed a partial httpx stub via setdefault that, if
collected before test_kv_cache_estimation.py, leaked into sys.modules without
HTTPError/Response and could break the transformers introspection tier by
collection order. Adopt the sister file's pattern: only stub when real httpx
is absent, and include the full symbol set.
Updates the affected draft-KV tests to assert the f16 floor.
* studio: guard httpx stub in test_mtp_vram_budget too
test_mtp_vram_budget.py installed a partial httpx stub via setdefault that, like
test_compute_buffer.py before it, lacked HTTPError/Response and could leak into
sys.modules ahead of tests that need huggingface_hub/transformers, breaking the
introspection tier by collection order. Apply the same guard used by
test_kv_cache_estimation.py: only stub when real httpx is absent, with the full
symbol set.
* studio: per-device layer-split reserve, effective spec-type, drafter weights, KV restore
Address PR review feedback (four findings in the auto-fit budget):
A. Reserve the per-device layer-split overhead. A layer (pipeline) split allocates
a fixed per-device overhead (CUDA context + per-device compute scratch) on every
participating GPU, beyond the slot-scaling compute buffer that is conserved across
the split. Measured ~0.9 GB/device on the Qwen3.6-27B GGUF (b9625), independent of
--parallel: layer-split TOTAL VRAM grew +894 MiB (parallel=8) / +946 MiB
(parallel=1) per extra GPU, ~linear to +2.6 GB at 4 GPUs. The fit folded a single
compute buffer for all subset sizes, so a k-GPU layer split was short by
~(k-1)*0.9 GB and could pin a context that fits the pool on paper but OOMs a device.
Reserve (k-1) * _PIPELINE_PER_DEVICE_OVERHEAD_MIB per subset in the layer-split fit;
k=1 adds nothing, so single-GPU sizing (and the validated benchmark rows) is unchanged.
B. Track the effective --spec-type. _extra_args_requests_mtp returned true on the
first MTP-ish --spec-type and consulted LLAMA_ARG_SPEC_TYPE even when a CLI
--spec-type was present, contrary to llama.cpp (last CLI value wins; a CLI flag
overrides the env). So `--spec-type draft-mtp --spec-type ngram-mod` or a non-MTP
CLI value with a stale MTP env over-reserved a drafter the launch won't load
(shrinking context / selecting extra GPUs). Route both detectors through a new
_effective_spec_type helper.
C. Keep known drafter weights in the fallback reserve. When a separate drafter's KV
metadata can't be sized, _estimate_mtp_overhead_bytes returned None and discarded
the drafter's known weight bytes, falling back to the flat 5% reserve; a drafter
larger than that cushion could launch over budget and OOM. Reserve the known
weights even when KV sizing fails (None only when nothing is known).
D. Restore quantized KV on tensor->layer-split downgrade. The tensor attempt drops a
quantized KV cache (tensor mode aborts on it). When the GPU-count or capacity gate
then downgrades to layer split -- which supports quantized KV -- the dropped type
was lost and the launch used f16, using more VRAM and shrinking context. Remember
the dropped type and restore it on downgrade (the launch re-emits it from the var).
Adds regression tests for each.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: per-device overhead in GPU pin, skip CPU draft, gate env spec-type
Address PR review feedback (three follow-up findings):
F1. Reserve the per-device layer-split overhead in the pin path too. The earlier
per-device reserve was added to the auto-context fit loops but not to
_select_gpus, which the explicit-ctx and file-size-only paths use to PIN GPUs
with -ngl -1 (no --fit fallback). A 2+ GPU pin within ~1 GiB/extra-GPU of the
budget could OOM a device at load. Add a per_device_overhead_bytes arg to
_select_gpus so a k-GPU pin must hold model + (k-1)*overhead; pass the pipeline
overhead at both pin call sites. Single-GPU pins are unchanged.
F2. Don't charge a CPU-offloaded drafter against the GPU budget. A user passing
--spec-draft-ngl 0 or --spec-draft-device none/cpu keeps the separate draft
model's weights + KV on CPU, but the budget still charged the full drafter GGUF
size, auto-reducing context or downgrading GPU selection. Detect the CPU-offload
flags and drop the separate drafter (and its flat fallback) from the budget; an
embedded head follows the main -ngl and is unaffected.
F3. Consult LLAMA_ARG_SPEC_TYPE only when it can reach the child. llama-server's CLI
args override env, and _build_speculative_flags emits a --spec-type/--spec-default
for every UI mode except "off". So a stale MTP env on a non-MTP model (auto mode)
made the fit reserve MTP that the emitted --spec-default disables, shrinking
context / picking extra GPUs. Gate the env consult on "no user --spec-type and UI
mode off"; the MTP-model auto path still engages via Studio's own detection.
Adds regression tests for each.
* studio: drafter budget precedence and --spec-default in effective spec-type
Two spec-precedence fixes surfaced by an independent multi-reviewer pass:
R3. Size the drafter the launch actually loads. _mtp_draft_for_budget consulted
LLAMA_ARG_SPEC_DRAFT_MODEL (via _extra_args_mtp_draft_path's env fallback)
before Studio's resolved mtp_draft_path, but _build_speculative_flags emits
--model-draft mtp_draft_path, which overrides the env at launch. With a stale
(smaller) env drafter, the budget under-reserved and could OOM. Order the
budget by what actually launches: CLI extras --model-draft (appended last,
wins), then Studio's emitted mtp_draft_path (when MTP engages and the user
doesn't own --spec-type), then the env drafter.
R4. Treat --spec-default as a CLI spec override in _effective_spec_type. It only
recognized --spec-type, so extras=["--spec-default"] with LLAMA_ARG_SPEC_TYPE=
draft-mtp fell through to the env and over-reserved MTP, even though the CLI
--spec-default overrides the env to a non-MTP default. Recognize it as a CLI
spec flag (resolves to "default", non-MTP) that suppresses the env fallback.
Adds regression tests for each.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: refine MTP draft reserve (parallel slots, last-wins, KV cushion, ranking)
Address PR review feedback (five follow-up findings, all edges of this session's
earlier MTP/auto-fit changes):
G1. Price the separate drafter's KV per --parallel slot. _mtp_draft_kv_bytes called
the drafter's _estimate_kv_cache_bytes with the default n_parallel=1, but the
drafter is served under the main model's slot count; a sliding-window drafter
(Gemma) grows KV per slot and was under-reserved. Thread n_parallel through the
draft KV / overhead estimate and the fit closure.
G2. Honor last-wins for the draft-offload flags. _extra_args_draft_offloaded_to_cpu
returned True on the first CPU value, so --spec-draft-ngl 0 --spec-draft-ngl -1
(final = GPU) wrongly dropped the drafter reserve while the server kept it on
GPU -> OOM. Decide on the final value of each flag only.
G3. Keep the flat cushion when only the drafter weights could be sized. The weights
fallback installs mtp_overhead_fn, which made callers drop the flat MTP reserve,
leaving the still-unsized draft KV with no cushion. Keep the flat fraction on in
that weights-only case, on top of the byte-accurate weights.
G4. Rank auto/cap GPU subsets by the active budget fraction. The ranking used a
hard-coded 0.95 while the fit tests _pin_fraction (lowered by the flat MTP
reserve); on mixed-total GPUs that could order subsets differently and pick a
worse plan. Rank with the same fraction the fit uses.
G5. Keep the embedded-head flat reserve under a draft CPU-offload flag. F2's
not-_draft_on_cpu guard also dropped the reserve for an embedded MTP head, which
is part of the main model and stays on GPU regardless of --spec-draft-ngl. Only
suppress the flat reserve for a CPU-offloaded separate drafter (no embedded head).
Adds regression tests for each.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: keep GPU on non-integer total, keep tensor flat reserve for weights-only
Two review findings:
- _get_gpu_memory dropped a whole GPU when nvidia-smi reported a non-integer
memory.total ("N/A" on some drivers / MIG / vGPU): index, free and total were
parsed in one try/except that skipped the line on any ValueError, so the GPU
vanished from the probe and the load could silently spill to CPU. Parse index
and free (required) first, then total separately, defaulting to 0 (the fit then
uses the free*frac path for that GPU). Adds N/A and bad-free test cases.
- Tensor planning skipped the flat MTP reserve for a weights-only drafter (file
size known, KV unsizable): the capacity gate used the byte floor whenever
mtp_overhead_fn was set, so it reserved only the drafter weights and no draft
KV. Tensor mode has no --fit valve, so that could overcommit and OOM. Keep the
flat reserve (never below the byte floor) in the weights-only case too, mirroring
the layer-split _mtp_kv_unsized handling. Adds a regression test.
(A third suggestion -- fold --batch-size into the compute-buffer reserve -- was
checked on hardware and declined: -b 8192 -ub 512 used identical VRAM to the
default at -c 64000, so the logical batch does not size the graph buffer; the
estimate correctly uses the physical micro-batch.)
* studio: budget the main KV from LLAMA_ARG_CACHE_TYPE env when Studio emits none
The child inherits LLAMA_ARG_CACHE_TYPE_K / LLAMA_ARG_CACHE_TYPE_V, but Studio
emits --cache-type-k/-v only when the param or extras set the type. When neither
does, a heavier env type (f32) reaches the child while the auto-fit budget
assumed the f16 default, under-reserving the main KV and risking OOM at the
advertised context. This is the one main-KV axis that lacked the env-aware
handling the other axes already have (spec-type, draft model, draft cache type,
ubatch).
load_model now adopts the heavier of the two env types when it exceeds f16 (only
f32 does), and the launch re-emits it so child and budget stay byte-consistent.
Quantized env types are <= f16 and remain safely over-reserved by the default,
so they are left untouched (no change). A single value is used because the
budget's KV estimate has one cache_type_kv knob, matching parse_cache_override's
existing key/value collapse.
Adds _env_main_cache_type_for_budget plus regression tests covering f32 adoption,
the K/V heavier-of collapse, quantized/unknown no-ops, and the load_model source
precedence.
* studio: budget tensor parallel when LLAMA_ARG_SPLIT_MODE env selects it
Studio emits --split-mode tensor only on its tensor branch; the default
layer-split path emits nothing and resolve_tensor_parallel consults only extras.
The child inherits LLAMA_ARG_SPLIT_MODE, so a tensor env on a layer-split plan
silently runs the child tensor-parallel (heavier per-device compute buffer)
while the budget reserved only the layer-split per-device overhead, under-
reserving on multi-GPU.
load_model now flips the plan to tensor when extras do not set a split mode and
the env selects tensor, so Studio plans, reserves, and emits tensor consistently.
The flip is one-directional (guarded on not tensor_parallel and no extras
split-mode) so an existing tensor plan is never downgraded and extras keep
precedence. Other env modes (layer/row/none) are not a runtime-heavier surprise
and are left untouched.
Adds _env_split_mode_is_tensor plus unit and load_model source-level tests.
* studio: reconcile inherited llama.cpp env with the budgeted launch decision
Addresses a review pass over the VRAM auto-fit work. The budget now sizes the
right amount, but the child process inherits LLAMA_ARG_* env (see
child_env_without_native_path_secret), and a few axes could still run the child
in a mode Studio neither chose nor budgeted.
Mixed known/unknown GPU totals over-advertised the pooled layer-split budget.
_pool_budget_mib pooled free and total separately, so an unknown-total GPU
(MIG/vGPU/N/A) contributed its full free with no cushion when mixed with
known-total GPUs (~(1-frac)*free over-advertise, about 500 MiB in a two-GPU
case). It now sums each GPU's own usable budget, and the layer-split fit calls
take that as an absolute budget (budget_frac=1.0, total_mib=None) so the fit and
the footprint check agree. All-known-total pools are unchanged.
LLAMA_ARG_SPLIT_MODE=tensor survived a tensor-to-layer downgrade. The downgrade
only stripped CLI extras, so the inherited env still ran the child tensor while
Studio budgeted layer split. When the final decision is layer split, a non-layer
inherited split mode (and any paired LLAMA_ARG_TENSOR_SPLIT) is now cleared from
the child env.
Inherited quantized LLAMA_ARG_CACHE_TYPE_K/_V crashed tensor mode. Tensor mode
aborts on a quantized KV cache; Studio drops a quantized cache_type_kv for the
tensor attempt but the inherited env reached the child anyway. When the final
decision is tensor split, a quantized cache-type env is now cleared so the child
uses the tensor-safe default that was budgeted.
Env-derived cache budget no longer mutates the emitted launch flags. An env-only
main KV type now informs the budget only; it is not re-emitted, so an asymmetric
K=f32,V=f16 env reaches the child as set instead of being rewritten to symmetric
--cache-type-k/-v f32.
Adds source-level regression tests for all four and confirms the documented
single-GPU/tensor/pipeline numbers are byte-identical before and after.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: tighten comments in the VRAM auto-fit code
Compress the verbose docstrings and inline comments added by this work to
succinct 2-4 line versions, drop restated/obvious ones, and cut duplicated
rationale across the two tensor-downgrade branches. Keeps the non-obvious intent
(env-inheritance precedence, the #24102 embedded-draft floor, the per-device
overhead and pool-budget rationale) while removing roughly 120 lines of comment
text from llama_cpp.py. Also trims the few longest test-comment blocks; concise
per-test scenario notes are left intact.
No logic change: verified with comment_tools.py check --strip-docstrings (code-
only signature unchanged vs the prior commit) and the full backend suite still
passes (824).
* studio: lock in env-drafter engagement for the separate-draft reserve
A review suggested an env-provided LLAMA_ARG_SPEC_DRAFT_MODEL would skip the
draft reserve and OOM. It does not: the gate's _extra_args_mtp_draft_path(extra_args)
call defaults env=None, which consults os.environ, so an env-only drafter still
sets _user_draft_via_extras and is sized via _env_draft_for_budget. Add a source
guard that the gate keeps the env-inclusive form (not extras-only env={}) and a
behavioral test mirroring the reviewed scenario, so a future cleanup can't
regress it. No production change.
* studio: carry the unsized MTP reserve and env split/offload into tensor planning
Addresses a review pass over the multi-GPU and env-inheritance paths.
Tensor planner dropped the unsized draft-KV cushion. When a separate drafter has
known weights but unreadable KV metadata, _plan_tensor_parallel receives a
non-None weights-only mtp_overhead_fn and applied the flat 2 GiB reserve only for
the no-fn case, so its binary search spent the unsized-KV cushion on context and
over-advertised. Add mtp_flat_reserve_bytes (subtracted from the pooled budget and
the even-split check), and pass it from load_model whenever _mtp_kv_unsized. The
layer path and the tensor pre-gate already kept this cushion.
Stale LLAMA_ARG_TENSOR_SPLIT survived in tensor mode. When the planner picks an
even split it emits no --tensor-split, so an inherited tensor-split env reached the
child and overrode the budgeted split. The layer downgrade branch cleared it; the
tensor branch now does too.
Env-only draft CPU offload was ignored. _extra_args_draft_offloaded_to_cpu checked
extras but not LLAMA_ARG_N_GPU_LAYERS_DRAFT, so an env-offloaded drafter was still
charged GPU budget and under-advertised context. It now consults that env (the
device flag has no env), called with env=os.environ.
Layer-split compute buffer had no fallback when GGUF dims are missing. The estimate
returns 0 then, so the layer path folded no buffer while the tensor path falls back
to the flat reserve. Use the flat reserve for the layer path too (a safe upper
bound, since the tensor buffer >= the layer one).
All four are gated on conditions the documented benchmarks don't hit; the
single-GPU/tensor/pipeline reconfirm numbers are byte-identical, and the full
backend suite passes (830) with regression tests for each fix.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: share the env-aware tensor decision across load and dedup matchers
A review pass found the inherited-LLAMA_ARG_SPLIT_MODE=tensor flip lived only
in load_model, so the two duplicate-load matchers disagreed with it.
Consolidate the decision into _effective_tensor_parallel (extras + toggle, then
flip on when extras set no split mode and the child inherits a tensor split
env). load_model, the backend matcher (_already_in_target_state) and the route
matcher (_request_matches_loaded_settings) now all call it. Before, an env-driven
tensor server compared against resolve_tensor_parallel (env-blind) in both
matchers, so a follow-up load that should dedup was seen as a mismatch and the
healthy server was needlessly killed and reloaded.
Also finish the tensor cache-type handling: when the tensor attempt drops a
quantized KV it now re-adopts a heavier inherited env cache type (f32) for the
budget, mirroring the initial adoption; and the two layer-split downgrades clear
_cache_type_from_env so the restored quantized type is actually re-emitted rather
than left to a stale inherited env.
All gated on inherited env the documented benchmarks don't set; the single-GPU,
tensor and pipeline reconfirm numbers are byte-identical, and the full backend
suite passes (832) with unit + source regression tests for the shared helper and
the route matcher.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: complete the env-aware tensor/spec handling across all paths
A second review pass found the env-aware tensor/MTP handling was applied
asymmetrically: some paths inherited LLAMA_ARG_* env, others didn't. Three real
follow-ups, plus a small consolidation so the env semantics live in one place.
1. Tensor fallback ignored the inherited tensor env. load_with_tensor_fallback
computed its retry gate with the env-blind resolve_tensor_parallel, so an
env-only tensor load (toggle off, no --split-mode extra) that crashed on a
tensor-incompatible GGUF re-raised instead of retrying layer split. It now
uses the env-aware decision; and since the inherited env would otherwise
re-engage tensor on the retry (CLI args persist, the env does too), the retry
forces --split-mode layer (CLI wins over env) so it can't re-crash.
2. Duplicate-load matchers looped reloads after a tensor->layer downgrade. Both
matchers compared the env-expanded tensor decision against the loaded server,
but load_model may downgrade tensor to layer (capacity/buffer) and scrub the
child env. The still-set parent env then made every identical request look
like a mismatch, killing and reloading a healthy layer server. Add
_tensor_parallel_matches_loaded, which only lets an inherited tensor env raise
a match against a server that actually launched tensor; a downgraded server
matches the same request (an identical load would downgrade the same way).
3. MTP binary-capability fallback leaked an inherited LLAMA_ARG_SPEC_TYPE. When
the binary lacks MTP, _emit_mtp degraded but emitted no spec flag, so an
inherited LLAMA_ARG_SPEC_TYPE=draft-mtp still reached the child and attempted
MTP the gate had budgeted off. It now emits --spec-default (CLI wins over env)
like the sibling no-head / non-MTP fallbacks.
Consolidation: moved _env_split_mode_is_tensor / _effective_tensor_parallel into
llama_server_args.py (with the new _tensor_parallel_matches_loaded) so the
lightweight tensor_fallback module can share them without importing llama_cpp;
llama_cpp re-exports them for back-compat.
All gated on inherited env the documented benchmarks don't set; the single-GPU,
tensor and pipeline reconfirm numbers are byte-identical, and the full backend
suite passes (883) with regression tests for each fix.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: budget the heavier axis of asymmetric --cache-type-k/-v extras
A review pass found the explicit-extras counterpart of the env cache-type fix.
load_model adopts the heavier inherited LLAMA_ARG_CACHE_TYPE_K/_V env for the
reserve, but the explicit-extras path used resolve_cache_type_kv, which collapses
both axes to one last-wins value. So extras such as
--cache-type-k f32 --cache-type-v f16 (lighter axis last) budgeted f16 for both
axes while the child allocates f32 on K, over-advertising context and
re-opening the OOM path this PR closes.
Add parse_cache_override_per_axis (keeps the K/V last-wins values apart) and
_extra_args_main_cache_type_for_budget (the heavier of the two by bytes/elem),
and budget from it. The user's extras are appended last and win per axis at the
child, so this only raises the reserve; the emitted command and the asymmetric
child cache are unchanged, and the common single-axis / symmetric cases resolve
to the same type as before.
Reconfirm numbers (single-GPU table, tensor, pipeline) are byte-identical, and
the full backend suite passes (892) with per-axis parser and heavier-axis budget
regression tests.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: fix tensor-safety masking and strip inherited HF drafter selectors
A review pass found two more env/extras edge cases on the speculative and tensor
cache paths.
Tensor-safety could miss a quantized axis. The previous change budgets the
heavier-by-bytes cache type, but that masks a quantized axis paired with a
heavier one: --cache-type-k f16 --cache-type-v q4_0 resolves to f16, so the
tensor-safety block did not fire and the q4_0 axis survived into tensor mode,
which aborts on quantized KV. Test each explicit --cache-type-k/-v axis (not just
the budget type) so any quantized axis drops the cache for the tensor attempt.
Inherited HF drafter selectors were not stripped. _extra_args_mtp_draft_path
treats --spec-draft-hf / -hfd / -hfrd / --hf-repo-draft as drafter selectors, but
_SPEC_FLAGS only stripped the local --model-draft selectors, so on an inherited-
extras Apply a stale HF drafter survived and last-wins-overrode Studio's
re-derived spec choice. Add the HF aliases to _SPEC_FLAGS. The per-drafter tuning
knobs (--spec-draft-type-*, -ngld, --spec-draft-device) are intentionally left in
place: the VRAM budget reads them via the same parsers the child honors, so they
stay consistent on inherit, and stripping them would silently move a CPU-offloaded
drafter back onto the GPU.
A third flagged item -- that the HF draft env var should be LLAMA_ARG_HFD_REPO --
was a false positive from a stale manpage; the bundled binary's common/arg.cpp
sets LLAMA_ARG_SPEC_DRAFT_HF_REPO for --spec-draft-hf, which the code already
uses, so it is left unchanged.
Reconfirm numbers (single-GPU table, tensor, pipeline) are byte-identical, and
the full backend suite passes (899) with regression tests for both fixes.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* studio: preserve asymmetric cache on tensor downgrade and skip CPU-drafter reserve
A review pass found two more tensor-path edges, one a regression from the
per-axis cache change.
Tensor-to-layer downgrade collapsed asymmetric cache extras. The per-axis
tensor-safety check strips an asymmetric --cache-type-k/-v (tensor rejects
quantized KV), but the downgrade restored only the scalar heavier type, so a
layer fallback silently rewrote --cache-type-k q4_0 --cache-type-v f16 to
symmetric f16/f16 even though layer split supports the original. Save the
original extras before the tensor strip and restore them verbatim (minus the
user --split-mode) on both downgrade points; the budget still uses the heavier
scalar, the child gets the real asymmetric cache. Before the per-axis change this
case happened to survive (last-wins was f16, untouched), so this restores that.
Tensor mode reserved GPU VRAM for a CPU-offloaded drafter. The layer path drops
the flat MTP reserve when the only drafter is a separate CPU one with no embedded
head, but the tensor capacity gate and planner still charged it, under-advertising
context. Gate the tensor reserve on the same condition via _mtp_reserves_gpu.
Reconfirm numbers (single-GPU table, tensor, pipeline) are byte-identical (both
fixes are gated on conditions the benchmarks don't hit), and the full backend
suite passes (901) with regression tests for each.
* studio: drop now-unused llama_server_args imports from llama_cpp
The refactor re-pointed load_model and the matchers off resolve_tensor_parallel /
resolve_cache_type_kv and moved the env split-mode helper into llama_server_args,
leaving those three names imported but unused in llama_cpp. The repo's import-hoist
safety-net lint blocks that, so drop them; the env split-mode test now imports
_env_split_mode_is_tensor from its real home (llama_server_args).
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* fix(unsloth-cli): route hub_path/hub_token correctly in --push_model save block
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* fix(unsloth-cli): let --push_gguf work without --save_gguf
Route into the GGUF branch when either --save_gguf or --push_gguf is set, and
guard the local save_pretrained_gguf call behind --save_gguf. Previously
--push_gguf alone fell through to the merged-save else branch and pushed
nothing (flagged in review).
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Warn when --push_gguf is used without --save_gguf in unsloth-cli
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
* Studio: auto-render fenced HTML in chat replies as canvas cards
After an assistant reply finishes, append a clickable canvas card for each fenced html block in its text, with no render_html tool call and no extra message. Fragments and non-collapsed documents are covered; full documents already collapsed in place and blocks rendered by the render_html tool are skipped so nothing shows twice.
Hoists the fence helpers out of markdown-text.tsx into a shared module (html-fences.ts) and adds a line-based multi-fence scanner so several html blocks in one reply are all found.
* Studio: add HTML Code button to canvas cards and keep diffusion code visible
When Canvas mode is on or a diffusion model is loaded, the canvas card shows a Preview and an HTML Code button side by side; Code opens the panel source view. The requested view is threaded through openArtifact so the surface opens to preview or code.
Diffusion no longer collapses its full HTML answer, so the raw code stays in the message and the trailing canvas card is appended next to it.
* Studio: drop the Code button on diffusion cards since their code is already inline
* Studio: address review feedback on HTML canvas auto-cards
- Build the fence-body indent regex once per fence instead of per line.
- Only skip full-doc fences the in-place collapse can render (plain
unindented triple-backtick), so 4-backtick or indented docs still get a card.
- Scan each text part on its own so a fence cannot stitch across a tool,
source, or reasoning part.
- Exclude diffusion replies from the collapse/skip gates and the card Code
button, since diffusion keeps its HTML inline.
---------
Co-authored-by: Daniel Han <michaelhan2050@gmail.com>
* Add Studio API activity monitor
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Mark disconnected monitor streams cancelled
* Fix monitor lifecycle, parsing, and clock issues for PR #5558
Backend:
- Finalize the monitor entry as error on the chat-completions validation
reject paths (addresses the codex P2 comment around line 2337). Adds a
small _reject helper next to the api_monitor.start so each early-raise
in the GGUF tool-passthrough, non-empty-messages, GGUF vision/audio,
PIL image decode, and text-only model branches no longer leaves the
entry stuck running until eviction.
- _monitor_openai_chunk is now defensive about malformed shapes
(non-list choices, non-dict choice/delta) so a misbehaving provider
chunk does not raise into the streaming generator and abort the
user's response.
- _monitor_openai_sse_line accepts both data:value and data: value per
SSE spec; previously the single-space-only prefix silently dropped
tokens from compact emitters.
- openai_completions stream now keeps a residual buffer between
aiter_bytes chunks so a data: line whose newline lands in the next
TCP frame is reconstructed before parsing, instead of being dropped
by the per-chunk splitlines call.
api_monitor:
- duration_ms is derived from time.monotonic anchors and clamped at 0,
so NTP / manual clock steps no longer produce negative durations.
- finish() and fail() are idempotent: a second call (for example [DONE]
arriving after the generator's finally block already ran) no longer
moves finished_at or finished_monotonic.
- set_usage only derives total_tokens when no authoritative total has
been recorded, so a later partial-usage chunk does not clobber a
provider-reported total from an earlier chunk.
- _trim guards limit < 3 so the slice cannot underflow.
Tests:
- Adds regression coverage for the new idempotency, total preservation,
monotonic clock, and _trim guard behaviour.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Mark monitored stream failures correctly
* Fix completions monitor failures
* Fix responses stream monitor cleanup
* Fix audio input API monitor lifecycle
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Fix API monitor review follow-ups
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Fix completions monitor usage accounting
* Fix API monitor cancellation and usage gaps
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Move API monitor into settings
* Fix API monitor review followups
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Lazy load API monitor details
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Fix API monitor review issues
* Finalize passthrough monitor on clean EOF
* Finalize monitor on chat validation rejects
* Fix API monitor review follow-ups
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Fix API monitor follow-up review issues
* Monitor embeddings and tool-call replies
* Cover tool-call monitor replies and cancellations
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Scope API monitor entries by subject
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Finalize monitor entries on cancellation
* fix: address PR 5558 CI failures
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* fix: update provider proxy test stub
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: imagineer99 <samleejackson0@gmail.com>
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
* Studio: add shared Xet-primary download helper with HTTP stall fallback
Xet is the fast default transport in huggingface_hub, but a stalled Xet
transfer hangs with no progress and no exception, and a blocked native thread
cannot be killed. The safetensors inference path already recovers (subprocess
watchdog + respawn with HF_HUB_DISABLE_XET=1); the GGUF and training paths do
not. Add a reusable helper that the in-process paths can adopt.
utils/hf_xet_fallback.py:
- DownloadStallError (moved here from core/inference/orchestrator.py, which now
imports it; behavior unchanged, still a RuntimeError subclass).
- get_hf_download_state / start_watchdog: a no-progress watchdog built on the
sparse-aware hub.utils.hf_cache_state helpers; fires only while a .incomplete
is present and the on-disk byte total is unchanged for stall_timeout.
- hf_hub_download_with_xet_fallback: cached files short-circuit; otherwise the
download runs in a spawn child (own process group) supervised by the watchdog.
On a stall it kills the child, makes the partial safe for HTTP via
prepare_cache_for_transport, and respawns once with HF_HUB_DISABLE_XET=1. Cancel
and deterministic errors (auth/missing/disk) propagate without a fallback.
Tests cover the watchdog state machine, the transport decision logic, and a
regression lock that HF_HUB_DISABLE_XET is honored in a fresh interpreter.
* Studio: route GGUF Chat-Mode downloads through the Xet->HTTP fallback
The GGUF load path (_download_gguf main+shards, _download_companion_gguf for
mmproj/MTP) called a bare blocking hf_hub_download with no recovery, so a Xet
stall hung the Chat-Mode load with no fallback. Route those three calls through
hf_hub_download_with_xet_fallback: Xet stays primary, HTTP is used only if Xet
stalls, per-file so finished shards stay cached. The existing _cancel_event is
threaded through, the Cancelled sentinel is preserved, and companions stay
best-effort (a terminal stall is swallowed to None). Cached files short-circuit
in the helper with no subprocess, so the fast path is unchanged.
The two offline mmproj tests are repointed from huggingface_hub.hf_hub_download
to the new call boundary (the helper) since the download now goes through it.
* Studio: recover a stalled training model-load via Xet->HTTP respawn
Training runs in a spawn subprocess and FastModel.from_pretrained downloads
internally, so the download cannot be wrapped per-file like GGUF. Instead the
worker now watches the HF cache during the model-load phase (emitting
model_load_started / model_load_completed and a stall event), and the parent
recovers a stall by terminating the worker and respawning it once with
HF_HUB_DISABLE_XET=1.
worker.py: set HF_HUB_DISABLE_XET=1 before any HF import when the parent passes
disable_xet (respawn), and wrap trainer.load_model with start_watchdog.
training.py: plumb disable_xet through the config; track the model-load window;
on a first-load stall arm a one-shot respawn (handled on the exiting pump thread,
so no pump self-join) that preserves the DB run row (history is not duplicated)
and re-runs the load over HTTP. A second stall, or a stall outside model-load,
surfaces as a normal error. W&B init happens after model-load, so a pre-load
respawn cannot duplicate it; the dataset is re-formatted in the new worker.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: add gated test-only fault-injection hook for the Xet stall path
UNSLOTH_HF_XET_FORCE_STALL=1 makes the Xet download attempt write a partial
blob and hang, so the no-progress watchdog and the HTTP fallback can be
exercised end to end against a real repo (never set in production). Used to
verify recovery on real models: a forced Xet stall on a 5.37GB Qwen3.5-35B-A3B
shard triggered the watchdog and the HTTP retry downloaded the correct file
(sha256 verified).
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: tighten Xet-fallback comments and consolidate its tests
Trim docstrings and inline comments across the Xet->HTTP fallback code to
the non-obvious why (spawn-not-thread, killpg-not-getpgid, the sparse-partial
HTTP-resume hazard); drop comments that merely restate the code. Verified
comment-only with an AST signature check.
Merge the three helper-level test files (watchdog, transport policy, and the
HF_HUB_DISABLE_XET regression lock) into tests/test_hf_xet_fallback.py, and
prefer the real structlog over a bare stub so test collection order cannot
leak an incomplete module to others that log at import.
Full backend suite: 3455 passed, 14 pre-existing flash-attn failures only.
* [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>
* Studio: hide the llama-server install validation probe from model pickers
The ggml-org/models / stories260K probe (install_llama_prebuilt validates
the prebuilt binary against it) can land in the HF cache and, being a 260K
toy model, sorts smallest and gets auto-selected for chat. Hide it in
_is_hidden_model alongside the RAG embedding model so it never surfaces.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* test: assert the validation probe is hidden across all model pickers
_is_hidden_model gates the model list, local, cached-GGUF and cached-models
endpoints that feed the picker search, so cover both the repo-id and the
on-disk snapshot-path forms callers pass.
* Studio: match the validation probe by exact filename, not a bare substring
Use the probe's repo id (ggml-org/models) plus its exact filename
(stories260K.gguf) so the picker filter does not also hide unrelated repos
that merely reference stories260K, e.g. user/stories260K-finetune-GGUF.
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* Harden Trainer._load_rng_state against malicious checkpoints (CVE-2026-1839)
Resuming from an untrusted checkpoint loads its rng_state.pth via transformers' Trainer._load_rng_state, which calls torch.load without weights_only=True. On torch < 2.6 (weights_only not yet the default, safe_globals() does not restrict the unpickler) a crafted rng_state.pth runs arbitrary code. transformers fixed this in 5.0.0rc3, but we pin 4.57.x for compatibility, so add an import-time monkey patch instead.
patch_unsafe_trainer_rng_load() wraps the method and forces torch.load with weights_only=True for the call, mirroring the upstream fix while keeping the surrounding distributed/device logic intact and tracking transformers' own safe_globals() allowlist. It is idempotent and a no-op on torch >= 2.6 (already the default) and transformers >= 5.0.0rc3 (already fixed), so it self-disables once the pin is bumped. Wired into _gpu_init.py so it applies on import unsloth.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Gate Trainer._load_rng_state via check_torch_load_is_safe instead of forcing weights_only
Review feedback was correct: forcing weights_only=True is not the right fix. It is not a safe boundary below torch 2.6 (CVE-2025-32434), where transformers' safe_globals() is also a nullcontext, so a legitimate rng_state.pth (which holds numpy state) would fail to load there. Call transformers' own check_torch_load_is_safe() before the load instead: it raises on torch < 2.6 and is a no-op on torch >= 2.6, where torch.load already defaults to weights_only=True. This also drops the temporary global torch.load swap, removing the thread-safety race.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Trim comments in the RNG-load guard
* Harden only the rng torch.load via a thread-local gate
Address review feedback on the rng-load guard:
- Import Trainer on its own and fall back to a local torch-version check when
transformers does not export check_torch_load_is_safe, so older supported
4.51.x installs are still protected.
- Force weights_only=True at the rng load so TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD
cannot re-enable pickle execution.
- Gate at the actual rng torch.load through a thread-local flag rather than up
front, so model-only or rng-less resumes still proceed on torch < 2.6 and
other torch.load callers are unaffected.
* [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>
* Studio: drop the VBS launcher to clear the Kaspersky false positive
The Windows shortcut launched Unsloth Studio through wscript.exe ->
launch-studio.vbs, and that VBS used CreateObject("WScript.Shell").Run to
start a hidden -ExecutionPolicy Bypass PowerShell. That wscript + .vbs +
bypass-powershell shape is the canonical trigger for generic VBS-dropper
heuristics (Kaspersky HEUR:Trojan.VBS.Agent.gen). The launcher is benign;
only its shape is the problem.
- install.ps1: stop generating launch-studio.vbs and point the Desktop /
Start Menu .lnk straight at powershell.exe -WindowStyle Hidden running
launch-studio.ps1. The shortcut is saved WindowStyle 7 (minimized) so the
brief console flash is muted. launch-studio.ps1 (health poll, port,
mutex, browser) is byte-for-byte unchanged.
- install.ps1: delete a pre-existing launch-studio.vbs on upgrade, so the
flagged file does not linger on machines that already installed it.
- install.ps1 / install.sh: run the heavier ie4uinit -ClearIconCache plus
StartMenuExperienceHost tile-cache rebuild only on a first install or a
real icon change, instead of on every no-op reinstall. That repeated
clear-cache plus kill cluster is itself a dropper-like behavioral pattern.
- tests: forbid VBS generation and require the legacy-VBS cleanup.
Linux, macOS and WSL install paths are unchanged. WSL already targets
wsl.exe from its .lnk and never used a VBS; its only change is the same
icon-cache gating.
* Studio: add launcher-chain smoke coverage to the Windows UI CI
The shortcut launch path was previously untested: studio-windows-ui-smoke
installed then booted `unsloth studio` directly, so a broken .lnk could ship
silently. After install the job now seeds a legacy launch-studio.vbs, asserts
the upgrade removed it, asserts the .lnk targets hidden powershell.exe (never
wscript.exe), and launches via the shortcut's stored command, waiting for
/api/health to report healthy.
* [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>
* Make Studio installer resilient to transient uv download failures
Updating an existing Studio install via install.sh could hard-fail and roll
back when a wheel download (torch, unsloth) hit a transient connection reset:
x Failed to download unsloth==2026.6.6
error decoding response body -> error reading a body from connection
-> connection reset
restoring previous environment after failed install...
Root cause: that error chain is a mid-stream HTTP/2 body read failure. uv did
not retry this class until 0.8.16 (astral-sh/uv#15675, h2 was shadowing the
underlying IO error), but the installer pinned UV_MIN_VERSION=0.7.22, so a stale
uv got zero retries and a single blip aborted the whole update under set -e.
Fix (installer only, backwards compatible, no change on success):
- Raise UV_MIN_VERSION to 0.8.16 so stale uv is upgraded to a version that
retries HTTP/2 streaming body errors.
- Export UV_HTTP_RETRIES=5 and UV_HTTP_TIMEOUT=180 (override-preserving :=).
- Add run_install_cmd_retry (retry-with-backoff around run_install_cmd) and use
it for the network-heavy uv pip install steps (torch, unsloth, unsloth-zoo
from git, ROCm torch repair, no-torch runtime deps). Local editable overlays
and venv creation are left to fail fast.
run_install_cmd_retry preserves the final exit code on permanent failure, so the
existing set -e rollback trap still fires.
* Apply the same transient-download resilience to the Windows installer
install.ps1 is the native-Windows installer and had the identical issue as
install.sh: it pinned $UvMinVersion=0.7.22 (below uv 0.8.16, which is where uv
started retrying HTTP/2 streaming body errors), set no UV_HTTP_* defaults, and
ran each 'uv pip install' once via Invoke-InstallCommand, so a single connection
reset aborted the update and triggered the Exit-InstallFailure rollback.
install.ps1:
- Raise $UvMinVersion to 0.8.16.
- Default $env:UV_HTTP_RETRIES=5 and $env:UV_HTTP_TIMEOUT=180 (preserving overrides).
- Add Invoke-InstallCommandRetry and use it for the network-heavy uv pip install
steps (torch, unsloth, unsloth-zoo from git, ROCm torch, no-torch runtime deps).
Local editable overlays and venv creation stay single-shot.
install.sh:
- Align UNSLOTH_INSTALL_RETRIES sanitization with the PowerShell version: a
non-positive-integer value now falls back to the default of 3 instead of
silently disabling retries (set =1 to disable). Keeps both installers identical.
* Adopt pre-marker Studio llama.cpp and sidecar dirs on update
After the uv retry fix, an update now reaches studio/setup.sh, whose
Studio-owned ownership guard rejects a llama.cpp or sidecar venv created by an
earlier install that predates the .unsloth-studio-owned marker:
ERROR: .../llama.cpp already exists and is not marked as a Studio-owned
llama.cpp install.
The marker and UNSLOTH_PREBUILT_INFO.json were introduced in the same commit,
so a directory from before that point carries neither signal and a legitimate
self-update fails for anyone who installed earlier (reported on issue #6274).
Fold a one-time adoption into _assert_studio_owned_or_absent (setup.sh) and
Assert-StudioOwnedOrAbsent (setup.ps1): when a custom-home directory lacks the
marker, backfill it and proceed only when there is positive evidence it belongs
to an established Studio home -- the directory carries UNSLOTH_PREBUILT_INFO.json,
or STUDIO_HOME already holds Studio's CLI shim or studio.conf from a prior run.
Both installers write the shim and studio.conf only after invoking setup, so a
fresh install into a dirty custom home (the case the guard protects) does not
have them yet and is still rejected. The venv marker is excluded because install
writes it before setup and so cannot tell a prior install from a fresh one.
* Review fixes: restrict llama.cpp adoption to dir-local evidence; restore install.sh +x
Addresses the PR review on the marker-migration change.
P1 - the adoption helper keyed on root-level Studio sentinels ($STUDIO_HOME/bin
/unsloth, share/studio.conf), so once a home was recognized every unmarked child
passed to the guard became adoptable, and an unrelated directory at a
Studio-managed path could be silently marked and overwritten. Base adoption on
evidence inside the directory instead:
- UNSLOTH_PREBUILT_INFO.json, written by the prebuilt llama.cpp installer (the
default path, in place well before the marker), or
- a top-level llama-quantize symlink, written by source builds (a plain
llama.cpp checkout keeps the binary under build/bin, not a root symlink).
A foreign llama.cpp now stays rejected even inside an established Studio home,
and sidecar venvs (no such fingerprint) stay subject to the strict guard; their
marker has been written since the guard was introduced, so a real custom install
already carries it.
P2 - restore the executable bit on install.sh; a stray mode change to 100644
would break ./install.sh --local on Unix.
On Windows the prebuilt metadata is the signal; source builds are git checkouts
indistinguishable from a user clone, so they are left to the strict guard.
* Bound UNSLOTH_INSTALL_RETRIES / _DELAY before numeric use
An oversized all-digit override (e.g. a fat-fingered
"99999999999999999999") passed the digit-only validation and then reached the
numeric comparison: POSIX `[ -ge ]` errored with "Illegal number" mid-loop and
could spin instead of falling back, and PowerShell's `[int]` cast threw an
Int32 overflow under $ErrorActionPreference = "Stop" before any install ran.
Sanitize with a length guard + range check (sh) and [int]::TryParse with bounds
(ps1), so out-of-range or oversized values fall back to the default. Bounds:
1..100 retries, 0..3600s base delay.
* Studio installers: scope llama.cpp adoption to prebuilt metadata; reject leading-zero retry delay
setup.sh: drop the top-level llama-quantize symlink as an ownership-adoption signal, leaving UNSLOTH_PREBUILT_INFO.json as the sole fingerprint. The shared ownership guard runs immediately before a destructive replace / rm -rf, and a bare root llama-quantize symlink is user-creatable (a user can keep their own llama.cpp build with such a convenience symlink at a custom UNSLOTH_STUDIO_HOME), so the old check could adopt and then delete a user directory. This matches the Windows installer, which already keeps markerless source builds strict. Pre-marker prebuilt installs still adopt via the metadata file, so the original update fix is preserved.
install.sh: reject leading-zero values for UNSLOTH_INSTALL_RETRY_DELAY. A value like 08 or 09 passed the range check but then hit the backoff doubling $((_ricr_delay * 2)), where a non-octal leading zero is a fatal arithmetic error mid-retry. The 0?* pattern routes such values to the default; bare 0 stays valid.
* Tighten the comments added in this PR
* Condense the comments in this PR
Linux tool calling used Qwen3.5-2B IQ3_XXS. That quant is aggressive
enough that the model intermittently emits a malformed tool call
(doubled </parameter>, stray </tool_call>), and llama-server's
peg-native parser rejects it with a 500, failing the job. Mac and
Windows already run this test at Q4_K_XL; align Linux.
Also copy ~/.unsloth/studio/logs (backend server log + llama-server log)
into the tool-calling and ui-smoke artifacts on stop. Previously only
studio.log + install.log were uploaded, so a /v1/chat/completions or
/api/inference/load 500 had no server-side traceback to diagnose from.
* Package scanners: cut false positives and make the CI gate blocking
scan_packages.py and scan_npm_packages.py red-failed on legitimate
library code, so the security-audit steps were left advisory. Reduce
the false positives at the source and flip both gates to blocking.
scan_packages.py:
- Scan code only: blank comments and bare docstrings/doctests before
matching (line numbers preserved), so prose and >>> examples cannot
trip a finding.
- Drop the platform.system() branch from the anti-analysis regex (under
DOTALL it matched across the whole file, so every cross-platform
library tripped it) and fix the dead /proc/self/status alternative.
- Add a reviewed baseline allowlist (scan_packages_baseline.json) keyed
on (package, basename, check): only non-baselined CRITICAL/HIGH exit
1, and a new kind of finding in a listed file still fails.
- sdist fallback: when --with-deps cannot resolve a shard (a sdist-only
package or a version conflict), drop to per-spec and fetch the raw
sdist from the PyPI JSON API (no pip build, no setup.py), so every
package is still scanned and no shard exits 2.
scan_npm_packages.py:
- Mirror the code-only JS/TS scanning (blank // and /* */ comments,
string/template/regex aware) and the baseline allowlist. The npm
corpus is clean today, so the baseline is empty.
security-audit.yml:
- Flip both scan steps to blocking (SCAN_ENFORCE=1), capturing the
scanner exit via PIPESTATUS so tee does not mask it.
tests/security: add coverage for the strip, baseline and sdist paths.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Address review feedback on the package scanners
- Do not blank f-strings during code-only scanning (they evaluate at
import); and when a file uses exec/eval, rescan the original for
payload carriers hidden in a docstring/string so exec(__doc__) style
payloads stay visible.
- sdist fallback: recover transitive deps with their version specifier
(fetch the pinned version, not latest), and recover deps in the
--no-deps branch too so a sdist-only transitive dependency is still
scanned instead of silently skipped.
- Baseline: key by package-relative path, not basename, so a future
same-named file in another directory is not auto-suppressed.
Regenerated the baseline accordingly.
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* Studio: omit --threads when unset so llama.cpp picks physical cores
Studio passed --threads -1 when no thread count was set. The intent was physical cores, but an explicit --threads -1 makes llama.cpp's arg parser resolve it to hardware_concurrency() (every hyperthread), which contends on the memory bus and slows CPU and hybrid decode (a user saw about 60-75 fall to about 20-30 tok/s under CPU offload). Leaving --threads unset keeps n_threads at -1, which llama.cpp resolves to physical cores via common_cpu_get_num_math(). Omit the flag when unset; still pin it for an explicit override and the Windows full-offload OpenMP cap.
* Studio: drop inherited LLAMA_ARG_THREADS when omitting --threads
Omitting --threads relies on llama.cpp resolving physical cores via common_cpu_get_num_math. But the child inherits os.environ and llama.cpp also reads --threads from LLAMA_ARG_THREADS, which routes through the arg handler and maps <=0 to hardware_concurrency. So an ambient LLAMA_ARG_THREADS would silently override the physical-core default. Scrub it from the child env only when we omit the flag.
* Chat search: match across all messages, not just title and preview
The Cmd/Ctrl+K chat search only matched the thread title and a 120
character preview of the newest message, so keywords anywhere else in a
conversation were unfindable. Build a per-thread haystack from the title
plus every message's text and match against that.
The haystack is lowercased once at index build time, so the keystroke
filter only normalizes the short query instead of re-lowercasing the
whole conversation per item.
* Chat search: search user messages first, expand to all only if no hit
User messages are short while assistant replies can be very long, so
matching the whole conversation on every keystroke scales with the
assistant text. Store a separate userSearchText (title plus user
messages) and match it first, expanding to the full per thread
searchText only when nothing matches user text anywhere. Row filtering
moves into selectVisibleChats with cmdk shouldFilter disabled so the
tier choice is deterministic.
* Chat search: include tool calls and sources in the full-text tier
extractText now also pulls reasoning/thinking, tool call name/args/result
and cited source title/url, so the expanded full-conversation tier finds
keywords that only appear in tool activity. Drop the now-unused preview
field since search no longer matches on it.
* Chat search: drop base64 image/audio payloads from the index
extractText stringified tool-call results wholesale, so an
image_generation result (image_b64) or audio payload would pour megabytes
of base64 into searchText and get lowercased on every rebuild. Add
searchableText, which keeps readable tool args/results (tool name, prompt,
text) but skips binary keys and strips data URLs, long base64 runs and the
__IMAGES__ suffix.
* Studio: log transformers version-switching decisions and stop swallowing MLX activation failures
Two logging gaps in dynamic transformers version switching (issue #6103):
1. get_transformers_tier returned a tier with no trace of why. Add an
info log at each decision point naming the model and the trigger
(which substring matched, or which config check fired), so a model
landing on the wrong tier is diagnosable.
2. The MLX fast-path in run_training_process activated the transformers
version inside a bare 'except Exception: pass', silently swallowing
failures while the non-MLX path reports them. A missing or broken
version venv (e.g. Gemma-4 needing 5.5.0) left no trace and only a
confusing downstream crash. Extract a small _activate_transformers_version_or_warn
helper that logs a warning on failure while keeping the non-fatal
fall-through, and call it from the MLX path.
Adds tier-selection logging tests and helper warn/silent tests.
* Studio: clarify path-prepend log, warn on venv version mismatch, log per-package install progress
Completes the remaining logging items of #6103 in studio/backend/utils/transformers_version.py:
- activate_transformers_for_subprocess: the early "Activated transformers X.X.X" line was misleading because at that point only the venv directory has been prepended to sys.path, not imported. It now says it prepended the venv to sys.path and notes the loaded version is confirmed later by "Subprocess loaded transformers ...".
- _venv_dir_is_valid: a detected version mismatch is logged at warning instead of info, since it immediately triggers a full venv wipe and reinstall that should be visible in the logs.
- _ensure_venv_dir: log each package as it starts installing with an N/M progress counter, so a slow runtime install is not mistaken for a hang (pip/uv output is piped and only surfaced on error).
Adds tests covering all three behaviours; pre-existing unused imports are left untouched.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: make tier log-capture tests independent of import order
The new issue #6103 caplog assertions in test_transformers_version.py
relied on the module-level sys.modules.setdefault("loggers", stub)
winning the import race. In a full backend pytest run another module
(for example test_log_filter_no_truncation, collected earlier) imports
the real loggers first, so the setdefault is a no-op and
transformers_version.logger becomes a structlog/stdout logger that
caplog cannot capture -- the tier, activation, venv-mismatch and
install-progress log assertions then fail even though the line was
emitted.
Bind a real stdlib logger to transformers_version.logger for the
duration of each test via an autouse fixture, so the module logs through
logging and caplog captures them regardless of collection order.
* Studio: log local checkpoint tier decisions and warn on MLX inference activation
- get_transformers_tier: the local config.json fast path returned a tier
without logging it, so local checkpoints stayed opaque while HF ids were
traceable. Log each decision there too, with a caplog regression test.
- inference worker: the MLX path swallowed _activate_transformers_version
failures with a bare except, the same gap issue #6103 fixed for training.
Warn instead, keeping the non-fatal fall-through.
---------
Co-authored-by: Daniel Han <michaelhan2050@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* Studio: warn when a GPU model silently loaded on CPU
llama-server can serve HTTP 200 while running a model entirely on CPU when its GPU backend fails to init, so Studio could run a GGUF on CPU without saying so (#5807 / #5106 / #5830). The silent-CPU warning already exists but stopped firing on current llama.cpp because _classify_gpu_offload keyed only on the dropped 'model buffer size' lines. Add a shared classify_gpu_offload_lines (offloaded N/M counts, GPU model-buffer markers excluding _Host, device_info disconfirm-only) and delegate to it so the warning fires again. Log-only: no install or load behavior changes.
Pure classification of already-captured startup log lines, run once after load; no new subprocess, no slowdown.
* Studio: key the CPU-offload warning on the main model, not a draft
With MTP/speculative decoding llama-server logs 'offloaded N/M layers to GPU' twice: once for the main model and once for the small draft model. The old scan returned True on any non-zero count, so a drafter that fits on GPU while the main GGUF runs on CPU suppressed the warning (the Qwen3.6-27B-MTP case). Decide on the line with the most layers (the main model) instead, so a drafter cannot mask a main model on CPU.
* Studio: make lifespan shutdown resilient to a dead default executor
On an abrupt shutdown (closing the Windows console window, or interpreter
teardown racing uvicorn's graceful stop) the event loop's default thread-pool
executor can already be shut down by the time the FastAPI lifespan shutdown
runs. The first post-yield statement was an unguarded
`await asyncio.to_thread(terminate_hub_downloads)`, so executor.submit raised
`RuntimeError: cannot schedule new futures after shutdown`. That raise
propagated up through every nested merged_lifespan __aexit__, aborted the rest
of the cleanup (DEVICE reset, compiled-cache clear), and surfaced as
"Application shutdown failed. Exiting."
Extract the post-yield cleanup into utils/lifespan_shutdown.run_lifespan_shutdown
and guard each step independently. On the to_thread RuntimeError, fall back to
running the (already best-effort, quick) terminate inline on the loop thread so
shutdown still completes cleanly. The helper is dependency-injected and free of
the heavy backend import graph, so it is unit-tested in isolation.
Add tests/test_lifespan_shutdown.py (4 cases: dead-executor survival, normal
path, terminate error, clear error). Validated on windows-latest and
ubuntu-latest runners on Python 3.13.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: only retry terminate inline when scheduling fails, not when the body raises
Address review feedback on the shutdown helper: the previous
`except RuntimeError` after `asyncio.to_thread(terminate_downloads)` could
not tell a dead-executor scheduling failure from a RuntimeError raised by
terminate_downloads itself, so a body-side RuntimeError on a healthy executor
ran the cleanup a second time inline.
Schedule via loop.run_in_executor and await separately: a dead default executor
raises synchronously at submit time (inline fallback), while a body exception
only surfaces when the future is awaited (logged, never retried). Add a
regression test that a body RuntimeError runs terminate exactly once.
* Studio: run terminate cleanup with a copied context (parity with asyncio.to_thread)
Simulation across the executor-state x exception x DEVICE matrix surfaced the one
behavioural difference from the original implementation: asyncio.to_thread copies
the caller's contextvars into the worker thread, while a bare
run_in_executor(None, fn) does not. Restore exact parity by scheduling
ctx.run(terminate_downloads) from a contextvars.copy_context(), so the refactor
is a behavioural no-op apart from the intended dead-executor recovery. Add a
regression test asserting the copied context is visible to terminate_downloads.
* Studio: tighten comments in lifespan_shutdown helper and tests
Condense the verbose docstrings/comments to the non-obvious rationale and drop
the self-evident ones. Verified comment-only with comment_tools.py check
--strip-docstrings (code signature unchanged); tests and sims still green.
* Studio: address review nits on lifespan shutdown helper
Annotate hw_module as types.ModuleType, reword the schedule/await comment
(inline fallback runs, it does not retry), and add a test for the public
loop.shutdown_default_executor() path (the 'Executor shutdown has been called'
RuntimeError that real uvicorn shutdown takes, distinct from the submit-time
'cannot schedule new futures after shutdown').
---------
Co-authored-by: Daniel Han <michaelhan2050@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* chore: add Studio version info to General settings
* chore: cache Studio version fetch
* fix: clear cached Studio version request on failure
* fix: retry incomplete Studio version fetch
* fix: remove cached Studio version fetch
* chore: move connections below API
* fix: localize studio version rows
* feat: implement thread forking functionality with associated database updates and UI components
* fix(studio/chat): register fork-count listener even when thread unsaved
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Polish thread fork action menu
* fix-studio-fork-project-test-order
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: imagineer99 <samleejackson0@gmail.com>
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
* Fix the libaray path for probe_server_capabilities()
even when running something as simple as `./llama-server --help`,
the binary still requires correct LD_LIBRARY_PATH to work - or it
returns merely an "error while loading shared libraries":
"libllama-server-impl.so: cannot open shared object file: No such file or directory"
For a local installation with no LD_LIBRARY_PATH specifically set,
the probe_server_capabilities() run of `./llana-server --help` should
share the same libaray resolution logic as start_llama_server().
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Adjust and readd comments
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: imagineer99 <samleejackson0@gmail.com>
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
* feat(studio): expose provider_type selector in model provider dialog
Use the configured provider_type in the model provider payload instead of hardcoding openai. Add a provider type selector to the model provider dialog for the provider types supported by Data Designer.
* feat(studio): add MiniMax-M2.7 inference defaults
Add MiniMax-M2.7 model family entry to inference_defaults.json
with recommended parameters (temperature=1.0, top_p=0.95, top_k=40).
Pattern placed before minimax-m2.5 for correct longest-match-first
ordering.
* Fix/adjust provider type support for PR #4277
* Clarify provider type validation for PR #4277
* Fix provider type default validation for PR #4277
---------
Co-authored-by: wasimysaid <wasimysdev@gmail.com>
Co-authored-by: PR Bot <pr-bot@minimaxi.com>
* Studio: guard the training import against a namespace-package shadow of unsloth
A directory named unsloth without an __init__.py on sys.path (a stray checkout,
a partial clone, or a polluted PYTHONPATH) makes the path finder return a
namespace package, so the worker's 'from unsloth import FastLanguageModel' fails
with 'cannot import name ... (unknown location)'. A normal site-packages install
always wins this race, so only source/editable installs are exposed. Before the
import, drop the offending sys.path entries, bind the real packages, then
restore sys.path so other modules on those entries keep importing. It is a
no-op when unsloth already resolves to a real package.
* Studio: import unsloth before unsloth_zoo in the namespace-shadow guard
_ensure_real_packages imported the requested names in argument order, so a
unsloth_zoo namespace shadow made it import unsloth_zoo directly before
unsloth. That skips unsloth.__init__ -> _gpu_init, which runs its ROCm and
Windows bitsandbytes fixes before its own import unsloth_zoo, so the recovery
path could import zoo with those guards skipped and fail on the ROCm/Windows
cases those fixes handle.
Import parent-first via reversed(names) so unsloth is imported first and pulls
in the real unsloth_zoo after _gpu_init has run; the later cached import is a
no-op. This also covers the case where only unsloth_zoo is shadowed: the bad
sys.path entry is still dropped and unsloth owns the zoo import. Detection,
sys.path pruning, shadow-cache clearing, and restoration are unchanged.
Add tests/test_namespace_shadow_guard_pr6269.py: CPU-only subprocess scenarios
(only zoo shadowed, both shadowed, only unsloth shadowed, healthy no-op, real
package absent, multiple shadow entries) that assert unsloth imports before
unsloth_zoo and that sys.path is restored.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: restore sys.path even if invalidate_caches fails in the shadow guard
Move importlib.invalidate_caches() inside the try/finally so a failure there
still restores sys.path, and tighten the import-order comment. Add a test that
forces invalidate_caches to raise and asserts sys.path is restored.
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* Studio: show llama.cpp version and GPU specs in the About panel
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: run the hardware endpoint off the event loop
* Studio: show the full llama.cpp release tag (incl -mix-<sha>) in the About panel
* Studio: order About-panel GPUs by visible ordinal and skip the llama.cpp probe during updates
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Fix hardware info refresh and endpoint scope
* [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: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: Etherll <61019402+Etherll@users.noreply.github.com>
* Studio: use the shared spinner for loading toasts
The loading toast used a different spinner from the model download toast,
so 'Unloading model' and 'Downloading model' looked inconsistent. Switch
the sonner loading icon to the app-wide Spinner so they match.
* Studio: consolidate loading spinners onto the shared Spinner
Replace the duplicate ToolCallSpinner and the remaining inline loading
spinners (hugeicons Loading03, lucide Loader2/Loader/LoaderCircle, and
the hand-rolled border rings) with the shared Spinner component, then
delete tool-call-spinner.tsx. Refresh and regenerate action icons and
the shimmer button are left alone since they are not loading spinners.
* Studio: align and even out sidebar hover pills
The Recents chat rows started further left than the nav items above them
and the hover pill widths varied. Give every nav and chat group the same
pl-1.5 pr-2 so the pills line up and share one width, and on hover the
chat rows only reserve room for the kebab so the title keeps one more
character.
* Studio: address review feedback on spinner and sidebar polish
- recipe-graph-node: restore rounded-full bg-background on the spinner
wrapper so it keeps masking the grid lines behind the floating spinner
(the original hand-rolled ring had this mask).
- app-sidebar: revert the pinned-row open-state padding to pr-8 so the
unpin button stays clear of the title when the kebab menu is open.
- app-sidebar: correct the now-stale inset comment (pl-1.5 = 6px, 18px).
* Fix undefined variable 'e' in Version parsing exception
* Fix SyntaxError for tuple unpacking in array index for Python < 3.11
---------
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>