Repository navigation
cmake : remove STATIC from impl libraries, enable LLAMA_BUILD_APP by default - #23462
Merged
Merged
Conversation
…ntrol Remove explicit STATIC from all -impl libraries (server, cli, completion, bench, batched-bench, fit-params, quantize, perplexity) so BUILD_SHARED_LIBS controls shared vs static linkage. Add WINDOWS_EXPORT_ALL_SYMBOLS ON for proper DLL export on Windows. Assisted-by: llama.cpp:local pi
Assisted-by: llama.cpp:local pi
ggerganov
force-pushed
the
gg/impl-shared-libs
branch
from
May 21, 2026 16:02
07bb337 to
7bd176e
Compare
ggerganov
marked this pull request as ready for review
May 21, 2026 16:03
ServeurpersoCom
approved these changes
May 21, 2026
angt
approved these changes
May 21, 2026
CISC
approved these changes
May 21, 2026
allozaur
approved these changes
May 21, 2026
7 tasks done
Member
|
Not sure what went wrong here? |
|
This change seems to have broken the Vulkan docker image. Build command: Running Running |
ProTekk
referenced
this pull request
in ProTekk/buun-llama-cpp
May 22, 2026
…default (#23462) * cmake : remove STATIC from impl libraries, allow BUILD_SHARED_LIBS control Remove explicit STATIC from all -impl libraries (server, cli, completion, bench, batched-bench, fit-params, quantize, perplexity) so BUILD_SHARED_LIBS controls shared vs static linkage. Add WINDOWS_EXPORT_ALL_SYMBOLS ON for proper DLL export on Windows. Assisted-by: llama.cpp:local pi * cmake : enable LLAMA_BUILD_APP by default Assisted-by: llama.cpp:local pi * ci : disable app in build-cmake-pkg.yml
Alex7MV
pushed a commit
to Alex7MV/claude_llama.cpp
that referenced
this pull request
May 22, 2026
…default (ggml-org#23462) * cmake : remove STATIC from impl libraries, allow BUILD_SHARED_LIBS control Remove explicit STATIC from all -impl libraries (server, cli, completion, bench, batched-bench, fit-params, quantize, perplexity) so BUILD_SHARED_LIBS controls shared vs static linkage. Add WINDOWS_EXPORT_ALL_SYMBOLS ON for proper DLL export on Windows. Assisted-by: llama.cpp:local pi * cmake : enable LLAMA_BUILD_APP by default Assisted-by: llama.cpp:local pi * ci : disable app in build-cmake-pkg.yml
gabe-l-hart
added a commit
to gabe-l-hart/llama.cpp
that referenced
this pull request
May 22, 2026
* origin/master: server: only parse empty msg if continuing an assistant msg (ggml-org#23506) perplexity : fix integer overflow (ggml-org#23496) SYCL: improve MoE prefill throughput (ggml-org#23142) sycl : Level Zero detection in ggml_sycl_init (ggml-org#23097) SYCL : gated_delta_net K>1 (ggml-org#23174) SYCL: add BF16 to DMMV kernel path (~4x tg speedup on Intel Arc) (ggml-org#21580) docs: Update documentation with Granite 4.0/4.1 (ggml-org#23404) ggml-zendnn : add Q8_0 quantization support (ggml-org#23414) cmake : build router app only during standalone builds (ggml-org#23521) vocab : fix HybridDNA tokenizer (ggml-org#23466) cmake : add install() for impl libraries + fix apple builds (ggml-org#23511) CUDA: fix PDL CC check for JIT compilation (ggml-org#23471) cmake : remove STATIC from impl libraries, enable LLAMA_BUILD_APP by default (ggml-org#23462) Update WebGPU support and add link to blog/demo (ggml-org#23483) vulkan: fuse snake activation (mul, sin, sqr, mul, add) (ggml-org#22855)
baramofme
pushed a commit
to baramofme/llama-cpp-turboquant
that referenced
this pull request
May 23, 2026
…default (ggml-org#23462) * cmake : remove STATIC from impl libraries, allow BUILD_SHARED_LIBS control Remove explicit STATIC from all -impl libraries (server, cli, completion, bench, batched-bench, fit-params, quantize, perplexity) so BUILD_SHARED_LIBS controls shared vs static linkage. Add WINDOWS_EXPORT_ALL_SYMBOLS ON for proper DLL export on Windows. Assisted-by: llama.cpp:local pi * cmake : enable LLAMA_BUILD_APP by default Assisted-by: llama.cpp:local pi * ci : disable app in build-cmake-pkg.yml
srossitto79
pushed a commit
to srossitto79/llama.cpp
that referenced
this pull request
May 23, 2026
…default (ggml-org#23462) * cmake : remove STATIC from impl libraries, allow BUILD_SHARED_LIBS control Remove explicit STATIC from all -impl libraries (server, cli, completion, bench, batched-bench, fit-params, quantize, perplexity) so BUILD_SHARED_LIBS controls shared vs static linkage. Add WINDOWS_EXPORT_ALL_SYMBOLS ON for proper DLL export on Windows. Assisted-by: llama.cpp:local pi * cmake : enable LLAMA_BUILD_APP by default Assisted-by: llama.cpp:local pi * ci : disable app in build-cmake-pkg.yml
danielhanchen
added a commit
to unslothai/unsloth
that referenced
this pull request
May 24, 2026
…5741 comments (#5746) * ci: broaden Linux llama.cpp runtime pattern to lib*.so* #5741 patched the explicit Linux pattern list to add ``libllama-*-impl.so*`` after ggml-org/llama.cpp#23462 (between b9279 and b9283) split each binary's entry code into a paired ``lib<binary>-impl.so`` shared library. Same class of upstream repackaging will hit us again whenever a new shared lib is added. Mirror what macOS already does and replace the per-lib list with a single ``lib*.so*`` glob. ``copy_globs`` (line 3614) unions patterns, so the per-variant ``libggml-cuda.so*`` / ``libggml-hip.so*`` entries were never filtering anything; the spec lives in ``runtime_payload_health_groups`` (line 5209) which keeps the explicit minimum-required list per variant. Dry-run against b9296-bin-ubuntu-x64.tar.gz: 40 files copied (all ggml, llama, mtmd, impl variants + the two binaries we ship), 22 skipped (other CLIs, rpc-server, LICENSE). Functionally equal to the post-#5741 set. * cleanup: trim #5741 comments on the pydantic split Comments added in #5741 explained the original bug in full each time. They are mostly redundant with the commit message and the PR. Trim them to one short paragraph per site. No behavior change. * ci: narrow Windows runtime pattern to llama-server.exe + llama-quantize.exe Studio only invokes llama-server and llama-quantize. Mac and Linux already filter to those two binaries; Windows was the odd one out with ``*.exe`` copying every CLI upstream ships (llama-cli, llama-bench, llama-mtmd-cli, ...). Dry-run on b9296 (win cpu-x64, cpu-arm64, cuda-13.1, hip-radeon): 20 unused EXEs skipped per variant, all DLLs (incl. the new llama-*-impl.dll family) still copied via ``*.dll``. ``existing_install_matches_choice`` already checks llama-server.exe exists explicitly (line 5297), so the health gate is unchanged.
rhsCZ
pushed a commit
to rhsCZ/unsloth
that referenced
this pull request
May 24, 2026
…nslothai#5741 comments (unslothai#5746) * ci: broaden Linux llama.cpp runtime pattern to lib*.so* unslothai#5741 patched the explicit Linux pattern list to add ``libllama-*-impl.so*`` after ggml-org/llama.cpp#23462 (between b9279 and b9283) split each binary's entry code into a paired ``lib<binary>-impl.so`` shared library. Same class of upstream repackaging will hit us again whenever a new shared lib is added. Mirror what macOS already does and replace the per-lib list with a single ``lib*.so*`` glob. ``copy_globs`` (line 3614) unions patterns, so the per-variant ``libggml-cuda.so*`` / ``libggml-hip.so*`` entries were never filtering anything; the spec lives in ``runtime_payload_health_groups`` (line 5209) which keeps the explicit minimum-required list per variant. Dry-run against b9296-bin-ubuntu-x64.tar.gz: 40 files copied (all ggml, llama, mtmd, impl variants + the two binaries we ship), 22 skipped (other CLIs, rpc-server, LICENSE). Functionally equal to the post-unslothai#5741 set. * cleanup: trim unslothai#5741 comments on the pydantic split Comments added in unslothai#5741 explained the original bug in full each time. They are mostly redundant with the commit message and the PR. Trim them to one short paragraph per site. No behavior change. * ci: narrow Windows runtime pattern to llama-server.exe + llama-quantize.exe Studio only invokes llama-server and llama-quantize. Mac and Linux already filter to those two binaries; Windows was the odd one out with ``*.exe`` copying every CLI upstream ships (llama-cli, llama-bench, llama-mtmd-cli, ...). Dry-run on b9296 (win cpu-x64, cpu-arm64, cuda-13.1, hip-radeon): 20 unused EXEs skipped per variant, all DLLs (incl. the new llama-*-impl.dll family) still copied via ``*.dll``. ``existing_install_matches_choice`` already checks llama-server.exe exists explicitly (line 5297), so the health gate is unchanged.
Alex7MV
pushed a commit
to Alex7MV/claude_llama.cpp
that referenced
this pull request
May 27, 2026
…default (ggml-org#23462) * cmake : remove STATIC from impl libraries, allow BUILD_SHARED_LIBS control Remove explicit STATIC from all -impl libraries (server, cli, completion, bench, batched-bench, fit-params, quantize, perplexity) so BUILD_SHARED_LIBS controls shared vs static linkage. Add WINDOWS_EXPORT_ALL_SYMBOLS ON for proper DLL export on Windows. Assisted-by: llama.cpp:local pi * cmake : enable LLAMA_BUILD_APP by default Assisted-by: llama.cpp:local pi * ci : disable app in build-cmake-pkg.yml
danielhanchen
added a commit
to unslothai/unsloth
that referenced
this pull request
May 27, 2026
* Studio: expose --parallel / -np on `unsloth studio run` The CLI was hardcoding `llama_parallel_slots=4` in `run_kwargs` at `unsloth_cli/commands/studio.py`, leaving users unable to tune the concurrent decode slot count even though the engine, KV-cache math, and `studio.backend.run.run_server(llama_parallel_slots=...)` plumbing all already accepted any N. This change adds a `--parallel` / `--n-parallel` / `-np` typer option (default 4 -- matches the previous hardcoded value), forwards it into `run_kwargs`, and pins the new surface with 4 unit tests. Per-request state in `routes/inference.py` is already isolated (`cancel_event` and `prev_text` are per-request locals in every streaming handler; the `_lock` / `_serial_load_lock` only wrap load/unload, not chat completions), so no concurrency refactor is needed alongside this -- the engine layer already handles N concurrent requests on one loaded model when llama-server is told to. Range guards: 1 <= N <= 64. With higher N each slot gets ctx/N KV cache; users tuning this should be aware that per-call context shrinks proportionally. `unsloth studio` (the bare default command, no subcommand) still defaults to llama_parallel_slots=1 via `run_server`'s own default; this PR does not change that path -- it only exposes the knob on the one-liner `studio run` command that already silently used 4. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Forward --parallel through venv re-exec and drop colliding short aliases `unsloth studio run` re-execs into the Studio venv when invoked from outside it (the common path). The arg-builder forwards every typer option but the new --parallel, so the child re-execs at the default 4 and any user value is silently dropped. Worse: pre-PR users who already pass `-np N` as a pass-through extra (where llama.cpp's last-wins parsing made it stick) silently lose N after this PR lands. Forward --parallel explicitly in the re-exec arg list. While auditing the re-exec path, also drop the colliding 1-char short aliases -m (--model) and -f (--frontend) plus the redundant -hfr. Click's short-option clustering had been silently mis-parsing ~11 llama-server short flags via the pass-through path: -fa as `-f a`, -mg 0 as `-m g` + stray 0, -fitt 1024 as `-f itt` + stray 1024, -hff path as `-f f` + stray `-h path`, -cmoe / -cram / -sm / -ncmoe etc. The docstring promise ("any flag this command does not recognize is forwarded verbatim") was silently violated. -hf (2-char) is kept because Click treats multi-char shorts atomically (no clustering of -hff / -hfv / -hffv / -hft) and -hf is documented in basics/api/README.md. --model / --hf-repo / --frontend long forms all unchanged. studio_default keeps -f because it has no pass-through. Tests: - test_studio_run_parallel_flag.py: 8 new re-exec coverage cases (all 3 aliases, 3 platforms via sys.platform mock, pre-PR `-np` regression, mixed with pass-through extras). - test_studio_run_short_alias_clashes.py (new): surface checks that the removed shorts cannot reappear, plus 11 parametrized cases proving each previously-broken llama-server short flag now passes through verbatim, plus a happy-path test that documented -hf still works for `org/repo:variant` syntax. All 27 tests pass. Negative test (revert either fix) shows the new tests catch the regression. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix stale studio run docstring describing rejected llama-server flags The pre-PR docstring listed --port, -c / --ctx-size, --api-key, -ngl, --jinja, --flash-attn, --no-context-shift as "rejected with HTTP 400", but only --port and --api-key (plus other networking / auth / model identity / single-model UI flags) are actually in studio/backend/core/inference/llama_server_args.py's denylist. -c / -ngl / --jinja / --flash-attn / --no-context-shift are pass-through and last-wins-override Studio's auto-set value. Rewrite the docstring to match the real denylist groups and point at the canonical source. Also add --parallel to one of the examples now that it is a first-class flag. * ci: broaden Linux + narrow Windows llama.cpp runtime patterns + trim #5741 comments (#5746) * ci: broaden Linux llama.cpp runtime pattern to lib*.so* #5741 patched the explicit Linux pattern list to add ``libllama-*-impl.so*`` after ggml-org/llama.cpp#23462 (between b9279 and b9283) split each binary's entry code into a paired ``lib<binary>-impl.so`` shared library. Same class of upstream repackaging will hit us again whenever a new shared lib is added. Mirror what macOS already does and replace the per-lib list with a single ``lib*.so*`` glob. ``copy_globs`` (line 3614) unions patterns, so the per-variant ``libggml-cuda.so*`` / ``libggml-hip.so*`` entries were never filtering anything; the spec lives in ``runtime_payload_health_groups`` (line 5209) which keeps the explicit minimum-required list per variant. Dry-run against b9296-bin-ubuntu-x64.tar.gz: 40 files copied (all ggml, llama, mtmd, impl variants + the two binaries we ship), 22 skipped (other CLIs, rpc-server, LICENSE). Functionally equal to the post-#5741 set. * cleanup: trim #5741 comments on the pydantic split Comments added in #5741 explained the original bug in full each time. They are mostly redundant with the commit message and the PR. Trim them to one short paragraph per site. No behavior change. * ci: narrow Windows runtime pattern to llama-server.exe + llama-quantize.exe Studio only invokes llama-server and llama-quantize. Mac and Linux already filter to those two binaries; Windows was the odd one out with ``*.exe`` copying every CLI upstream ships (llama-cli, llama-bench, llama-mtmd-cli, ...). Dry-run on b9296 (win cpu-x64, cpu-arm64, cuda-13.1, hip-radeon): 20 unused EXEs skipped per variant, all DLLs (incl. the new llama-*-impl.dll family) still copied via ``*.dll``. ``existing_install_matches_choice`` already checks llama-server.exe exists explicitly (line 5297), so the health gate is unchanged. * Lower default weight_decay in RL config from 0.01 to 0.001 (#5747) In full FT, AdamW weight decay shrinks the parameter directly so the implicit prior is W -> 0. In LoRA the trained parameters are A and B while the effective weight is W = W_init + (alpha/r) * B @ A; decaying A and B separately drives BA -> 0, hence W -> W_init rather than 0. The previous default of 0.01 inherited from full-FT recipes adds a measurable pull on the merged adapter back toward the base model over a few thousand steps. 0.001 keeps a small Frobenius-norm prior on ||A||^2 + ||B||^2 for numerical stability without meaningfully biasing the merged weight toward init, and aligns with the value used across the unsloth notebook templates. * Studio: strip orphan tool_call XML leaking into visible content (#5735) * Studio: strip orphan tool_call XML from streamed visible content The speculative-buffer state machine in `studio/backend/core/inference/llama_cpp.py` can slice a tool_call XML block between the silent DRAINING path and the user-visible content_accum, depending on when in the model's emission the BUFFERING -> STREAMING -> DRAINING transitions fire. Three leak shapes were observed in a 2026-05-22 sweep of 900 Qwen3.5 / Qwen3.6 GGUF runs: Pre-fix XML leak rate: 20/900 (2.22%), concentrated 6.7% on the larger Q8 / MTP configs: Qwen3.6-35B-A3B Q8_0 4/60 (6.7%) Qwen3.6-35B-A3B-MTP Q4 4/60 (6.7%) Qwen3.5-35B-A3B Q8_0 3/60 (5.0%) Qwen3.6-27B Q8_0 3/60 (5.0%) The existing `_TOOL_XML_RE` only matched well-formed `<tool_call>...</tool_call>` and `<function=...></function>` pairs, so unterminated openings (close was DRAINED) and orphan closes (opening was DRAINED) survived the strip and reached the user. Fix relaxes the regex to also strip: 1. Orphan opening up to end-of-string: `(?:</tool_call>|\Z)` 2. Orphan closing tag: bare `</tool_call>` / `</function>` Verified on the full sweep: 20/900 -> 0/900 (100% of detected leaks eliminated). 16 unit tests in `test_tool_xml_strip.py` pin all three leak shapes plus the well-formed cases, plus parametrised checks on the 5 actual real-world leak samples from the sweep data. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio: strip tail-only </parameter> orphan + tighten regex The 2026-05-22 gdpval sweep surfaced a 4th XML-leak shape not caught by the earlier regex: a bare `</parameter>\n\n` at end-of-buffer (7 of 192 trials, all Qwen3.5-27B + a few Qwen3.6-27B). The model emits the full `<tool_call><function=...><parameter=...>...content... </parameter></function></tool_call>` envelope, the speculative buffer DRAINS the opening tags as intended, but EOS (max_tokens cutoff) truncates the outer `</function></tool_call>` close, leaving just `</parameter>` as the visible tail. We strip this ONLY when end-anchored (`\s*\Z`) so legitimate mid-text uses (user code samples, documentation discussing the Qwen tool-call XML shape) survive. Verified on the 192-trial gdpval corpus: before=7, after=0. While at it, fold the five top-level alternations into three by sharing tag-name and prefix subgroups: <tool_call>... + <function=\w+>... + --> <(?:tool_call|function=\w+)>... </tool_call> | </function> --> </(?:tool_call|function)> Semantically identical (verified by replay over the 192-trial corpus + adversarial inputs, 0 diffs) and 1.34x faster on real workloads. Backtracking-safety pinned by two new perf guards (256KB '<' spam, 1000x orphan opens). Tests: 16 -> 28 (6 new functional + 4 well-formed-vs-orphan + 2 perf guards). * Tighten comments in XML-strip regex and tests Code says what it does; comments were repeating it. Strip the verbose explanations down to the WHY-only bits (engine quirk, tail-anchor rationale, real-world source of each test sample). No code changes. inference.py: 21 -> 12 lines around _TOOL_XML_RE test_tool_xml_strip.py: 343 -> 259 lines (-84) Tests: 28/28 still pass. * [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> * Address review: deny pass-through --parallel, preserve legacy short aliases, fix test harness Round 1 review fixes for #5737: 1. Deny --parallel / --n-parallel / -np in the pass-through validator. Without this, `unsloth studio run --model X --parallel 8 -- --parallel 999` would last-win-override the running llama-server slot count while Studio's app.state.llama_parallel_slots and KV-cache fitting stay at the typer value (8), so the resource plan and the running process disagree. Also bypasses the typer 1..64 range guard. Reject so the only path is the first-class typer flag. 2. Backwards-compat shim for -m / -hfr / -f. Dropping the short aliases from typer broke any script using `unsloth studio run -m X` or `-hfr Y` or `-f dist`. Add _consume_legacy_short_aliases which pops EXACT whole-token matches (or `-x=value` inline form) from ctx.args into the corresponding typer parameter. Clustered tokens (`-fa`, `-mg`, `-fitt`, ...) are left in the pass-through tail unchanged. --model becomes Optional with an explicit missing-required check after the preprocessor so legacy `-m X` still satisfies the "must specify a model" requirement. 3. Drop mix_stderr from CliRunner. Typer 0.25.1 / Click 8.4.1 removed the kwarg; the test harness raised TypeError before exercising the PR behaviour. Tests run cleanly on current and older Typer/Click. 4. Correct the -np regression test docstring. Pre-PR `-np 8` was clustered by Click as `-p 8` (port=8) + stray `-n`, silently breaking the port binding -- not "passed through as 8 slots". The post-PR assertion (child gets --parallel 8) is unchanged. 5. Update studio run docstring listing rejected flags so it now correctly includes --parallel / -np / --n-parallel. New tests: - test_llama_server_args.py: parametrized denylist coverage for --parallel / --n-parallel / -np including equals-form, including out-of-range bypass attempts (999, 0). is_managed_flag flips True. - test_studio_run_short_alias_clashes.py: legacy -m / -hfr / -f promote to typer params; --model X + -m Y conflict errors; clustered -mg / -fa / -fitt still pass through (the original bug fix holds). 132 tests pass (98 backend + 34 cli). * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Extend legacy-alias shim tests for repo:variant, inline value form, and missing model Three additional edge cases for the -m / -hfr / -f preprocessor: - `-m unsloth/foo:UD-Q4_K_XL` round-trips through both the preprocessor and _split_repo_variant so the child sees --model + --gguf-variant. - `-m=foo` inline value form is promoted just like `-m foo`. - Missing --model after the preprocessor raises typer.Exit(2) cleanly (replacing typer's pre-PR required-flag enforcement now that --model is Optional to allow the legacy promotion path). * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Scrub .github/workflows for staging push (matches staging base) * Fix studio CLI argv handling and pass-through docstring drift - studio/backend/core/inference/llama_server_args.py: drop the stale ``-np``/``--parallel`` entry from the docstring's pass-through tunable list. These flags moved into _DENYLIST_GROUPS so the docstring now contradicts the validator and would mislead future maintainers debugging the ValueError from validate_extra_args(["--parallel","8"]). The deleted wording was introduced by dbea77e ("Studio: forward llama-server args from `unsloth studio run`, activate `unsloth run`, and allow passing model:quant to load models") when --parallel was still a documented pass-through; the same commit's "quant" reference is about the model:quant syntax, unrelated to the parallel slot wording being deleted here. - unsloth_cli/commands/studio.py: add _expand_attached_np_short next to _consume_legacy_short_aliases. Both work around Click's short-option clustering for this command -- the legacy preprocessor for `-m` / `-f` / `-hfr` and this one for the attached `-np<N>` form. Click clusters `-np8` as `-n -p 8` because `-p` is the typer short for `--port`, silently setting port=8 and dropping the parallel value; rewriting the attached form into separated `-np <N>` in sys.argv before Click parses preserves the user's value. Space/equals forms (`-np 8`, `-np=8`) already work and are left alone. - unsloth_cli/__init__.py: import _expand_attached_np_short from the studio command and run it only when argv[0] looks like the unsloth console-script or workspace cli.py, so importing this module from a notebook or pytest run does not mutate the caller's argv. * Tighten the -np canonicaliser comments Drop the helper's co-location sentence (location is self-evident from grep) and shorten the entry-gate rationale to one short sentence covering the why. * Sync .github/workflows with upstream author branch * Sync .github/workflows with upstream author branch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Bump install.sh / install.ps1 pin to unsloth>=2026.5.7 (#5753) PyPI release unsloth 2026.5.7 is now live. Bumps the pinned floor in install.sh and install.ps1 from unsloth>=2026.5.6 to unsloth>=2026.5.7 so fresh installs resolve to the new wheel. Tagged on main as v0.1.416-beta. * Catch attached `-np<N>` form in backend pass-through validator The CLI-side `_expand_attached_np_short` rewrites `-np8` to `-np 8` before Click parses, but HTTP /load `llama_extra_args=["-np8"]` goes straight to `validate_extra_args` which only matched the exact token. Reproducer: `validate_extra_args(["-np8"])` previously returned `["-np8"]` instead of raising; once forwarded to llama-server it last-win-overrode Studio's slot count while `app.state.llama_parallel_slots` stayed at the typer value. Normalise `-np<digits>` to `-np` in `_flag_name` so the denylist catches the attached form alongside `-np`, `-np=8`, `--parallel`, `--parallel=8`, and `--n-parallel`. Tests parametrize the new form including out-of-range values. * Restore _consume_legacy_short_aliases unit tests + _expand_attached_np_short tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Restore .github/workflows from origin/main Earlier merge from claude_review's staging-scrub commits accidentally deleted production CI workflows. Restore them to main's state. * Scrub .github/workflows for staging push (matches staging base) * Sync .github/workflows with upstream author branch * Round 5+6: broaden -np gate to exact basenames + runtime parallel test Reviewer-flagged improvements squashed into one commit so the auto-push review bot doesn't keep stomping the branch: - unsloth_cli/__init__.py: exact-basename match instead of endswith('cli.py'). Covers unsloth, unsloth.exe, unsloth-cli, unsloth-cli.exe, cli.py, unsloth-cli.py. A third-party mycli.py that happens to import unsloth_cli no longer has its argv mutated. - unsloth_cli/tests/test_studio_run_parallel_flag.py: parametrised runtime test (N in {1, 4, 8, 64}) that fakes the in-venv path and asserts run_server is invoked with llama_parallel_slots=N. Complements the existing source-text check so refactors that preserve runtime semantics don't trip a false failure. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Round 7: respect '--' end-of-options and reject flag-as-value Round 7 reviewer flagged three legitimate edge cases: - _expand_attached_np_short rewrote post-'--' tokens. Convention: '--' ends option processing; payload after it is raw. Stop the loop there. - _consume_legacy_short_aliases promoted post-'--' legacy aliases for the same reason. Treat post-'--' tail as raw. - Legacy '-m -fa' silently consumed '-fa' as the model name, hiding the real CLI shape error. Reject any next-token that starts with '-' (except the lone '-' stdin/path sentinel) with a clear BadParameter. Also expanded the missing-model error string to mention the still- supported legacy '-m' / '-hfr' aliases so users hitting that diagnostic on legacy scripts get the right migration hint. Added four regression tests covering each new behaviour. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Round 8: soften flag-as-value to long-form only + normalise is_managed_flag Round 8 reviewer flagged two cleanups: - _consume_legacy_short_aliases rejected any next token starting with '-' as a flag, which would break legitimate values like '-foo' (path or model name with leading dash). Narrow the rejection to '--long' tokens only; '-x' short forms still pass through. - is_managed_flag did raw _DENYLIST membership while validate_extra_args goes through _flag_name first, so '-np8' / '--parallel=8' / '--port=9000' classified as not-managed by the helper but rejected by the validator. Route is_managed_flag through _flag_name so the two helpers agree on every form callers might use. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Round 9: also catch -np-1 / -np+1 signed attached forms in denylist Round 9 reviewer noticed _flag_name normalised -np<digits> but missed signed variants -np-1 and -np+1, so validate_extra_args waved them through while rejecting --parallel -1. llama.cpp would error out on negative slot counts anyway, but the validator should classify every form of the managed flag identically so the boundary is consistent. * Round 10: signed -np in CLI canonicaliser + reject empty inline aliases Round 10 reviewer flagged two real issues: - _expand_attached_np_short rewrote only -np<digits>; signed forms -np-1 / -np+1 fell through. Backend _flag_name already classifies them as managed, so the CLI rewriter must too -- otherwise Click clusters -np-1 into -n -p -1 (port=-1) and never reaches the backend validator at all. - -m= / -hfr= / -f= empty inline forms were accepted and produced --model '' / --frontend '' (then Path('') silently became '.') on re-exec. Reject empty inline values at the preprocessor with a clear BadParameter so the malformed input fails fast. Both behaviours pinned with parametrised regression tests. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Expose --parallel on plain `unsloth studio` for API-path parity The PR added --parallel to `unsloth studio run` but the plain `unsloth studio` callback (used for API-only / bare-server launches) still hardcoded llama_parallel_slots to its run_server default. With --parallel now denied as a llama_extra_args pass-through, that flow had no first-class way to raise concurrency. - unsloth_cli/commands/studio.py: add --parallel / --n-parallel typer Option (default 4, range 1..64) to studio_default, forward through the venv re-exec, and pass llama_parallel_slots= to run_server in the in-venv path. - studio/backend/run.py: argparse --parallel / --n-parallel with the same range guard so the spawned child accepts the forwarded flag. - unsloth_cli/tests/test_studio_run_parallel_flag.py: test pins the new option presence, aliases, default and range guards. * Round 12: narrow entry-point gate, preserve pre-PR plain-studio default, drop brittle source-text test Three Opus subagent reviewers (security / backcompat / code-quality) flagged the same handful of real issues. Consensus fixes: - unsloth_cli/__init__.py: narrow the -np canonicaliser gate to just {unsloth, unsloth.exe} (the only pyproject-declared console_script). The previous cli.py / unsloth-cli.py entries would silently rewrite sys.argv for any third-party myproj/cli.py that happens to import unsloth_cli. Dev users running python cli.py ... -np N still work via the space form, which parses without the rewrite. - unsloth_cli/commands/studio.py + studio/backend/run.py: restore the pre-PR llama_parallel_slots default of 1 on plain unsloth studio and python studio/backend/run.py. unsloth studio run keeps its hardcoded-pre-PR default of 4. Without this, my earlier API-path parity commit silently dropped per-call context to ctx/4 for the plain-studio flow. - unsloth_cli/tests/test_studio_run_parallel_flag.py: drop the brittle source-text grep test (test_run_kwargs_use_parallel_value). The parametrised runtime test test_in_venv_path_passes_parallel_to_run_server already pins the same intent against actual behaviour. - unsloth_cli/tests/test_studio_run_short_alias_clashes.py: pin the narrow entry-point gate with a parametrised negative test covering seven third-party argv[0] basenames (cli.py, /path/myproj/cli.py, pytest, unsloth-cli, etc.). Re-broadening the gate now trips a test instead of silently mutating an unrelated CLI's argv. * Round 13: shared parallel constants, denylist invariant test, defence-in-depth Three Opus subagent reviewers (adversarial-user / maintenance / cross-file consistency) flagged a consistent set of cleanups; folded into one commit to avoid the pre-commit.ci force-push race. unsloth_cli/commands/studio.py: - Extract _PARALLEL_MIN / _PARALLEL_MAX / _PARALLEL_DEFAULT_RUN / _PARALLEL_DEFAULT_PLAIN module-level constants and use them in both typer Options (plain studio_default = 1, studio run = 4). - _expand_attached_np_short now rewrites -np<junk> when the suffix starts with a digit (or signed digit) so '-np8x' surfaces as a clean '-np takes an int' typer error instead of a baffling '--port invalid' complaint after Click clusters '-n -p 8x'. - Re-exec forwarding emits --load-in-4bit / --no-load-in-4bit explicitly in both directions; previously the True default relied on both layers sharing the same default forever. - run() docstring now explicitly says --parallel / -np pass-through via llama_extra_args is denied (use the typer flag above). studio/backend/run.py: - Mirror the parallel constants and route the argparse default, range check, and error message through them. Help text mentions the asymmetry with 'unsloth studio run' so direct-launch dev users aren't confused by Default 1 in isolation. studio/backend/core/inference/llama_server_args.py: - _flag_name strips surrounding whitespace before denylist lookup so a caller can't slip a managed flag past the boundary with a trailing space (the trimmed form is what downstream parsers see). Tests: - New typer-aliases-subset-of-denylist invariant: every alias the typer Option claims as --parallel on run() MUST be in the backend parallel denylist group. Catches the failure mode where someone adds a new alias and forgets the boundary. - Extended denylist parametrize to cover ~14 previously untested aliases (-mu, -dr, -hfv/-hfrv/-hffv family, -mmu, full --ui group, --models-preset / --models-autoload / --no-models-autoload). - Whitespace-padded denylist rejection (' --parallel', '-np ', etc). - --load-in-4bit re-exec test pinning both polarities + default. - -np<junk> argv rewriter regression tests. - Cross-reference headers between the two test files. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: repair mlx studio base export save_method (#5727) * Round 14: align backend -np recogniser with CLI rewriter + reject parent --parallel Round 14 (reviewer.py --parallel 20 with gpt-5.3-codex-spark) flagged two real P1s and a stale-rebase warning. All three addressed. - studio/backend/core/inference/llama_server_args.py: widen _flag_name so -np<digit-prefix> with trailing junk (-np8x, -np-1foo, -np+1bar, -np9zzz) classifies as managed flag -np, matching the CLI _expand_attached_np_short rewriter. Without this, POST /api/inference/load with llama_extra_args=['-np8x'] slipped past the boundary while the CLI canonicalised the same form. The two sides now agree on every digit-prefix form. - unsloth_cli/commands/studio.py: reject --parallel on the studio group when a subcommand is invoked. Pre-PR the studio callback had no --parallel; my Round 12 addition made 'unsloth studio --parallel 8 run ...' silently drop the 8 because typer doesn't propagate parent options into subcommand kwargs. Now errors with exit 2 and a message pointing the operator at the correct invocation ('unsloth studio run --parallel 8 ...'). - Picked up origin/main via merge (parent commit 0caf0526): the pre-flight stale-rebase detector found 2 lines on main in studio/backend/core/export/export.py missing from PR HEAD. Merged cleanly with no conflicts. Tests: - Parametrised denylist coverage for -np<digit-prefix>+junk forms. - New runtime test confirms exit 2 + helpful error when the group --parallel is supplied alongside an invoked subcommand. - Test that the default group --parallel value still lets a subcommand resolve (no false-positive rejection). * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio: tighten code comments across --parallel PR Comment-only pass over the seven PR-touched files; trim verbose docstrings, collapse multi-line section dividers, and drop redundant prose that the code already conveys. No behaviour change. * Studio: trim remaining verbose docstrings missed in last pass Shorten the test_studio_run_parallel_flag.py module docstring and the `Re-exec arg-builder coverage` block. No behaviour change. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio: second comment-tightening pass across PR-touched code Trim docstrings and inline comments in studio.py, run.py, llama_server_args.py, and unsloth_cli/__init__.py. No behaviour change; all 215 tests still pass. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio: deny --embedding / --rerank / --tools pass-through `--embedding` and `--rerank` flip llama-server into single-endpoint mode, which breaks Studio's /v1/chat/completions hop. llama-server's own `--tools` flag silently stacks on top of Studio's tool policy resolved by `--enable-tools` / `--disable-tools`. Add all three (plus the `--embeddings` / `--reranking` plural aliases) to the boundary denylist so HTTP /load and pass-through extras both reject them cleanly instead of silently desyncing the server surface. Test added to the existing `test_denylist_rejects_all_aliases` parametrize. 220 tests pass. * Studio: make PR-touched tests robust to minimal envs + Windows Two cross-OS CI findings: 1. `test_typer_parallel_aliases_are_subset_of_backend_denylist` was doing `from core.inference.llama_server_args import _DENYLIST_GROUPS` which triggers `core/inference/__init__.py` and pulls in the full backend chain (fastapi / structlog / loggers / utils.hardware). The invariant only needs the constants tuple, so load the module directly via `importlib.util.spec_from_file_location` -- the test now runs with just typer + pytest installed. 2. `test_legacy_frontend_alias_still_promotes_to_frontend` asserted the literal string `"/tmp/dist"` after the value round-trips through `Path()`. On Windows `str(Path("/tmp/dist"))` is `"\tmp\dist"`, so the assertion tripped on the same logical path. Compare via `Path(x) == Path("/tmp/dist")` so the test passes on every OS. Both surfaced by the staging-4 cross-OS CI; no production-code change. 220 tests still pass locally. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio: load llama_server_args.py directly in its unit tests Same fix as the previous CLI-test commit: import the module via `importlib.util.spec_from_file_location` instead of `from core.inference.llama_server_args import ...`, so the test no longer needs the full backend chain (fastapi / structlog / loggers / utils.hardware) installed via `core/inference/__init__.py`. The boundary validator is intentionally dependency-free; its unit tests should reflect that. * Fix test_main_composer_has_dir_auto anchor after PR #5784 PR #5784 ("Improve image generation UI") rewrote the message-input textarea's static `aria-label="Message input"` into a JSX conditional `aria-label={overlay ? "Image edit instructions" : "Message input"}` but did not update the RTL bidi-attribute regression test, leaving the literal-string `find('aria-label="Message input"')` anchor with no match. The `Repo tests (CPU)` job has been red on main since. Anchor on the inner `"Message input"` string literal instead -- it survives both spellings and still pins the same textarea element so the `dir="auto"` assertion has the right block to inspect. Verified by re-running the exact CI command: 954 passed, 3 skipped, 23 deselected (was 948 passed, 1 failed). --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Long Yixing <longyixing331@gmail.com>
turbo-tan
pushed a commit
to turbo-tan/llama.cpp-tq3
that referenced
this pull request
Jun 2, 2026
…default (ggml-org#23462) * cmake : remove STATIC from impl libraries, allow BUILD_SHARED_LIBS control Remove explicit STATIC from all -impl libraries (server, cli, completion, bench, batched-bench, fit-params, quantize, perplexity) so BUILD_SHARED_LIBS controls shared vs static linkage. Add WINDOWS_EXPORT_ALL_SYMBOLS ON for proper DLL export on Windows. Assisted-by: llama.cpp:local pi * cmake : enable LLAMA_BUILD_APP by default Assisted-by: llama.cpp:local pi * ci : disable app in build-cmake-pkg.yml
rsd-darshan
pushed a commit
to rsd-darshan/unsloth
that referenced
this pull request
Jun 3, 2026
…nslothai#5741 comments (unslothai#5746) * ci: broaden Linux llama.cpp runtime pattern to lib*.so* unslothai#5741 patched the explicit Linux pattern list to add ``libllama-*-impl.so*`` after ggml-org/llama.cpp#23462 (between b9279 and b9283) split each binary's entry code into a paired ``lib<binary>-impl.so`` shared library. Same class of upstream repackaging will hit us again whenever a new shared lib is added. Mirror what macOS already does and replace the per-lib list with a single ``lib*.so*`` glob. ``copy_globs`` (line 3614) unions patterns, so the per-variant ``libggml-cuda.so*`` / ``libggml-hip.so*`` entries were never filtering anything; the spec lives in ``runtime_payload_health_groups`` (line 5209) which keeps the explicit minimum-required list per variant. Dry-run against b9296-bin-ubuntu-x64.tar.gz: 40 files copied (all ggml, llama, mtmd, impl variants + the two binaries we ship), 22 skipped (other CLIs, rpc-server, LICENSE). Functionally equal to the post-unslothai#5741 set. * cleanup: trim unslothai#5741 comments on the pydantic split Comments added in unslothai#5741 explained the original bug in full each time. They are mostly redundant with the commit message and the PR. Trim them to one short paragraph per site. No behavior change. * ci: narrow Windows runtime pattern to llama-server.exe + llama-quantize.exe Studio only invokes llama-server and llama-quantize. Mac and Linux already filter to those two binaries; Windows was the odd one out with ``*.exe`` copying every CLI upstream ships (llama-cli, llama-bench, llama-mtmd-cli, ...). Dry-run on b9296 (win cpu-x64, cpu-arm64, cuda-13.1, hip-radeon): 20 unused EXEs skipped per variant, all DLLs (incl. the new llama-*-impl.dll family) still copied via ``*.dll``. ``existing_install_matches_choice`` already checks llama-server.exe exists explicitly (line 5297), so the health gate is unchanged.
zommiommy
pushed a commit
to zommiommy/llama.cpp
that referenced
this pull request
Aug 18, 2026
…default (ggml-org#23462) * cmake : remove STATIC from impl libraries, allow BUILD_SHARED_LIBS control Remove explicit STATIC from all -impl libraries (server, cli, completion, bench, batched-bench, fit-params, quantize, perplexity) so BUILD_SHARED_LIBS controls shared vs static linkage. Add WINDOWS_EXPORT_ALL_SYMBOLS ON for proper DLL export on Windows. Assisted-by: llama.cpp:local pi * cmake : enable LLAMA_BUILD_APP by default Assisted-by: llama.cpp:local pi * ci : disable app in build-cmake-pkg.yml
danielhanchen
added a commit
to unslothai/unsloth
that referenced
this pull request
Sep 8, 2026
… the probe asks Two of the three items from the last round, both measured against a b10360 managed install rather than a fixture. 1. The Linux impl split had no payload group. ggml-org/llama.cpp#23462 moved each binary's entry code into a lib<binary>-impl library. Windows already required llama-server-impl.dll, gated on the build the split landed in. Linux got the same treatment upstream and none of it here: the groups name the shared libraries only, so libllama-server-impl.so and libllama-quantize-impl.so were required by nothing. Measured on a copy of the managed install, removing either one: installed_runtime_health -> (True, "") _existing_install_runs -> False which is the disagreement the whole probe exists to remove, in the direction that hurts: preflight says Ready and llama-server dies in the loader. _linux_split_entrypoint_groups adds both, gated the same way the Windows side is and on the same build number, since it is one upstream commit rather than one platform's packaging. A source build ships neither whatever the tag says, and a pre-split archive is healthy without them, so requiring one of those would reinstall on every check forever. 2. The exact-release reuse decision asked exists() where the probe asks for the execute bit. installed_runtime_health asks _entrypoint_is_runnable, because that is what _find_llama_server_binary asks. existing_install_matches_choice asked exists(), and its Linux ldd gate reads a non-executable ELF quite happily. Clearing the execute bit on llama-server, again on a real copy: installed_runtime_health -> (False, "llama_runtime_binaries_missing") _existing_install_runs -> False exists() -> True preflight_linux_installed_binaries -> passes so preflight says broken, repair says the exact release is already installed, nothing is downloaded, and the next launch says broken again. Both attempts land in the same place. It now calls _entrypoint_is_runnable, which is the same test both other answers use. Tests: - test_quarantining_a_split_entrypoint_library_is_reported_broken, per library, against a managed copy, asserting the two answers agree afterwards. - test_an_older_monolithic_linux_release_is_not_asked_for_the_impl_libraries, which pins the gate rather than the requirement: b9279 owes neither, b9283 owes both, a source build owes neither. - test_a_stripped_execute_bit_is_not_reused_as_an_exact_release_match, checking the two gates directly. - The fixture tables gain both libraries, and the shape writers now chmod the entrypoints, which a real extraction does and the reuse decision now reads. tests/studio/install: 5991 passed, 6 skipped. cargo test --bin unsloth-studio: 571 passed. The third item, a versionless libllama.so surviving its SONAME's removal, does not apply: the release ships exactly two names per library, libllama.so.0 beside libllama.so.0.0.10360, with no versionless file and no symlinks. Replied on the thread.
zbrad
pushed a commit
to zbrad/llama.cpp
that referenced
this pull request
Sep 10, 2026
…default (ggml-org#23462) * cmake : remove STATIC from impl libraries, allow BUILD_SHARED_LIBS control Remove explicit STATIC from all -impl libraries (server, cli, completion, bench, batched-bench, fit-params, quantize, perplexity) so BUILD_SHARED_LIBS controls shared vs static linkage. Add WINDOWS_EXPORT_ALL_SYMBOLS ON for proper DLL export on Windows. Assisted-by: llama.cpp:local pi * cmake : enable LLAMA_BUILD_APP by default Assisted-by: llama.cpp:local pi * ci : disable app in build-cmake-pkg.yml
danielhanchen
added a commit
to unslothai/unsloth
that referenced
this pull request
Sep 22, 2026
…t model load (#10494) * Repair a quarantined llama.cpp runtime at launch instead of failing at model load Smart App Control and antivirus quarantine individual files out of the managed llama.cpp tree while leaving the marker and the rest of the tree in place. The desktop's preflight decided staleness on the managed Python alone, so it reported Ready, launched, and the missing DLL only surfaced later at model load as an error that looked like a bad GGUF or an out of memory. Repairing it meant running `unsloth studio update` by hand, which nothing told the user to do. install_llama_prebuilt gains installed_runtime_health(), which reuses the same payload tables the keep-install path already uses rather than duplicating the required file list. The CLI reports it in the desktop capability payload and the Tauri preflight treats an explicit false as stale, so the existing repair path picks it up. Only an explicit false is stale. None covers both "no runtime installed yet" and "this CLI predates the field", and neither is a broken runtime: treating them as stale would send every existing install through repair on its first launch after an upgrade. For the same reason desktop_manageability_version is not bumped. The capability cache had to move too. Its fingerprint covered the managed venv only, so a file quarantined out of the runtime left it identical and preflight answered Ready from cache without ever asking the CLI. The runtime's file count and total size now join the fingerprint, and the cache schema goes to 4. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Close the gaps the simulations found, and prove the repair cannot loop The launch probe has to be no stricter than the setup scripts' own keep-or-reinstall decision. A tree the probe rejects but _existing_install_runs keeps would be repaired, kept unchanged by the repair and rejected again on the next launch, which is a loop with no way out. That property is now stated where the code is and tested as a property over the whole matrix. Three real gaps closed: The payload groups are libraries, and on Linux and macOS they name no executable at all, so a quarantined llama-server passed as a complete install. Both entrypoints are now looked for, which is what _existing_install_runs already requires, so the check stays on the safe side of the rule above. platform_only_host() left macos_version unset while detect_host() derives it from platform.mac_ver(), which reads no hardware. The parity test would have failed on a macOS runner. A relative UNSLOTH_LLAMA_CPP_PATH resolved against the desktop's working directory, which is not the one the CLI child runs under. Fingerprinting whatever sits beside the desktop is worse than not fingerprinting, so it now degrades to no coverage. A leading ~ is expanded, since the Python reader calls expanduser. Deliberately not changed, and pinned by a test so it is not "fixed" later: a marker that exists but does not parse still reads as nothing installed. Reporting it broken looks right, but confirm_install_tree only checks that the marker file exists, so the offline keep path keeps such a tree, and broken plus kept is exactly the loop above. Tests. 1361 cases over [Windows, Linux, macOS] x [cpu, cuda, rocm, vulkan, metal] x thirteen shipped marker shapes x damage modes, asserting both the verdict and the invariant, with the invariant's sensitivity checked by mutation rather than assumed. Healthy installs of every shipped shape stay healthy on every platform, which is the backwards-compatibility half. The verdict is unchanged across NVIDIA, AMD, Intel and no GPU at all. Rust gains JSON round trips proving a pre-PR payload still parses and reads Ready, that an old cache file is ignored rather than fatal, and that a newer CLI's unknown keys are inert. The frontend no longer tells a user with a quarantined runtime that their install is too old. * Format the new install-health files with the pinned ruff scripts/run_ruff_format.py runs whatever ruff is on the path; the hook pins 0.6.9, and these four files were written under a newer one. The two disagree about assert-message wrapping and about when a def signature goes one parameter per line, which is what pre-commit.ci reported as "files were modified by this hook". * Fix the review round, and two defects the simulations found under it Seven review items were genuine and are fixed here. Two of them contradicted calls I had made earlier and the review was right both times. A marker that exists but does not parse is now graded rather than reported as nothing installed. I had argued the opposite, on the grounds that the offline keep path keeps such a tree and broken plus kept is a loop. The narrower split is the correct one: an absent marker file is a runtime nobody installed, a present one that will not parse is a real tree, and when it has lost a library as well, answering None left preflight Ready with no repair offered. The keep path already reads such a marker as an unknown backend and grades the same payload, so nothing here asks for more than it does. A relative UNSLOTH_LLAMA_CPP_PATH was falling through to the legacy tree, which is a regression I introduced in the commit that was meant to prevent exactly that. Dropping it to None is not the fix either: None matches None, so the cache keeps hitting while the real runtime rots. It is anchored to this process's working directory now, which is where relative_override_pins joins the child's copy from. On Windows the tilde resolves through USERPROFILE rather than dirs::home_dir(), because that is what the child uses and a portable profile moves the second one; process.rs carries the same note at its own read. A quarantined llama-server was already covered. Managed runtime health is now reported only when the managed runtime is the one that would actually load, since _find_llama_server_binary prefers LLAMA_SERVER_PATH and the folder chosen in settings ahead of it, and grading the managed tree regardless sent a user running their own build into a repair that offline cannot even succeed. Two further defects came out of the simulations rather than the review. A dead Linux runtime was reported healthy. A release ships libllama.so.0 beside libllama.so.0.0.10360 and the group pattern matches both, so quarantining the SONAME, which is the name the loader asks for, left the group satisfied by the twin. Measured against a real install: llama-server exits 127 with a loader error while the probe said healthy. No fixture could show it, because a fixture writes one file per library and a release writes two. Dangling symlinks in a versioned chain were passing for the same reason and are covered by the same check. The repair could not terminate. setup.sh skipped the source rebuild whenever the two entrypoints were executable, which is true of every tree that lost only a library, so offline the repair returned the tree byte for byte identical and reported success while the next launch rejected it again. It asks reusable_existing_install first now. That branch only runs once the prebuilt path has failed, where keeping a tree that cannot load is never right, and a tree with no marker is a real source build and keeps the old test. Tests: 5965 in tests/studio/install, 561 in the Tauri crate. The convergence census that pinned the loop as a fact now pins its absence, and the strict xfail holding the SONAME defect is an ordinary assertion. * Give a runtime tree with no build/bin its own fingerprint read_dir(build/bin).ok()? answered None for two different states: no llama.cpp runtime at all, and a runtime tree whose binary directory is gone. The capability verdict is not the same in both. With no marker, installed_runtime_health answers None, llama_runtime_ok stays null, and desktop_capability_stale_reason deliberately does not call that stale, so a Ready gets cached. Once a marker sits over a missing build/bin the CLI would answer llama_runtime_dir_missing, but the fingerprint had not moved, the cache still matched, and preflight served the old Ready without ever asking. The root's own entries now stand in when build/bin cannot be read, so the marker arriving moves the key. None still means the tree is not there, which is the NotInstalled case. * Catch up the fingerprint tests with the bin and nobin prefixes The previous commit gave a runtime tree with no build/bin its own fingerprint and prefixed the walk, which left five assertions on the old shape and the crate red at 557 of 562. Three are the format: bin: now leads the count. Two are the semantics the change introduced, and they are worth stating rather than patching. "No runtime at all" is the absence of the tree, so its test points at a path that does not exist instead of an empty directory, which is now the broken-install state. A root that is present but whose binary directory cannot be read is broken too, not absent: sharing None with a machine that never installed a runtime is exactly what let a cache written before an install keep matching after it broke. * Tighten the comments on the runtime health preflight Shorten every comment and docstring this branch added, keeping the reasoning that is load bearing and dropping the restatements of the code beside them. No code, only comments and whitespace. * Address review: per-library macOS payload groups, ~user paths, blank runtime override - installed_runtime_health now checks each macOS library on its own instead of three broad globs, so a bundle missing libllama-common, libggml-base or libggml-cpu is graded incomplete rather than healthy. Verified against the real macos-arm64 bundle: complete is healthy, removing any one library is not, and dropping the metal, blas and rpc backends stays healthy. - llama_runtime_override_from resolves ~user/... through getpwnam on unix rather than treating it as a relative path. - A blank UNSLOTH_LLAMA_CPP_PATH no longer counts as an override. - The CLI capability payload only reports the managed runtime when the managed runtime is the one that would actually run. Fixtures that built macOS trees from the old broad names now write the names a real bundle ships. * Address review: macOS SONAME links, the execute bit, and which runtime is graded - A dylib chain now follows the same rule as an .so: losing the middle libggml.0.dylib left the group satisfied by the terminal libggml.0.23.0.dylib while dyld could not launch, since the LC_LOAD_DYLIB entry names the link. - llama-server and llama-quantize must still carry the execute bit on POSIX. _existing_install_runs already asks that way, so an install with the bit cleared was rejected there and reported healthy here. - The CLI capability helper follows the finder's own file status rather than the directory entry, so a pinned symlink whose target was quarantined falls through to the managed tree the backend really loads instead of suppressing its verdict. - UNSLOTH_LLAMA_CPP_PATH outranks the stored custom folder in the finder and default_managed_llama_dir points at exactly that tree, so it is graded even when an older selection is still in the settings database. - ~name on Windows resolves to the sibling profile the same way process.rs and ntpath.expanduser resolve it, instead of being fingerprinted under the cwd. - A runtime verdict the CLI skipped is no longer cached, since nothing in the fingerprint watches which runtime is selected and clearing a custom selection would otherwise keep serving a Ready nobody had graded. * Ask for the execute bit by the tree's platform, not the interpreter's A Windows bundle unpacked on a POSIX filesystem carries no execute bit, and the host is a parameter here, so os.access X_OK graded every real windows-x64 bundle in the history suite as missing its binaries. Windows has no execute bit for os.access to read anyway, so presence is the whole question there. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address review: the fingerprint's blind spots, and which tree it watches - The runtime fingerprint counted regular files only, so deleting an install-name link (libggml.0.dylib, or the SONAME link on Linux) or taking the execute bit off llama-server left it identical. Both are states installed_runtime_health rejects, so a cached Ready went on matching a tree that could no longer launch. Links and executable files are counted now. - An installer-managed venv under a custom root names its own runtime. The desktop strips UNSLOTH_STUDIO_HOME before spawning the CLI, but the CLI re-infers the root from sys.prefix and points UNSLOTH_LLAMA_CPP_PATH at its llama.cpp, so reading only the ambient environment fingerprinted the legacy tree and quarantine in the real one never invalidated anything. - An override holding no server does not end the search, so it is no longer treated as final on either side. The CLI names the tree it grades rather than answering yes or no, so the two cannot pick different trees. - ~name resolves through getpwnam_r. getpwnam hands back process-global storage and this runs on a Tauri worker thread, where a concurrent lookup could overwrite pw_dir mid-copy. * Address review: entrypoints that are files, the mode bits, and one override spelling installed_runtime_health asked os.access(X_OK), which a directory named llama-server answers true for because directories are searchable, while the finder asks is_file() and rejects the tree. Both now go through one helper that _existing_install_runs shares, so the keep-or-reinstall decision and the launch verdict cannot disagree. The runtime fingerprint counted files with any execute bit, but os.access(X_OK) asks whether this user may run one: 0755 to 0655 leaves group and other executable and the owner unable to run it, so the count never moved for a state health rejects. The permission bits themselves are summed instead. _find_llama_server_binary read UNSLOTH_LLAMA_CPP_PATH literally while default_managed_llama_dir, get_stored_custom_llama_cpp_path and the desktop's pinning all expand it, so a ~/llama.cpp written into a service unit or a .env sent discovery to a folder named ~ beside the working directory and every other component reported on a different tree. It expands now, and so does the orphan reaper's allowlist, which has to name the tree discovery spawns from. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address review: the split entrypoint libraries, and reuse asking what the probe asks Two of the three items from the last round, both measured against a b10360 managed install rather than a fixture. 1. The Linux impl split had no payload group. ggml-org/llama.cpp#23462 moved each binary's entry code into a lib<binary>-impl library. Windows already required llama-server-impl.dll, gated on the build the split landed in. Linux got the same treatment upstream and none of it here: the groups name the shared libraries only, so libllama-server-impl.so and libllama-quantize-impl.so were required by nothing. Measured on a copy of the managed install, removing either one: installed_runtime_health -> (True, "") _existing_install_runs -> False which is the disagreement the whole probe exists to remove, in the direction that hurts: preflight says Ready and llama-server dies in the loader. _linux_split_entrypoint_groups adds both, gated the same way the Windows side is and on the same build number, since it is one upstream commit rather than one platform's packaging. A source build ships neither whatever the tag says, and a pre-split archive is healthy without them, so requiring one of those would reinstall on every check forever. 2. The exact-release reuse decision asked exists() where the probe asks for the execute bit. installed_runtime_health asks _entrypoint_is_runnable, because that is what _find_llama_server_binary asks. existing_install_matches_choice asked exists(), and its Linux ldd gate reads a non-executable ELF quite happily. Clearing the execute bit on llama-server, again on a real copy: installed_runtime_health -> (False, "llama_runtime_binaries_missing") _existing_install_runs -> False exists() -> True preflight_linux_installed_binaries -> passes so preflight says broken, repair says the exact release is already installed, nothing is downloaded, and the next launch says broken again. Both attempts land in the same place. It now calls _entrypoint_is_runnable, which is the same test both other answers use. Tests: - test_quarantining_a_split_entrypoint_library_is_reported_broken, per library, against a managed copy, asserting the two answers agree afterwards. - test_an_older_monolithic_linux_release_is_not_asked_for_the_impl_libraries, which pins the gate rather than the requirement: b9279 owes neither, b9283 owes both, a source build owes neither. - test_a_stripped_execute_bit_is_not_reused_as_an_exact_release_match, checking the two gates directly. - The fixture tables gain both libraries, and the shape writers now chmod the entrypoints, which a real extraction does and the reuse decision now reads. tests/studio/install: 5991 passed, 6 skipped. cargo test --bin unsloth-studio: 571 passed. The third item, a versionless libllama.so surviving its SONAME's removal, does not apply: the release ships exactly two names per library, libllama.so.0 beside libllama.so.0.0.10360, with no versionless file and no symlinks. Replied on the thread. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address review: resolve a named-user override the same way on both sides of a spawn preflight::managed resolves UNSLOTH_LLAMA_CPP_PATH=~alice/llama.cpp through getpwnam_r, the way posixpath.expanduser does. process.rs, pinning the same variable for the child it is about to move, read it with the Windows rule, which leaves ~alice alone off Windows, so it fell through to being anchored under the directory being left. Measured, with the two halves side by side on this host: preflight::managed ~root/llama.cpp -> /root/llama.cpp process.rs pin ~root/llama.cpp -> /mnt/work/session/~root/llama.cpp The child is handed the pinned value, so it grades and runs one tree while the cache fingerprints another. Quarantining a file from the tree actually in use then never invalidates a Ready verdict, which is the whole failure this fingerprint exists to catch. expand_user now makes the split every other reader in that function already makes, Windows rules on Windows and the native ones off it, and expand_posix_user resolves the named form through the same lookup preflight::managed uses, which is now pub(crate) rather than duplicated. An unknown name is left exactly as it arrived, which is what expanduser does, so both halves fall through to anchoring it and still agree. Bare ~ and ~/rest are unchanged: they went through expand_windows_user's empty-name arm, which answers home on any platform, and they go through the posix one now. Windows was already aligned. named_user_home's Windows arm delegates to named_windows_user_home, which is the same rule expand_windows_user applies. Tests, all three unix-only since they turn on the password database: - a_named_user_override_is_pinned_where_the_fingerprint_watches, asserting the pin is not under the directory being left and equals what the fingerprint side reaches through the same lookup. - an_unknown_named_user_is_left_alone_and_anchored_by_both_halves. - a_bare_tilde_override_still_reaches_home_off_windows, for the form that already worked. cargo test --bin unsloth-studio: 574 passed. tests/studio/install: 5991 passed, 6 skipped. * Address review: the SONAME beside a flattened versionless copy, and an override that cannot expand Two items, and the first one corrects a rebuttal of mine that was wrong. 1. A versionless copy kept the group satisfied after the SONAME was quarantined. I argued against this last round on the grounds that the bundle ships exactly two names per library. That was true of the tree I measured, the b10360 install on this machine, and it is not true of what the installer produces today. Two things moved underneath it: - b10840 ships the trio. libllama.so -> libllama.so.0 -> libllama.so.0.4.0, as symlinks in the tarball. - copy_globs flattens them. It selects with is_file() and copies with shutil.copy2, which follows symlinks, so all three arrive in build/bin as independent regular files. My reproduction moved the extracted tree instead, which kept the links, and a dangling link is dropped by is_file() on its own. That made the argument look decisive about a shape no install has. Extracting b10840 with prebuilt_core.extract_archive and installing it through copy_globs, which is the path install_prebuilt takes: baseline health=(True, "") quarantine libllama.so.0 health=(True, "") runs=False quarantine libggml.so.0 health=(True, "") runs=False quarantine libllama-common.so.0 health=(True, "") runs=False quarantine libggml-base.so.0 health=(True, "") runs=False quarantine libmtmd.so.0 health=(True, "") runs=False _payload_match_is_loadable can no longer answer from the name alone, because libllama.so means one thing in a family that has a SONAME and another in a family that never had one. It now reads the family: a versionless name counts only when nothing versioned sits beside it. Any versioned sibling disqualifies it, not only a SONAME-shaped one, since asking for a SONAME specifically would read the family as versionless again exactly when the SONAME is the file that went missing. Same rule on macOS, where dyld asks for the install name in LC_ID_DYLIB: libggml.dylib does not stand in for libggml.0.dylib. 2. An unresolvable named user made runtime discovery throw. Path.expanduser() raises RuntimeError when it cannot determine the home, which includes every unknown named user, unlike os.path.expanduser, which hands the value back: Path("~no-such-account-anywhere/llama.cpp").expanduser() RuntimeError: Could not determine home directory. Every reader of UNSLOTH_LLAMA_CPP_PATH used the raising form, so a pin left in a service unit or a .env after an account rename took discovery down with it rather than being treated as unusable, and several callers have no exception boundary. Four sites, not the one reported: the search, the process allowlist that has to name the same tree, the settings reader and the status payload the UI renders. expanded_user_path is the non-raising expansion, shared by all four so they cannot diverge, and it leaves an unresolvable name exactly as it arrived, which is what the desktop's pinning does with one too. Tests: - test_the_flattened_trio_installs_healthy and test_quarantining_a_soname_beside_a_versionless_copy_is_reported_broken, per library, built from the b10840 asset through copy_globs, asserting the three names really are flattened regular files first, so the test cannot pass for the dangling-link reason instead. Skipped where the asset is absent, as the rest of that file is. - test_a_family_that_only_ever_ships_one_name_is_still_loadable and the macOS counterpart, both artifact-free, for the half a blunter rule would break: libggml-cpu-x64.so has no versioned copy anywhere and is the name the loader asks for. - The expanduser guard, including the status payload and discovery reaching its own answer rather than raising. - test_a_dangling_library_symlink_does_not_count_as_present now builds all three rungs, which is what its own docstring already described; it had been building the outer two, and the SONAME rung is what the new rule needs to see. tests/studio/install: 5999 passed, 6 skipped. studio/backend/tests -k "llama_cpp or path_settings": 1928 passed, 4 skipped. cargo test --bin unsloth-studio: 574 passed. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address review: the HIP module by name, both impl halves, and a marker that will not parse Six items, each measured against a real bundle built through copy_globs rather than reasoned about. ggml-hip.dll by name, not "*hip*.dll". app-b10798-mix-659e406-windows-x64-rocm-gfx1150 ships amdhip64_7.dll, hipblas.dll and libhipblaslt.dll beside it, and all four match that pattern, so quarantining the one module ggml actually loads left the group satisfied by three libraries that cannot stand in for it. Measured: installed_runtime_health answered (True, "") with the backend gone. llama-quantize-impl.dll under the same split-build gate as the server's. llama-quantize.exe links against its own impl library exactly as llama-server.exe does, and every post-b9283 bundle ships both, so requiring one let a quarantined quantize implementation read as healthy while quantization could not start. A marker that exists but will not parse names no source, and the source-gated groups were dropped with it: on Windows llama.dll stood in for the whole payload, so an interrupted marker plus a quarantined ggml-base.dll answered healthy. Assuming "published" instead would be worse, because a statically linked source build ships none of those names and would fail health on a tree the setup scripts keep, repairing on every launch with nothing to change. _tree_looks_prebuilt reads the source off the tree from a name only a release ships, and stays lenient when quarantine has taken the evidence too. The runtime fingerprint now includes entry names. Its four counters are all aggregates, and a rename in place moves none of them, so security software that renames ggml-base.dll beside itself left the cached Ready matching a tree installed_runtime_health rejects, and preflight answered Ready without running the capability probe. FNV-1a rather than DefaultHasher, so a toolchain bump does not invalidate every cached verdict; summed rather than folded, because read_dir order is unspecified and the counters beside it are order-independent for that reason. The CLI classifies UNSLOTH_LLAMA_CPP_PATH instead of trusting the marker. studio/backend/main.py calls mark_managed_llama_cpp_path before discovery, which marks any override equal to the managed tree however it got there, and the finder then skips it; _ensure_studio_env_exported writes exactly that value under a custom STUDIO_HOME and sets no marker. So preflight graded the managed tree as a user pin while the backend walked past it to the stored selection, blocking launch over a tree nothing would open. Compared against the studio home's own path with the override out of the way, never default_managed_llama_dir(), which reads the override first and would call every user pin managed. expanded_user_path at the last Path.expanduser left in the doctor, a gap in the earlier pass: a "~name" naming no account raises RuntimeError out of a command whose whole job is to answer. Also: the stored-selection stub in test_desktop_capability_llama_runtime was missing expanded_user_path, so the helper's import raised and every test in that file was silently grading the except fallback rather than the code it names. tests/studio: 10171 passed, 198 skipped. cargo test --bin unsloth-studio: 575 passed. The four new payload tests and the CLI one all fail on the previous head. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Gate the PowerShell reuse shortcut on runtime health, as the shell already is The POSIX half of this loop was closed and the Windows half was not. setup.ps1 reaches "already built" through an elseif on Test-PathQuiet $LlamaServerBin, so a Windows repair that fell through to the source stage kept a tree whose ggml-base.dll had been quarantined, returned it byte for byte identical, and reported success. Desktop preflight grades that same tree on every launch, so it offered the same repair forever. Test-LlamaTreeStillHealthy makes the same --check-existing-install call setup.sh makes, through Invoke-LlamaHelper, so there is one definition of healthy rather than two that can disagree. It answers healthy on any failure to ask: a helper that cannot run must not turn into a multi-gigabyte source build on a machine whose only fault was an unreadable tree. The reason this survived is in the convergence model's own comment, which said setup.ps1 had no reuse step and that a Windows update reaching the source stage always builds. That was wrong, and the model therefore never exercised the Windows branch. It now models both shells, and reverting the gate in the model fails every Windows cell of test_no_damaged_tree_survives_the_shell_rebuild_skip, which is what it should have done all along. A model cannot catch a shell losing its gate, so test_both_shells_gate_their_reuse_shortcut_on_the_same_check reads both scripts and asserts the check sits on the shortcut itself rather than somewhere else in the file. tests/studio/install: 6007 passed, 6 skipped. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Reject a library renamed in place instead of letting it satisfy its own group * Grade the root entrypoint, and plan the Windows rebuild the health gate forces * Grade the studio root the desktop scrubbed, and fix the Windows fixture * Reject empty payload files, and stop grading a runtime no repair can reach * Give the entrypoint check one owner, and cache only what the probe read * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Close the repair loop between the launch check and the update Found by simulating the cycle a user walks rather than by testing the new check on its own, and reproduced before being fixed. installed_runtime_health rejects a root llama-server or llama-quantize that lost its execute bit, so the desktop marks the install stale and offers a repair. existing_install_current_without_plan is the shortcut install_prebuilt returns on before reinstalling anything, and it checked os.access(X_OK) under build/bin ONLY, while _find_llama_server_binary reaches the install root's copy FIRST. So the repair ran, changed nothing, and the next launch was stale again. Measured, with an undamaged install accepted as the control: root llama-server -x launch health broken, shortcut True, full False root llama-quantize -x launch health broken, shortcut True, full False build/bin copy -x launch health broken, shortcut False, full False _damaged_entrypoint now answers for the shortcut too, which is what its own docstring already asks for: one owner, so a tree one check rejects and another keeps cannot be repaired by changing nothing. It is four stat plus os.access calls and spawns nothing, so the shortcut stays a shortcut. test_the_execute_bit_is_checked_on_the_runtime_copies_only pinned the old boundary, so it is rewritten as test_the_execute_bit_is_checked_on_every_entrypoint, keeping the visual server as the control that stops the fix becoming "reject everything". tests/studio/install plus test_llama_cpp_path_settings.py: 8787 passed, 65 skipped. * Require the split entrypoint dylibs on macOS The impl split that gave Windows llama-server-impl.dll and Linux libllama-server-impl.so gives macOS libllama-server-impl.dylib and libllama-quantize-impl.dylib, and the macOS branch of runtime_payload_health_groups was the one that did not ask for them. Quarantining either left every library group satisfied and both execute-bit checks passing while dyld could not start the entrypoint, so installed_runtime_health answered Ready on the platform this probe exists for. Verified against the real bundles rather than assumed: both llama-b10840-mix-d5c17a0 and llama-b11007-mix-3e83366 macos-arm64 ship the two, unversioned, beside the entrypoints. _PREBUILT_TREE_EVIDENCE already reads libllama-server-impl*.dylib as proof of a prebuilt macOS tree. _macos_split_entrypoint_groups delegates the gate to its Linux counterpart rather than repeating it, so the source labels and the b9283 floor cannot drift. The three fixtures that claimed to list a real macOS bundle were short of these two files and now carry them, and the matrix states the requirement independently with the matching pre-split pruning. New test with the loop-direction control: a pre-split upstream b6099 bundle never shipped them and stays healthy. * Shorten the macOS split docstring Comment-only; the AST gate reports install_llama_prebuilt.py code-unchanged. * Grade the macOS fixture against a real bundle, and stop a test carrying one machine's path Merging main up brought in test_macos_loader_probe_record.py from #11237, whose install fixture writes three dylibs. This branch replaced main's three loose macOS globs with one group per library, so the fixture is now thinner than any install the code grades and 18 of its 26 tests failed on the merge. The tightening is the right side of that: on the shipped llama-b11030-mix-5ff778e-bin-macos-arm64 bundle, quarantining libggml-base leaves main answering healthy, because libggml-blas, libggml-metal and libggml-rpc still satisfy libggml*.dylib, and answers llama_runtime_payload_incomplete here. The fixture now writes the libraries that bundle actually carries, including the two impl dylibs the unparseable release tag requires under the same gate Windows has always used. test_llama_runtime_install_history.py defaulted its asset directory to one machine's workspace path. The default is now under the user's own cache, and the presence check no longer lets a PermissionError through: the file promises to skip when an artifact is not available, and an unreadable directory failed 24 tests instead. tests/studio/install: 8796 passed, 76 skipped. * Name the folder the antivirus advice is asking the user to allow The message told the user to allow "the folder" without saying which one, and finding it is the whole work of acting on that sentence. It now names the default root, spelled the way the platform spells it, off the same navigator test the roaming-profile hint beside it already uses. Hedged with "usually" rather than asserted: UNSLOTH_LLAMA_CPP_PATH can move the root and preflight does not carry the path, so a definite statement would be wrong for anyone who set it. No navigator at all keeps the POSIX spelling rather than dropping the advice. Three tests: every reason names a folder, Windows gets the Windows spelling and no POSIX one (a path a user cannot paste into an exclusion list is not advice), and a missing navigator still answers. 15 of 15 in the file. * Grade a root entrypoint only when discovery would start it The root check was stricter than the resolver it was modelling. It rejected any root copy that existed and was not runnable, but resolve_llama_server_binary returns the first USABLE candidate, and _usable_binary is is_file() plus os.access(X_OK) off Windows. A root wrapper whose execute bit is gone is walked past, build/bin runs, and the tree is fine; grading it damaged sent a working runtime through a repair, and where repair is unavailable it blocked the launch. Reproduced against the real resolver on a Linux tree before changing anything: root wrapper executable backend picks <root>/llama-server health (True, '') root wrapper chmod 0644 backend picks <root>/build/bin/llama-server health (False, binaries_missing) root wrapper executable but empty backend picks <root>/llama-server health (False, binaries_missing) The middle row is the false positive. The third is the one that matters and is kept: an empty wrapper keeps is_file() and its execute bit, so discovery does select it and the exec dies on ENOEXEC. _discovery_would_select mirrors _usable_binary, so the root copy is graded only when the resolver would stop there. Windows keeps the current behaviour, since _usable_binary does not consult the execute bit there and an empty .exe is still what gets selected. test_a_rotten_root_entrypoint_is_not_saved_by_a_healthy_build_bin asserted the old behaviour on the strength of "the finder reaches the root first", which is true but incomplete: it reaches it and skips it. Replaced with the empty-wrapper case, plus a test that a 0644 wrapper and a directory of that name are both walked past, the Windows empty-exe case, and a parity test against the backend's own _usable_binary so the two cannot drift. tests/studio/install/test_installed_runtime_health.py: 53 passed. * Scope the root-entrypoint relaxation to the launch verdict The first cut changed _damaged_entrypoint for all three callers, which also relaxed the installer's keep decision and the release-reuse check. Those ask a different question: whether this tree can be reused as it stands, and a rotten root wrapper is worth replacing, which is what a reinstall is for. selected_root_only is the launch probe's rule alone. The launch verdict follows the resolver, so it walks past a wrapper with no execute bit and does not mark a working runtime stale; the keep decision stays as it was. The direction is the safe one: the launch verdict is never stricter than the repair that answers it, which is the property that stops a tree being repaired by changing nothing and rejected again on the next launch. test_a_damaged_root_entrypoint_stops_the_release_being_reused keeps its point, that the keep decision must reject where the shortcut once kept, and gains the empty-wrapper row, which is the damage the resolver does select and run. tests/studio/install: 8970 passed, 76 skipped, 1 failed, and that one (test_an_unserved_preferred_arch_source_builds_rather_than_using_the_igpus_bundle) fails identically on a clean origin/main worktree. * Grade the runtime a master root install actually uses Under UNSLOTH_HOME the runtimes sit beside studio/, and the CLI exports <master>/llama.cpp while UNSLOTH_STUDIO_HOME stays <master>/studio. The comparison path dropped the override and called default_managed_llama_dir(), which reads only the studio home, so it answered <master>/studio/llama.cpp: the installer's own export did not match, the tree in use graded as a user pin, and master root installs never reached the health check. The rule now lives in _master_root_llama_dir() and both the export and the grading read it, so there is one answer rather than two that can drift. * Fingerprint the runtime a master root install actually loads The Rust half of the same split: inferred_studio_llama_root() answered <master>/studio/llama.cpp for an install recorded under UNSLOTH_HOME, while the CLI grades and the backend loads <master>/llama.cpp. One healthy cached result then survived quarantine in the runtime in use, so preflight kept returning Ready and the failure only appeared at model load, with no repair offered. It now reads share/.unsloth-master-root under the same containment rule the CLI applies (the recorded root must exist and contain this Studio tree) and fingerprints the sibling runtime. Tests cover the note, a note naming somebody else's root, an empty one and one naming a directory that is not there. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: danielhanchen <unslothshared@gmail.com> Co-authored-by: oobabooga <112222186+oobabooga@users.noreply.github.com> Co-authored-by: danielhanchen <unslothai@gmail.com> Co-authored-by: danielhanchen <moonshotaisubstack@gmail.com>
shaojun
pushed a commit
to shaojun/unsloth
that referenced
this pull request
Sep 29, 2026
…t model load (unslothai#10494) * Repair a quarantined llama.cpp runtime at launch instead of failing at model load Smart App Control and antivirus quarantine individual files out of the managed llama.cpp tree while leaving the marker and the rest of the tree in place. The desktop's preflight decided staleness on the managed Python alone, so it reported Ready, launched, and the missing DLL only surfaced later at model load as an error that looked like a bad GGUF or an out of memory. Repairing it meant running `unsloth studio update` by hand, which nothing told the user to do. install_llama_prebuilt gains installed_runtime_health(), which reuses the same payload tables the keep-install path already uses rather than duplicating the required file list. The CLI reports it in the desktop capability payload and the Tauri preflight treats an explicit false as stale, so the existing repair path picks it up. Only an explicit false is stale. None covers both "no runtime installed yet" and "this CLI predates the field", and neither is a broken runtime: treating them as stale would send every existing install through repair on its first launch after an upgrade. For the same reason desktop_manageability_version is not bumped. The capability cache had to move too. Its fingerprint covered the managed venv only, so a file quarantined out of the runtime left it identical and preflight answered Ready from cache without ever asking the CLI. The runtime's file count and total size now join the fingerprint, and the cache schema goes to 4. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Close the gaps the simulations found, and prove the repair cannot loop The launch probe has to be no stricter than the setup scripts' own keep-or-reinstall decision. A tree the probe rejects but _existing_install_runs keeps would be repaired, kept unchanged by the repair and rejected again on the next launch, which is a loop with no way out. That property is now stated where the code is and tested as a property over the whole matrix. Three real gaps closed: The payload groups are libraries, and on Linux and macOS they name no executable at all, so a quarantined llama-server passed as a complete install. Both entrypoints are now looked for, which is what _existing_install_runs already requires, so the check stays on the safe side of the rule above. platform_only_host() left macos_version unset while detect_host() derives it from platform.mac_ver(), which reads no hardware. The parity test would have failed on a macOS runner. A relative UNSLOTH_LLAMA_CPP_PATH resolved against the desktop's working directory, which is not the one the CLI child runs under. Fingerprinting whatever sits beside the desktop is worse than not fingerprinting, so it now degrades to no coverage. A leading ~ is expanded, since the Python reader calls expanduser. Deliberately not changed, and pinned by a test so it is not "fixed" later: a marker that exists but does not parse still reads as nothing installed. Reporting it broken looks right, but confirm_install_tree only checks that the marker file exists, so the offline keep path keeps such a tree, and broken plus kept is exactly the loop above. Tests. 1361 cases over [Windows, Linux, macOS] x [cpu, cuda, rocm, vulkan, metal] x thirteen shipped marker shapes x damage modes, asserting both the verdict and the invariant, with the invariant's sensitivity checked by mutation rather than assumed. Healthy installs of every shipped shape stay healthy on every platform, which is the backwards-compatibility half. The verdict is unchanged across NVIDIA, AMD, Intel and no GPU at all. Rust gains JSON round trips proving a pre-PR payload still parses and reads Ready, that an old cache file is ignored rather than fatal, and that a newer CLI's unknown keys are inert. The frontend no longer tells a user with a quarantined runtime that their install is too old. * Format the new install-health files with the pinned ruff scripts/run_ruff_format.py runs whatever ruff is on the path; the hook pins 0.6.9, and these four files were written under a newer one. The two disagree about assert-message wrapping and about when a def signature goes one parameter per line, which is what pre-commit.ci reported as "files were modified by this hook". * Fix the review round, and two defects the simulations found under it Seven review items were genuine and are fixed here. Two of them contradicted calls I had made earlier and the review was right both times. A marker that exists but does not parse is now graded rather than reported as nothing installed. I had argued the opposite, on the grounds that the offline keep path keeps such a tree and broken plus kept is a loop. The narrower split is the correct one: an absent marker file is a runtime nobody installed, a present one that will not parse is a real tree, and when it has lost a library as well, answering None left preflight Ready with no repair offered. The keep path already reads such a marker as an unknown backend and grades the same payload, so nothing here asks for more than it does. A relative UNSLOTH_LLAMA_CPP_PATH was falling through to the legacy tree, which is a regression I introduced in the commit that was meant to prevent exactly that. Dropping it to None is not the fix either: None matches None, so the cache keeps hitting while the real runtime rots. It is anchored to this process's working directory now, which is where relative_override_pins joins the child's copy from. On Windows the tilde resolves through USERPROFILE rather than dirs::home_dir(), because that is what the child uses and a portable profile moves the second one; process.rs carries the same note at its own read. A quarantined llama-server was already covered. Managed runtime health is now reported only when the managed runtime is the one that would actually load, since _find_llama_server_binary prefers LLAMA_SERVER_PATH and the folder chosen in settings ahead of it, and grading the managed tree regardless sent a user running their own build into a repair that offline cannot even succeed. Two further defects came out of the simulations rather than the review. A dead Linux runtime was reported healthy. A release ships libllama.so.0 beside libllama.so.0.0.10360 and the group pattern matches both, so quarantining the SONAME, which is the name the loader asks for, left the group satisfied by the twin. Measured against a real install: llama-server exits 127 with a loader error while the probe said healthy. No fixture could show it, because a fixture writes one file per library and a release writes two. Dangling symlinks in a versioned chain were passing for the same reason and are covered by the same check. The repair could not terminate. setup.sh skipped the source rebuild whenever the two entrypoints were executable, which is true of every tree that lost only a library, so offline the repair returned the tree byte for byte identical and reported success while the next launch rejected it again. It asks reusable_existing_install first now. That branch only runs once the prebuilt path has failed, where keeping a tree that cannot load is never right, and a tree with no marker is a real source build and keeps the old test. Tests: 5965 in tests/studio/install, 561 in the Tauri crate. The convergence census that pinned the loop as a fact now pins its absence, and the strict xfail holding the SONAME defect is an ordinary assertion. * Give a runtime tree with no build/bin its own fingerprint read_dir(build/bin).ok()? answered None for two different states: no llama.cpp runtime at all, and a runtime tree whose binary directory is gone. The capability verdict is not the same in both. With no marker, installed_runtime_health answers None, llama_runtime_ok stays null, and desktop_capability_stale_reason deliberately does not call that stale, so a Ready gets cached. Once a marker sits over a missing build/bin the CLI would answer llama_runtime_dir_missing, but the fingerprint had not moved, the cache still matched, and preflight served the old Ready without ever asking. The root's own entries now stand in when build/bin cannot be read, so the marker arriving moves the key. None still means the tree is not there, which is the NotInstalled case. * Catch up the fingerprint tests with the bin and nobin prefixes The previous commit gave a runtime tree with no build/bin its own fingerprint and prefixed the walk, which left five assertions on the old shape and the crate red at 557 of 562. Three are the format: bin: now leads the count. Two are the semantics the change introduced, and they are worth stating rather than patching. "No runtime at all" is the absence of the tree, so its test points at a path that does not exist instead of an empty directory, which is now the broken-install state. A root that is present but whose binary directory cannot be read is broken too, not absent: sharing None with a machine that never installed a runtime is exactly what let a cache written before an install keep matching after it broke. * Tighten the comments on the runtime health preflight Shorten every comment and docstring this branch added, keeping the reasoning that is load bearing and dropping the restatements of the code beside them. No code, only comments and whitespace. * Address review: per-library macOS payload groups, ~user paths, blank runtime override - installed_runtime_health now checks each macOS library on its own instead of three broad globs, so a bundle missing libllama-common, libggml-base or libggml-cpu is graded incomplete rather than healthy. Verified against the real macos-arm64 bundle: complete is healthy, removing any one library is not, and dropping the metal, blas and rpc backends stays healthy. - llama_runtime_override_from resolves ~user/... through getpwnam on unix rather than treating it as a relative path. - A blank UNSLOTH_LLAMA_CPP_PATH no longer counts as an override. - The CLI capability payload only reports the managed runtime when the managed runtime is the one that would actually run. Fixtures that built macOS trees from the old broad names now write the names a real bundle ships. * Address review: macOS SONAME links, the execute bit, and which runtime is graded - A dylib chain now follows the same rule as an .so: losing the middle libggml.0.dylib left the group satisfied by the terminal libggml.0.23.0.dylib while dyld could not launch, since the LC_LOAD_DYLIB entry names the link. - llama-server and llama-quantize must still carry the execute bit on POSIX. _existing_install_runs already asks that way, so an install with the bit cleared was rejected there and reported healthy here. - The CLI capability helper follows the finder's own file status rather than the directory entry, so a pinned symlink whose target was quarantined falls through to the managed tree the backend really loads instead of suppressing its verdict. - UNSLOTH_LLAMA_CPP_PATH outranks the stored custom folder in the finder and default_managed_llama_dir points at exactly that tree, so it is graded even when an older selection is still in the settings database. - ~name on Windows resolves to the sibling profile the same way process.rs and ntpath.expanduser resolve it, instead of being fingerprinted under the cwd. - A runtime verdict the CLI skipped is no longer cached, since nothing in the fingerprint watches which runtime is selected and clearing a custom selection would otherwise keep serving a Ready nobody had graded. * Ask for the execute bit by the tree's platform, not the interpreter's A Windows bundle unpacked on a POSIX filesystem carries no execute bit, and the host is a parameter here, so os.access X_OK graded every real windows-x64 bundle in the history suite as missing its binaries. Windows has no execute bit for os.access to read anyway, so presence is the whole question there. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address review: the fingerprint's blind spots, and which tree it watches - The runtime fingerprint counted regular files only, so deleting an install-name link (libggml.0.dylib, or the SONAME link on Linux) or taking the execute bit off llama-server left it identical. Both are states installed_runtime_health rejects, so a cached Ready went on matching a tree that could no longer launch. Links and executable files are counted now. - An installer-managed venv under a custom root names its own runtime. The desktop strips UNSLOTH_STUDIO_HOME before spawning the CLI, but the CLI re-infers the root from sys.prefix and points UNSLOTH_LLAMA_CPP_PATH at its llama.cpp, so reading only the ambient environment fingerprinted the legacy tree and quarantine in the real one never invalidated anything. - An override holding no server does not end the search, so it is no longer treated as final on either side. The CLI names the tree it grades rather than answering yes or no, so the two cannot pick different trees. - ~name resolves through getpwnam_r. getpwnam hands back process-global storage and this runs on a Tauri worker thread, where a concurrent lookup could overwrite pw_dir mid-copy. * Address review: entrypoints that are files, the mode bits, and one override spelling installed_runtime_health asked os.access(X_OK), which a directory named llama-server answers true for because directories are searchable, while the finder asks is_file() and rejects the tree. Both now go through one helper that _existing_install_runs shares, so the keep-or-reinstall decision and the launch verdict cannot disagree. The runtime fingerprint counted files with any execute bit, but os.access(X_OK) asks whether this user may run one: 0755 to 0655 leaves group and other executable and the owner unable to run it, so the count never moved for a state health rejects. The permission bits themselves are summed instead. _find_llama_server_binary read UNSLOTH_LLAMA_CPP_PATH literally while default_managed_llama_dir, get_stored_custom_llama_cpp_path and the desktop's pinning all expand it, so a ~/llama.cpp written into a service unit or a .env sent discovery to a folder named ~ beside the working directory and every other component reported on a different tree. It expands now, and so does the orphan reaper's allowlist, which has to name the tree discovery spawns from. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address review: the split entrypoint libraries, and reuse asking what the probe asks Two of the three items from the last round, both measured against a b10360 managed install rather than a fixture. 1. The Linux impl split had no payload group. ggml-org/llama.cpp#23462 moved each binary's entry code into a lib<binary>-impl library. Windows already required llama-server-impl.dll, gated on the build the split landed in. Linux got the same treatment upstream and none of it here: the groups name the shared libraries only, so libllama-server-impl.so and libllama-quantize-impl.so were required by nothing. Measured on a copy of the managed install, removing either one: installed_runtime_health -> (True, "") _existing_install_runs -> False which is the disagreement the whole probe exists to remove, in the direction that hurts: preflight says Ready and llama-server dies in the loader. _linux_split_entrypoint_groups adds both, gated the same way the Windows side is and on the same build number, since it is one upstream commit rather than one platform's packaging. A source build ships neither whatever the tag says, and a pre-split archive is healthy without them, so requiring one of those would reinstall on every check forever. 2. The exact-release reuse decision asked exists() where the probe asks for the execute bit. installed_runtime_health asks _entrypoint_is_runnable, because that is what _find_llama_server_binary asks. existing_install_matches_choice asked exists(), and its Linux ldd gate reads a non-executable ELF quite happily. Clearing the execute bit on llama-server, again on a real copy: installed_runtime_health -> (False, "llama_runtime_binaries_missing") _existing_install_runs -> False exists() -> True preflight_linux_installed_binaries -> passes so preflight says broken, repair says the exact release is already installed, nothing is downloaded, and the next launch says broken again. Both attempts land in the same place. It now calls _entrypoint_is_runnable, which is the same test both other answers use. Tests: - test_quarantining_a_split_entrypoint_library_is_reported_broken, per library, against a managed copy, asserting the two answers agree afterwards. - test_an_older_monolithic_linux_release_is_not_asked_for_the_impl_libraries, which pins the gate rather than the requirement: b9279 owes neither, b9283 owes both, a source build owes neither. - test_a_stripped_execute_bit_is_not_reused_as_an_exact_release_match, checking the two gates directly. - The fixture tables gain both libraries, and the shape writers now chmod the entrypoints, which a real extraction does and the reuse decision now reads. tests/studio/install: 5991 passed, 6 skipped. cargo test --bin unsloth-studio: 571 passed. The third item, a versionless libllama.so surviving its SONAME's removal, does not apply: the release ships exactly two names per library, libllama.so.0 beside libllama.so.0.0.10360, with no versionless file and no symlinks. Replied on the thread. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address review: resolve a named-user override the same way on both sides of a spawn preflight::managed resolves UNSLOTH_LLAMA_CPP_PATH=~alice/llama.cpp through getpwnam_r, the way posixpath.expanduser does. process.rs, pinning the same variable for the child it is about to move, read it with the Windows rule, which leaves ~alice alone off Windows, so it fell through to being anchored under the directory being left. Measured, with the two halves side by side on this host: preflight::managed ~root/llama.cpp -> /root/llama.cpp process.rs pin ~root/llama.cpp -> /mnt/work/session/~root/llama.cpp The child is handed the pinned value, so it grades and runs one tree while the cache fingerprints another. Quarantining a file from the tree actually in use then never invalidates a Ready verdict, which is the whole failure this fingerprint exists to catch. expand_user now makes the split every other reader in that function already makes, Windows rules on Windows and the native ones off it, and expand_posix_user resolves the named form through the same lookup preflight::managed uses, which is now pub(crate) rather than duplicated. An unknown name is left exactly as it arrived, which is what expanduser does, so both halves fall through to anchoring it and still agree. Bare ~ and ~/rest are unchanged: they went through expand_windows_user's empty-name arm, which answers home on any platform, and they go through the posix one now. Windows was already aligned. named_user_home's Windows arm delegates to named_windows_user_home, which is the same rule expand_windows_user applies. Tests, all three unix-only since they turn on the password database: - a_named_user_override_is_pinned_where_the_fingerprint_watches, asserting the pin is not under the directory being left and equals what the fingerprint side reaches through the same lookup. - an_unknown_named_user_is_left_alone_and_anchored_by_both_halves. - a_bare_tilde_override_still_reaches_home_off_windows, for the form that already worked. cargo test --bin unsloth-studio: 574 passed. tests/studio/install: 5991 passed, 6 skipped. * Address review: the SONAME beside a flattened versionless copy, and an override that cannot expand Two items, and the first one corrects a rebuttal of mine that was wrong. 1. A versionless copy kept the group satisfied after the SONAME was quarantined. I argued against this last round on the grounds that the bundle ships exactly two names per library. That was true of the tree I measured, the b10360 install on this machine, and it is not true of what the installer produces today. Two things moved underneath it: - b10840 ships the trio. libllama.so -> libllama.so.0 -> libllama.so.0.4.0, as symlinks in the tarball. - copy_globs flattens them. It selects with is_file() and copies with shutil.copy2, which follows symlinks, so all three arrive in build/bin as independent regular files. My reproduction moved the extracted tree instead, which kept the links, and a dangling link is dropped by is_file() on its own. That made the argument look decisive about a shape no install has. Extracting b10840 with prebuilt_core.extract_archive and installing it through copy_globs, which is the path install_prebuilt takes: baseline health=(True, "") quarantine libllama.so.0 health=(True, "") runs=False quarantine libggml.so.0 health=(True, "") runs=False quarantine libllama-common.so.0 health=(True, "") runs=False quarantine libggml-base.so.0 health=(True, "") runs=False quarantine libmtmd.so.0 health=(True, "") runs=False _payload_match_is_loadable can no longer answer from the name alone, because libllama.so means one thing in a family that has a SONAME and another in a family that never had one. It now reads the family: a versionless name counts only when nothing versioned sits beside it. Any versioned sibling disqualifies it, not only a SONAME-shaped one, since asking for a SONAME specifically would read the family as versionless again exactly when the SONAME is the file that went missing. Same rule on macOS, where dyld asks for the install name in LC_ID_DYLIB: libggml.dylib does not stand in for libggml.0.dylib. 2. An unresolvable named user made runtime discovery throw. Path.expanduser() raises RuntimeError when it cannot determine the home, which includes every unknown named user, unlike os.path.expanduser, which hands the value back: Path("~no-such-account-anywhere/llama.cpp").expanduser() RuntimeError: Could not determine home directory. Every reader of UNSLOTH_LLAMA_CPP_PATH used the raising form, so a pin left in a service unit or a .env after an account rename took discovery down with it rather than being treated as unusable, and several callers have no exception boundary. Four sites, not the one reported: the search, the process allowlist that has to name the same tree, the settings reader and the status payload the UI renders. expanded_user_path is the non-raising expansion, shared by all four so they cannot diverge, and it leaves an unresolvable name exactly as it arrived, which is what the desktop's pinning does with one too. Tests: - test_the_flattened_trio_installs_healthy and test_quarantining_a_soname_beside_a_versionless_copy_is_reported_broken, per library, built from the b10840 asset through copy_globs, asserting the three names really are flattened regular files first, so the test cannot pass for the dangling-link reason instead. Skipped where the asset is absent, as the rest of that file is. - test_a_family_that_only_ever_ships_one_name_is_still_loadable and the macOS counterpart, both artifact-free, for the half a blunter rule would break: libggml-cpu-x64.so has no versioned copy anywhere and is the name the loader asks for. - The expanduser guard, including the status payload and discovery reaching its own answer rather than raising. - test_a_dangling_library_symlink_does_not_count_as_present now builds all three rungs, which is what its own docstring already described; it had been building the outer two, and the SONAME rung is what the new rule needs to see. tests/studio/install: 5999 passed, 6 skipped. studio/backend/tests -k "llama_cpp or path_settings": 1928 passed, 4 skipped. cargo test --bin unsloth-studio: 574 passed. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address review: the HIP module by name, both impl halves, and a marker that will not parse Six items, each measured against a real bundle built through copy_globs rather than reasoned about. ggml-hip.dll by name, not "*hip*.dll". app-b10798-mix-659e406-windows-x64-rocm-gfx1150 ships amdhip64_7.dll, hipblas.dll and libhipblaslt.dll beside it, and all four match that pattern, so quarantining the one module ggml actually loads left the group satisfied by three libraries that cannot stand in for it. Measured: installed_runtime_health answered (True, "") with the backend gone. llama-quantize-impl.dll under the same split-build gate as the server's. llama-quantize.exe links against its own impl library exactly as llama-server.exe does, and every post-b9283 bundle ships both, so requiring one let a quarantined quantize implementation read as healthy while quantization could not start. A marker that exists but will not parse names no source, and the source-gated groups were dropped with it: on Windows llama.dll stood in for the whole payload, so an interrupted marker plus a quarantined ggml-base.dll answered healthy. Assuming "published" instead would be worse, because a statically linked source build ships none of those names and would fail health on a tree the setup scripts keep, repairing on every launch with nothing to change. _tree_looks_prebuilt reads the source off the tree from a name only a release ships, and stays lenient when quarantine has taken the evidence too. The runtime fingerprint now includes entry names. Its four counters are all aggregates, and a rename in place moves none of them, so security software that renames ggml-base.dll beside itself left the cached Ready matching a tree installed_runtime_health rejects, and preflight answered Ready without running the capability probe. FNV-1a rather than DefaultHasher, so a toolchain bump does not invalidate every cached verdict; summed rather than folded, because read_dir order is unspecified and the counters beside it are order-independent for that reason. The CLI classifies UNSLOTH_LLAMA_CPP_PATH instead of trusting the marker. studio/backend/main.py calls mark_managed_llama_cpp_path before discovery, which marks any override equal to the managed tree however it got there, and the finder then skips it; _ensure_studio_env_exported writes exactly that value under a custom STUDIO_HOME and sets no marker. So preflight graded the managed tree as a user pin while the backend walked past it to the stored selection, blocking launch over a tree nothing would open. Compared against the studio home's own path with the override out of the way, never default_managed_llama_dir(), which reads the override first and would call every user pin managed. expanded_user_path at the last Path.expanduser left in the doctor, a gap in the earlier pass: a "~name" naming no account raises RuntimeError out of a command whose whole job is to answer. Also: the stored-selection stub in test_desktop_capability_llama_runtime was missing expanded_user_path, so the helper's import raised and every test in that file was silently grading the except fallback rather than the code it names. tests/studio: 10171 passed, 198 skipped. cargo test --bin unsloth-studio: 575 passed. The four new payload tests and the CLI one all fail on the previous head. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Gate the PowerShell reuse shortcut on runtime health, as the shell already is The POSIX half of this loop was closed and the Windows half was not. setup.ps1 reaches "already built" through an elseif on Test-PathQuiet $LlamaServerBin, so a Windows repair that fell through to the source stage kept a tree whose ggml-base.dll had been quarantined, returned it byte for byte identical, and reported success. Desktop preflight grades that same tree on every launch, so it offered the same repair forever. Test-LlamaTreeStillHealthy makes the same --check-existing-install call setup.sh makes, through Invoke-LlamaHelper, so there is one definition of healthy rather than two that can disagree. It answers healthy on any failure to ask: a helper that cannot run must not turn into a multi-gigabyte source build on a machine whose only fault was an unreadable tree. The reason this survived is in the convergence model's own comment, which said setup.ps1 had no reuse step and that a Windows update reaching the source stage always builds. That was wrong, and the model therefore never exercised the Windows branch. It now models both shells, and reverting the gate in the model fails every Windows cell of test_no_damaged_tree_survives_the_shell_rebuild_skip, which is what it should have done all along. A model cannot catch a shell losing its gate, so test_both_shells_gate_their_reuse_shortcut_on_the_same_check reads both scripts and asserts the check sits on the shortcut itself rather than somewhere else in the file. tests/studio/install: 6007 passed, 6 skipped. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Reject a library renamed in place instead of letting it satisfy its own group * Grade the root entrypoint, and plan the Windows rebuild the health gate forces * Grade the studio root the desktop scrubbed, and fix the Windows fixture * Reject empty payload files, and stop grading a runtime no repair can reach * Give the entrypoint check one owner, and cache only what the probe read * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Close the repair loop between the launch check and the update Found by simulating the cycle a user walks rather than by testing the new check on its own, and reproduced before being fixed. installed_runtime_health rejects a root llama-server or llama-quantize that lost its execute bit, so the desktop marks the install stale and offers a repair. existing_install_current_without_plan is the shortcut install_prebuilt returns on before reinstalling anything, and it checked os.access(X_OK) under build/bin ONLY, while _find_llama_server_binary reaches the install root's copy FIRST. So the repair ran, changed nothing, and the next launch was stale again. Measured, with an undamaged install accepted as the control: root llama-server -x launch health broken, shortcut True, full False root llama-quantize -x launch health broken, shortcut True, full False build/bin copy -x launch health broken, shortcut False, full False _damaged_entrypoint now answers for the shortcut too, which is what its own docstring already asks for: one owner, so a tree one check rejects and another keeps cannot be repaired by changing nothing. It is four stat plus os.access calls and spawns nothing, so the shortcut stays a shortcut. test_the_execute_bit_is_checked_on_the_runtime_copies_only pinned the old boundary, so it is rewritten as test_the_execute_bit_is_checked_on_every_entrypoint, keeping the visual server as the control that stops the fix becoming "reject everything". tests/studio/install plus test_llama_cpp_path_settings.py: 8787 passed, 65 skipped. * Require the split entrypoint dylibs on macOS The impl split that gave Windows llama-server-impl.dll and Linux libllama-server-impl.so gives macOS libllama-server-impl.dylib and libllama-quantize-impl.dylib, and the macOS branch of runtime_payload_health_groups was the one that did not ask for them. Quarantining either left every library group satisfied and both execute-bit checks passing while dyld could not start the entrypoint, so installed_runtime_health answered Ready on the platform this probe exists for. Verified against the real bundles rather than assumed: both llama-b10840-mix-d5c17a0 and llama-b11007-mix-3e83366 macos-arm64 ship the two, unversioned, beside the entrypoints. _PREBUILT_TREE_EVIDENCE already reads libllama-server-impl*.dylib as proof of a prebuilt macOS tree. _macos_split_entrypoint_groups delegates the gate to its Linux counterpart rather than repeating it, so the source labels and the b9283 floor cannot drift. The three fixtures that claimed to list a real macOS bundle were short of these two files and now carry them, and the matrix states the requirement independently with the matching pre-split pruning. New test with the loop-direction control: a pre-split upstream b6099 bundle never shipped them and stays healthy. * Shorten the macOS split docstring Comment-only; the AST gate reports install_llama_prebuilt.py code-unchanged. * Grade the macOS fixture against a real bundle, and stop a test carrying one machine's path Merging main up brought in test_macos_loader_probe_record.py from unslothai#11237, whose install fixture writes three dylibs. This branch replaced main's three loose macOS globs with one group per library, so the fixture is now thinner than any install the code grades and 18 of its 26 tests failed on the merge. The tightening is the right side of that: on the shipped llama-b11030-mix-5ff778e-bin-macos-arm64 bundle, quarantining libggml-base leaves main answering healthy, because libggml-blas, libggml-metal and libggml-rpc still satisfy libggml*.dylib, and answers llama_runtime_payload_incomplete here. The fixture now writes the libraries that bundle actually carries, including the two impl dylibs the unparseable release tag requires under the same gate Windows has always used. test_llama_runtime_install_history.py defaulted its asset directory to one machine's workspace path. The default is now under the user's own cache, and the presence check no longer lets a PermissionError through: the file promises to skip when an artifact is not available, and an unreadable directory failed 24 tests instead. tests/studio/install: 8796 passed, 76 skipped. * Name the folder the antivirus advice is asking the user to allow The message told the user to allow "the folder" without saying which one, and finding it is the whole work of acting on that sentence. It now names the default root, spelled the way the platform spells it, off the same navigator test the roaming-profile hint beside it already uses. Hedged with "usually" rather than asserted: UNSLOTH_LLAMA_CPP_PATH can move the root and preflight does not carry the path, so a definite statement would be wrong for anyone who set it. No navigator at all keeps the POSIX spelling rather than dropping the advice. Three tests: every reason names a folder, Windows gets the Windows spelling and no POSIX one (a path a user cannot paste into an exclusion list is not advice), and a missing navigator still answers. 15 of 15 in the file. * Grade a root entrypoint only when discovery would start it The root check was stricter than the resolver it was modelling. It rejected any root copy that existed and was not runnable, but resolve_llama_server_binary returns the first USABLE candidate, and _usable_binary is is_file() plus os.access(X_OK) off Windows. A root wrapper whose execute bit is gone is walked past, build/bin runs, and the tree is fine; grading it damaged sent a working runtime through a repair, and where repair is unavailable it blocked the launch. Reproduced against the real resolver on a Linux tree before changing anything: root wrapper executable backend picks <root>/llama-server health (True, '') root wrapper chmod 0644 backend picks <root>/build/bin/llama-server health (False, binaries_missing) root wrapper executable but empty backend picks <root>/llama-server health (False, binaries_missing) The middle row is the false positive. The third is the one that matters and is kept: an empty wrapper keeps is_file() and its execute bit, so discovery does select it and the exec dies on ENOEXEC. _discovery_would_select mirrors _usable_binary, so the root copy is graded only when the resolver would stop there. Windows keeps the current behaviour, since _usable_binary does not consult the execute bit there and an empty .exe is still what gets selected. test_a_rotten_root_entrypoint_is_not_saved_by_a_healthy_build_bin asserted the old behaviour on the strength of "the finder reaches the root first", which is true but incomplete: it reaches it and skips it. Replaced with the empty-wrapper case, plus a test that a 0644 wrapper and a directory of that name are both walked past, the Windows empty-exe case, and a parity test against the backend's own _usable_binary so the two cannot drift. tests/studio/install/test_installed_runtime_health.py: 53 passed. * Scope the root-entrypoint relaxation to the launch verdict The first cut changed _damaged_entrypoint for all three callers, which also relaxed the installer's keep decision and the release-reuse check. Those ask a different question: whether this tree can be reused as it stands, and a rotten root wrapper is worth replacing, which is what a reinstall is for. selected_root_only is the launch probe's rule alone. The launch verdict follows the resolver, so it walks past a wrapper with no execute bit and does not mark a working runtime stale; the keep decision stays as it was. The direction is the safe one: the launch verdict is never stricter than the repair that answers it, which is the property that stops a tree being repaired by changing nothing and rejected again on the next launch. test_a_damaged_root_entrypoint_stops_the_release_being_reused keeps its point, that the keep decision must reject where the shortcut once kept, and gains the empty-wrapper row, which is the damage the resolver does select and run. tests/studio/install: 8970 passed, 76 skipped, 1 failed, and that one (test_an_unserved_preferred_arch_source_builds_rather_than_using_the_igpus_bundle) fails identically on a clean origin/main worktree. * Grade the runtime a master root install actually uses Under UNSLOTH_HOME the runtimes sit beside studio/, and the CLI exports <master>/llama.cpp while UNSLOTH_STUDIO_HOME stays <master>/studio. The comparison path dropped the override and called default_managed_llama_dir(), which reads only the studio home, so it answered <master>/studio/llama.cpp: the installer's own export did not match, the tree in use graded as a user pin, and master root installs never reached the health check. The rule now lives in _master_root_llama_dir() and both the export and the grading read it, so there is one answer rather than two that can drift. * Fingerprint the runtime a master root install actually loads The Rust half of the same split: inferred_studio_llama_root() answered <master>/studio/llama.cpp for an install recorded under UNSLOTH_HOME, while the CLI grades and the backend loads <master>/llama.cpp. One healthy cached result then survived quarantine in the runtime in use, so preflight kept returning Ready and the failure only appeared at model load, with no repair offered. It now reads share/.unsloth-master-root under the same containment rule the CLI applies (the recorded root must exist and contain this Studio tree) and fingerprints the sibling runtime. Tests cover the note, a note naming somebody else's root, an empty one and one naming a directory that is not there. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: danielhanchen <unslothshared@gmail.com> Co-authored-by: oobabooga <112222186+oobabooga@users.noreply.github.com> Co-authored-by: danielhanchen <unslothai@gmail.com> Co-authored-by: danielhanchen <moonshotaisubstack@gmail.com>
frostyautumnleaf
pushed a commit
to frostyautumnleaf/llama.cpp
that referenced
this pull request
Oct 5, 2026
…default (ggml-org#23462) * cmake : remove STATIC from impl libraries, allow BUILD_SHARED_LIBS control Remove explicit STATIC from all -impl libraries (server, cli, completion, bench, batched-bench, fit-params, quantize, perplexity) so BUILD_SHARED_LIBS controls shared vs static linkage. Add WINDOWS_EXPORT_ALL_SYMBOLS ON for proper DLL export on Windows. Assisted-by: llama.cpp:local pi * cmake : enable LLAMA_BUILD_APP by default Assisted-by: llama.cpp:local pi * ci : disable app in build-cmake-pkg.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
STATICfrom all-impllibraries (server, cli, completion, bench, batched-bench, fit-params, quantize, perplexity) soBUILD_SHARED_LIBScontrols shared vs static linkageWINDOWS_EXPORT_ALL_SYMBOLS ONfor proper DLL export on Windows when built as shared librariesLLAMA_BUILD_APPby defaultAdditional information
Addresses comment and comment from PR #23296.
Requirements