All-backends server fat jars as GitHub Release assets + model-cache overhaul + llama.cpp b9894#301
Merged
bernardladenthin merged 17 commits intoJul 7, 2026
Conversation
Investigation for the per-classifier fat-jar work surfaced that CLAUDE.md still claimed the Windows build ships three co-located DLLs (jllama.dll + llama.dll + ggml.dll) that the loader must resolve together. That has been stale since BUILD_SHARED_LIBS was forced OFF: llama.cpp and ggml are statically linked, so every platform produces a single monolithic jllama library (verified against the published 5.0.5 jars on Maven Central, which contain only jllama.dll per Windows arch). Loading any jar via java -jar therefore works on Windows today; the suspected missing-sibling-DLL extraction bug does not exist. The one true gap the audit found: initialize() extracts ggml-metal.metal on macOS, but cleanup()'s shouldCleanPath() never matched the "ggml" prefix, so a stale extracted copy accumulated in the temp dir forever. shouldCleanPath() now also matches "ggml"; covered by two new LlamaLoaderTest cases (27 total, all passing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
Groundwork for per-OS "all backends" server fat jars distributed as GitHub Release assets: one jar carrying the default CPU natives plus every GPU backend for its OS/arch in backend subdirectories (net/ladenthin/llama/<os>/<arch>/<backend>/), described by a jllama-backends.txt manifest in priority order. Loader behavior: - Without a manifest (every existing artifact) nothing changes; the backend block is skipped entirely. - With a manifest, each listed backend subdirectory is tried in order; the first whose library loads wins (a missing vendor runtime fails the load cleanly via UnsatisfiedLinkError and the next backend is tried), falling back to the default CPU library after the list. - A manifest line may name extra files (e.g. a bundled OpenCL ICD loader) that are extracted and loaded before the backend's main library; a backend whose extra module is already resident from a previously failed attempt is skipped to avoid by-name import cross-wiring. - New system property net.ladenthin.llama.backend forces one backend (fail-loud, no fallback) or skips all backends via default/cpu. - Backends extract into per-backend temp subdirectories (jllama-backend-<name>/, same file names across backends); cleanup() now deletes directories recursively. Pure parsing/selection logic is package-private static and covered by 15 new LlamaLoaderTest cases (42 total, all passing; javadoc clean). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
Assembles the multi-backend server fat jars for GitHub Release distribution: for each OS/arch with GPU classifiers (linux-x86-64, linux-aarch64, windows-x86-64, windows-aarch64) the default jar-with-dependencies is copied and every backend's native tree is added under net/ladenthin/llama/<OS>/<ARCH>/<backend>/ plus a jllama-backends.txt priority manifest that LlamaLoader consumes at runtime. The default CPU fat jar is copied to the output as-is; each output jar gets a .sha256 file. Enumeration is driven by the <classifier> set in llama/pom.xml (source of truth), cross-checked in both directions against the classifier jars actually built, with fail-loud handling for unparseable classifiers, unranked backends, missing native trees, and zip-update corruption (entry list, Main-Class, sample byte-compare). Excluded by design: msvc-windows (redundant CPU variant) and opencl-android-aarch64 (no java -jar on Android). Sibling files like openvino-windows' bundled OpenCL.dll are recorded as manifest extras. Verified locally against synthetic fixture jars mirroring the real 16-classifier set: 4 combined jars + default assembled with correct manifests; missing-jar and unknown-jar cases fail loud; a LlamaLoader probe run against the fixture combined jar exercised the full manifest -> per-backend extraction -> load-failure fallback chain and the forced/default override paths end-to-end. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
Runs after `package` on every pipeline (PR/main/tag) so the assembly mechanism is validated long before a release: downloads the llama-jars artifact and runs .github/package-fatjars.sh, uploading the 4 combined per-OS fat jars + the default CPU fat jar + sha256 files as the `llama-fatjars` artifact (compression off - jars are deflated; retention 7 days - the release jobs consume it within the same run). Central deployment is untouched (deploy runs without `assembly`). Also refreshes the stale Build JARs comment (the fat-jar Main-Class is ServerLauncher, and the fat jar now seeds the GitHub-Release assets). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
Two new jobs (needs: package-fatjars + download-models) launch the all-<os>-x86-64 fat jar via a real `java -jar` with the cached AMD-Llama-135m draft model (--chat-template chatml; the base code model has no embedded template), poll GET /health to 200, POST /v1/chat/completions and assert choices[0].message, and require the loader's backend-selection log line - pinning that the manifest was read and the per-backend extraction + clean-load-failure fallback chain executed end-to-end on runners without any GPU vendor runtime. Server logs are uploaded on failure. package-fatjars additionally uploads the two smoke jars as single-jar artifacts so the smoke jobs don't download the multi-GB full set. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
- github-release-signed and github-snapshot additionally download the llama-fatjars artifact into their asset directory, so the 4 combined per-OS fat jars, the default CPU fat jar, and the sha256 files land on the tag release and the rolling snapshot pre-release next to the signed Maven artifacts. GitHub download assets only - the Central deploy invocations are untouched (no assembly profile there). - publish-snapshot and publish-release now also need package-fatjars + both smoke jobs (fail-loud release gating: a broken fat-jar assembly or a failing java -jar smoke blocks any publish). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
README: new "Standalone server fat jars (GitHub Releases)" section after the classifier table - per-OS download table, java -jar usage, backend auto-selection semantics with CPU fallback, the net.ladenthin.llama.backend override (also added to the System Properties Reference), sha256 files, and the not-on-Central policy. CLAUDE.md: new section documenting the three-piece mechanism (package-fatjars.sh fail-loud enumeration/assembly, LlamaLoader manifest-driven backend selection, publish.yml smoke + release-asset wiring) plus the zero-devices trade-off and its escape hatch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
Matches the other .github/*.sh scripts; CI invokes it via bash, so this is consistency, not a functional fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
reuse lint is green again (371/371 files with copyright + license). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
Sonar "Build and analyze" failed spotbugs-check on PR #301. Real fixes: hoist the repeated getNativeResourcePath() call out of the backend loop (PRMC), narrow cleanPath's catch to IOException|RuntimeException (REC), and build the backend temp dir as a Path instead of File->toPath (UAC). Documented exclusions following existing precedent: BackendEntry keeps its List parameter because extra files are ORDER-SIGNIFICANT (OCP, same rationale as SessionCheckpoint), and the diagnostic triedPaths list has no knowable size to preallocate (PSC, same rationale as OpenAiServerCli.parseChatTemplateKwargs). Verified locally: spotbugs:check reports 0 bugs; LlamaLoaderTest 42/42. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
Tiny upstream range (2 files, ~2.4 KiB, 1 commit): CUDA flash attention
extends K-type validation to V-types (#24403, upstream-compiled CUDA TU
only) plus an upstream router python test addition (not compiled here).
Verified in the sandbox: all eight priority API headers and every
patch-target file (common/arg.{h,cpp}, server-context/-common/-models/
-http/-stream/server.cpp, tools/tts/tts.cpp, tests/test-arg-parser.cpp)
are byte-identical across the range, and all eight patches
(0001-0008) apply cleanly in filename order onto a clean b9888
checkout. Full build + ctest confirmed by the CI pipeline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
SonarCloud's quality gate failed PR #301 with 38.8% coverage on new code (required >= 80%): the backend-manifest runtime paths only ran with a real multi-backend jar. New BackendManifestLoadTest drives LlamaLoader.initialize() end-to-end against two committed fixture trees, selected via the existing osinfo.architecture override so no real native library or manifest is ever visible to other tests: - backendtest/ (unloadable fakes): every failure branch - manifest read, per-backend temp-dir extraction, missing extra file, missing backend dir, clean load-failure fallback to the default path, the forced-backend fail-loud error (known + unknown name), the default/cpu skip, and the no-manifest legacy path. - backendtest-ok/ (two trivial real x86-64 ELF dummies built from a one-line C TU): the success path ("using native backend"), the resident-extra bookkeeping, and the same-extra clash skip. Remaining uncovered new lines are the unreachable IOException catches (~9 lines), putting new-code coverage around 90%. REUSE.toml annotates the fixture trees (the .so placeholders cannot carry inline headers). Full suite green: 1401 tests, 0 failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
The Windows fat-jar smoke failed in run 28805360584 because the Windows-side gguf-models-v1 cache entry is a poisoned EMPTY archive (343 bytes; cache entries are per-OS and the Windows one was evidently evicted and re-saved empty by a later job's post step). The smoke script's hard Test-Path check was the first thing to catch it - validate-models.bat had printed "ERROR: Model not found" for all ten models yet the step passed (exit code not propagated under the pwsh step shell), so the Windows Java test jobs have been silently self-skipping their model-backed tests. The smoke jobs need exactly one ~80 MB model, so they now ensure it themselves: keep the cache restore (free when warm) and download the draft model directly when absent, using download-models' own curl pattern. This removes the fragile validate-models dependency from the smoke path on both OSes. The underlying repo-wide issues (poisoned Windows cache entry, validate-models.bat exit propagation) are reported separately - they predate and exceed this PR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
… miss" This reverts commit 78053bf.
Implements the intended model-cache design end-to-end: download-models is the ONLY place that downloads and the only writer of the cache; every consumer restores read-only and the validate gate fails loud on an invalid cache. Three changes: 1. download-models' cache step sets enableCrossOsArchive: true, so the one ubuntu-built entry is the same entry macOS AND Windows restore. Without it entries are versioned per-OS: the Windows-side entry was unreachable from the ubuntu writer and was found re-saved EMPTY (343 B) after an eviction in run 28805360584. 2. All 10 consumer cache steps (test-java-*, langchain4j integration, Android emulator, both fat-jar smokes) switch to the restore-only actions/cache/restore action with the matching cross-OS flag - a consumer running on a cache miss can never re-save an empty/partial entry under the immutable key again. 3. validate-models.bat: the MODELS list is now unquoted. The previous set "MODELS=..." "..." form embedded literal quotes, which made the for-loop see the whole list as ONE token; `if not exist` parsed it as path-plus-command, so only a fragment was ever checked and the exit /b 1 never fired - the ERROR printed but the step passed, letting the empty Windows cache sail through the gate while every model-backed Windows test silently self-skipped. Note: the old per-OS cache entries under gguf-models-v1 are obsolete (the cross-OS flag changes the entry version, so they can never match again); deleting them is cleanup, not a prerequisite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
Introduces .github/models.csv (filename,url per row) as the single source of truth for the CI model set: - download-models replaces its ten hardcoded curl steps with one manifest-driven loop; the *_URL workflow env vars are gone (the *_NAME vars stay as consumer-side -D property wiring). - The cache key is now gguf-models-<hash of models.csv>: editing the manifest automatically creates a fresh, complete cache entry - the manual "delete the immutable cache entry when the model set changes" runbook is obsolete (that stale-entry hazard is likely how the Windows cache first went bad). - validate-models.sh and validate-models.bat read their required list from the same manifest, so the gates can never drift from what download-models actually fetches. Verified locally (.sh): all 10 manifest models parse and validate; a missing file and a corrupted magic both exit 1. - New verify-model-cache matrix job (ubuntu / macOS / Windows) runs right after download-models: restore-only with fail-on-cache-miss plus the full validate gate, proving the single cross-OS entry is restorable AND complete on every OS. All ten model-consuming jobs now `need: verify-model-cache` instead of download-models, so nothing model-backed starts before the cache is proven working. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
26 files, ~340 KiB upstream range — nearly all GPU-backend kernel code inside upstream-compiled TUs (large CUDA dequantize/convert refactor, new/extended OpenCL flash-attention kernels with Adreno tuning, new Metal ops, a Vulkan SET_ROWS f16 guard). CPU-side additions are AIX/PowerPC core detection in common.cpp (irrelevant to our targets) and a router download-thread join deadlock fix in server-models.cpp. Verified in the sandbox: all eight priority API headers and all other patch-target files are byte-identical across the range, and all eight patches (0001-0008) apply cleanly in filename order onto a clean b9894 checkout (0008's server-models.cpp changed in a different function). Full build + ctest confirmed by the CI pipeline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq
|
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.



Summary
Per-OS all-backends server fat jars as GitHub Release assets (never Maven Central). Every pipeline run assembles
llama-<version>-all-<os>-<arch>-jar-with-dependencies.jarforlinux-x86-64(CUDA 13, ROCm, SYCL fp16/fp32, Vulkan, OpenVINO),linux-aarch64(Vulkan),windows-x86-64(CUDA 13, ROCm, SYCL, Vulkan, OpenCL, OpenVINO) andwindows-aarch64(OpenCL/Adreno), plus the default CPU fat jar — each with a.sha256file. The release jobs attach them to the tag GitHub Release and the rollingsnapshotpre-release; the Central deploy invocations are untouched. Users run the embedded server with one download:java -jar llama-<version>-all-linux-x86-64-jar-with-dependencies.jar -m model.gguf --port 8080.LlamaLoadergains manifest-driven backend selection: each combined jar carries every GPU backend of its OS/arch undernet/ladenthin/llama/<OS>/<ARCH>/<backend>/plus ajllama-backends.txtpriority manifest; the first backend whose native library loads wins (a missing vendor runtime fails cleanly → next backend), with automatic fallback to the default CPU natives. New system propertynet.ladenthin.llama.backendforces one backend (fail-loud) ordefault/cpu. Jars without a manifest behave exactly as before (zero behavior change for all existing artifacts). Covered by 15 new pure-JavaLlamaLoaderTestcases plus the fixture-drivenBackendManifestLoadTest, which drivesinitialize()end-to-end (extraction, extra files, clash skip, forced/default overrides, success path via two trivial committed ELF dummies) — SonarCloud coverage on new code: 94%..github/package-fatjars.shenumerates the classifier set fromllama/pom.xml(source of truth), cross-checks it in both directions against the built classifier jars, and fails loud on unparseable classifiers, unranked backends, missing native trees, or zip-update corruption — a future classifier cannot be silently skipped. Newsmoke-fatjar-linux/smoke-fatjar-windowsjobs run the combined jar via realjava -jaron GPU-less runners (health poll + chat completion + backend-selection log assertion) and gate publishing together withpackage-fatjars. Both smokes are green in CI.Model-cache overhaul (single source of truth + 3-OS verification gate). The Windows smoke exposed that the Windows-side model cache entry had been re-saved empty after an eviction and that
validate-models.batprinted its ERROR without failing the step — the Windows Java test jobs had been silently self-skipping every model-backed test. Fixes:.github/models.csv(filename,url per row) is the single source of truth;download-modelsreplaces its ten hardcoded curl steps with one manifest-driven loop and is the only writer of the cache. The cache key isgguf-models-<hash of models.csv>, so editing the manifest automatically creates a fresh complete entry (no manual cache deletion), andenableCrossOsArchive: truemakes the one ubuntu-built entry the same entry macOS and Windows restore.verify-model-cachematrix job (ubuntu / macOS / Windows) proves the entry is restorable (actions/cache/restore+fail-on-cache-miss) and complete (validate gate) on every OS before any model-consuming job starts; all ten consumer jobs use the restore-only action (a consumer can never re-save an empty/partial entry) andneed: verify-model-cache.validate-models.{sh,bat}read their required list from the manifest; the.batquoting bug that swallowed theexit /b 1is fixed, so an invalid cache now fails loud on Windows too. Verified in CI: Windows validated all ten models for the first time and its model-backed test suites are executing again.llama.cpp b9888 → b9894 (two bumps: b9886→b9888, b9888→b9894). Both ranges verified in the sandbox: all eight priority API headers byte-identical, all eight
patches/apply cleanly in filename order on clean checkouts (incl.0008despite an unrelatedserver-models.cppchange in the b9894 range); the b9894 bulk is GPU-backend kernel code inside upstream-compiled TUs (CUDA convert refactor, OpenCL flash-attention kernels, Metal ops, Vulkan guard).Housekeeping surfaced along the way: corrected stale CLAUDE.md claims about split Windows DLLs (the build ships one monolithic
jllama.dllsinceBUILD_SHARED_LIBS OFF; verified against the published 5.0.5 jars); fixed a small leak where the extracted macOSggml-metal.metalwas never cleaned from the temp dir; fixed 5 SpotBugs findings in the new loader code (3 code fixes + 2 documented exclusions following existing precedent); REUSE/SPDX headers for all new files; README gains the "Standalone server fat jars (GitHub Releases)" download table and thenet.ladenthin.llama.backendsystem-property row.Test plan
LlamaLoaderTest42/42,BackendManifestLoadTest7/7, full suite 1401 tests / 0 failures; javadoc jar clean;package-fatjars.shverified against synthetic fixture jars incl. both fail-loud directions;validate-models.shmanifest parsing verified incl. missing-file and bad-magic failures)verify-model-cacheon 3 OSes,package-fatjars, both fat-jar smokes, SonarCloud quality gate passed with 94% coverage on new code; the b9894 bump run is in progress re-validating the same graph)docs/history/llama-cpp-breaking-changes.mdrows for both bump ranges)Related issues / PRs
Checklist
CONTRIBUTING.mdandCODE_OF_CONDUCT.mdSECURITY.md)🤖 Generated with Claude Code
https://claude.ai/code/session_01V6QLUwyT2vDGK2Z4MNzQXq