Upgrade llama.cpp from b10456 to b10679 - #403
Conversation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Upstream #27376 splits server_task_result_metrics: the /slots payload moves to a new server_task_result_slots fed by SERVER_TASK_TYPE_SLOT_GET. Re-point handleSlotAction LIST at the new task (it still compiled, but would have returned an empty object) and move the C++ slots assertions to the new type. Refresh patch 0007, whose route-table removal block no longer matched after upstream #26347 dropped two trailing comments. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Upstream #27511 replaces the server json alias (nlohmann::ordered_json) with the new common_json pimpl wrapper. Migrate the project C++ to it and close the two traps that compile silently: a common_json still binds to a const nlohmann::json parameter through operator std::string() (turning require_json_field into a runtime type_error inside handleInfill), and an unscoped enum binds to the bool ctor, so vocab_type was serialised as true/false. Both are now covered by tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Also refreshes documentation the walk proved stale: the removed -DLLAMA_TAG second pin, the patch list in the bump runbook, the JSON_ASSERT/json comment in utils.hpp, and the claim that <cpp-httplib/httplib.h> resolves through a PUBLIC ../vendor include dir on llama-common (upstream #27304 replaced it with PUBLIC vendor::nlohmann / vendor::sheredom, which re-export the same root). Records the pre-existing ServerMetrics payload drift in TODO.md. Verified at the target: fail-loud PATCH_COMMAND clean on a fresh FetchContent configure, full build, ctest 491/491, mvn test 1405 run / 0 failures, NativeLibraryLoadSmokeTest green, clang-format 22.1.8 clean, javadoc clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
The b10585 trap was that a common_json still binds to a const nlohmann::json
parameter through operator std::string(). The fix so far was a template plus a
warning comment; make it structural by dropping the now-unused
nlohmann/json.hpp include from jni_helpers.hpp, json_helpers.hpp and
jllama.cpp, so that conversion is not reachable from those translation units
at all. test_jni_helpers.cpp keeps testing the helper with a real
nlohmann::json and now includes the header itself. log_helpers.hpp and
train_engine.cpp are unaffected -- they use nlohmann deliberately.
Also corrects a comment: the split-off METRICS to_json() returns json{}, which
is JSON null, not an empty object.
TODO.md records three pre-existing defects found while verifying the bump, none
of them introduced by it: model-gated Java tests silently self-skip in CI
(Surefire resolves models/ against llama/, CI restores it to the repo root --
verified with a throwaway probe); the getMetrics() payload became a bare slot
array at b10408 and ServerMetrics, LlamaModelTest#testGetMetrics and
OpenAiCompatServer /slots all still assume the old object shape; and the patch
applier cannot detect an already-applied 0001 because 0006/0007 rewrite the
same region of server.cpp, so any reconfigure of an existing build dir fails.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
The enum-to-bool trap fixed in jllama.cpp during the b10585 step also hit
upstream own get_res_model_info(), which builds the GET /models and
GET /v1/models payload and emits {"vocab_type", meta.model_vocab_type} --
an unscoped enum. common_json_value integral ctor template is is_integral
gated, which excludes enums, so it binds to the bool ctor and the numeric
vocab type goes on the wire as true/false. It was correct while the alias was
nlohmann::ordered_json, so upstream regressed it silently in #27511.
This ships: server-context.cpp is compiled into libjllama and both routes are
served by NativeServer, the default fat-jar Main-Class, in full and in attach
mode. patches/0009 casts the value to int at the emit site, mirroring what
jllama.cpp already does. A sweep confirmed it is the only such site in the
upstream server sources the project compiles: server_context_meta has exactly
two enum-typed members and the other, pooling_type, is never serialised.
Also corrects two history rows that were scoped too narrowly (the b10585 row
claimed no JSON output shape changed anywhere; the b10507 row dismissed hidden
router models without accounting for NativeServer raw argv passthrough), and
records two RouterClient limitations found by the same review: it cannot send
an API key, and /models stopped being a public endpoint at b10519.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
The review caught three documentation defects in the previous commit. 0009 is a burned number: it names the subprocess.h patch dropped at the b10280 bump, and CLAUDE.md carries a prose note three lines below the patches table saying so. A live 0009 row above it would read as if that note described the new patch, whose documented precedent would then be "upstream merged it, delete it". Renamed to 0010 -- filename-only, same sort position, re-checked by applying the whole set to a clean b10618 checkout -- and the row now explains why, and states the bump-time rule every neighbouring row carries: if upstream casts the value themselves, drop this patch rather than refresh it. The final-target verification row certified a 6-patch build; the shipped tree has 7. Restated for the re-run: 7 patch markers including the new cast, 491/491 ctest, 1405 Java tests. The bump runbook enumerated six patches by name one commit after being rewritten -- replaced with glob-truthful wording, since apply-llama-patches.cmake globs the directory and any list here can only rot. Also records that --mmproj-device was left unwired on purpose, so the next reviewer does not re-derive it: it is new public Java API, reachable meanwhile through NativeServer raw argv passthrough. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
The docs-accuracy review audited the ~50 new history rows against the upstream
mirror and found the per-range patch-risk prose rests on a directory scan
(common, tools/server, tools/mtmd, vendor, CMakeLists) that misses 35 of the 40
files the patch set touches -- patch 0001 alone flips ~34 standalone main()
call sites under tools, examples and tests. Two ranges did touch one of those
and said otherwise: b10488-b10499 (tools/perplexity/perplexity.cpp, two lines
from an 0001 hunk, the closest call of the walk) and b10590-b10593
(tests/test-recurrent-state-rollback.cpp, +172 lines above an 0001 hunk). Both
rows corrected, "the five patched files" relabelled as the five core files, and
the final row now explains how to read the per-chunk prose and gives the
mechanical check. The verdicts themselves never came from that scan -- each
chunk applied the whole set to a clean checkout, which covers all 40 files.
Also corrected: json{} is JSON null, not an empty object (two docs still said
otherwise after the code comment was fixed, and one cited a test name that no
longer exists); the common_json migration touched fourteen literals across three
files, not seven, and test_utils.cpp direct-init json{1,2,3} is a second,
distinct incompatibility; MTMD_VIDEO stays on for Android here because dockcross
sets neither ANDROID nor CMAKE_SYSTEM_NAME, not because LLAMA_SUBPROCESS is
unset (upstream defaults it off there); git ls-tree compares blob hashes, so the
unchanged-file-set claim needs --name-only.
And two places where this branch shipped a claim it had itself disproved:
CLAUDE.md and the applier header called the patch applier idempotent, and the CI
model policy said the model tests run on every platform. Both now state the real
behaviour and point at TODO.md, as does the vocab_type guard note, since the
LlamaModelTest assertion is model-gated and only CommonJsonEnumTrap runs in CI.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
A reconfigure of an existing build directory always aborted with
"apply-llama-patches: 0001-... does not apply cleanly -- a llama.cpp version
bump probably shifted the patched code", which is not what happened. The
applier decided "already applied?" with a per-patch `git apply --reverse
--check`, and `--check` never mutates the tree: 0006/0007 rewrite the region of
tools/server/server.cpp that 0001 also patches, so reversing 0001 against the
fully patched tree does not match, the applier fell through to a forward apply,
and that failed too. Reversing the scan order does not help for the same reason
-- nothing is ever undone between checks.
The decision now comes from a stamp file in the fetched source tree
(.jllama-patches-applied: the checked-out llama.cpp commit plus each patch's
SHA-256) combined with git's clean/dirty state, which is the part that makes it
safe:
clean tree -> nothing applied yet (fresh fetch, or a re-checkout after a
GIT_TAG change), apply forward and write the stamp;
dirty tree -> already patched: no-op when the stamp matches this exact commit
and patch set, otherwise abort saying to configure into a fresh
build directory.
Deriving from git state rather than the stamp alone is what stops the dangerous
case: a stamp left behind after FetchContent re-checks-out a new tag would
otherwise skip patching and silently ship an unpatched build. A source tree
supplied via -DFETCHCONTENT_SOURCE_DIR_LLAMA.CPP that is not a git work tree has
neither oracle and keeps the old per-patch path.
Verified against the real tree (the previously fatal `cmake -B build`
reconfigure now succeeds; a fresh configure applies all 7 patches to a clean
clone, writes the stamp, and reconfigures as a no-op) and against a
two-patches-one-file fixture that reproduces the old failure with the previous
applier and passes with this one, including the stale-stamp-on-a-clean-tree
case.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Three independent sweeps over the upstream range -- completeness, adaptation correctness, test integrity -- found no missed adaptation: the request-field set, its bounds and the emitted response keys are identical at both ends, libjllama links with zero undefined upstream symbols, and patch 0007's route-table invariant still holds byte-for-byte. What they did find were documentation errors, one real hang, and coverage that did not guard what it claimed to. All of it is fixed here. Wrong facts, corrected - The -1 context-size sentinel was dropped upstream at b10273 (#26524), not b10275: git tag --contains puts it in b10273, and b10273..b10275 is empty over arg.cpp and server-schema.cpp. It was wrong in 4 shipped Javadoc blocks, 4 exception messages, and every doc that cited it. b10275 stays where it is correct -- the unrelated server-schema.h signature break. - The state-file break IS reachable from public API. saveSlot/restoreSlot are public and reach llama_state_seq_save_file, which is stamped with LLAMA_STATE_SEQ_VERSION (2 -> 3 at b10644). The CHANGELOG named the package-private handleSlotAction instead, so a consumer grepping for the API they call found nothing. Both methods now document the version lock and that a mismatch surfaces as upstream's misleading "No available space in KV cache or invalid slot save file". - CLAUDE.md claimed only AudioInputIntegrationTest self-skips. LlamaTrainerIntegrationTest does too: net.ladenthin.llama.train.model is set by no job and its model is in no models.csv row. A comment that would have blocked a real fix post_and_wait said the idle-sleep guard was unreachable because the getter is not on server_context's public header. True premise, wrong conclusion: server_response_reader::queue_tasks and server_queue::is_sleeping() are both public. Since post_task does not wake a sleeping queue, a getMetrics() issued while asleep blocked until close(). The predicate now mirrors upstream's own, closing || queue_tasks.is_sleeping(). Needs setSleepIdleSeconds(> 0). Guards that did not guard - patches/0010 had none. CommonJsonEnumTrap builds its own JSON and calls no project code, so reverting the cast left it green; get_res_model_info is static and unreachable from jllama_test. NativeServerAttachIntegrationTest now drives GET /v1/models and asserts vocab_type is an integer -- covering the patch on every Java platform, and doubling as the signal to DROP it if upstream ever casts the value themselves. - train_engine.cpp carried the same postprocess_cpu_params pair as tts_params.hpp -- inline, and its only test never runs in CI. Both now call one shared jllama::resolve_cpu_params (cpu_params.hpp) with three tests, so the JVM-abort bug cannot regress unseen in the trainer. - testGetMetrics checked 8 of the 21 merged keys, so a rename in the C++ helper alone still shipped and the getter silently returned its default. It now asserts the full set, with the right JSON type per key. - Two gates still read their property with a bare System.getProperty, bypassing the resolver whose absence muted the whole model-backed suite. Both routed through it, and both modules gained a rule that fails with file:line if a new test reintroduces one -- a rule, not a snapshot of today's call sites. - llama-langchain4j's copied resolver had no test at all; it has one now. - The langchain4j streaming test had been weakened to "content OR thinking", which a content-routing regression passes. Budget raised past Qwen3-0.6B's ~200 thinking tokens so it can assert real content again. Gaps found while looking - Deprecated withTfsZ, withPenalizeNl and both withPenaltyPrompt overloads: tfs_z, penalize_nl and penalty_prompt appear nowhere upstream, and the schema discards unknown fields rather than rejecting them, so they have been doing nothing while their Javadoc described working knobs. - setMmprojDevice and setMmprojOffload(false) both write mmproj_use_gpu, and argv comes out of a HashMap -- so the winner was hash order. The device setter now clears the conflicting flags. - getModelMeta() emitted only vision and audio while upstream has tracked has_inp_video for releases and emits all three from /props; added, with ModelMeta.supportsVideo() and tests including the older-metadata shape. - ServerMetrics gained typed accessors for the window timings t_prompt_processing / t_tokens_generation, which were emitted but unreachable. - getMetrics() documents that the merged payload is not an atomic snapshot and that it defers idle-sleep, which upstream's /metrics stopped doing at b10644. Verified: ctest 512/512 (509 + 3 new), full Java suite 1443 run / 0 failures, javadoc jar builds clean, spotless applied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Two CI-hygiene changes shared with the other three Java repos. 1. "Print crash logs (on failure)" ran on `if: failure()` -- so on EVERY red job, the overwhelming majority of which are ordinary assertion failures that never write a crash log. It nonetheless printed "The fork died without the JVM writing a crash log", stated as fact, under a heading that in this repo also echoed perfectly healthy llama-server logs. Reading a normal red test, that is an invented crash to chase; it cost real time in this very session. All six copies now report the observation, say plainly that no file is the EXPECTED case for a normal failure, and name the one signature -- "The forked VM terminated without properly saying goodbye", or an exit with no test results -- that would actually justify the JVM-abort conclusion. 2. `publish.yml` had no `concurrency:` group, so every push started a full parallel 64-job pipeline while the superseded ones kept draining; four were live at once in one session, which makes "what is CI saying right now" genuinely ambiguous and wastes a lot of runner time on results nobody reads. `cancel-in-progress` is scoped to `pull_request` ONLY: a push to main or a v* tag is a release path and cancelling one midway could leave a partially published artifact set, so those always run to completion. Verified: the workflow parses with the expected concurrency mapping. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
The branch's 60 commit subjects were rewritten to Conventional Commits, which
changes every SHA. Five references in TODO.md name a specific head to tie a
documented CI result to an exact tree ("run 32950691947 on <sha>"), so they
became dangling; they now name the rewritten commits.
The claims stay true: the rewrite touched commit messages only, so each tree is
byte-identical to the one those runs actually validated (verified: `git diff`
between the pre-rewrite backup and the rewritten head is empty).
f9c43dc -> 999034b (2 references)
cbb5e62 -> cfda4a9 (1 reference)
42ce225 -> 7be24a6 (2 references)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
610d957 to
f9f654c
Compare
The `modalities` line gained a third entry (`video`) after the last clang-format pass in this branch, and I hand-wrapped it -- 22.1.8 fits it on one line. That turned the pinned `clang-format` check red on every head since the audit commit. Caught by CI, not locally, because the format pass ran before that edit rather than after it. Re-verified the whole enforced set afterwards: every file under src/main/cpp and src/test/cpp now passes `clang-format --dry-run -Werror` with the pinned 22.1.8 (jllama.h stays excluded -- it is javac-generated). No behaviour change; ctest still 512/512. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
The first range in this bump that broke the project's own compile, and it broke
it in exactly the place the priority review table warns about.
Upstream threaded a new mtmd_helper_init_opt -- video decode settings: fps
target, ffmpeg binary directory, timestamp interval -- through every helper that
can ingest media. Four call sites lost their signatures:
tts_engine.cpp:95 mtmd_helper_bitmap_init_from_file (speaker reference clip)
jllama.cpp x3 tokenize_input_prompts
jllama.cpp x1 format_prompt_rerank
Every one of those passes mctx = nullptr or handles audio, so none of them wants
video settings; each now passes upstream's own mtmd_helper_init_opt_default().
Only a real compile finds this: server-common.h is a same-repo header the project
#includes directly rather than one reachable through the documented dependency
graph, which is the second failure class CLAUDE.md's priority table calls out.
The wire contract did not move. All three mechanical greps are identical across
the range -- 68 request fields, 23 bounds, 304 response keys over all six server
.cpp files -- and common/arg.cpp removed or renamed zero flags. common/common.h
and common/speculative.h are additive only.
All 8 patches apply unchanged, which was the first thing verified because six
patch-target files changed in the range (common/arg.cpp, tests/test-arg-parser.cpp,
tools/mtmd/mtmd-cli.cpp, tools/server/server-context.{cpp,h}, tools/tts/tts.cpp).
Upstream's edits fall outside every patched region.
Sizing: 107 KiB reviewable, or 72 KiB excluding ggml-metal -- Metal backend
internals behind unchanged public headers, the same exclusion rationale already
applied to Hexagon -- so a single reviewed step, no chunking.
Of the 7 new upstream flags, two are exposed and five deliberately are not:
ADDED --n-cpu-moe / -ncmoe -> ModelParameters.setCpuMoeLayers(int)
--n-cpu-ffn / -ncffn -> ModelParameters.setCpuFfnLayers(int)
Real VRAM knobs, and the companions to setGpuLayers: where that moves
whole layers these move only the weight class that dominates a model's
size. --n-cpu-moe had never been exposed either. Five tests.
REFUSED --spec-synth-len / --spec-synth-rates. Upstream's own help text says
"benchmarking only" -- they synthesise fake per-position acceptance
probabilities so llama.cpp's speculative harness can be measured
without a draft model. That is an instrument for benchmarking llama.cpp
itself, not a knob for an application, and as library API it would
invite callers to "tune" numbers that fabricate rather than measure.
Anyone who wants them has them: NativeServer forwards argv verbatim.
REFUSED --video-fps / --video-timestamp-interval / --video-ffmpeg-dir. They
configure video decoding and ContentPart has no way to submit a video,
so they would be inert -- the same dead-but-documented shape that just
forced withTfsZ / withPenalizeNl / withPenaltyPrompt to be deprecated.
TODO.md records what a real video-input feature needs, including that
upstream shells out to ffmpeg/ffprobe.
Verified: 8 patches in the stamp (head 2bb9bddafad4), Release build clean, ctest
512/512, nm -D 40 Java_ exports and 0 mangled, NativeLibraryLoadSmokeTest 3/3
with 0 skipped, full Java suite 1448 run / 0 failures, javadoc jar clean,
clang-format clean across the whole enforced set.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
A second verification pass over this branch's own work found one runtime defect and a set of claims and guards that did not hold up. The defect: with setSleepIdleSeconds(> 0) the model became permanently unusable after the first idle period. server_queue::post() does not leave the sleeping state -- it notifies the condition variable, whose sleeping predicate tests req_stop_sleeping, so the loop woke, re-tested and slept again with the task still queued. Upstream performs the wake for the caller in server_res_generator's constructor, but only for readers built through create_response(); this layer builds its readers with the CLI-facing get_response_reader(), which does not, and nothing here called wait_until_no_sleep() at all. Every later call then blocked until close() or threw "No result", for the process lifetime. All six post sites now go through wake_and_post(). The earlier is_sleeping() wait predicate turned the hang into an exception but did not address the cause; it stays as a backstop, with its comment corrected to say so. Extracted the trainer's common_params assembly into train_params.hpp, the sibling of tts_params.hpp. train_engine.cpp is compiled into jllama only, never into jllama_test, and LlamaTrainerIntegrationTest is gated on a property no CI job sets -- so its resolve_cpu_params() call, which guards a JVM abort, had no runnable guard on any platform. The comment claiming otherwise was wrong. Verified by mutation: removing the call now reds TrainParams.ResolvesBothCpuThreadCounts. Deprecated InferenceParameters.withUseChatTemplate and withChatTemplate. Neither key is read from a request body upstream -- use_jinja is set only by --jinja/--no-jinja, and the only "chat_template" string in common/ or tools/server/ is the one /props emits -- so both were silently no-ops, including at three call sites that used withUseChatTemplate(true) to "enable jinja for tools". Those calls are removed; tool calling was unaffected in practice only because upstream defaults use_jinja to true. Made the mmproj device/offload clearing symmetric. Clearing in one direction still lost the HashMap-order race whenever setMmprojOffload was called second, which is exactly what the one-sided version claimed to fix. Strengthened guards that mutation testing showed were weak or vacuous: the source-scanning rule matched only a bare string literal, so it could not see the house idiom (a PROP_* constant) or a wrapped call, and had no true positives -- it now inspects each call's own argument list, and is proven against both forms; the parent-directory resolver test passed with the branch deleted, so it is dropped in favour of the pre-existing test that does fail, and the langchain4j mirror gains the branch test it never had; the batch-inheritance test derived its fixture from the host core count so it could pass with the bug on a 3-core runner; the modality fixtures all set audio and video alike, so a getter wired to the wrong key still passed. Added the first assertion that the native side actually emits modalities.video, and the first that the mmproj pair clears both ways. Corrected version attributions that were wrong in shipped Javadoc: the /metrics idle-timer exemption is b10519 (#27376), not b10644; the LLAMA_STATE_SEQ_VERSION bump is b10642, not b10644; --n-cpu-moe has existed since b6089 (#15077) and was merely never exposed here, only --n-cpu-ffn is new at b10649. The restore-slot Javadoc quoted an unwrapped message a caller never sees. The bump record overstated the response-key count and the diff size and listed a WebUI change the range does not contain. 516/516 C++ tests, 1449 Java tests with 0 failures, javadoc and clang-format clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
setVideoFps, setVideoTimestampInterval and setVideoFfmpegDir map upstream's --video-fps / --video-timestamp-interval / --video-ffmpeg-dir. These were refused earlier in this branch on the reasoning that they would be inert without a ContentPart video factory. That was wrong twice over. They are not inert. server_context::load_model copies params_base.video_* into its own mtmd_helper_init_opt when the projector loads, and that init_opt is what server-context.cpp passes to process_mtmd_prompt on the task.cli_files path this binding drives -- so they apply to any media a caller attaches, today. And video decoding is genuinely in the shipped artifact: MTMD_VIDEO defaults ON, gated only on LLAMA_SUBPROCESS which is also ON, and libjllama.so carries the ffmpeg invocation strings. setVideoFfmpegDir is the one that earns its place: upstream otherwise resolves ffmpeg and ffprobe from PATH, and a JVM process -- an application server, an Android app, a container built for the JAR alone -- frequently has neither on PATH even when they are installed. Without this flag there is no way to point at them. Raw video bytes already reach the decoder through ContentPart.imageBytes(bytes, "video/mp4"), because mtmd_helper_bitmap_init_from_buf sniffs the container and falls through to video rather than trusting the declared MIME type. What remains is only the ergonomic entry point, ContentPart.videoFile(Path), which stays in TODO.md along with the corrected reasoning. The --spec-synth-* refusal stands: upstream marks those benchmarking-only. 1452 Java tests, 0 failures; javadoc clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
It is model-backed and crosses JNI, so a forked test JVM there can abort exactly the way the six test-java-* jobs can -- but it had neither the echo step nor the dump upload, so such an abort would have left no diagnostic on either path. Same step and same path set as those jobs, scoped to this module. Pre-existing, not a regression from this branch; noticed while auditing this session's own CI changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Both tests capped generation at 320 tokens, which is exactly on the boundary for this model. Run 33109360197 proved it: from the same prompt against the same Qwen3-0.6B, chatReturnsAssistantText finished thinking at 267 tokens and passed, while streamingDeliversTokensThenCompletes consumed all 320 inside <think> -- "eval time = 6490.39 ms / 320 tokens" -- emitted no assistant content, and failed its non-empty assertion. The assertions are right and stay: a reply that is only a thinking block is a real failure, and accepting "content or thinking" is what the earlier 8-token version of the streaming test did wrong. The budget was the problem. Both now use a shared MAX_OUTPUT_TOKENS of 1500, matching the core module's ReasoningBudgetTest N_PREDICT for this same model, with the measurement that motivated it recorded on the constant. It is a cap, not a target -- a healthy run stops around 270-340 tokens -- so this costs nothing except in the pathological case it exists to absorb. The streaming test's future timeout goes 60s -> 180s, since 60s was sized for the old cap and leaves no margin for a worst-case 1500 on a shared runner. 46 tests, 0 failures, 4 skipped locally. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Both were still marked [OPEN] while their fixes shipped in this same branch, so the file claimed two live bugs that no longer exist. SessionForkRewindIntegrationTest: the entry's "not a bump regression" conclusion was right, but its diagnosis -- "it is the KV restore, not the bookkeeping" -- was wrong. Qwen3-0.6B is a reasoning model and the tests' budget was consumed inside <think>, so generation completed normally and simply returned no assistant content. Fixed in cc67ea7; the identical root cause resurfaced in llama-langchain4j and was fixed the same way in dd07b0e. NativeServerAttachIntegrationTest.completion_overHttp_served: fixed by local patch 0011 (ca60947) -- common_peg_until_parser honoured leniency on an incomplete trailing UTF-8 sequence but not on an invalid byte, so one malformed byte turned a finished generation into a 500. The entry now records the mechanism and the runnable guard. No [OPEN] entries remain in the jllama-specific section. The cross-cutting section is unchanged: those are standing repo-hygiene items, not upgrade work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Final sweep over this branch's own documentation. Corrected in CHANGELOG: "304 response keys" -> 286 over the six compiled server TUs, and "of the 7 new upstream flags" -> 6, with --n-cpu-moe named as pre-existing (b6089) rather than new. The b10644-b10649 verification row still said ctest 512/512 and ModelParametersTest 73/73, which the second audit's TrainParams and video-flag cases moved to 516/516 and 77/77. Withdrew an untested workaround. An earlier draft told readers to submit video via ContentPart.imageBytes(bytes, "video/mp4"), reasoning that mtmd_helper_bitmap_init_from_buf sniffs the container. Reading upstream properly: the wire type for video is input_video, and it calls handle_media with accept_base64_uri = false -- raw base64 only, NOT the data: URI form the image factories build. The image_url route might still work by sniffing, but it is untested here and additionally gated on allow_image, so it is no longer documented as supported. README and TODO now state the verified shape a future ContentPart.videoFile must emit. README also gained the mmproj device/offload exclusivity rule, which was behaviour a caller can hit with no note anywhere, and a short video section covering the three new setters. Recorded the teardown ordering wake_and_post depends on: upstream's loop leaves `sleeping` set when it exits on !running, so a caller parked in wait_until_no_sleep() is released only because Java_..._delete drains users == 0 before calling terminate(). Verified correct today; the comment exists so a future reorder does not silently deadlock. Whole-reactor mvn verify green (SpotBugs 0 bugs, enforcer incl. dependency convergence, javadoc), ctest 516/516, clang-format clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
A workflow of 25 agents re-audited this branch: 39 findings, 18 upheld after adversarial verification. Two were real defects in code this PR itself added, both confirmed here against pristine upstream b10649. setVideoFps rejected every value <= 0, but mtmd-helper.h documents fps_target as "<= 0 means use the video's native fps" and the decoder resolves it as `fps_target = arg > 0 ? arg : orig_fps`. The guard deleted the only way to say "match this clip's own rate" -- the target is fixed when the projector loads, long before a clip is attached, so a caller cannot type the rate in instead. It now rejects only NaN and infinity; infinity is the value that genuinely breaks, reaching ffmpeg as "fps=inf". The same header shows `timestamp_interval_ms <= 0 means NO timestamp`, so the test comment claiming 0 meant "every frame" was backwards. setMmprojOffload dropped --mmproj-device unconditionally. Only some combinations actually clash: a named device and --mmproj-offload both resolve to (use_gpu=true, device=named) in either argv order, so clearing there threw away a real multi-GPU pin to set a field that already defaults to true. Clearing is now restricted to the pairs whose meaning depends on argv order -- any device against --no-mmproj-offload, and "none" (which sets use_gpu=false) against --mmproj-offload. Also bounded setVideoTimestampInterval at INT_MAX: the upstream field is int64_t but the flag is registered with an int handler dispatched through std::stoi, so a larger value aborts the whole argv parse and surfaces only as "Failed to parse model parameters", naming neither flag nor reason. Tests, each verified to fail on the regression it guards: - ModelParametersTest: the two tests that asserted the defects are replaced by ones asserting the sentinel passes through, plus non-finite rejection, the INT_MAX boundary, and both directions of the mmproj rule. - test_tts_params.cpp: TrainParams.MapsTheContextAndScheduleFields covers the seven build_train_params fields no other test reads. Mutation-checked -- deleting the decay_epochs assignment reds it, and nothing else. - test_json_helpers.cpp: the fractional, above-INT_MAX and exactly-INT_MAX terms of parse_positive_int_config, previously covered only for raw <= 0. - test_tts_wav.cpp: the four put_u16 fmt fields and byte_rate. No test read a u16 from the header, so the s390x big-endian gate could not see a put_u16 byte-order regression at all. - TrainingParametersTest: the LR-schedule wire keys, whose native fallbacks are byte-identical to the Java defaults -- a renamed key silently reverts the schedule instead of failing. - LangChain4jMappingTest: pins that no "use_jinja" is emitted, the one remaining unguarded site of the three no-op calls this PR removed. Deferred to TODO.md rather than widening the bump: five ModelParameters flags the server arg parser rejects (--grp-attn-* are example-scoped away from SERVER, so a grep sweep cannot see them), the context refcount helpers, OSInfo's archMapping aliases, the trainer's untested end-to-end path, LlamaLoader's jar internals, and dead Java8CompatibilityHelper code. Reactor verify green (SpotBugs 0, enforcer, javadoc), ctest 520/520, 1456 Java + 46 langchain4j tests, clang-format clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
No project-source change was required, and that is a checked result: every
tools/server/*.h header plus server-schema.cpp, server-task.cpp,
server-common.cpp, common/chat.h and mtmd-helper.h are byte-identical across
the range (compared by blob SHA), so the request-field set, its bounds, the
emitted response keys and the mtmd surface cannot have moved. The in-scope
delta is 172 lines over 8 files; the rest of the 159-file range is tools/ui
(rebuilt from GIT_TAG by CI) and backends this project does not build.
Two additive upstream features arrive and both are exposed:
- setKvUnifiedPerSlot(int) for --kv-unified-per-slot. It is registered
set_examples({LLAMA_EXAMPLE_SERVER}) and jllama.cpp parses with that
example, so it binds; its cap half is applied in server-context.cpp's new
n_ctx_slot(), whose value this binding reads through
server_context_meta::slot_n_ctx and feeds to eval_llama_cmpl_schema, so it
also moves where the repeat_last_n / dry_penalty_last_n sentinels expand
to. The Javadoc is deliberately narrower than upstream's help text:
upstream's second effect, sizing the shared KV pool to n_parallel * N when
no context size is given, lives in llama_server() and therefore applies to
NativeServer only, never to a model loaded from this builder.
- setTensorReadLazy(TensorReadLazyMode) for --tensor-read-lazy, over a new
args enum mirroring llama_lazy_mode (off/auto/on = 0/1/2). It has no
set_examples restriction and common_model_params_to_llama copies lazy_mode
into llama_model_params, so it reaches a plain LlamaModel load.
Two further upstream changes need no adaptation:
llama_model_quantize_params gained max_buf_size, but LlamaQuantizer builds
its params from llama_model_quantize_default_params(); and the
get_slot_n_ctx() -> n_ctx_slot() rename is a private member the project
never calls.
Patch 0001 shrinks from 37 to 36 files. Upstream rewrote
tests/test-save-load-state.cpp's main() to strip --models DIR into its own
filtered_argv before calling common_params_parse, so by that patch's own
rule the site no longer wants the _main() flip; the hunk is dropped, not
refreshed. The patch itself is still required, verified rather than assumed:
common_params_parse in pristine b10679 still carries the count-guarded
GetCommandLineW override and common_params_parse_main is absent from
b10679:common/arg.h. Patches 0010 and 0011 were re-checked the same way and
both remain unfixed upstream. All 8 apply with zero fuzz despite three
patch-target files changing; patch 0007's route-table invariant survives
because the new KV-pool-sizing block sits before the extracted table.
Also corrects a stale README claim that setMmprojDevice and setMmprojOffload
are mutually exclusive with each always clearing the other. Round 3 narrowed
that; the paragraph is replaced by the four-row truth table re-derived from
the two upstream handlers, which shows only two of the four combinations are
order-dependent.
Verified locally at the b10679 pin: fresh build directory through the real
FetchContent path so the fail-loud PATCH_COMMAND ran, Release build clean,
ctest 520/520, mvn clean test 1474 run / 0 failures with
NativeLibraryLoadSmokeTest 3/3 and 0 skipped (the pin cross-check against
the linked build-info), reactor mvn verify BUILD SUCCESS including SpotBugs
and the javadoc jar, clang-format 22.1.8 clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Acts on a five-agent audit of the b10456 -> b10679 bump (upstream API
completeness, Java API semantics, test integrity by mutation, patches/build/CI,
and documentation fact-checking). No blockers were found; the adaptation itself
is complete -- 9 flags added across the range, 0 removed, 0 renamed, all 9 either
exposed or refused with a written rationale; 68 request fields, 23 bounds and the
emitted response-key set identical end to end; all patches applying with zero
fuzz. What follows is what the audit did find.
Three defects, two of them in code changed by this PR:
- patches/0001 never flipped tools/tokenize/tokenize.cpp. The patch REMOVES the
Windows UTF-8 argv recovery from common_params_parse, so every unflipped
standalone main() loses it -- and llama-tokenize is a real binary that passes
the process argv. Impact here is zero (LLAMA_BUILD_TOOLS is OFF, so the file is
never compiled), but the patch is documented as the complete upstream change,
and submitting it verbatim would have regressed llama-tokenize on Windows.
Flip added; 36 -> 37 files; all 8 patches re-verified against a pristine
b10679 checkout.
- The concurrency block added earlier in this PR did not protect a release run,
and its comment claimed it did. cancel-in-progress: false does not help:
GitHub cancels a *pending* run whenever a newer run joins the same group,
independent of that setting, so a queued publish_to_central dispatch on main
could be dropped by a later push to main. Every non-PR run now gets its own
group via run_id and can never be queued behind a sibling. The three sibling
repos carry the same block and still need this.
- setMmprojDevice("none") cleared --no-mmproj-offload although that pair
resolves to (use_gpu=false, device=null) in either argv order. The code's
comment table listed three of the four combinations and the README table
written earlier in this PR listed four, so the two disagreed. Now exactly one
flag is contradicted per device value, never both; the missing fourth-row test
is mutation-verified.
Nine wrong upstream tag attributions, seven of which ship in the javadoc jar.
Every one named a tag this bump chunked through rather than the tag that
introduced the change; each corrected value was bisected against the tags:
--n-cpu-ffn b10645 (was b10649), the three --video-* flags and
mtmd_helper_init_opt b10647 (was b10649), --kv-unified-per-slot b10662 (was
b10679), --tensor-read-lazy b10653 (was b10679), dedup-cache-models b10505 (was
b10507), the /metrics idle-timer exemption b10519 (was b10644, and it
contradicted our own javadoc), and the server-schema.h n_ctx_slot drop b10273 --
the same commit as the -1 sentinel removal, not the unrelated b10275 break it
was documented as. mtmd's video path itself predates the whole bump (b9562);
b10647 only added the CLI flags that surface it.
Three wrong mechanisms, all in text a future bump would act on:
- The justification for exposing --kv-unified-per-slot said the cap reaches
eval_llama_cmpl_schema and moves where the repeat_last_n / dry_penalty_last_n
sentinels expand to. Both are false at b10679: that function lost its
n_ctx_slot parameter at b10273 and takes four arguments, and both fields carry
set_hard_limits(0, INT32_MAX) so no sentinel expands to anything. The cap does
reach this binding -- as every slot.n_ctx and as the budget passed to
format_prompt_infill. Conclusion unchanged, reasoning corrected.
- The patch-0011 note said upstream has no FAIL-on-invalid-UTF-8 test through
the until parser. It has three; they parse in strict mode, which is the actual
reason the lenient-only change leaves them green.
- The json_helpers comment said n_prompt_cached has no Prometheus counterpart.
to_metrics() has emitted llamacpp:prompt_tokens_cached_total since the same
commit; what it lacked was a JSON representation.
A guard added earlier in this PR had a hole, now closed and demonstrated both
ways: TestConstantsTest's raw-property scan matched only a literal or a PROP_*
constant, so System.getProperty(LlamaSystemProperties.PREFIX + ".tts.model")
passed silently -- a guard against silent test-skipping that skipped silently.
The widened clause fails on that form; the old guard passes 8/8 on the identical
source.
Also: propsJson now emits modalities.video, so it stops contradicting the
ModelMeta.supportsVideo() this PR added; setVideoTimestampInterval documents
upstream's 0 = no timestamps; setCpuMoeLayers no longer claims 0 keeps experts
on the GPU (it adds no override at all); CLAUDE.md's C++ test recipe no longer
shows a five-argument eval_llama_cmpl_schema call removed at b10273; and stale
counts are corrected (twelve tools/server/*.h headers not six, 1474 tests not
1456, three build_tts_params tests not five, 1909 lines not ~1850). The
"286 response keys" figure is removed rather than adjusted: it could not be
reproduced by any sweep, so the claim now states only the part that is both
load-bearing and checkable -- that the key set does not move.
Two pre-existing gaps are recorded in TODO.md with reproductions rather than
folded in: the two all-*-aarch64 fat jars attached to releases with no smoke job,
and the patch applier silently accepting a partially-reverted source tree.
Verified: fresh build directory through the real FetchContent PATCH_COMMAND
(stamp head 50f068ff, all 8 patches), ctest 520/520, mvn test 1474 run /
0 failures / 17 model-gated skips, reactor mvn verify clean, clang-format 22.1.8
clean, publish.yml parses with 62 jobs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Changing the concurrency group expression has a one-shot side effect worth recording, because it looks like the change is broken when it is not: GitHub reads `concurrency` from the workflow file at each run's own ref, so a run started before the change sits in the old group and a run started after it sits in the new one. Different groups do not supersede, so the push that lands the change leaves the previous run draining. Observed here: the run on 267d975 stayed in_progress across the push of 6566b5d instead of being cancelled. It self-heals from the next push onward. The three sibling repos carry the same block and will see the identical overlap when the fix is ported to them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Acts on the mutation-testing arm of the b10679 audit. Its verdict on the existing tests was clean: 27 mutations applied across C++, Java and the fetched llama.cpp source, 26 red on the test that claims them. The one that stayed green is host-dependent by design (TtsParams' inherit assertions coincide on a 4-core host; ResolveCpuParams derives its probe from the host precisely to avoid that, and went red). PIT is 318/318 killed, 0 no-coverage, test strength 100%, so the mutationThreshold 100 gate holds with this branch's additions. Earlier rounds each found a test that passed with its subject deleted; this round found none. What it did find is production code with no runnable guard. Two are closed here: - wake_and_post() had zero coverage of any kind. It is the largest new runtime behaviour in jllama.cpp and it fixes a defect that made a model permanently unusable after its first idle period, yet jllama.cpp is not in the jllama_test target and the only setSleepIdleSeconds reference asserted the flag string. IdleSleepWakeIntegrationTest loads with a one-second idle window, waits past it, and requires three further completions plus getMetrics() to be serviced. The bounded @timeout is the assertion that matters: without the fix the method does not fail, it hangs. - jsonSchemaToGrammar needs no model, but both its assertions lived in LlamaModelTest, whose @BeforeAll gates the whole class on a GGUF -- so on a host without models the native call was never made at all. Moved into NativeLibraryLoadSmokeTest, which runs wherever libjllama exists (now 4 tests, 0 skipped). Both directions are kept: a valid schema must cross JNI and return a grammar, and a malformed one must surface as LlamaException rather than letting a C++ json::parse exception escape and abort the JVM. The LlamaSystemProperties.PREFIX hole closed in the core guard one commit ago existed in llama-langchain4j's copy of the same rule as well; closed there too. Two documentation corrections, both established by running the thing: - CLAUDE.md said a model-gated class "reported as skipped". It does not. A class-level @BeforeAll assumption makes Surefire record tests="0" errors="0" skipped="0" -- the class contributes no entries at all, so a guard of the form "did the run skip anything?" is structurally blind to it. That is exactly how the model-gated suite stayed silently muted for months. Confirmed first-hand: the new model-gated test above reports tests=0, skipped=0 on a host with no models. - The PIT figure was stale (304 -> 318 mutations, all killed). Three gaps are recorded in TODO.md with reproductions rather than folded in: patches/0010 has no guard that runs on a model-free host (reverting its cast leaves ctest at a clean 520/520; the model-gated guard does run on all six CI Java jobs, so this is coverage rather than shipping risk), theShippedModelConstantsGoThroughTheResolver is vacuous when the fixture is absent, and nothing asserts a floor on the number of tests actually executed -- the one check that would have caught the original silent mute directly. Verified: mvn test 1475 run / 0 failures / 17 model-gated skips, langchain4j 46 run / 0 failures, reactor mvn verify BUILD SUCCESS, clang-format 22.1.8 clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
IdleSleepWakeIntegrationTest, added one commit earlier to close the wake_and_post coverage gap, immediately found a real JVM crash: SIGSEGV at llama_context::get_model(), deterministic on all six CI platforms, on the first request issued after an idle-sleep window. Upstream's handle_sleeping_state(true) calls destroy(), which frees the model and context and sets ctx_tgt/model_tgt to nullptr; resuming calls load_model() again and produces a NEW model. Two things in this layer assumed otherwise, and both are reachable from public API whenever --sleep-idle-seconds is set: 1. server_context::get_meta() dereferences ctx_tgt/model_tgt and is read on every request to build the task. Upstream says as much of the sibling accessor in its own header -- "get the underlaying llama_context, can return nullptr if sleeping" -- so calling it while asleep is a null dereference. That is the observed crash. 2. jctx->vocab is captured once after the initial load and used on every tokenize/detokenize/rerank/infill path. The reload replaces the model, so the cached pointer dangles: a use-after-free that happens to survive only by allocator luck. The field's comment claimed it was "valid for the lifetime of this context", which holds only while idle sleep is off -- the default, which is why this went unseen. The earlier wake_and_post() fix was necessary but not sufficient: it wakes at POST time, and both reads above happen before the post. Upstream sidesteps the whole question by caching meta once after load (server_routes::update_meta) and serving cached /props and /models while asleep; this layer reads live state per request, so it must wake first and then re-sync. Both now go through one wake_server() choke point that waits out the sleep and re-reads the vocab, plus wake_and_get_meta() for the eight per-request meta reads. It is called from every entry point that touches the model, and is a no-op when sleeping is off and in vocab-only mode (which owns its model and has no server). The load-path get_meta() is deliberately left direct, with a comment saying why: the model is freshly loaded there and the worker loop is not yet servicing waits. Teardown must not call it -- a caller parked in wait_until_no_sleep() when the queue terminates is never released, which is safe only because every caller runs inside a jllama_context_guard and delete() drains users to zero before terminate(). Also fixes the same class of defect the investigation surfaced in the builder: setSleepIdleSeconds emitted 0 and values below -1, which upstream's own handler throws on, aborting the entire argv parse and reaching the caller only as "Failed to parse model parameters" -- naming neither the flag nor the reason. They are rejected here now, with the -1 boundary asserted so the guard cannot drift. The test that pinned 0 as serialising to "0" was pinning a value the server cannot accept, and is replaced. Its Javadoc also said the server "shuts down" after the idle window; it does not, it releases the model and reloads it on the next request. Verified: ctest 520/520, mvn test 1476 run / 0 failures / 17 model-gated skips, reactor mvn verify BUILD SUCCESS, clang-format 22.1.8 clean. The runtime behaviour itself can only be verified by CI -- this sandbox has no GGUF models, so the crashing path cannot be reproduced locally. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
Second crash on the idle-sleep path, found by the same test after the previous
commit fixed the first one. The wake now works -- and the SIGSEGV moved from
llama_context::get_model() on "main" to
server_context_impl::load_progress_callback() on the server worker thread, with
the model reload visibly in progress in the stack. That is a latent defect in
our own patches/0002, reachable only via a SECOND load_model() call.
Upstream declares the progress user data as a LOCAL of load_model():
load_progress_data load_progress_text(this, "text_model");
...
params_base.load_progress_callback_user_data = &load_progress_text;
storing the address of a stack local into a field that outlives the call. That
is sound upstream only because they re-assign BOTH fields unconditionally on
every load_model(), so the pointer always names the current frame.
patches/0002 made that assignment conditional on `== nullptr` so an embedding
caller's LoadProgressCallback trampoline survives. On the first load the caller's
field is null and ours is installed, pointing at load #1's frame. Resuming from
the sleeping state calls load_model() again; by then params_base holds OUR
callback, the nullptr guard is false, neither field is re-assigned, and
user_data still points into load #1's dead stack frame. The callback dereferences
it on the next load and segfaults.
The guard now accepts its own callback as well as nullptr, so our user_data is
refreshed on every load while a caller-supplied callback is still never
clobbered. CLAUDE.md's patch table records why the second disjunct exists, since
a future reader would otherwise read it as redundant with the nullptr check and
simplify it straight back into this crash.
Verified: all 8 patches apply to a pristine b10679 checkout with zero fuzz;
fresh build directory through the real PATCH_COMMAND (stamp 50f068ff), Release
build clean, ctest 520/520, mvn test 1476 run / 0 failures / 17 model-gated
skips. As with the previous commit the runtime path itself is only reachable in
CI -- this sandbox has no GGUF models.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH
|
Final CI result — run 33243049437, head
|
| Head | Result | Crash frame |
|---|---|---|
b32485f |
6/6 fail | llama_context::get_model(), on main |
9eb94b7 |
6/6 fail, differently | server_context_impl::load_progress_callback(), on the server worker |
cce1911 |
6/6 pass | — |
The frame moving after the first fix is what showed the fix worked rather than merely relocating the
symptom.
Release gating — all green
The long pole was Build Windows 2025 x86_64 CUDA at 1 h 56 m (nvcc is deliberately not wrapped
by sccache on Windows, so the device passes are uncached). Everything downstream of it then came in
green:
Package JARs,Package all-backends fat jars,Package + Validate Android AARsSmoke test packaged natives (macOS),Smoke test all-backends fat jar (Linux),(Windows)—
the three checks that exercise the published artifact rather than a job-local buildAndroid emulator on-device test (x86_64),Integration Test llama-langchain4j (model-backed),
C++ Tests,Build and Test llama-kotlin, vmlens- every native build: Linux x86-64/aarch64/s390x, Windows x86-64/x86/arm64, both Android,
Vulkan ×2, ROCm ×2, CUDA ×2, OpenVINO ×2, SYCL ×3, OpenCL ×2
Also green outside the workflow: SonarCloud, CodeQL, osv-scanner, clang-format, and Coveralls at
87.011 %.
The three red checks, and why none of them is this branch
Verify GPG signing key×2 — both fail in ~2 s, before any step runs. They declare
environment: maven-central, whose deployment-branch policy admits onlymainand tags, so a
feature branch is rejected at job start. Not re-runnable to a different outcome from here.claude-review— fails API-side: the log shows the SDK returningis_error: trueafter
243 ms withnum_turns: 1,total_cost_usd: 0and an emptymodelUsage, i.e. the model call
never happened. No fix for this exists in this repository.License Compliance(a commit status from a GitHub App, not a workflow job) —error,
17 issues found. Pre-existing: the same finding count is recorded against PRs Add Windows Ninja Multi-Config build with sccache caching #248 and Feature wave: Android AAR + Kotlin facade, server attach/router modes, LangChain4j streaming, GGUF tooling (llama.cpp b9878) #298 in
TODO.md, and the components it flags are third-party test-scope dependencies (JMH, Logback,
jcstress, Lincheck, jqwik, JUnit Jupiter). This is what holdsmergeable_stateatblocked—
not CI. The scanner's host is outside this environment's egress allowlist, so the individual
findings could not be read from here.
The six skipped jobs are Check: main branch / SNAPSHOT, Check: v* tag, Publish Snapshot to Central, Publish Release to Central, Update Snapshot Pre-release on GitHub and Attach Signed Binaries to GitHub Release — the publish path, which correctly does not run on a PR branch.
One note on the concurrency change in this PR
Run 33215659259, on the now-superseded head 267d975, was never cancelled. That is expected and
one-time: GitHub reads the concurrency group from each run's own ref, and that run predates the
group-expression change. It is self-healing — the very next push did cancel all 62 jobs of run
33217708855. Cancelling the stale run returns 403 from this session, so its status is not a
statement about this PR.
Generated by Claude Code



Summary
Upgrades the pinned llama.cpp from b10456 to b10679 across the four pin sites
(
llama/CMakeLists.txtGIT_TAG,LlamaCppVersion.LLAMA_CPP_VERSION, the README badge,CLAUDE.md),walked in individually reviewed steps sized by
git diffbytes. Upstream split, gated and renamedseveral things across that window, so the bump carries the adaptations with it:
getMetrics()rebuilt from two upstream tasks. b10408 (server: refactor + correctness fixes for metrics ggml-org/llama.cpp#26920) reducedserver_task_result_metrics::to_json()to a bare slot array and b10519 (server: refactor sleep handling, allow access /metrics during sleep ggml-org/llama.cpp#27376)split the task in two. No signature moved, everything still compiled and linked, and the Java
payload was quietly the wrong shape for hundreds of builds.
server_metrics_to_json()mergesSERVER_TASK_TYPE_METRICSandSERVER_TASK_TYPE_SLOT_GETback into the shape the Java API hasalways documented, converting upstream microseconds to the milliseconds the payload uses, and
surfacing counters that existed only inside the Prometheus text.
0010castsvocab_typeat upstream'sGET /modelsemit site — sincethe
common_jsonswitch (common: add json.h abstraction ggml-org/llama.cpp#27511) an unscoped enum binds to thebooloverloadand serialises as
true/false.0011makescommon_peg_until_parserrespect leniency on aninvalid UTF-8 byte the way it already does on an incomplete one: a single malformed byte
anywhere in a model's output was turning a finished generation into an HTTP 500. Both are
upstream-submittable, and both were re-checked at b10679 against the pristine upstream file —
upstream has fixed neither, so both stay.
RouterClientAPI-key support (server : make models endpoints private when authentication is enabled ggml-org/llama.cpp#26347 dropped/models+/v1/modelsfromthe public set),
ModelParameters.setMmprojDevice()(b10541), andrepeat-last-n/dry-penalty-last-nrejecting-1, the sentinel upstream dropped at b10273(Samplers: Remove ability to specify "full-context windows" from history-based samplers ggml-org/llama.cpp#26524).
warns about. Upstream threaded a new
mtmd_helper_init_opt(video-decode settings) through everymedia-ingesting helper, changing
mtmd_helper_bitmap_init_from_file,tokenize_input_promptsandformat_prompt_rerank. Four call sites adapted; each passesmtmd_helper_init_opt_default(), sinceall of them use
mctx = nullptror handle audio. Only a real compile finds this —server-common.his a same-repo header the project
#includes directly rather than one reachable through thedocumented dependency graph.
each patch's SHA-256, so a reconfigure of an existing build directory no longer aborts.
What changes for consumers
Three things here are visible to callers. Each is expanded below, and
CHANGELOG.mdcarries the samelist for the release notes.
LLAMA_SESSION_VERSION9 → 10 andLLAMA_STATE_SEQ_VERSION2 → 3 (b10642). This reachesLlamaModel.saveSlot/restoreSlotand theserver's
/slots/{id}?action=save; existing files have to be regenerated. The in-memorySessionsnapshot/fork feature is unaffected — it never writes a file. Detail: One consumer-visible break.
ModelParameters.setSleepIdleSecondsnow throwsIllegalArgumentExceptionon0and on valuesbelow
-1. Upstream rejects both, and emitting them aborted the entire argv parse into a bare"Failed to parse model parameters"that named neither the flag nor the reason.-1(disable) andany positive count are unchanged.
InferenceParametersmethods are deprecated as silent no-ops —withTfsZ,withPenalizeNl, bothwithPenaltyPromptoverloads,withUseChatTemplateandwithChatTemplate.Upstream reads none of those keys from a request body, so they never had an effect; they still
compile and still do nothing. Three production call sites that used
withUseChatTemplate(true)to"enable jinja" were removed.
Not a break, but worth knowing: with
setSleepIdleSeconds(> 0)every request now waits out anin-progress idle sleep before the task is built (
wake_server()). With idle sleep off — the default— it is a no-op.
The final step: b10649 → b10679
No project-source change was required, and that is a checked result rather than an assumption.
server-schema.cpp,server-task.cpp,server-common.cppand all sixtools/server/*.hheaders are byte-identical across the range (compared by blob SHA), as are
common/chat.handtools/mtmd/mtmd-helper.h— so the request-field set, its bounds, the emitted response keys, thegetMetrics()-class of silent contract break and thetts_engine.cppsurface are all provably outof scope, not merely "checked and unchanged". The in-scope delta is 172 changed lines over 8
files; the rest of the 159-file range is
tools/ui(rebuilt fromGIT_TAGby CI) and backendsthis project does not build.
Three patch-target files were touched (
common/arg.cpp,server-context.cpp,server.cpp) and alleight patches still apply with zero fuzz. Patch
0007's standing invariant survives because thenew KV-pool-sizing block in
llama_server()sits before the extracted route table, not inside it.Patch
0001shrank from 37 to 36 files at this step, then grew back to 37 in the audit below.Upstream rewrote
tests/test-save-load-state.cpp'smain()to strip a--models DIRoption into itsown
filtered_argvand callcommon_params_parse(fargc, filtered_argv.data(), …). By that patch's ownrule — an embedded caller that builds its own argv must call
common_params_parsedirectly — thatsite no longer wants the
_main()flip, so the hunk was dropped, not refreshed. The patch itselfis still required, verified rather than assumed:
common_params_parsein pristineb10679:common/arg.cppstill carries the#ifdef _WIN32count-guardedargv = utf8.ptrs.data()override, and
common_params_parse_mainappears nowhere inb10679:common/arg.h— upstream hasadopted neither direction proposed in
ggml-org/llama.cpp#26416.
Two additive upstream features arrived, and both are exposed:
--kv-unified-per-slot(b10662)ModelParameters.setKvUnifiedPerSlot(int)set_examples({LLAMA_EXAMPLE_SERVER})andjllama.cppparses withLLAMA_EXAMPLE_SERVER, so it binds. Its cap half is applied inserver-context.cpp's newn_ctx_slot(), whose value this binding reads throughserver_context_meta::slot_n_ctx— it becomes everyslot.n_ctxand is the context budget handed toformat_prompt_infill.--tensor-read-lazy(b10653)ModelParameters.setTensorReadLazy(TensorReadLazyMode)+ the newargs.TensorReadLazyModeenum (OFF/AUTO/ON, mirroringllama_lazy_mode)set_examplesrestriction, andcommon_model_params_to_llamacopieslazy_modeintollama_model_params, so it reaches a plainLlamaModelload.The
--kv-unified-per-slotJavadoc is deliberately narrower than upstream's help text. Upstream'ssecond effect — sizing the shared KV pool to
n_parallel * Nwhen no context size is given — lives inllama_server(), which aModelParameters-loaded model never enters, so it applies toNativeServeronly. Stating that half unconditionally would have shipped an accurate-for-upstream sentence that is
wrong for the API it documents; the first draft did exactly that and it was caught by reading
server.cpprather than the help string.Two more upstream changes needed no adaptation, each for a specific reason:
llama_model_quantize_paramsgainedmax_buf_size, butLlamaQuantizerbuilds its params fromllama_model_quantize_default_params(), so upstream initialises the new field; and theget_slot_n_ctx()→n_ctx_slot()rename is a private member of the server impl that the projectnever calls — it reads the value through the unchanged
server_context_meta::slot_n_ctx.New upstream flags across the whole range: seven exposed, two refused
Nine flags were added across b10456 → b10679; none was removed or renamed. The decision was made per
flag, not by default:
--n-cpu-ffn/-ncffn(b10645)ModelParameters.setCpuFfnLayers(int)--video-fps,--video-timestamp-interval,--video-ffmpeg-dir(b10647)setVideoFps/setVideoTimestampInterval/setVideoFfmpegDir--mmproj-device/-mmdev(b10541)setMmprojDevice(String)--tensor-read-lazy(b10653),--kv-unified-per-slot(b10662)setTensorReadLazy/setKvUnifiedPerSlot(see above)--spec-synth-len,--spec-synth-rates(b10649)--n-cpu-moe/-ncmoeis not new — it has existed upstream since b6089(ggml-org/llama.cpp#15077) and b10649 only refactored its lambda onto a shared helper. It had simply
never been exposed here, so
setCpuMoeLayers(int)is added alongsidesetCpuFfnLayers(int). Both arethe companions to
setGpuLayers: where that moves whole layers, these move only the weight class thatdominates a model's size, usually fitting a much larger model into the same VRAM at a smaller speed
cost.
The three
--video-*flags were initially refused as "inert without aContentPartvideofactory". The follow-up audit showed that reasoning was wrong twice over, and they are now exposed:
server_context::load_modelcopies them into its ownmtmd_helper_init_optwhen the projector loads,and that
init_optis whatserver-context.cpphands toprocess_mtmd_prompton thetask.cli_filespath this binding drives — so they take effect for any media a caller attaches. Video decoding is also
genuinely compiled in:
MTMD_VIDEOdefaultsON(gated only onLLAMA_SUBPROCESS, alsoON) and theshipped
libjllama.socarries the ffmpeg invocation strings.setVideoFfmpegDiris the one that earnsits place — upstream otherwise resolves
ffmpeg/ffprobefromPATH, which a JVM process frequentlydoes not have them on. The content part itself — upstream's
input_video, which takes raw base64rather than a
data:URI — remains inTODO.md.The
--spec-synth-*pair synthesises fake per-position acceptance probabilities so llama.cpp's ownspeculative harness can be measured without a draft model — an instrument for benchmarking llama.cpp,
not a knob for an application. That refusal stands, with its reasoning in
TODO.md.One consumer-visible break
include/llama.hbumpedLLAMA_SESSION_VERSION9 → 10 andLLAMA_STATE_SEQ_VERSION2 → 3 (atb10642, inside the b10639 → b10644 step), following from
llama_kv_cell_extgaining atokfieldthat must survive a state save/restore.
This is a state-file format break, not an API break, and it is reachable from public API: the
public
LlamaModel.saveSlot(int, String)/restoreSlot(int, String)reachllama_state_seq_save_file, which is stamped with that constant. Every slot file written by an earlierbuild is rejected. Both methods now document the version lock, and that a mismatch surfaces as
upstream's misleading
"Unable to restore slot: No available space in KV cache or invalid slot save file". The in-memorySessionsnapshot/fork feature is unaffected — it never writes a file.Post-upgrade audits
Four rounds. The first covered b10456→b10644 (completeness, adaptation correctness, test integrity).
The second re-audited what the first could not see: its own fixes, the b10644→b10649 adaptation, and
the CI changes. The third mutation-tested every guard the first two added and swept for untested
surfaces. The fourth re-ran all of it against the b10679 target with five parallel agents.
Each round found mistakes made by the previous one, which is the argument for having run all of
them.
No missed adaptation, confirmed repeatedly. The mechanical server-contract checks are identical at
both ends of every range (b10644→b10649: 68 request fields and 23 bounds, unchanged; b10649→b10679: the
question does not arise, since the emitting files are byte-identical).
libjllama.solinks withzero undefined upstream symbols — 531 undefined, all of them versioned libc/libstdc++/libgomp plus
three
_ITM_*/__gmon_start__stubs — so there is no latent macOS-ld64/MSVC link error of theserver_mcpkind. Patch0007's route-table invariant was re-verified byte-for-byte at b10679: theextracted helper, the patch's removal block and the pristine upstream lines all hash identically.
Round 1 findings (all fixed)
patches/0010had no guard —CommonJsonEnumTrapbuilds its own JSON and calls no project code, so reverting the cast left it green.NativeServerAttachIntegrationTestdrivesGET /v1/modelsand assertsvocab_typeis an integer — and doubles as the signal to drop the patch if upstream ever casts it themselves.train_engine.cppcarried the samepostprocess_cpu_paramspair astts_params.hpp, inline, and its only test never runs in CI.jllama::resolve_cpu_params(cpu_params.hpp).testGetMetricschecked 8 of the 21 merged keys, so a rename in the C++ helper alone still shipped.withTfsZ,withPenalizeNl, bothwithPenaltyPromptoverloads do nothing — those fields appear nowhere upstream and the schema discards unknown fields.getModelMeta()emitted only vision and audio; upstream has trackedhas_inp_videofor releases.modalities.videoemitted,ModelMeta.supportsVideo()added.-1sentinel was cited as b10275 in 4 Javadoc blocks, 4 exception messages and ~9 docs. It is b10273.Round 2 findings (all fixed)
The round-1 fix for idle-sleep was itself wrong, and two of the guards round 1 added did not actually
guard anything — both established by mutation, not by reading.
setSleepIdleSeconds(> 0)the model became permanently unusable after the first idle period.server_queue::post()does not leave the sleeping state — it notifies the condition variable, whose predicate testsreq_stop_sleeping, so the loop woke, re-tested and slept again with the task still queued. Upstream wakes for the caller inserver_res_generator's constructor, but only for readers fromcreate_response(); this layer uses the CLI-facingget_response_reader(), and nothing here calledwait_until_no_sleep()at all. Round 1'sis_sleeping()predicate turned the hang into a thrown"No result"— it did not fix the cause.wake_and_post(). The predicate stays as a backstop, with its comment corrected.resolve_cpu_paramstests were claimed to guard the trainer. They do not:train_engine.cppis compiled intojllamaonly, neverjllama_test, andLlamaTrainerIntegrationTestis gated on a property no CI job sets. Deleting the call left the suite green.train_params.hpp(sibling oftts_params.hpp). Mutation-verified.PROP_*constant) or a wrapped call — it had zero true positives and could never fire.assumeTrue— a rule against silent skipping that silently skipped; nowassertTrue, plus an empty-scan failure.System.setProperty("user.dir")does not move the process CWD).withUseChatTemplate/withChatTemplateare silent no-ops — upstream reads neither key from a request body — and three production call sites usedwithUseChatTemplate(true)to "enable jinja for tools", which it cannot do.use_jinjato true.audioandvideoalike, so a getter wired to the wrong key passed; and nothing asserted the native side emitsmodalities.video./metricsidle-timer exemption is b10519 (ggml-org/llama.cpp#27376), not b10644; theLLAMA_STATE_SEQ_VERSIONbump is b10642, not b10644;--n-cpu-moeis b6089, not b10649.test-java-*jobs.Round 3 findings (all fixed)
A third pass — a 25-agent workflow scanning Java API coverage, C++ helper coverage, diff correctness
and cheap pre-existing gaps, then adversarially verifying each finding — returned 39 findings, 18
upheld. Two were defects in code round 2 had just added, both re-confirmed against pristine
upstream before anything was changed.
setVideoFpsrejected every value<= 0— butmtmd-helper.hdocumentsfps_targetas "desired output fps;<= 0means use the video's native fps", and the decoder resolves it asfps_target = arg > 0 ? arg : orig_fps. The guard deleted the only way to say "match this clip's own rate": the target is fixed when the projector loads, long before a clip is attached, so a caller cannot supply the rate instead.NaN/infinity now — infinity is the value that genuinely breaks, reaching ffmpeg asfps=inf. The same header also showed the test comment claiminginterval = 0meant "a timestamp every frame" was backwards:<= 0means no timestamps.setMmprojOffloaddropped--mmproj-deviceunconditionally. Only some combinations actually clash: a named device and--mmproj-offloadboth resolve to(use_gpu=true, device=named)in either argv order, so clearing there discarded a real multi-GPU pin in order to set a field that already defaults totrue.setVideoTimestampIntervalaccepted values aboveINT_MAX. The upstream field isint64_t, but the flag is registered with aninthandler dispatched throughstd::stoi, so a larger value throwsout_of_range,common_params_parsereturns false, and the caller sees only"Failed to parse model parameters"— naming neither the flag nor the reason.INT_MAX, with the boundary itself asserted so the guard cannot drift to>=.Six test groups were added, each checked to fail on the regression it guards:
ModelParametersTest(sentinel passthrough, non-finite rejection, the
INT_MAXboundary, both directions of the mmprojrule),
test_tts_params.cpp(TrainParams, mutation-checked),test_json_helpers.cpp(
parse_positive_int_configabove/atINT_MAX),test_tts_wav.cpp(theput_u16fmt fields — notest read a
u16from that header, so the s390x big-endian gate could not observe a byte-orderregression at all),
TrainingParametersTest(LR-schedule wire keys, whose native fallbacks arebyte-identical to the Java defaults, so a renamed key silently reverts the schedule), and
LangChain4jMappingTest(pins that no"use_jinja"is emitted).Seven items were deferred to
TODO.mdrather than widening a version bump — most notably thatModelParametersemits five CLI flags the server arg parser rejects (--grp-attn-n/-wareset_examples-scoped away fromLLAMA_EXAMPLE_SERVER, so a grep sweep is structurally blind tothem, and four existing tests pin the dead literals happily). That list was re-verified at b10679 and
is still accurate and complete; this branch adds no new dead flag.
Round 4 (b10679) — five agents, complete
Five parallel agents audited the b10679 target: upstream-completeness over the full b10456→b10679
diff, Java API semantics, test integrity by mutation, patches/build/CI, and documentation
fact-checking. Each was given this branch's complete diff, the upstream diff, and a tagged llama.cpp
checkout as the authority — with the explicit instruction that this repo's own docs are under audit,
never evidence.
No blockers. Two agents independently confirmed the adaptation is complete: across the whole
range 9 CLI flags were added, 0 removed, 0 renamed, and all 9 are either exposed or refused with a
written rationale — nothing silently missed. The mechanical contract checks come out identical end to
end, and for the final step the question does not arise at all: the emitting files are byte-identical.
Test honesty, measured rather than asserted. The mutation agent applied 27 mutations and 26 went
red on the test that claims them. The one that did not is host-dependent by design (a thread-count
fixture that coincides with a 4-core runner), is covered by a sibling test that derives its probe from
the host, and the file's own comment anticipates it. Independently, PIT: 318 generated, 318 killed,
0 survived, 0 no-coverage, test strength 100%. Rounds 1–3 each found tests that passed with their
subject deleted; this round found none.
Three real defects, two of them introduced by this PR:
patches/0001never flippedtools/tokenize/tokenize.cpp. The patch removes the Windows UTF-8 argv recovery fromcommon_params_parse, so every unflipped standalonemain()loses it — andllama-tokenizeis a real binary passing the process argv. Impact here is zero (LLAMA_BUILD_TOOLSis OFF, so the file never compiles), but the patch is documented as the complete upstream change and submitting it verbatim would have regressed that binary on Windows.concurrencyblock added earlier in this PR did not do what its comment claimed.cancel-in-progress: falsedoes not protect a release run: GitHub cancels a pending run whenever a newer run joins the same group, independent of that setting — so a queuedpublish_to_centraldispatch onmaincould be silently dropped by a later push tomain.run_id, so it can never be queued behind a sibling. PR runs still share a group per ref and supersede each other — verified empirically: the next push cancelled all 62 jobs of the previous run.setMmprojDevice("none")cleared--no-mmproj-offload, although that pair resolves to(use_gpu=false, device=null)in either argv order. The code comment listed three of the four combinations while the README table written earlier in this PR listed four — the two disagreed.Nine wrong upstream tag attributions, seven of which ship in the javadoc jar. Every one named a tag
this bump chunked through rather than the tag that introduced the change; two were written during
this round. Each corrected value was bisected against the tags:
--n-cpu-ffnb10645,the three
--video-*flags andmtmd_helper_init_optb10647,--kv-unified-per-slotb10662,--tensor-read-lazyb10653,dedup-cache-modelsb10505, and theserver-schema.hn_ctx_slotdrop b10273 — the same commit as the-1sentinel removal, not the unrelated b10275break it had been filed under.
Three wrong mechanisms, all in text a future bump would act on — including the justification for
exposing
--kv-unified-per-slot, which claimed the cap reacheseval_llama_cmpl_schemaand moves therepeat_last_n/dry_penalty_last_nsentinel expansion. Both false at b10679: that function lost itsn_ctx_slotparameter at b10273, and both fields carryset_hard_limits(0, INT32_MAX), so no sentinelexpands to anything. The conclusion held; the reasoning did not.
One number was removed rather than corrected. An earlier revision of this description claimed "286
response keys identical across the range". Swept four ways, the count comes out 229 / 282 / 254 / 317
depending on the extraction — never 286, and the figure could not be reconstructed. The claim now
states only the part that is both load-bearing and checkable: the key set does not move.
Three coverage gaps, two closed here:
wake_and_posthad no guard of any kind — the largest new runtime behaviour injllama.cpp.jllama.cppis not in thejllama_testtarget, and the onlysetSleepIdleSecondsreferenceasserted the flag string. Closed with
IdleSleepWakeIntegrationTest, and that test then foundtwo real JVM crashes — see below. It is the most valuable thing this audit produced.
jsonSchemaToGrammarneeds no model but was tested only inside a model-gated class, so on ahost without GGUFs the native call was never made. Both directions moved into
NativeLibraryLoadSmokeTest(now 4 tests, 0 skipped). TheLlamaSystemProperties.PREFIXguard holefixed earlier in the core module turned out to exist in the langchain4j copy too; closed there as
well.
patches/0010still has no guard that runs on a model-free host — reverting its cast leavesctestat a clean 520/520. Its model-gated guard does run on all six CI Java jobs, so this is acoverage gap rather than a shipping risk; recorded in
TODO.mdwith the reproduction.A documentation claim was corrected along the way that matters more than it looks:
CLAUDE.mdsaid aclass whose
@BeforeAllassumption fails "reported as skipped". It does not — Surefire recordstests="0" errors="0" skipped="0", i.e. nothing at all. Any future guard of the form "did this runskip anything?" would be structurally blind to it, which is exactly how the model-backed suite stayed
silently muted for months. The durable fix — a floor on tests actually executed — is in
TODO.md.Also recorded rather than folded in, both pre-existing: two
all-*-aarch64fat jars are attached toreleases with no smoke job (violating this repo's own "no release asset that CI has not run" rule,
on runners the workflow already uses), and the patch applier silently accepts a partially-reverted
source tree (CI is unaffected — every job configures into a fresh build directory).
The two crashes the new idle-sleep test found
Adding one model-backed test to an unguarded path immediately produced a deterministic SIGSEGV on
all six CI platforms. Both defects are pre-existing — reachable from public API for as long as
--sleep-idle-secondshas existed — and neither is caused by the version bump. Neither could bereproduced locally: this sandbox has no GGUF models, so CI was the only instrument.
The mechanism behind both: upstream's
handle_sleeping_state(true)callsdestroy(), which freesthe model and context and nulls
ctx_tgt/model_tgt; resuming callsload_model()again andproduces a new model. Three things in this layer assumed otherwise.
Crash 1 —
llama_context::get_model(), onmain.server_context::get_meta()dereferencesctx_tgt/model_tgtand is read on every request to build the task. Upstream says as much of thesibling accessor in its own header: "get the underlaying llama_context, can return nullptr if
sleeping". Separately,
jctx->vocabis captured once after the initial load and used on ~16tokenize/detokenize/rerank/infill paths — the reload replaces the model, so it dangles. (Its comment
claimed "valid for the lifetime of this context", true only while idle sleep is off, which is the
default and why this was never seen.) The round-2
wake_and_post()fix was necessary but notsufficient: it wakes at post time and both reads happen before the post. Both now go through one
wake_server()choke point that waits out the sleep and re-reads the vocab.Crash 2 —
load_progress_callback(), on the worker thread, mid-reload — and it was ours. Fixingcrash 1 made the resume actually happen, and the SIGSEGV moved, which is how we knew the first fix
worked. Upstream declares the progress user data as a stack local of
load_model()and stores itsaddress into a field that outlives the call — sound only because they re-assign both fields
unconditionally every load.
patches/0002made that conditional on== nullptrso a caller'sLoadProgressCallbacksurvives; on resume the guard saw our own callback from the first load, skipped there-assignment, and left
user_datapointing into a dead stack frame. A latent defect in thatpatch since the day it was written, reachable only via a second
load_model(). The guard now acceptsits own callback too, and
CLAUDE.mdrecords why the second disjunct exists — it otherwise reads asredundant and would be simplified straight back into the crash.
Confirmed by CI, which is the only place it could be: all six Java jobs failed on
b32485f, allsix failed differently on
9eb94b7, and all six pass oncce1911.A smaller sibling defect surfaced on the way:
setSleepIdleSecondsemitted0, which upstream'shandler throws on — aborting the entire argv parse into a bare
"Failed to parse model parameters".A test was pinning that value as correct. Both fixed, with the
-1boundary asserted.Test plan
FetchContentpath so the fail-loudPATCH_COMMANDran for real — checked first, because threepatch-target files changed in the final range. All 8 patches applied with zero fuzz (stamp
pinned to
50f068ff), Release build clean,ctest520/520,NativeLibraryLoadSmokeTest4/4with 0 skipped (including
nativeBuildInfoMatchesPinnedVersionConstant, the cross-check thatthe four pin sites and the linked
build-infoagree), full Java suite 1475 run / 0 failures /17 model-gated skips,
llama-langchain4j46 run / 0 failures, whole-reactormvn verifyclean (SpotBugs 0 bugs, enforcer incl. dependency convergence, javadoc jar), clang-format
22.1.8 clean across the enforced set,
publish.ymlparses with 62 jobs.on the test that claims them. The one that did not is host-dependent by design and covered by a
sibling test that derives its probe from the host. PIT: 318 generated, 318 killed, 0 survived,
0 no-coverage, test strength 100% — the
value/args/exception/jsongate at threshold 100.failure, 6 skipped. The only two failures were the
Verify GPG signing keyjobs below; the sixskips are the publish/release jobs, which correctly do not run on a PR branch.
Java Testsjobs (Ubuntu, macOS 14, macOS 15 Metal and no-Metal, WindowsNinja and MSVC),
Integration Test llama-langchain4j(model-backed),C++ Tests, andBuild and Test llama-kotlin.Package JARs,Package all-backends fat jars,Package + Validate Android AARs, and all three artifact smokes —Smoke test packaged natives (macOS),Smoke test all-backends fat jar (Linux)and(Windows). These are the checks that actually exercise the published artifact, and the macOSone exists because a corrupt dylib once shipped in three releases under a fully green pipeline.
cce1911):all six
Java Testsjobs pass — Ubuntu, macOS 14 Metal, macOS 15 Metal and no-Metal, WindowsNinja and MSVC. That matters because it is the only instrument available: the crashing path needs
a real GGUF, which this sandbox does not have. The progression is the evidence — 6/6 failed on
b32485fatllama_context::get_model(), 6/6 failed differently on9eb94b7atload_progress_callback(), 6/6 pass here. Also green:C++ Tests, the spotless + SpotBugs stylegate,
llama-langchain4j,llama-kotlin, vmlens, the WebUI build,verify-model-cacheon allthree OSes, and every completed native build (Linux x86-64/aarch64/s390x, Windows x86-64/x86/arm64,
both Android, Vulkan ×2, ROCm ×2, CUDA ×2, OpenVINO ×2, SYCL ×3, OpenCL ×2). The run is now
complete: 64 jobs — 56 success, 2 failure, 6 skipped, the same shape as the b10649 run. The two
failures are the
Verify GPG signing keypair below; the six skips are the publish/release jobs,which correctly do not run on a PR branch. The release gate came in green after the long
Build Windows 2025 x86_64 CUDAjob (1 h 56 m — nvcc is uncached on Windows by design):Package JARs,Package all-backends fat jars,Package + Validate Android AARs,Smoke test packaged natives (macOS),Smoke test all-backends fat jar (Linux)and(Windows).Coveralls reports 87.011 %; SonarCloud, CodeQL, osv-scanner and clang-format are all green.
License Compliance— a FOSSA-style GitHub App status, not a workflow. Pre-existing: the identicalfinding count is recorded against PRs Add Windows Ninja Multi-Config build with sccache caching #248 and Feature wave: Android AAR + Kotlin facade, server attach/router modes, LangChain4j streaming, GGUF tooling (llama.cpp b9878) #298 in
TODO.md. It is what keepsmergeable_stateatblocked. The scanner's host is outside this environment's egress allowlist,so the findings could not be read.
Verify GPG signing key×2 — fail in ~2 s before any step runs, because they declareenvironment: maven-central, whose deployment-branch policy admits onlymainand tags. Notre-runnable to a different outcome from a feature branch.
claude-review— API-side: the log shows the SDK returningis_error: trueafter 243 ms withnum_turns: 1,total_cost_usd: 0andmodelUsage: {}, i.e. the model call never happened. Nofix exists in this repo.
CLAUDE.md,CHANGELOG.md,README.md,docs/history/llama-cpp-breaking-changes.md(a change-inventory row and a paired verification rowper range, including b10649→b10679),
docs/upgrade/llama-cpp-version-bump.md,TODO.md.Related issues / PRs
Upstream references are fully qualified (
ggml-org/llama.cpp#N); a bare#Nanywhere in thisdescription is a PR in this repository.
--n-cpu-moe, exposed here for the first time-1as the context-size sentinel for the penaltywindows, and dropped
eval_llama_cmpl_schema'sn_ctx_slotparameter in the same committask; the latter also made
/metricsstop resetting the idle timerdedup-cache-models, which hides a cache model fromGET /models/models+/v1/modelsfrom the public-endpointset
--mmproj-devicejsonalias tocommon_jsonllama_kv_cell_ext.tok, and the session/state-seq version bumps that follow--n-cpu-ffnmtmd_helper_init_optthreading plus the three--video-*flags--spec-synth-*flags (refused)llama_lazy_mode+--tensor-read-lazy--kv-unified-per-slotllama_model_quantize_params::max_buf_size(no adaptation needed)patches/0011is not tied to a version in that list — the invalid-UTF-8 branch is long-standingupstream behaviour. It surfaced here only because this is the first CI run in which the model-backed
suite actually executed.
Checklist
CONTRIBUTING.mdandCODE_OF_CONDUCT.mdCONTRIBUTING.mdlists, each under 72 characters. The rewrite touched messages only: the tree atthe rewritten head was byte-identical to the pre-rewrite backup, verified with an empty
git diff.https://claude.ai/code/session_01AnNYn8W1xuVxVJtyL34GyH