All notable changes to chimera will be documented in this file. Format is loosely based on Keep a Changelog.
- Pin linenoise to tag
2.1(was themasterbranch), so fresh and local builds use the same commit.thirdparty/linenoise/is now untracked;make depswrites it.
-
Update llama.cpp to v0.4.0 (from v0.3.0); whisper.cpp and stable-diffusion.cpp unchanged. Two upstream signature changes, both caught at compile time.
llm_ffn_exps_block_regex(i)becamellm_ffn_block_regex(i, ffn_regex)when upstream generalised the MoE offload helpers to cover dense FFN tensors too (--n-cpu-ffn);--n-cpu-moenow passesLLM_FFN_EXPS_REGEXexplicitly. The siblingllm_add_n_cpu_ffn_overrides()was not adopted: it parks the pattern strings in a function-localstatic std::listthat grows on every call, which is fine for a process that loads one model and wrong for a library whoseload_*entrypoints can be called repeatedly; chimera already owns the storage inModelExtras.mtmd_helper_bitmap_init_from_file()gained a fourthmtmd_helper_init_optargument carrying the video decode parameters; the two call sites passmtmd_helper_init_opt_default(), since--video-fps/--video-timestamp-ms/--ffmpeg-dirreach the decoder throughmtmd_helper_video_init()on the explicit--videopath, not through image loading.Runtime-visible without a code change:
LLAMA_SESSION_VERSION9 -> 10 andLLAMA_STATE_SEQ_VERSION2 -> 3, so slot state written by an earlier chimera (--slot-save-path) is rejected on load. Not wrapped:llama_model_params::lazy_mode(on-demand tensor reads) and upstream's new--n-cpu-ffn.
- An embedding input longer than the context aborted the process.
Embedder::embed()tokenized withadd_specialand handed the result straight tollama_decode, but the encoder path assertsGGML_ASSERT(cparams.n_ubatch >= n_tokens && "encoder requires n_ubatch >= n_tokens")-- anabort()inside llama.cpp, not a return code, so there was nothing downstream to turn into a clean exit.chimera embedon any text past the model's context died this way, and so didchimera index ingestat its default settings: the chunkers clampchunk_tokenstoEmbedder::n_ctx(), which counts content tokens only, soadd_specialthen adds the model's[CLS]/[SEP]on top and a full-width chunk arrives over the line. Withbge-small-en-- whose 512-token training context is also the default chunk window -- that is the first full chunk of the first file, so ingesting a README with a default-configured collection never worked.-c/--ctx-sizewas no escape;n_ubatchis not derived from it.embed()now clamps tomin(n_ctx, n_ubatch), the same clamp rather than fail policy the two guards inchimera_serve.cppalready applied on the server paths, and re-seats the trailing separator token after the resize --clsandlastpooling both read the ends of the sequence, so dropping it would change what those two pool over. The first truncation perEmbedderwarns on stderr; later ones are silent, since ingest drives thousands of chunks through one instance. Thechunk_by_tokensdocs claimed the opposite invariant ("chunks always fit throughembed(text)without truncation") and have been corrected to describe the special-token headroom rather than promise it.
-
scripts/rat.py("run artifact tests"), a smoke-test runner for build artifacts.make testtests the tree you just built; nothing tested the binary CI produces, which is the one that gets released.rat.pychecks it before release:--bintakes a bare executable (a downloaded CI artifact or a local build),--asseta local archive or URL, andrat.py install --cudaresolves a GitHub release (default/releases/latest), picks the asset from the backend plus the host ----cudaon Linux ischimera-<version>-linux-x86_64-cuda.tar.gz-- and unpacks the binary intobuild/rat/;rat.py test test-allthen drives that binary's CLI across embeddings, transcription, generation, the vector store and image generation, andrunis install + test + clean in one command. Two things the suite knows that a hand-run CLI does not:--gpu-layersis not optional (chimera's llama-side subcommands default to 0, so a GPU release tested without it would pass every case while measuring nothing but the CPU path), and every case passes--db/--cache-dbexplicitly or points$CHIMERA_DBinto its own output directory, so a smoke run never creates collections in the user's real store. Its entire footprint is one directory,build/rat/(the binary,downloads/for the archives it came from,out/for everything a run produces;--build-dir, defaulting to the Makefile'sBUILD_DIR) -- already gitignored, already whatmake cleansweeps -- exceptmodels/, which stays at the root shared withmake testrather than re-downloading tens of GiB after every clean. Ported from cyllama's wheel tester, with the virtualenv machinery dropped -- a static binary has no interpreter to indirect through and no compile-time backend env to set -- and backend detection replaced by parsingchimera info'sbuilt:/loaded:lines, which also surfaces the case worth catching: a GPU build whose runtime brought up only CPU. A--binwithout its exec bits ischmod +x'd before use, because a binary downloaded as a GitHub Actions artifact arrives zipped with its POSIX mode stripped. Seedocs/dev/rat.md. -
scripts/case/*.share now cases in that suite. The twelve one-off shell scripts are gone; each survives as arat.pytest target, so they share one model registry, one timeout, one PASS/FAIL summary and one place that knows about--gpu-layers. Thez_turbo*.shscripts becametest-sd-{1..3}-- te-on-cpu + vae-tiling, cpu-offload + vae-on-cpu, and cfg-1 + offload + flash-attn -- mirroring the three cases in cyllama'srwt.pyso the two projects' results compare directly. All three fit an 8 GiB card; the all-on-GPU baseline (~9.4 GiB of weights) and the flash-attn-only variants were dropped, since the baseline OOMs there by construction.run --fastnow runstest-sd-3in place of the oldtest-sd-4. The tworag-chat-qwen3*.shvariants becametest-rag-{1,2}-- which is what found the ingest abort above, since they run at chimera's default chunk window rather than one picked to avoid it.
-
chunk_by_sentencesnever terminated when the chunk window was too small to carry an overlap. After emitting a packed chunk the loop walks back from the packed range to find the smallest tail worthoverlap_tokens, then restarts the next pack there. When only one sentence fits at a time -- the next one pushes the window overchunk_tokens-- that walk-back always lands onfirst, the sentence the chunk just started with, so the identical singleton was emitted forever while the output vector grew without bound. Two 6-token sentences withchunk_tokens = 10and any positive overlap is the minimal reproduction. The guard now requires strict forward progress (back > first), dropping the overlap for that one chunk rather than looping; a window that cannot hold two sentences cannot carry an overlap between them either. Reachable remotely:POST /v1/vector_stores/:name/fileschunks with the collection's stored window, so a collection created with a smallchunk_tokenshung the request thread and exhausted memory. -
The WAV parser sized an allocation from an unvalidated chunk header.
parse_wav_streamread the 32-bitchunk_sizeoff the wire and calledpcm_bytes.resize(chunk_size)before checking that the declared bytes were present, so a ~50-byte multipart upload declaring a 4 GiBdatachunk forced the allocation --std::bad_allocor the OOM killer, depending on the box, and onchimera servethat takes the whole server down rather than one request. The parser now measures the stream once up front and rejects any chunk claiming more than what remains, and a short read ondatais an error rather than a silently zero-filled tail. Rejecting rather than clamping is deliberate: a truncated buffer of zeroes transcribes as plausible silence, which is a worse failure than a 4xx. -
The text-only chat path prepended a second BOS. The comment above the call says chat templates emit
<|begin_of_text|>themselves and thatadd_specialmust therefore be false -- the multimodal path two branches up passesfalsefor exactly this reason -- but the text path passedtrue.chimera tokenizeshows the result directly: a Llama-3 templated prompt came back with128000 <|begin_of_text|>twice. Llama-3 reacts by emitting a stray<|start_header_id|>assistant<|end_header_id|>and losing track of the user turn. Nowfalse, matching both the comment and the mtmd path. -
RAG ingestion was neither atomic nor safely retryable. Each chunk was inserted in its own transaction, so a failed embed or insert partway through one request left the leading chunks committed while the caller saw an error -- and since nothing keys on
source_uri, retrying the same file duplicated everything that had already landed. One file (CLI) / one request (HTTP) is now one transaction.insert_documentmoved fromBEGIN/COMMITto a savepoint so it nests inside the caller's rather than failing with "cannot start a transaction within a transaction", and the newchimera_vector_store::Transactionis an RAII savepoint that rolls back unlesscommit()ran. -
Row iteration reported a failed query as a short result.
search_semanticloopedwhile (sqlite3_step(...) == SQLITE_ROW)and returned whatever it had collected, soSQLITE_BUSY,SQLITE_IOERRorSQLITE_CORRUPTmid-iteration surfaced as a valid partial answer -- and in a hybrid search, as the whole semantic leg silently vanishing. The siblingsearch_lexical_rawalready checked its terminal status; that pattern is now applied tosearch_semantic,list(collections),list_chats,load_messagesandlist_tables.find(collection by name) had the same shape in a different guise: it treated every non-SQLITE_ROWstatus as "no such collection", so a database error was reported as a missing row; it now distinguishesSQLITE_DONEfrom a genuine failure. -
Persisted media paths were interpolated into JSON rather than serialized. Interactive chat built
media_jsonby concatenating paths between quote characters, so any path containing a quote, a backslash (i.e. every Windows path) or a control character produced a body no JSON reader accepts -- losing the metadata the column exists to carry. It now builds anlohmann::jsonarray and dumps it. -
response_formatvalues outside the documented set were silently accepted. Anything that was nottext,srt,vttorverbose_jsonfell through to the default{"text": ...}body with HTTP 200, so a client that typo'dsrthad no way to notice. Unsupported values now return 400 naming the accepted set.
-
Application-level request limits on the audio, image and RAG routes. The staged cpp-httplib already carries a 256 MB per-request payload cap (
_patch_server_http_payload_cap), but that is a floor rather than a policy: 256 MB of WAV still decodes to gigabytes of float samples, and the expensive parameters -- image batch count, image dimensions -- are small numbers in a small body that no payload cap can bound. Each route now checks its own bound before committing memory or the model worker: audio upload size (CHIMERA_MAX_AUDIO_BYTES, 256 MB), imagen(CHIMERA_MAX_IMAGE_BATCH, 16) and per-side dimensions (CHIMERA_MAX_IMAGE_DIM, 4096), and RAG document size (CHIMERA_MAX_RAG_TEXT_BYTES, 64 MB) plus chunk count (CHIMERA_MAX_RAG_CHUNKS, 100k -- chunk count follows the collection's window, not the request, so the byte cap does not bound it). All are-D-overridable at configure time and set far above any legitimate use; the point is that the pathological case fails fast with a 4xx instead of occupying the single-threaded sd or whisper worker indefinitely. -
A startup warning when the server is reachable off-host with no
--api-key. Upstream'smiddleware_validate_api_keygates every route except/healthand the UI assets -- chimera's own routes included -- soPOST /v1/chimera/shutdownand the/v1/chatshistory endpoints are protected whenever a key is configured. But it is opt-in, and with no key anyone who can reach the port can stop the process, or read prompts, reasoning and chat contents by walking/v1/chats/:id(ids are sequential rowids, authorized by existence alone). That is a defensible default on loopback and a bad surprise off it, so binding to a non-loopback address without a key now says so once on stderr instead of leaving it to the docs. -
Regression tests for the three defects the suite could not have caught. An over-declared WAV
datachunk and an unknownresponse_formatare driven throughPOST /v1/audio/transcriptions(the server surviving the first is half the assertion); the chunker case ingests 6-token sentences into a collection with a 10-token window and a positive overlap, where the timeout is the assertion -- before the fix that ingest never returned.
-vdid not reach stable-diffusion, leaving every sd run diagnosable only by its errors.sd_log_callback()hard-filtered atSD_LOG_WARN, and sd.cpp installs no printer of its own (log_printf()hands every level to the callback and returns), sochimera sddropped the whole INFO/DEBUG tier no matter how it was invoked. That tier is where the lines that explain a low-VRAM failure live:Found N backend devices,total params memory size = ... (VRAM x, RAM y), and themodel_managerprepared params backend buffer/staged compute params to CUDA0/releasing compute paramssequence. Without them an OOM from weights pinned to the compute backend -- sd's default, sinceSDBackendManager::params_backend()falls back toruntime_backend()when no--params-backendspec is given -- is indistinguishable from one caused by a placement flag that did not take effect, and the two have opposite fixes. The threshold is now settable (chimera_set_sd_log_verbose()), and-vdrops it toSD_LOG_DEBUG. Warnings and errors are unaffected when the flag is absent.
- Global flags now bind after the subcommand as well as before it.
app.fallthrough(), sochimera sd -v ...works alongsidechimera -v sd ...; previously the former exited withThe following argument was not expected: -v. Subcommands bind their own options first, so fallthrough only catches what would otherwise have been an error.
Audit of chimera against the sibling cyllama (0.4.0--0.4.2) and inferna (0.2.0) releases, which fixed a set of defects in the same shared upstreams. Most of what those projects fixed chimera either fixed first (GGML_MAX_NAME, 0.2.12) or never had (the placement flags were never routed onto max_vram), but five carried over. Two are sd.cpp defects chimera cannot reach from its own code, which is what the new patch machinery is for; see Added.
-
The DRY sampler was disabled outright, and
--repeat-last-n -1silently disabled the repeat penalty. llama.cppv0.3.0removed the-1 = context sizesentinel fromllama_sampler_init_penalties()andllama_sampler_init_dry(): a negative window is nowstd::max(n, 0), and0is each sampler's disabled value (dry_enabledexplicitly requires!= 0). Upstream resolved-1one layer up and moved bothcommon_params_samplingdefaults to64when it dropped the sentinel; chimera keptdry_penalty_last_n = -1, so every DRY invocation since0.2.13has been a no-op, and--repeat-last-n -1-- documented as "scan the whole context" -- turned the repeat penalty off.make_sampler()now resolves a negative window to the context it is about to create (opts.n_ctx, falling back to the model's training context) before handing it tocommon_sampler_init, so both flags mean what their help text says.0still disables. Nothing in the pin-check machinery can catch this class: the fields kept their names and types, only the meaning of one value changed. -
_sync_ggml_abi()left stale objects behind, so a llama.cpp bump could link two ggml generations together. It replaces stable-diffusion.cpp's vendoredggml/with llama.cpp's, but leftbuild/stable-diffusion.cpp/buildin place -- andcopytreepreserves mtimes, so the incoming sources are never newer than the objects already built from the tree just replaced and make sees nothing to redo. SD then compiles against the new ggml and links objects built from the old one. The cmake tree is now dropped along with the swap. To avoid paying a full SD rebuild on every single build, the swap is skipped entirely when the ggml already in place is the one that would be copied:_ggml_tree_stamp()fingerprints the source tree (relative path, size, mtime) and the result is recorded in the destination, so only a real change -- a bump, or aggml-*.patchlanding on llama.cpp's copy -- re-copies and invalidates. -
A failed stable-diffusion text-encoder graph aborted the process (
scripts/patches/stable-diffusion.cpp-conditioner-compute-failure.patch).GGMLRunner::compute()reports failure as an emptystd::optional,take_or_empty()flattens it to an empty tensor, and the conditionersGGML_ASSERT(!hidden_states.empty())on the result, so any budget that pushed the text encoder itself through the graph-cut segmented path (e.g.--max-vram 3.5) killedchimera sd-- and killed the whole server on achimera serveimage route rather than failing one request. Several ofcompute()'s failure paths also log nothing, so the abort arrived with no cause. The patch logs the dropped failure and propagates it through the LLM conditioner's existing error channel, so generation fails cleanly. -
--max-vrambudgets ignored VRAM that was already in use (scripts/patches/stable-diffusion.cpp-graph-cut-budget-clamp.patch). The graph-cut planner clamps its budget to the VRAM actually free at plan time, but only under--stream-layers. Modules are budgeted once at init from free VRAM and a params storage block is only reclaimed when its tensors are disk-backed, so by the time the diffusion model plans its graph the text encoder's weights are still resident and several GiB of the init-time budget no longer exist. The patch ungates the clamp so each module plans against what is genuinely free. -
The MSVC stable-diffusion build lost
/EHsc./bigobj(needed sincesrc/stable-diffusion.cppcrossed the COFF 65,279-section limit at master-795) was supplied as-DCMAKE_CXX_FLAGS=/bigobj, mirroring upstream's own workflow. A command-line-Dpre-seeds the cache, so CMake's platform init never runs and that replaces the MSVC defaults instead of appending, silently dropping/EHscand/GRfrom every SD translation unit./bigobjnow goes into sd.cpp's ownif (MSVC)block next to the/MPand/utf-8it already sets (scripts/patches/stable-diffusion.cpp-msvc-bigobj.patch). chimera's own TUs were never affected --CMakeLists.txtsets the full option list. -
Metal shader compilation depended on the host binary, not the machine (
scripts/patches/ggml-metal-pin-msl-version{,-perkind}.patch). ggml compiles its embedded shader library with a bare[MTLCompileOptions new], so Metal derives the MSL version from the SDK the host process linked against rather than from the running OS. Below MSL 3.1 the library fails to compile outright (no matching constructor for initialization of 'threadgroup metal::half4x4[512]') and the Metal backend never initializes; below 3.1 the bf16 kernels are also#undef'd whileprops.has_bfloatstays true. Both patches install the same@availableladder (3.2 on macOS 15+, 3.1 on 14+, 3.0 on 13+), deliberately stopping below MSL 4.0 so the Metal 4 tensor kernels stay off. They differ only in context:-perkindmatches llama.cppv0.3.0's per-kind Metal library split, the plain one matches whisper.cppv1.9.2; exactly one applies per tree and the other self-disables.
-
Source-patch machinery (
GgmlBuilder._apply_source_patches()).scripts/patches/*.patchare applied to the cloned trees on every build,ggml-*.patchagainst all three and<project>-*.patchagainst the matching upstream. Each is applied withgit apply -p1and is idempotent and self-disabling: already-applied and no-longer-applies are logged and skipped, never fatal, so a version bump that lands an equivalent upstream fix does not break the build -- but it also stops fixing silently, so the build log is worth reading after a bump.scripts/patches/README.mdcarries the analysis for each. This covers failures the wrapper cannot reach at all: aGGML_ASSERTthat aborts the process, and a compile flag that has to be set inside upstream's ownif (MSVC)block. It replaces nothing -- the hand-rolled_patch_server_http_payload_cap()search-replace stays as it is. -
--backend,--params-backendand--auto-fit(sd), mirrored as--sd-*onserve. The first two passsd_ctx_params_t::backend/::params_backendthrough verbatim:backendassigns compute per module,params_backendassigns weight residency and additionally acceptscpuanddisk. Both take a bare target (cuda0,cpu) or comma-separated per-module assignments (diffusion=cuda0,te=cpu), with upstream's module keys. They are appended after whatever--offload-to-cpu/--clip-on-cpu/--vae-on-cpu/--control-net-cpucompose, because sd's parser is last-wins per key -- so an explicit spec overrides the coarse booleans rather than the other way round. Neither is validated chimera-side: sd parses them and reports its own errors, and mirroring the module list would add a second place to drift.--auto-fitsetssd_ctx_params_t::auto_fit, letting sd derive both specs from the model sizes and the available VRAM; it fits where an explicit placement would not, but on a single-GPU box it tends to resolve to all-CPU, so prefer an explicit placement when one is known. -
--load-mode autoand--load-mode mmap+mlock. llama.cppv0.3.0reshapedllama_load_mode: it addedLLAMA_LOAD_MODE_AUTO(-1, and upstream's new default -- the loader picks a strategy from the device's capabilities) and split the old combined value into a plainMLOCK(lock without mmap) andMMAP_MLOCK, movingDIRECT_IOfrom 3 to 4. chimera names the enumerators rather than their values, so nothing broke at compile time and only the parser needed widening.--mlockkeeps selecting plainMLOCK, which is what upstream's own deprecated-flag shim does;--load-mode mmap+mlockis the pre-v0.3.0meaning.
SDCPP_VERSIONgains a ceiling note. From master-817 on, stable-diffusion.cpp callsggml_mul_mat_i8_tensorwiseandggml_quantize_i8_convrot, which exist only in leejet's ggml fork; chimera compiles SD against llama.cpp's ggml, where they are undeclared, so every SD translation unit fails from 817 on.master-816-487de75is the last commit that builds. Observed by cyllama at its0.4.1and recorded here so the next bump does not walk into it; not re-verified against 817 directly.
- Update llama.cpp to v0.3.0 (from b10107), whisper.cpp to v1.9.2 (from v1.9.1), and stable-diffusion.cpp to master-816-487de75 (from master-795-87a0177). llama.cpp has moved from
bNNNNbuild tags to semver, soLLAMACPP_VERSIONchanges shape with it. whisper.cpp and sd.cpp built clean; every adaptation below is llama.cpp's.
-
Adapt to llama.cpp replacing
nlohmann::jsonwithcommon_json.json_schema_to_grammartakes the new type, so--json-schemaparses throughcommon_json::parse. Separately,server-common.hnow declares a globalusing json = common_json, which is ambiguous againstchimera_serve::jsoninsidecommand_serve-- that function pulls the namespace in with ausing-directive, so both names land in one scope. The one affected site is qualified rather than renaming chimera's alias, which is used throughout the serve TUs. -
Adapt to
common_chat_params::thinking_end_tagbecomingthinking_end_tags, andcommon_params_sampling::reasoning_budget_endbecoming a list of token sequences. chimera propagates the whole list instead of taking the first tag, so a template advertising several end tags still stops on all of them; the first also forms the forced sequence, as upstream specifies.ReasoningBudgetParams::thinking_end_tagis renamed and retyped to match, which is source-breaking for any external consumer that set it. -
Link llama.cpp's new
vendor-hasharchive.hash_sha256_hex, which backs mtmd-helper's content-addressed bitmap ids, moved out oflibmtmd.ainto its ownlibvendor-hash.a; without it the final link fails on an undefined symbol. Added to themanage.pytarget list and copy step, all three link paths insrc/chimera/CMakeLists.txt, andcombine_archives.pyso the redistributablelibchimera_thirdparty.acarries it too. -
Resolve
cpuparams.n_threadsbefore handingcommon_paramsto llama.cpp.common_init_from_params()now always builds a threadpool, and ggml sizes the worker array directly fromn_threads. chimera fillscommon_paramsby hand and so never runs the arg-parser postprocess where upstream turns the-1sentinel into a real count; ggml computed a ~2^64-byte allocation, it returned null, and thememsetof that null segfaultedchimera servebefore it logged a single line. Both construction sites inchimera_serve.cppnow call upstream's ownpostprocess_cpu_params(). The image routes failed the same way, reportingattempted to allocate 17592186044416.00 MB. Nothing in the pin-check machinery can catch this class: the field was already there and still compiles, it just started being read.
-
make test69 pass / 0 fail / 6 skip.make test-external-smokeandmake bump-checkclean. -
Not covered: CUDA, ROCm, SYCL and Vulkan were not built, and
CHIMERA_WEBUI_EMBED=ONwas not exercised.
-
GGML_MAX_NAMEhad diverged across the shared-ggml link, corrupting the heap on every SD run. The macro sizes a field insidestruct ggml_tensor, so it setssizeof(ggml_tensor)and the offset of every field aftername. llama.cpp and chimera compiled at 128, whisper.cpp at the 64 default, sd.cpp at 160; sd.cpp then wrote past the end of tensors ggml had allocated. All four now compile with one value. sd.cpp's own guard is a>=minimum, so nothing failed at build time, and the corruption only manifested when an unrelatedmallocwalked the damaged free list. -
Apply
GGML_MAX_NAMEto the CUDA and HIP languages, not just C/C++.CMAKE_C_FLAGS/CMAKE_CXX_FLAGSdo not reach.cufiles, soggml-cudadisagreed withggml-basein the same build. This predates the SD divergence and was present in prior releases. The HIP leg is reasoned-correct but was not exercised on ROCm hardware. -
Pin whisper.cpp's
GGML_MAX_NAMEat all. Its builder never passed the define. No whisper failure was ever observed, so this closes a latent defect rather than fixing an observed one. -
Fetch stable-diffusion.cpp before reading
GGML_MAX_NAMEoff it. On a clean tree the value was read before the SD clone existed and silently fell back to a constant, which would hand llama.cpp and whisper.cpp a stale value the first time upstream moves it -- reintroducing the ABI split this machinery exists to prevent. Both fallback paths now warn.
-
Derive
GGML_MAX_NAMEfrom stable-diffusion.cpp instead of pinning it. The value belongs to upstream SD, which has moved it before, and a stale copy fails silently at runtime rather than loudly at build time. Configure reports the resolved value so a future divergence shows in the build log.GGML_MAX_NAME_FALLBACK = 160covers only an unreadable checkout. Both parsers are inert underSD_USE_VENDORED_GGML=1, where no agreement is required.To audit agreement across a build tree, every
flags.makemust report the same number:find build/{llama.cpp,whisper.cpp,stable-diffusion.cpp}/build build/src \ -name flags.make | xargs grep -ho 'GGML_MAX_NAME=[0-9]*' | sort -u
-
Verified on the Z-Image Turbo case that exposed it (
scripts/case/z_turbo.sh), CUDA and Vulkan, four runs each to rule out nondeterminism.make testunchanged at 68 pass / 0 fail / 7 skip on both. -
Every
flags.makeacross all four trees reports the same value. The one legitimate exception isvulkan-shaders-gen, a host GLSL compiler that never includesggml.h. -
Not covered: ROCm/HIP and SYCL were not built.
- Update llama.cpp to b10107 (from b9979) and stable-diffusion.cpp to master-795-87a0177 (from master-775-b5d8120); whisper.cpp stays at v1.9.1. Both ranges broke the build the same way: a pair of booleans on a params struct collapsed into one richer field. The pin-check
static_asserts fired alongside the call sites, which is what they exist for.
-
Adapt to llama.cpp replacing
use_mmap/use_mlockwithllama_load_mode. chimera keeps its two booleans -- they are what--no-mmapand--mlockbind to -- and translates at the llama boundary in a new leaf header,chimera_llama_load_mode.h, kept dependency-light sochimera_embed.cppcan share it. -
Adapt to sd.cpp folding the reference-image booleans into
ref_image_args. chimera keeps--increase-ref-indexand--no-auto-resize-ref-imageand composes the string upstream's way. The flags are appended after the caller's passthrough string because sd resolves repeated keys last-wins, and the result is left empty when nothing is set so the architecture preset's defaults stand. -
Restore the Windows build: compile sd.cpp with
/bigobj. master-795 pushedstable-diffusion.cpppast MSVC's 65535-section cap. Upstream works around this in its own workflow rather than its CMakeLists, so every downstream consumer inherits the break. GCC and Clang have no equivalent limit, which is why it was Windows-only. -
Pre-empt the same limit in chimera's own MSVC build.
/bigobjadded to the MSVC flag list for chimera's C++ TUs.tests/external/deliberately does not get the flag: it models an ordinary downstream link, so ifchimera.hppever needs it, that test should reveal it. -
Quote multi-token CMake flag values in
manage.py. Values containing whitespace were split by the shell into a broken-Dargument.
-
--load-mode(gen / chat / embed). Acceptsnone | mmap | mlock | dio, namingllama_load_modedirectly and exposingdio, which chimera's booleans cannot reach. An explicit--load-modeoverrides--no-mmap/--mlock; precedence is fixed rather than positional because the booleans and the enum live in separate option fields. Upstream's-lmshort form is not mirrored -- CLI11 rejects multi-character short names. -
--ref-image-args(sd). Passessd_img_gen_params_t::ref_image_argsthrough verbatim, opening the reference-image surface master-795 added. Not validated chimera-side: sd warns and continues on unknown keys, so a typo degrades rather than aborts, and mirroring the key list would add a second place to drift.
- Model-free probes pin both translation layers: a truth table for
chimera_llama_load_mode()and one forchimera_sd::build_ref_image_args()intests/external/smoke.cpp, plus seven CLI-level checks inscripts/test.py. The sd composition was extracted into a pure function specifically so it could be tested without a diffusion model.
- Update llama.cpp to b9979 (from b9804), whisper.cpp to v1.9.1 (from v1.8.6), and stable-diffusion.cpp to master-775-b5d8120 (from master-721-8caa3f9). Whisper built clean. The other two each required one adaptation, both caught by the build and tests rather than by a pre-flip header audit.
-
Adapt to sd.cpp resignaturing
generate_imageto returnboolwith the image array and count as out-params. The copy loop now iterates the returned count instead of the requested batch count. -
Adapt to mtmd making
mtmd_input_text::text_lenstructural.mtmd_tokenizeno longer falls back tostrlen, so chimera's never-populatedtext_lenleft the marker scan empty and the vision pipeline returned nothing. Both call sites now set it.
- Update llama.cpp to b9804 (from b9741) and stable-diffusion.cpp to master-721-8caa3f9 (from master-709-92a3b73); whisper.cpp stays at v1.8.6. Both built clean -- the upstream surface chimera links against was unaffected. Deferred as unwrapped:
llama_model_n_layer_nextn(), the additive mtmd load-progress callback, and the/models/sseprogress feed (tied to upstream's model downloader, which chimera does not use). sd.cpp's newLOGIT_NORMAL_SCHEDULERis already reachable because--schedulerdelegates to upstream's parser.
--eager-load(sd) /--sd-eager-load(serve). sd.cpp defaults to lazy weight residency, so the first generation pays a warmup cost. The flag loads every weight up front, trading load time for steady-state latency from the first request. The useful case ischimera serve; for one-shotchimera sdit only shifts when the cost is paid.
docs/dev/cli-api-coverage.mdgains rows for--eager-loadand the previously-undocumented--stream-layers. Separately,CHIMERA_WEBUI_EMBED=ONwas re-verified on this pin (make testonly ever builds the defaultOFF): the bump shifted upstream's Vite output totools/ui/dist/, which manage.py's probe list already covered.
- Update llama.cpp to b9741 (from b9631) and stable-diffusion.cpp to master-709-92a3b73 (from master-700-c2df4e1); whisper.cpp stays at v1.8.6. Both required chimera-side changes; all three were caught at compile time.
-
Adapt to sd.cpp retyping
sd_ctx_params_t::max_vramfromfloattoconst char *. The field now also accepts a per-backend assignment spec. chimera's--max-vramstays a float GiB budget and is formatted at the boundary, so there is no behavior change and no new syntax exposed. -
Drop the removed
common_params::webuialias.--no-webuinow sets onlyparams.ui. -
Adapt to
common_params_model::namebecoming theget_name()accessor. This is field-to-accessor drift, whichdecltypepin-checks cannot guard, so it surfaced as a plain compile error.
docs/dev/maintenance.mddocuments the field-to-accessor pin-check blind spot: a pin can assert a member's type but not that it stayed a data member. Thebump-checkcommon.hdiff is the catch, and the PR template gains a matching pre-bump audit bullet.
- Update llama.cpp to b9631 (from b9592) and stable-diffusion.cpp to master-700-c2df4e1 (from master-685-19bdfe2); whisper.cpp stays at v1.8.6. The llama.cpp range was audited by diffing upstream headers between the two refs directly --
make bump-checkonly confirms the vendored headers match the currently-pinned ref, so it cannot report drift since the previous pin. Only sd.cpp required code changes.
-
Adapt to sd.cpp's
sd_ctx_params_toffload rework. The per-component offload booleans became backend-assignment spec strings. chimera's existing offload flags now translate into those strings the way the upstream example CLI does, so the public knobs are unchanged.vae_decode_onlyhas no replacement -- upstream always builds a full encode+decode VAE -- so the field is retained as an accepted no-op. -
Repair
CHIMERA_WEBUI_EMBED=ON, broken by the llama.cpp bump. Upstream's asset-embed helper switched from a fixed list of name/path pairs to a recursive directory, and the prebuilt UI became a SvelteKit tree with content-hashed assets served dynamically. chimera still assumed the old flat four-file model, so staging silently declined and an explicitONbuild failed at configure. Invisible tomake test, which only builds the defaultOFF.
-
Update llama.cpp to b9592 (from b9528), whisper.cpp to v1.8.6 (from v1.8.4), and stable-diffusion.cpp to master-685-19bdfe2 (from master-672-1f9ee88). The two breaking changes in this range lived in headers
bump-checkdid not watch (sampling.h,mtmd-helper.h), so the compile caught them instead. The watch list now covers both. -
Adapt to two breaking llama.cpp API changes.
common_sampler_types_from_namesdropped itsallow_alt_namesparameter, andmtmd_helper_bitmap_init_from_filegained a trailing parameter and now returns a wrapper struct carrying a video-decode context.
-
Video input on
chimera genandchimera chat. b9592 bundles upstream's video decoder, already compiled into chimera's vendored libmtmd.-
gen --video <path>(repeatable, requires--mmproj), distinct from--image:--imagekeeps upstream's content-based auto-dispatch with default decode params, while--videoalways routes through the video decoder and honors--video-fps,--video-timestamp-ms, and--ffmpeg-dir. ffmpeg/ffprobe are required at runtime. -
chat
/video <file>, with help, tab-completion, and the same session knobs. -
Honoring custom decode params needed a chimera-side helper, because upstream's bitmap loader hardcodes default video params internally.
-
A guard rejects video when the build or mmproj lacks video support. End-to-end decode is not CI-covered -- it needs a video-capable mmproj fixture (see
TODO.md).
-
- Video-decode context was leaked when adopting the new mtmd wrapper API, and video lazy bitmaps are single-use. The context is now RAII-owned for the lifetime of its bitmap, and chat rebuilds each video from its source path before every turn -- the REPL re-tokenizes the whole media history each turn, so reusing a consumed context made the video silently vanish after the first one.
-
Update llama.cpp to b9528 (from b9318). The wrapper audit found that every changed, deprecated, or renamed symbol in this range is one chimera does not call, so the clean build was not masking a semantic break. The vendored server stack picked up a new task type, a mid-struct
server_routesmember, and atask_params::streamdefault flip; none affect chimera, which constructsserver_routesvia the upstream constructor and binds routes by member name. -
Update stable-diffusion.cpp to master-672-1f9ee88 (from master-650-1ceb5bd). Two fields were inserted mid-
sd_ctx_params_t. This is layout-safe for chimera becauseload_modelinitializes via upstream'ssd_ctx_params_init()rather than aggregate init, so both receive their correct defaults -- a plain{}would have silently defaultedvae_formatto the wrong enumerator. Both are now exposed as knobs.
-
SD VAE-format override (
--vae-format/--sd-vae-format), acceptingauto | flux | sd3 | flux2. Upstream's string-to-enum mapper is file-static in the example CLI, so it is reimplemented chimera-side. -
SD CPU weight-streaming toggle (
--stream-layers/--sd-stream-layers). Only engages alongside--max-vram > 0; sd.cpp disables it otherwise and logs the reason, so no extra guard is added. -
HTTP server timeout knobs on
chimera serve(--http-timeout,--sse-ping-interval). Both default to0= leave the upstream default in place. -
Realtime reasoning termination on
chimera serve(POST /v1/chat/completions/control). A client that opened a streaming completion with"reasoning_control": truecan make the model stop thinking and answer immediately without aborting the stream. The chat handler already passed the flag through, so binding the route is the whole integration. -
Interactive reasoning termination in
chimera chat(--reasoning-control). With the flag set, the firstCtrl-Cwhile the model is inside its think block ends the reasoning instead of aborting the reply; a second one, or one while answering, aborts as before. The reasoning-budget sampler is armed with no fixed budget so it never auto-fires -- only the runtime force ends thinking. A non-reasoning template warns and the flag becomes inert. -
Python bindings track the new option fields, keeping
LlamaOptions/SdOptions/ServeOptionsat full parity with the C++ structs. Binding coverage is hand-maintained with the round-trip tests as the regression net;bindings/README.mdpreviously claimed a compile-time check that does not exist.
-
CI now covers the combined-archive layer, schema migrations, and the Python bindings. The matrix previously ran only
make build+make smoke, which exercise the executable and not the three redistributable archives -- the gap that let the 0.2.3combine_archives.pyregression ship silently. Three model-free legs close it:make test-external-smoke(all OSes, asserts the link contract and ggml backend self-registration),make test-db-migrate(all OSes), andmake test-bindings-pytest(Linux + macOS; gated off Windows because the recipe assumes the POSIX venv layout). -
Compile-only GPU backend CI (
.github/workflows/ci-gpu.yml). Aworkflow_dispatch-only workflow that builds CUDA and Vulkan on Linux and asserts the backend's ggml registration symbol actually linked -- catching a silently-dropped backend without a GPU. It does not validate runtime registration or kernel correctness; that needs a self-hosted GPU runner. Each leg uploads a stripped binary so users can try a GPU build on their own hardware. Manual-only until it proves stable. -
One-shot artifact bundle in both CI workflows. A final
collectjob merges the per-leg artifacts over the GitHub API so every platform can be pulled in one click. Per-leg artifacts are kept, and the job runsif: always()so a green leg is still bundled when a sibling fails. -
Compiler-launcher (ccache) forwarding on both configures. CMake does not read
CMAKE_*_COMPILER_LAUNCHERfrom the environment, so a wrapper was previously ignored on both the deps build (where the heavy nvcc compile lives) and chimera's own. Expands to nothing when unset. -
DEPS_EXTRAMakefile passthrough on thedepstarget, so CI can inject flags like--no-sd-exampleswithout a dedicated target. -
pytest suite for the Python bindings (
bindings/tests/) -- 11 no-model tests plus model-gated inference tests.conftest.pymakesimport chimerawork from the uninstalled standalone build and resolves model paths from env overrides, skipping rather than failing when a model is absent. -
make test-bindings-pytestruns the suite under the bindings venv interpreter so the ABI matches the freshly-built module.make clean-bindingsis a bindings-scoped clean.
-
combine_archives.pydropped object members with duplicate basenames, breaking the combined archives on Linux and Windows. A single static lib can contain several members with the same basename --libggml-cpu.aships twoquants.c.o,libllama.atwollama.cpp.o-- and the Linux path used a plainar x, which extracts them to the same file, so the later silently overwrote the earlier and consumers hit undefined references. macOS was unaffected becauselibtool -statickeeps all members -- which is why this stayed hidden until the new Linux CI leg exercised the combine path. -
combine_archives.pyunconditionally requiredlibggml-blas.a, which a default Linux/Windows CPU build never produces. Now include-if-present, same as libwebp/libwebm. -
make bindingsno longer prints a spuriousuv venvfailure on re-runs.
-
Update llama.cpp to b9318 (from b9284).
-
Update stable-diffusion.cpp to master-650-1ceb5bd (from master-645-645e6e9). New model support behind the existing API;
stable-diffusion.his unchanged, so there is no drift to adopt and the new models work through the existing path. -
WebUI embedding rewritten to track the llama.cpp b9318 restructure. Upstream replaced the asset-embed mechanism with a host generator, and
server-http.cpp-- which chimera compiles itself -- now includes the generated header unconditionally. This broke even the defaultWEBUI_EMBED=OFFbuild. chimera now always builds the generator and links a stub when embedding is off, andmanage.pystages the wholedist/tree instead of four named files.make bump-checklearned to recognize the new layout so the next rearrangement fails loudly instead of crashingmake build. Upstream no longer ships prebuilt assets, soONrequires annpm run buildfirst; full write-up indocs/dev/webui.md.
-
chimera serve --no-webuiwas silently ignored on b9318 embedded builds. Upstream switched to readingparams.ui, of whichparams.webuiis now only a default-initializer alias. chimera sets both. -
make build-with-webuifailure mode + stale guidance. The CMake hint and Makefile comment claimed no Node toolchain was required, which is now false, and pointed at an insufficient recovery command. -
combine_archives.pyrequired libwebp/libwebm archives that the default build no longer produces, so the combine step and every consumer of the archives failed. It went unnoticed becausemake testexercises the executable, not the archives. Both are now include-if-present.
-
Field-level pin-check expansion across all three engine wrappers. Previously only llama.cpp had a meaningful compile-time contract. Whisper (38 asserts) and sd (93) now get the same discipline, so a bump that silently retypes a field chimera assigns fails at a labeled line instead of misbehaving at runtime. The whisper and sd blocks stay in their own TUs per the ggml enum-collision isolation.
-
Python bindings scaffold (
bindings/) over thechimera.hppOOP layer, via nanobind. ExposesLlama,Embedder,Tokenizer,Server, and modality-gatedSD/Whisper, with exception translation, GIL release on the long compute calls, and full field coverage of all five option structs. Coverage is hand-maintained with the round-trip tests as the regression net.make bindings/make test-bindingsauto-provision the toolchain whenuvis available. Documented indocs/bindings.md.
-
Update llama.cpp to b9284 and stable-diffusion.cpp to master-645-645e6e9.
-
WebP and WebM disabled in the sd.cpp build by default. sd.cpp auto-detects its vendored submodules and compiles 128 extra translation units into archives chimera never links, since it reads and writes PNG via stb_image. Saves 3-5 minutes of cold dependency build time; no binary-size change. Re-enable with
SD_WEBP=1/SD_WEBM=1. -
Binary size note. The release binary is ~44 MB, up from ~31 MB on 0.2.1, entirely from code growth in the pinned upstreams (49 sd.cpp commits, 165 llama.cpp commits). All reachable code with dead-strip already active, so there is no easy chimera-side reduction. The
0.2.0.1/0.2.1.1portable backports carry the same OpenSSL-free fix against the lighter pins. -
OpenSSL is now opt-in and statically linked. The release binary previously picked up homebrew's libssl/libcrypto as dynamic dependencies, making it non-portable across macOS machines. The new
CHIMERA_OPENSSLoption defaultsOFF, and the matching environment variable must agree with it or the link fails with undefined SSL symbols.chimera serveis HTTP-only by default; TLS via a reverse proxy is the recommended deployment.
- SD inference regression on macOS Metal after the sd.cpp master-645 bump. Every SD-touching test slowed 3-7x. The cause was upstream's new memory-mapped weights, which default off upstream but were switched on during the dev sync: the Metal encoder fails to resolve buffer IDs for mmap-backed tensors and sd.cpp falls through to per-tensor host-to-device copies. sd.cpp's own backend-aware check reports true on Apple Silicon's unified memory but the encoder fails anyway, so the upstream gating is necessary but not sufficient. mmap is now forced off on Apple and honors
--no-mmapelsewhere, where it is the win it was designed to be. Full investigation log, including the failed bisects and the measurement artifacts that nearly diverted the fix, indocs/dev/regression-b9284-investigation.md.
-
Per-run test-timing capture and diff tooling.
scripts/test.py --timings-outwrites per-test wall-clock plus a version header;--timings-baselineannotates each line with the delta. The regression threshold isabs > 0.5s AND rel > 20%so trivial flips do not crowd the output. -
scripts/test_diff.py-- offline diff between two timings JSONs, sorted by absolute delta. Exits 1 on a regression past both thresholds, making it usable as a CI gate. -
make test-bench/make test-bench-fast. Capture a baseline on a known-good ref, capture current on a suspect ref, diff.
-
Three chimera-specific
/v1/chimera/*endpoints, bound unconditionally. Designed as the read surface a downstream desktop wrapper consumes for its About pane, DB footer, and graceful-exit path, but useful to any HTTP client.-
GET /v1/chimera/info-- JSON form ofchimera info: versions, backends, enumerated ggml devices, capability flags, and build flags (emitted only when set, so the object is empty rather than all-empty-strings on a CPU build). -
GET /v1/chimera/db-- JSON form ofchimera db status. Table names are validated before being spliced into the row-count SQL. The endpoint runs migrations so a fresh response on a never-used host is populated rather than a 500. -
POST /v1/chimera/shutdown-- graceful exit. Returns202and then runs the same teardown as the SIGINT handler after a short delay; without the delay the listener can close before the response is flushed, so the client sees a closed connection instead of the 202.
-
-
make test-fast/make test-slowsplit the suite by wall-clock cost. Three tests account for most of the runtime;test-fastruns the rest for tight iteration on the LLM / embed / RAG / chat paths,test-slowruns only those three for SD and vision work. The set is centralised inSLOW_TEST_RE; update it when a new test crosses ~20 seconds. -
scripts/test.py --slow-only/--no-slow/--exclude.--exclude REGEXis the general complement to--filter; the two compose. The other two are aliases wiringSLOW_TEST_REinto each.
-
CTest registration for external smoke tests, with labels so
make test-external-oopcan filter to the OOP lane. The "inference probe: SKIP" line maps to ctest'sSkippedoutcome so CI can distinguish "passed with fixture" from "ran without one". -
src/chimera/chimera_llama.{h,cpp}-- moves the llama.cpp glue and thecommand_prompt/command_embed/command_tokenizeentrypoints out of the CLI shell and into the library, which previously had no direct text-generation entrypoint.command_chatdeliberately stays in the CLI because it owns terminal I/O, signal handling, linenoise, and color streaming. -
src/chimera/chimera.hpp-- optional header-only OOP layer over the procedural surface. Persistent-handle classes load the model once and reuse it across calls. Not compiled into the archive; consumers include it at their call site. Seedocs/dev/oop-layer.md. -
tests/external/hpp_smoke.cpp-- parallel external smoke test throughchimera.hpp, asserting persistent-handle behavior: the cached context is stable across calls,reset()preserves it,reset(rebuild=true)drops it.
-
chimera::Llamacaches thellama_contextacrossgenerate()calls. Semantics are unchanged -- the KV cache is cleared at the start of each call -- so this is an internal optimization. Sampler and LoRA are rebuilt per call so those knobs take effect immediately; context-creation fields silently no-op untilreset(rebuild=true). -
Streaming callback on
chimera::Llama::generate. Library consumers feeding a WebSocket, notebook cell, or log file now have a hook instead of inheriting stdout streaming. Thebool streamoverload is preserved and routes through the callback path. -
Path-only convenience constructors on every persistent-handle wrapper, delegating to the full-options constructor. The lazy-context design means callers can still override defaults between construction and first call.
-
chimera::Whisperandchimera::SDare now persistent-handle -- no model reload between calls. The CLI and OOP paths share one post-load helper each, so behavior is identical between the subcommand and the wrapper. Load-time fields silently no-op after construction;reset(reload=true)honors new values.
-
stable-diffusion.cpp pin:
master-596-90e87bc->master-637-ef92a00. Bump-check flagged one changed header; the pin-check asserts all held. The API drift on chimera's call path is additive (new sampler methods, schedulers, types, and struct fields), and the two breaking signature changes --generate_videoandnew_upscaler_ctx-- are functions chimera does not call, verified by grep. Upstream also added a libwebp dependency, which builds cleanly but lengthens the cold SD build.- Runtime slowdown (known, accepted on the dev branch). SD-touching tests are 4-8x slower on this pin. The cause is a ggml/Metal ABI mismatch in the shared-ggml build path, not graph-cut offload: every leaf tensor fails Metal buffer-id lookup and falls through to a slow path. Output is still correct. Resolves once upstream llama.cpp ships a matching ggml-Metal change, sd.cpp reverts, or chimera flips to a vendored ggml (~3 MB binary cost). Main branch unaffected.
-
Upstream-drift defenses widened to cover stable-diffusion.cpp and the post-b9200
webui->uirename.-
make bump-checkcovers two repos in one run, with--skip-llama/--skip-sdto scope it. SD'smaster-<count>-<sha>format is auto-reduced to the SHA that raw.githubusercontent.com expects. -
SD pin-check assertions in
chimera_sd.cpp-- struct field types, sentinel enum counts, and seven function signatures. Kept in that TU rather than the central pin-check because SD's ggml enums collide with llama's. -
common_params::uipin-check. Upstream still haswebuias an alias; asserting the new canonical field means that when the alias is dropped, the failure points at the replacement rather than at the disappearing name.
-
-
.github/workflows/release.yml-- fail-fast tag-existence guard. Runs before checkout and reports the missing tag with a copy-pasteable fix and a listing of the tags that do exist, catching typos like0.17vs0.1.7. Replaces three opaque checkout retries. -
scripts/test.py-- distinguish subprocess timeouts from wrong-exit-code, and raise the budget on the two tests that run backend init before reaching their check path. CI macos-metal runners have no real GPU, so the Metal probe falls through over 5-10s and exceeded the old budget -- a timeout that masqueraded as a signal-kill in the 0.1.7 release CI. -
llama.cpp pin:
b9119->b9264. Header drift in six files plus build-system drift not visible in headers; no rename hit chimera's call surface.-
Graceful webui staging across upstream layouts. Around b9200 upstream moved the webui to a Vite project with no prebuilt assets in the source tree, and the hard-coded copy raised.
manage.pynow probes three candidate directories in order and skips with a log when none has assets. -
Define both
LLAMA_BUILD_WEBUIandLLAMA_BUILD_UI, soCHIMERA_WEBUI_EMBED=ONworks on either upstream generation. -
make bump-checkextended with a build-system drift probe. Header diffs miss non-header files -- exactly the failure mode that bit this bump, where the webui asset directory disappeared with no header change. A missing required path now fails bump-check instead of surfacing as an opaquemake buildfailure later. -
docs/dev/webui.mddocuments the manual npm build now required for embedding, and why requiring Node as a hard build dependency would be a regression.
-
-
Documentation sweep addressing a comprehensive audit -- 11 files, no code change. Fixed stale test counts and script names, corrected
doc/->docs/path labels that were lying while the relative links happened to resolve, moved an orphaned image-generation section under its heading, reconciled the "deliberately not bound" route lists against what had shipped, resolved a routes-table contradiction indocs/dev/sqlite.md, and grew the README docs table from 5 rows to 12 with a new-contributor reading order. Deliberately left alone: CHANGELOG history, the webui Variant B post-mortem, the router-mode decision record, and the TODO wontfix block -- these are institutional memory, and pruning them is a regression in discoverability for the next maintainer asking why something was not done.
-
Converted
scripts/test.shtoscripts/test.py. Same coverage, ~1100 LOC down from ~1500. Python because it is cross-platform and the stdlib covers everything the bash shelled out tocurl/sqlite3/python3 -cfor, so the runner no longer needs those on PATH. Per-test wall-clock timing is collected and printed slowest-first.-
New flags:
--smoke,--filter REGEX,--no-color,--verbose,--no-timing. -
Refactor win: eight nearly-identical spawn/poll/teardown blocks now route through one
chimera_serve()context manager handling port selection, readiness polling, graceful teardown, and log capture. -
Behavior changes: the SKIP path honors
--filter, so filter-excluded tests no longer emit fixture-missing noise; and the two SD CLI tests each run their own generation instead of sharing one output, costing ~10s but decoupling them so a regression in one no longer cascades into a SKIP of the other.
-
-
scripts/test_fixtures.py-- local runner for the six adapter / aux-model success-path tests thatmake testleaves as SKIPs. Probes the env vars, validates paths up front, and forwards toscripts/test.py --filter. Partial or invalid configuration exits 2 rather than skipping, matching the misuse-not-SKIP contract used internally.- Why local, not CI: this replaces a deleted draft workflow. Adapter fixtures are individually licensed, mirrors come and go, and the runs are gigabytes long. Chasing moving availability for hosted CI did not pay off.
-
POST /v1/audio/detect-language-- exit-after-detect probe. Returns{"language", "duration"}from the same multipartfilefield as transcription; other transcription knobs are not honored because they are irrelevant for detect-only.-
Why a separate endpoint, not a query parameter: OpenAI's audio surface has no detect-only concept, and conflating the two would force every transcription client to handle a silently-suppressed-transcription path.
-
The integration test asserts a well-formed response, not
language=="en": the bundled fixture is an English-only fine-tune whose language-id pass is noise. Verifying detection correctness needs a multilingual fixture chimera does not ship; the wiring is what gets caught here.
-
-
Opt-in fixture-driven success-path tests for the SD adapter surfaces -- three on the CLI, three on serve, covering LoRA, ControlNet, and PhotoMaker. Gating tests already covered the 400-class responses, but actual generation output went unverified because chimera does not ship adapter fixtures. Env-var gated: unset skips, set-but-missing fails, since partial configuration is misuse rather than absence. Each test is independent, and each serve test spawns its own server -- coupling four orthogonal opt-ins behind one gate would let partial fixture coverage silently skip everything. The bar is exit 0 with a non-empty PNG, or HTTP 200 with non-empty base64: the value is catching wiring regressions, not validating adapter math.
-
chimera serveper-request LoRA selection on all three image endpoints, plusGET /v1/images/lora-adapters. Closes the final numbered item of the server parity roadmap.-
Server-init: repeatable
--sd-lora <name>=<path>registers a closed allowlist. SD reloads LoRA tensors per generate, so the map is pure metadata and no files are opened at startup. Malformed specs fail at startup, not on first request. -
Closed-set by design. Requests reference adapters by name only and cannot supply filesystem paths -- the safer default for any deployment beyond personal localhost use. A
--sd-allow-lora-pathsopt-in could gate path mode later. -
Per-request shape is an array of objects,
[{"name","scale"}], chosen over the CLI's"name:scale"string form because future per-adapter fields extend cleanly without inventing parsing rules. -
Precise 400s for a non-array body, a non-object element, a missing name, an unknown alias (listing the known ones so the client can self-correct), and a non-numeric scale.
-
-
chimera serveper-request PhotoMaker on all three image endpoints, closing the last server-init gap on the SD side.-
Server-init:
--sd-photo-maker,--sd-pm-id-dir(each subdirectory becomes one named identity set), and--sd-pm-id-embed-path. The identity directory is scanned eagerly at startup so a misconfigured set fails fast; non-image files are skipped silently while an empty subdirectory is a configuration error. -
JSON-base64 shape sidesteps the multipart-plural design question. OpenAI's body shape has no precedent for repeatable image uploads, so identity images travel as a base64 array rather than an invented multipart convention. Elements accept a raw payload or a
data:URI. -
Two request shapes with explicit precedence: fully per-request
pm_id_imageswins over the admin-curatedpm_id_image_set, matching precedence elsewhere in chimera. -
Precise 400s for every PM field against a server missing the corresponding flag, an unknown set name (listing the known ones), a non-array or non-string element, a decode failure, and an undecodable image (415).
-
-
chimera serveSD perf/offload flag family -- 16--sd-*flags, each mirroring achimera sdflag. With this, the fullchimera_sd::LoadParamssurface is exposed on serve (30 flags total). The four enum flags are CLI11-validated so a bogus value exits before model load with the accepted set listed.--sd-threadsis guarded on> 0so the chimera-side "leave default" sentinel does not override sd's thread auto-pick. -
chimera serveSD split-checkpoint flag family -- 13--sd-*path flags, sochimera sdcommand lines port 1:1. Gating broadened from requiring--enable-imageto accepting either a combined checkpoint or a diffusion model, the same allowance the CLI uses. Unlocks serving Flux, SD3, Z-Image, and Qwen-Image; the existing per-request fields work for them without extra wiring because the engine path is shared. -
chimera serve --sd-control-net+ per-requestcontrol_image/control_strengthon all three image endpoints -- the highest-impact server-init gap on the SD side. This is where the fullchimera_sd::LoadParamswas first wired through, making the two flag families above one line per flag. A request supplyingcontrol_imagewithout the flag returns 400 naming it.- Honest correction: the roadmap framed this step as an unblocking trio.
--sd-upscale-modelunblocks nothing -- the upscale model is per-request and loaded fresh each generate, already covered -- and was removed.--sd-photo-makeris half a feature without its per-request bundle and was split out.
- Honest correction: the roadmap framed this step as an unblocking trio.
-
chimera serve --audio-*flags + per-request VAD bundle. Plumbschimera_whisper::LoadParamsthrough the serve path and unblocks per-requestvad=true. The VAD model path is server-init-only, deliberately not accepted from clients, to avoid letting external callers direct the server to read arbitrary filesystem paths. Existing--enable-audiodeployments are unaffected; VAD is opt-in on both sides. -
Audio wave 2 -- four decoder-fail thresholds on the transcription endpoints. The sentinel is NaN, not a negative number, because
logprob_thold's own upstream default is negative and a negative sentinel could not distinguish "leave the default" from "explicitly requested the default".no_fallback=truestill wins when both arrive, mirroring the CLI. The per-request audio surface is now closed; the remaining CLI flags all need either a model at startup or a dedicated endpoint. -
Image wave 2 -- hires-fix and cache/SCM bundles, 13 fields into the shared filler so all three endpoints pick them up at once. Validation runs through the same parser the CLI uses, so HTTP errors are byte-identical, and fires before generate so a typo does not waste an inference pass. The
Modelupscaler accepts the field but fails downstream because the serve path had no upscale model at startup -- blocked on the server-init work above; the latent upscalers work. -
Image wave 1 -- 14 per-request fields into the shared filler: sampler and generation core, VAE tiling, skip-layer guidance, and custom sigmas. Sentinel defaults preserve upstream behavior for omitted keys.
skip_layersandsigmasaccept a JSON array or a comma-separated string, matching the CLI's shape, and reject malformed entries with the offending token named.- Unrelated: a Metal-side crash surfaced when running two image generations back-to-back on one server context. It reproduces without wave-1 fields set -- pre-existing sd.cpp/Metal behavior, not caused by this wiring.
-
Audio wave 1 -- nine per-request fields, all mapping to existing engine fields: decoding strategy, region of audio, segment shaping, and stereo diarization.
temperaturewas previously parsed and discarded. Diarization mirrors the CLI's energy-ratio classifier and stamps both the structuredSegment.speakerfield and the text prefix, so every response format reflects speakers. Mono uploads return 400 before transcription runs.
-
chimera whisper --detect-language-- exit-after-detect language identification. whisper.cpp short-circuits before any decode pass, so chimera writes just the ISO code and no format files are produced, which is the right behavior for a probe. English-only models do not really detect language and can return a wrong code; that is a model-side artifact, documented in the coverage table. -
chimera whisper --diarize-- stereo speaker diarization, mirroring whisper-cli's per-segment energy-ratio algorithm and threshold. The WAV reader now keeps a per-channel view alongside the downmixed samples, populated only for multi-channel input. The label flows out two ways: a structuredSegment.speakerfield and a text prefix, so the existing format writers render it without change. Mono input with--diarizeexits withBadInputbefore model load. -
chimera whisperconstrained decoding:--grammar,--grammar-file,--grammar-rule,--grammar-penalty. whisper ships its GBNF parser inexamples/rather than in the library, so it is vendored verbatim (~450 LOC, MIT) with a header noting the source version for clean diffing on the next bump.--grammar-fileis a chimera-side ergonomic that whisper-cli lacks. Mutual exclusion and bad-rule validation fire before the WAV load. -
chimera gen/chimera chatlong-tail closer -- 19 flags, closing every documented not-implemented row in the coverage table.-
Extra samplers:
--typical,--top-nsigma,--xtc-probability/--xtc-threshold,--dynatemp-range/--dynatemp-exp. -
Sampler chain ordering:
--samplers, same shape as llama-cli's flag.--sampler-seqis not added separately because it is redundant. -
Perf:
--threads-batch,--swa-full. Vision-token budget:--image-min-tokens,--image-max-tokens. -
MoE expert offload:
--cpu-moe,--n-cpu-moe N. Both stack with--override-tensor. -
Manual overrides:
--override-tensor(upstream's parser isstaticso it could not be reused; the buffer-type lookup reports available names on a typo) and--override-kv(reuses upstream's parser so the grammar is exact). -
Activation steering:
--control-vector,--control-vector-scaled,--control-vector-layer-start/-end. Layer defaults mirror upstream. -
Reclassified out of scope, documented in place rather than silently skipped:
--keepis architecture-mismatched (it drives upstream's context-shift loop, which chimera's KV-prefix chat path does not run), and--ctx-checkpoints/--checkpoint-every-n-tokens/--cache-ramare server-only fields the CLI path never reads.
-
-
chimera chat --reasoning-budget Nis now enforced at the sampler level, replacing the "parsed but not yet enforced" warning. The earlier claim that this required restructuring the sample loop was wrong: the integration point is entirely upstream ofcommon_sampler_init. The active template's thinking tags are probed once at startup, and--reasoning-budget-messageis tokenized as the forced termination sequence so generation ends cleanly inside the reasoning block instead of cutting mid-thought. A template advertising no thinking tags warns once and the budget is ignored -- warn but do not fail, matching chimera's handling of marginally-wrong input. -
Doc-only:
--disable-image-metadatareclassified out of scope. sd-cli's flag disables a metadata chunk written by a patchedstbi_write_pngin sd's vendored fork; chimera links stock stb_image_write, so its PNGs are already metadata-free and there is nothing to disable. Writing generation params for parity would be net-new functionality, not a port. -
chimera sd --embd-dir <dir>-- textual-inversion directory, mirroring sd-cli: non-recursive scan, filename stem becomes the prompt token. Invalid paths exit before model load. -
chimera sdcache / SCM bundle ----cache-mode,--cache-option,--scm-mask,--scm-policy, mirroring sd-cli's two-flag surface exactly so command lines port 1:1.--cache-optionuses sd-cli'skey=valuegrammar with the same per-mode key mapping. Validation runs before model load so a typo does not waste one. -
chimera sdcoverage closer -- 33 flags across six rounds, closing all remaining unforced gaps. All defaults preserve existing behavior: floats use negative sentinels because the upstream defaults areINFINITYor specific positive numbers, ints use 0 because that is upstream's own "leave default".-
Perf / offload:
--fa,--no-mmap,--max-vram,--clip-on-cpu,--vae-on-cpu,--control-net-cpu,--force-sdxl-vae-conv-scale. -
Sampler core:
--img-cfg-scale,--eta,--timestep-shift,--sigmas,--prediction,--lora-apply-mode. -
Model-loading completers:
--taesd,--clip-vision,--llm-vision,--tensor-type-rules,--photo-maker. -
PhotoMaker generation bundle:
--pm-id-images-dir(an empty scan result is an error so a typo does not silently disable PM),--pm-id-embed-path,--pm-style-strength. -
Reference images:
--ref-image(repeatable),--increase-ref-index,--no-auto-resize-ref-image. -
Hires-fix bundle:
--hires,--hires-upscaler,--upscale-model,--hires-width/-height/-scale/-steps/-denoising-strength/-upscale-tile-size. -
Out of scope, documented: video-only flags, standalone-mode flags, shell features, and chroma/qwen-specific tuning.
-
-
chimera whispercoverage closer -- 22 flags. Every numeric knob uses a sentinel that leaves the upstream field untouched, so existing invocations are byte-identical.-
Region of audio:
--offset,--duration. The sample-offset form is internal to whisper-cli's WAV reader and not exposed by the params struct, so only the ms forms are wired. -
VAD:
--vadplus--vad-model(required when--vadis set) and six tuning knobs inheriting whisper's defaults. -
Segment shaping:
--max-len,--max-tokens,--split-on-word. Decoder-fail thresholds:--temperature-inc,--entropy-thold,--logprob-thold,--no-speech-thold(NaN sentinel, sincelogprob_thold's default is itself negative). -
Perf:
--audio-ctx,--tinydiarize. Token suppression:--suppress-regex,--suppress-nst. Context params:--flash-attn,--no-gpu,--device. -
Parallel decode:
--processors N. Upstream warns this degrades accuracy at chunk seams, so the serial default is intentional.
-
-
chimera sdskip-layer guidance + high-noise model slot.--skip-layersand its three scalars; an empty list disables SLG regardless of the scalars, and a non-integer entry fails rather than being silently dropped.--high-noise-diffusion-modelexposes only the model-loading slot: the rest of the--high-noise-*family is video-only in sd.cpp and chimera-sd is image-gen only. -
chimera sdperf + RNG knobs:--diffusion-conv-direct,--vae-conv-direct(measurable win on modern dGPUs), and--rng/--sampler-rng.--sampler-rng cpuis what matches ComfyUI seeds across implementations. -
chimera embedoutput-shape gaps:--embd-output-format(''preserves current output byte-for-byte;array,json,rawadded),--embd-separatorfor one vector per split piece,--attention causal|non-causal(a required override for some encoder checkpoints), and--pooling rankso the existing subcommand can drive cross-encoder rerankers. -
chimera sdControlNet + VAE tiling + model-loading completers. ControlNet:--control-net,--control-image,--control-strength. VAE tiling lets large outputs render without OOM at a small quality cost. Completers:--clip-g,--type,--lora(repeatable),--lora-model-dir-- closing the asymmetry where--lorahad landed on serve/gen/chat but not sd. -
chimera sd --guidance/--flow-shift. Closes the Flux/SD3 generation-side gap that paralleled the earlier Z-Image model-loading fix. -
chimera whisperdecoding-strategy flags:--prompt(the most-requested missing flag),--carry-initial-prompt,--beam-size,--best-of,--temperature,--no-fallback. -
chimera info --list-devicesprints one ggml device name per line, suitable for piping into--device. -
Whisper output formats:
--output-fileplus--output-txt/-srt/-vtt/-json/-json-full/-csv/-lrc, combinable. Segment-level timestamps are auto-enabled whenever any format file is requested -- independent of--timestamps, which still controls inline streaming -- so the writers do not emit garbage times. CLI11 forbids multi-char short flags, so upstream's-osrt-style aliases are long-only here. -
Broad llama.cpp CLI coverage uplift on
gen/chat/embed-- 20 flag groups, ~40 options, driven by the coverage audit: the sampler long tail (grammar, JSON schema, penalties, mirostat, the DRY family, logit bias), performance and context (--flash-attn, KV cache types,--ubatch-size),--lora(closing the asymmetry with serve), the RoPE/YaRN family, multi-GPU and device selection,--no-mmap/--mlock(mmap was previously hard-coded), and chat-only template and reasoning flags.--list-deviceswas skipped as a better fit forchimera info. -
Vulkan build validated end-to-end. Both an AMD iGPU and an NVIDIA dGPU enumerate on the same host, so dispatch is not vendor-locked. The binary is 92M on Linux x86_64, roughly half the CUDA build, because Vulkan ships compact SPIR-V rather than per-arch cubins -- the smaller-footprint option when raw CUDA perf is not required.
-
Split-checkpoint and Z-Image support on
chimera sd.--diffusion-model,--vae,--clip-l,--t5xxl,--llm,--offload-to-cpu,--diffusion-fa.-mis no longer required: pass it for combined checkpoints, or use component paths for split layouts (Z-Image, Flux, SD3). Validated end-to-end on Z-Image-Turbo on a CUDA build.
-
Windows build works end-to-end across CPU, CUDA, and Vulkan. Two regressions blocked it:
-
The MSVC branch of the library link never added the GGML backend libs, so any GPU build failed to resolve the backend registration symbol that
ggml-backend-reg.objcalls directly. The Linux branch already had the per-backend append. -
The Makefile's Python autodetect picked the Microsoft Store
python3.exeshim, which exists on PATH but only prints an install prompt and exits non-zero. Candidates are now probed with--versioninstead ofcommand -v.
-
-
KV-cache slot snapshots and LoRA hot-swap routes -- four upstream handlers previously called out as deliberately not exposed.
-
GET /slots-- per-slot status. -
POST /slots/:id_slot?action={save,restore,erase}-- KV-cache snapshot I/O;saveandrestorerequire--slot-save-path. Skipping prefill on a multi-thousand-token system prompt or RAG context turns a multi-second first-token latency into a sub-second restore. -
GET /lora-adaptersandPOST /lora-adapters-- list and re-weight adapters without a model reload. Adapters must be on the startup--loralist; the route can re-weight but cannot register new files at runtime.
-
-
--slot-save-path <dir>and repeatable--lora <path[:scale]>onchimera serve. The scale parser uses the rightmost colon and falls back to treating the whole string as a path, so Windows drive-letter paths round-trip.--slot-save-pathis normalized to end with a separator, which upstream's own parser does but chimera bypasses. -
Pin-check asserts for the four new route handlers, so a bump that renames one fails that TU first instead of cascading through
chimera_serve.cpp. -
Chat history read endpoints over HTTP, bound only when
--persist-chatsis on so there is no orphan endpoint without a write path producing data.GET /v1/chatslists,GET /v1/chats/:idreturns metadata and ordered messages including interrupted turns,GET /v1/chats/searchruns FTS5 with highlighted snippets. Unknown ids return 400 rather than 404 because cpp-httplib's default error handler overwrites 404 bodies. Closes the read-side gap: until now--persist-chatswas write-only. -
chimera serve --public-path <dir>mounts an external directory atGET /, true parity with llama-server's flag. Independent ofCHIMERA_WEBUI_EMBED; when both apply the mount point wins, and the startup banner reports both so precedence is visible. Lets users point chimera at their own UI or a third-party frontend without baking it into the binary. A chimera-specific UI was prototyped against this flag and abandoned as a poor fit for the busybox identity; seedocs/dev/webui.md. -
chimera db backup --to <path>-- snapshot viaVACUUM INTO, producing a single defragmented file with no WAL/SHM siblings to copy. Refuses to overwrite.chimera db vacuumdefragments in place; its error message names the lock failure mode explicitly, since that is the only realistic failure once the migration ran. -
Build-time modularity for whisper.cpp and stable-diffusion.cpp via
CHIMERA_WITH_WHISPER/CHIMERA_WITH_SD(AUTOdefault, following the existing tri-state pattern). When OFF the modality disappears completely: subcommand, serve flags, routes,chimera infoline, and translation units.gen --mmproj --imagestays available regardless because it routes through libmtmd, not chimera_sd. A text-only Apple-silicon build drops from ~34 MB to ~12 MB. Caveat: thewhisper --help/sd --helpsmoke lines pass spuriously on OFF builds because CLI11 falls through to the global help. -
Static library form for chimera.
libchimera.ais now produced alongside the executable, which becomes a thin shim over the CLI TU. Link libraries arePUBLICand the platform whole-archive options areINTERFACE, so a downstream CMake consumer inherits both the transitive archive list and the wrappers that keep backend static initializers alive. Compile defines split by audience: modality gating and version strings propagatePUBLICso consumers see the same gating chimera's own code does. Opens a use case that did not exist -- Python bindings, plugin hosts, or non-CMake apps can embed chimera's serve / chat-store / RAG plumbing in-process instead of spawning the binary and parsing output. -
scripts/combine_archives.pybundles chimera's ~19 transitive archives into two grouped outputs:libchimera_thirdparty.a(normal-linked) andlibchimera_ggml.a(whole-archived). The split is forced by the linker contract, not aesthetics: a single merged archive would force-load duplicate definitions of generic helpers defined independently in two upstreams, and the consumer link would fail. Splitting along the whole-archive boundary keeps duplicates pruned while backend constructors still run. Includes a guard that fails loudly if an edit ever pulls whisper's or sd's sibling ggml builds into the inventory, which would produce silent duplicate-symbol corruption. Scope is host-optimized builds, not cross-machine binary distribution. -
tests/external/-- standalone consumer smoke test that deliberately does not use chimera's own CMake targets, linking the three archives by raw path the way a non-CMake consumer would. Four probes: backend device count (proves the whole-archive wrapper was applied -- silently 0 without it), a thirdparty symbol, a chimera symbol, and an optional model-gated inference probe asserting non-zero finite logits. Newmake combineandmake test-external-smoketargets. -
docs/dev/combine_archives.md-- design and status doc for the three-archive split: scope, motivation, why the deps had to split into two, the per-platform bundling tools and the GNU-ar footguns the script avoids, what the bundle does not do (consumers still owe the whole-archive wrapper), deferred questions, and a validation plan with per-platform status markers. -
Experimental: embedded llama.cpp web chat UI, opt-in via
-DCHIMERA_WEBUI_EMBED=ON. Costs ~6 MB on a stripped build -- the baked byte arrays live in the data section, whichstripcannot drop. Disable at runtime withchimera serve --no-webui. The UI is pinned to whichever llama.cpp version chimera vendored, so UI updates require a rebuild.docs/dev/webui.mdcovers the wiring rationale and the seams worth watching.
-
src/layout split along the library / CLI boundary the CMake targets already enforced: the CLI shell moved tosrc/chimera_cli/, everything else stays insrc/chimera/, which now contains library code only.src/chimera/keeps the project name because it produces the artifact users link; the CLI is the late addition. Pure structural refactor, no source edits to any moved file. -
chimera_serve.cppsplit into per-modality translation units (2249 LOC -> 871), leaving lifecycle,build_common_params, and the secondary-context helpers behind. New TUs:chimera_serve_audio.cpp,_images.cpp,_rag.cpp,_chat_persist.cpp,_chats_read.cpp, pluschimera_serve_internal.hdeclaring the seams. Mechanical, no behavior change. Closes a self-prescribed TODO whose "currently ~600 LOC" estimate was stale by a factor of four. -
chimera.cpp::main()extracted from 475 lines of inline CLI11 wiring to a 46-line parse-and-dispatch driver: aParsedClistruct, elevenbind_*_cmdhelpers, and adispatch_cli. Mechanical; no behavior or--helpchange. -
Stale "Phase N" labels stripped from docs and from the last inline comments in the source tree. The labels referred to a one-time delivery roadmap and have been noise to fresh readers since each feature shipped. Also corrected two doc claims that had been overtaken by shipped work.
-
README.mdgains a "Dropping modalities at build time" section with a table of what disappears per modality and the binary-size delta. -
Security hardening: per-request payload cap. New
CHIMERA_HTTP_PAYLOAD_MAX_BYTES(default 256 MB). Upstream's default is unbounded, so a multi-gigabyte upload to the transcription or vector-store routes could OOM the process; requests are now rejected with 413 before any handler runs. Implemented as a chimera-local patch to the vendoredserver-http.cpp, reapplied idempotently bymanage.pyso it survives everymake deps. -
FTS5 metacharacter robustness in
GET /v1/chats/search. Queries containing FTS5 operators or unbalanced quotes returned 500 with the raw SQLite error. The handler now mirrors the vector-store pattern: retry once with the input wrapped as a literal phrase. Both search surfaces now guarantee that a free-text query never errors -- it matches the literal phrase. -
Pin-check coverage for seven previously-unpinned
common_paramsfields, each touched bybuild_common_params. A bump that renames or retypes one now fails with a chimera-specific message naming the dependent call site. -
Error messages distinguish "model file not found" from "loaded but not parseable".
load_llama_modelstats the path first and reports three distinct failures -- missing, directory, unparseable -- where all three previously collapsed into one generic line. -
chat_id_existsno longer swallows DB-open failures as 404. A bare catch-all silently converted a corrupt DB or failed migration into "no such chat", serving a 404 for an operator-actionable 500-class problem. -
persist_non_streamingandpersist_streaminglog instead of swallowing exceptions. Operators now see persistence failures rather than deducing them from missing assistant content. -
scripts/test.shgains 12 tests covering the slots and LoRA routes (including a save-to-disk-and-restore round trip) and the chat history endpoints plus--public-path, seeded with a deliberately rare token for unambiguous FTS5 assertions. Suite total is now 44. -
docs/serve.mddocuments the new routes and flags;docs/dev/server-router-mode.mdis a new decision record on why chimera does not implement llama-server's router mode. The useful part is the concurrency analysis: router mode buys model-residency parallelism (several distinct models hot at once) but not compute parallelism on a single GPU, since kernel submissions serialize at the device. Three concrete revisit triggers are recorded.
-
ggml_dev_type_labelnow handles the fifthGGML_BACKEND_DEVICE_TYPE_*enumerator added upstream, silencing a-Wswitchwarning on every build. -
chats-searchtest assertion was over-specific in two compounding ways: it checked only the first hit, though FTS5 rank order between two matching messages is implementation-defined, and it compared case-sensitively though FTS5 matches case-insensitively. The endpoint was correct all along; the test's narrower assumption was the bug. -
chat-kv-cachetoken budget raised from 32 to 64. The 1B model often prepends a preamble before echoing the recall target, pushing it past the cutoff and producing intermittent failures.
-
Sentence-aware chunking for
chimera index ingestand the equivalent serve route, replacing the fixed token-window splitter. Text splits on sentence terminators and paragraph breaks, then greedy-packs into the collection's token budget, with overlap carried as whole-sentence tails. Pathological input (run-on sentences, source code, base64) falls back to the token splitter for the offending span, so ingestion never refuses input. Improves retrieval on prose because embedded text now corresponds to complete thoughts rather than arbitrary mid-sentence cuts. -
Hybrid retrieval with reciprocal-rank fusion. A new FTS5 table over document text is created in schema v5 and back-populated from existing rows, with triggers keeping it in sync. Three modes:
semantic(unchanged KNN),lexical(BM25, falling back to a phrase-quoted query on FTS5 syntax errors so user-typed input never 500s), andhybrid(default; merges the top hits from both legs by RRF score). Exposed aschimera search --modeand a"mode"request field. Lexical-only short-circuits the embedding model load -- a BM25 lookup does not need a 100+ MB GGUF paged in. Making hybrid the default changes results for existing collections, which keep working because the FTS5 index is rebuilt during migration. -
X-Chimera-Chat-Idrequest/response header on/v1/chat/completions. Closes the gap where--persist-chatsproduced one chat row per request, since the OpenAI API has no chat id. Without the header, a chat row is created before delegating (so its id is known in time for a streaming response) and echoed back; with a known id, only the last message plus the reply are appended; an unknown id returns 404 and a non-integer 400. Persistence still runs after the stream finishes, so a failed persist never breaks the request. -
Ctrl-C in
chimera chat --persistnow persists the in-flight assistant turn instead of discarding it. A SIGINT handler installed only for the duration of generation flips an atomic the token loop polls; the streamed content is written with a newpartialflag and the REPL prints an interrupted notice. Outside generation, Ctrl-C still goes to linenoise. Schema v4 adds the flag, backfilling existing rows to complete.--listand--resumeshow the interrupted count.
-
Schema v5 adds the documents FTS5 table with content-table backing and three sync triggers, rebuilding the index during migration so hybrid search works against previously-ingested documents without re-ingest.
-
Default retrieval mode flipped from semantic-only to hybrid. Recoverable via
--mode semantic. Justified because hybrid is a superset for prose: keyword recall is added, helping proper nouns and rare terms, without losing conceptual matches. Cost is one extra FTS5 select per request. -
scripts/test.shis now 32 tests (was 23), adding four hybrid-retrieval tests (including a default-mode regression guard) and fiveX-Chimera-Chat-Idtests ending in a DB-state check that proves the unknown and malformed cases never invoked the inner handler. Skipped, not failed, wherepython3/curl/sqlite3are absent. -
Privacy documentation for persistence features. A new section in
docs/serve.mdenumerates exactly what each persistence flag records -- and explicitly what it does not: client IPs, headers, API keys, HTTP bodies -- where the DB lives per platform, and how to wipe it. A matching table in the cheatsheet covers all five write-to-disk surfaces. Closes the gap where opt-in persistence shipped without a user-facing privacy note.
TODO.mdpruned of shipped items. Four were moved to a new "Out of scope (wontfix)" section with brief rationale rather than silently deleted, so the same proposals are not re-litigated from scratch:POST /propsruntime mutation (conflicts with "the CLI is the config"), multi-tenancy / router mode (one process = one model is core to the busybox identity), HTTPS direct serving, and auth beyond--api-key(both reverse-proxy territory). The web chat UI item was restructured as two independent opt-in variants.
-
chimera serve --enable-embeddings <model.gguf>loads a dedicated embedding model alongside the LLM and routes/v1/embeddingsto it, keeping the primary LLM generative. Wins over--embeddingsif both are passed. -
chimera serve --reranking <model.gguf>loads a cross-encoder and bindsPOST /v1/rerank, matching the pooling toggle llama-server uses. Natural follow-up to vector search: top-N hits, rerank, top-k, LLM.Both share a
SecondaryServerCtxhelper -- heap-allocated and non-movable becauseserver_routesholds a reference to its params -- with one worker thread each. Shutdown terminates secondaries before the primary so their loops unblock cleanly. -
Word-level timestamps in
/v1/audio/transcriptions. Passtimestamp_granularities=["word"]withresponse_format=verbose_jsonfor a top-levelwords[]array. The implementation filters whisper's timestamp special tokens and groups the rest into words by leading-space boundaries. -
POST /v1/audio/translations-- the existing transcription handler bound withtranslate=true; whisper does the to-English translation inline. -
Token-based chunking for ingestion, replacing the character-window splitter. Chunks are sized in tokens of the loaded embedding model's vocab, defaulting to 512 with 64 overlap to match the input limit of common encoders. Eliminates the 400-800 token variance of the old proxy and guarantees chunks fit through
embed()without truncation. -
Per-collection chunk and distance knobs in schema v3, set at
index createand read at ingest unless overridden per call:--distance cosine|l2|l1(default cosine, right for the L2-normalized embeddings chimera produces by default),--chunk-tokens,--chunk-overlap. Also accepted on the vector-store create route and reported undermeta. Existing rows backfill to sensible defaults. -
Persistent embedding cache via
--cache-embeddingson embed / ingest / search / serve, memoizingembed(text) -> vectorto SQLite so repeated work skips the model. The key is the model fingerprint plus a hash of the text; the fingerprint hashes the file size and its head and tail, and since GGUFs store metadata in the header this catches re-quantization, re-training, and architecture swaps without tracking model names. Vectors round-trip bit-identical. Default off, because cache rows take real disk (~1.5 kB per 384-dim row; ~150 MB for 100k entries). -
make bump-checkfetches upstream's server headers at a target llama.cpp ref and diffs them against the vendored copies, listing added and removed symbols. These headers are not part of upstream's stable API -- a CI failure the week before was caused by a new symbol appearing in one -- and this makes the surprise visible at bump time. It is a pre-bump audit step, not a CI guard. -
SD log capture for image-generation error bodies. sd.cpp's log lines are mirrored into a small ring buffer and the most recent are appended to the HTTP error body on failure, so the descriptive line -- buft failures, rejected sampler names, ggml backend errors -- reaches the client instead of only stderr. Does not help when sd.cpp aborts the process via
GGML_ASSERT.
-
Release workflow packages each binary as a compressed archive instead of a raw executable. The
.sha256sidecars were dropped because the release UI already shows a checksum per asset. The Windows matrix target was renamed so the archive name no longer ends in.exe.zip. -
Non-WAV audio in
/v1/audio/transcriptionsis now framed as a deliberate non-feature rather than a temporary limitation, with the error body pointing at anffmpegone-liner. Bundling codecs is out of scope: single-header decoders give partial coverage, and libavcodec does not pull its weight. Marked "do not revisit without a concrete user request". -
make test-goldenspawnschimera serveagainst fixed models, hits nine routes with fixed payloads, normalizes the volatile fields, and diffs against checked-in goldens. Catches runtime drift in upstream's route lambdas -- the class of regression where a smoke test cannot see a JSON key being renamed or dropped. Backend-pinned so the goldens stay portable across dev machines and CI. -
Widened
make bump-checkto coverllama.h,common.h,arg.h,chat.h, andmtmd.halongside the server headers -- the full set chimera links or compiles against. -
Compile-time pin assertions (
chimera_pin_check.cpp). Every route handler chimera binds, everycommon_paramsfield it pokes, the pooling-type enum values, and key function signatures are asserted. A bump that renames a handler or retypes a field now fails with a labeled error instead of cascading into a cryptic instantiation failure deep inchimera_serve.cpp. Verified against a deliberate type flip. -
.github/PULL_REQUEST_TEMPLATE.mdwith a dependency-bump checklist, anddocs/dev/maintenance.md, a strategy doc covering where breakage lands across upstream changes and the bespoke-vs-vendored map for triage. -
make test-db-migratebuilds a v1-schema DB, drives migration, and asserts the version advances, pre-existing rows survive, and later additions are backfilled. Verified to catch regressions. Pre-empts the class of bug where a new migration silently breaks the upgrade path for users still on the original schema.
chimera index ingest --chunk-chars, replaced by--chunk-tokens. The unit changed too, so a literal--chunk-chars 2048is closer to--chunk-tokens 512for English.--chunk-overlapsurvives with its unit changed to tokens.
-
chimera infoprints chimera's version and platform, then one block per bundled component: llama.cpp (versions, backends, enumerated devices with type tags, capability flags), whisper.cpp and stable-diffusion.cpp (versions, parsed CPU features), and sqlite + sqlite-vec versions. The output shape matches cyllama'sinfoso users hopping between the two see one familiar format. A single command captures every version and backend chimera saw at link time and startup, which makes it a useful bug-report artifact. -
chimera servephase 5: server-side chat persistence + the OpenAI Responses API.-
--persist-chatswraps the chat-completions handler with per-request DB writes. Clients see exactly the same bytes; a copy is saved after each exchange. Streaming SSE is handled, not just non-streaming JSON. Persistence errors are logged and never break the client's response. The DB is shared with the CLI and with--enable-rag. -
POST /v1/responsesis now bound. It was deferred in the original server work because serve was stateless across requests. The API is still stateful only within one invocation -- server-context holds thread state in-process -- but the underlying chat traffic is persisted, so audit-log use cases work. -
One chat row per request, by design: the OpenAI API has no chat id, so multi-turn clients that resend the full conversation produce overlapping rows. The duplication is the cost of staying API-compatible.
-
-
chimera servephase 4: OpenAI-shaped vector-store / RAG routes over the SQLite + sqlite-vec layer, opt-in via--enable-rag <embedding.gguf>and sharing the CLI's DB. Six routes: list, create, stats, delete, ingest (multipart upload or JSON body), and KNN search.-
One
Embedderper server, serialized on a mutex like whisper and SD. SQLite connections are opened per request rather than pooled -- open is microseconds in WAL mode, so the pool ceremony is not worth it. -
One embedding model per server in this cut; a collection recorded with a different model returns a clear 400.
-
Two server-http warts worked around: the wrapped cpp-httplib subset exposes only GET and POST, so drop is
POST :name/deleterather thanDELETE-- adding the verb would mean patching the vendored source and carrying that per llama.cpp version. And upstream's error handler unconditionally overwrites 404 bodies with a generic payload, so not-found errors return 400 to keep chimera's specific message visible. Defensible: the URL pattern matched, the named resource inside it did not.
-
-
Persistent chat history (phase 3), the secondary driver for embedding SQLite. New
chatflags:--persist(off by default; each turn becomes a row, with reasoning captured to its own column),--resume <id|last>(replays history into the in-memory conversation, and takes the model path from the saved row if-mis omitted),--listand--search(print-and-exit, no model load), and--db. In persistent mode/clearstarts a fresh chat row rather than wiping the active one, and/regendeletes the trailing assistant messages so the next attempt replaces them cleanly.Scope limits: interrupted turns are not saved (the DB stays consistent, the in-flight text is lost); attached media paths are recorded for forensics but
--resumedoes not reattach them; cross-model resume warns rather than blocks, and the template comes from the new model; and stored reasoning is for record-keeping, not for re-priming the KV cache. -
Vector store / RAG (phase 2), the primary driver for embedding SQLite -- a personal RAG index built entirely against local models, no server required.
chimera index createrecords the embedding model's dimension and creates a per-collection vector table;index ingestchunks and embeds files or globs, reusing oneEmbedderacross the batch so the model loads once;index list / stats / drop; andchimera searchembeds the query and prints top-k chunks with distance and source.Under the hood, the embedding loop was extracted from
command_embedinto a reusableEmbedderclass, and the SQL lives in a new vector-store module.Scope limits: chunking is character-based (token-based deferred); one embedding model per collection, enforced at ingest with a clear dim-mismatch error; and no re-ingest deduplication, so ingesting the same file twice duplicates chunks.
-
Embedded SQLite + sqlite-vec (phase 1). No user-visible change beyond a diagnostic subcommand; this lays the rails for RAG and chat history. Both are vendored as single translation units at pinned versions and compiled directly into chimera's target rather than built as separate libraries. New
chimera_dbmodule exposes an RAII connection, an XDG-compliant default path resolver, andopen_and_migrate. The v1 schema lands chats, messages with an FTS5 mirror, collections, and documents; per-collection vector tables are deliberately created on demand in phase 2 instead. Newchimera db statusverifies the extension actually loaded by executing a query against it, not merely that the symbol linked. Binary grows ~1.5 MB. -
chimera serve: bind the "group A" server-context routes previously deferred -- a registration change with no new handler logic. Newly always available:GET /metrics(with the upstream flag forced on so it works without extra flags),GET /props, legacy unprefixedPOST /chat/completions, the Anthropic Messages compat pair (so Anthropic-SDK-shaped clients can point at chimera unchanged),POST /infill,POST /tokenize//detokenize, andPOST /apply-template.POST /propsis deliberately not bound: runtime mutation of server state conflicts with chimera's "the CLI is the config" stance. -
chimera servephase 3:POST /v1/images/{generations,edits,variations}via stable-diffusion.cpp, opt-in with--enable-image <sd.gguf>. Output is PNG-encoded and base64'd into OpenAI's envelope. Concurrent requests are serialized because sd.cpp's generate is not thread-safe on a shared context.Like the audio phase, this does not bind an upstream handler -- there is none for image generation -- so chimera registers its own on the shared HTTP context, the same way llama-server registers its non-LLM routes.
Scope limits:
response_formatisb64_jsononly, since chimera serve has no static-file backend to host URLs from;model,user,quality, andstyleare ignored; and step progress goes to stderr, since the OpenAI spec defines no SSE for images.chimera_sd.cppwas reorganized behind a publicchimera_sd::API, withcommand_sdbecoming a thin caller that adds the CLI-only conveniences. -
chimera servephase 2:POST /v1/audio/transcriptionsvia whisper.cpp, opt-in with--enable-audio <whisper.gguf>. Response formats:json,text,verbose_json,srt,vtt. Requests are serialized becausewhisper_fullmutates the context.chimera deliberately does not bind upstream's own transcription handler: that route feeds audio through mtmd's audio mmproj, a fundamentally different pipeline from dedicated ASR. chimera's handler uses whisper.cpp directly, the same engine as the CLI subcommand.
Scope limits: WAV only (other formats need a real decoder; non-WAV returns 415), and
model/temperature/timestamp_granularities[]are ignored in this cut.The refactor exposed a latent bug: setting
detect_language = trueputs whisper into a language-id-only mode that returns without transcribing, solanguage="auto"now resolves through the language parameter alone. Unreachable from the CLI's default, so no existing test caught it; the HTTP handler exercises it by default. -
chimera serve: OpenAI-compatible HTTP server, phase 1 (LLM only). Links llama.cpp'sserver-context-- the same engine behind llama-server -- and exposes a curated route subset through the vendored cpp-httplib. Compilesserver-http.cppdirectly into the chimera target, since upstream does not ship it as a separate library. Exposed: health,/v1/models, chat completions (streaming and not), legacy completions, and embeddings.Everything else on
server_routesis a scope choice rather than a missing capability -- each could be enabled with a one-line binding. Server-mode features skipped outright: router mode, built-in tools, the MCP CORS proxy, GCP compat, the embedded web UI, and SSL (reverse-proxy territory).
-
chat: slash commands, multimodal input, tab completion, color, and a load spinner. New commands:/help,/regen,/clear,/readand/glob(attach text to the next message),/imageand/audio(attach media when--mmprojis given). Tab completion covers the command word and falls through to filesystem completion for the path-taking commands;/imageand/audioare only offered when the loaded mmproj advertises that modality. -
chat: multimodal turns. Once any media is attached, the loop switches from the text-only KV-prefix-reuse path to re-tokenizing and re-evaluating the whole conversation each turn -- correct, but O(history). -
chat: ANSI color via the rang single-header dep, controlled by--color {auto,always,never}. Concrete colors route through a semantic-tag layer so re-skinning is a single-site edit. The prompt's color codes are emitted around the line-read call rather than inside the prompt string, because ANSI bytes in the prompt break linenoise's width math and corrupt the cursor under multi-line edits. -
chat: thinking text rendered grey. Replies are re-parsed and diffed per token so reasoning content prints grey while the answer prints normally. Only the content portion is stored in history -- the next turn does not reinject the model's prior thinking. Matches llama-cli. -
chat: background spinner during model load, auto-disabled when stderr is not a TTY so piped logs stay clean. -
Optional linenoise integration for
chat-- line editing, history, and editing keys, with history persisted at$CHIMERA_HISTORYor a home-directory default. Engaged only on a TTY, so scripts and the test suite are unaffected. Controlled byCHIMERA_LINENOISE(AUTO/ON/OFF). -
genmultimodal input via--mmproj+ repeatable--image. Auto-prepends the media marker if the user did not place it, and auto-wraps the prompt in the model's chat template, since VL models are typically instruct-tuned and stall without it. -
chat: persistent KV cache across turns. The context and sampler are built once per session; each turn finds the longest common prefix already resident, rewinds, and decodes only the tail. The previous implementation rebuilt the context per turn and paid full prompt re-decoding every time. -
sdimg2img and inpainting via--init-image,--mask-image,--strength. Image dimensions must match-W/-H; there is no internal resizing. -
make install/make uninstallhonoringPREFIXandDESTDIR, andmake rebuildas a deps-skipping shortcut. -
CI workflow building and smoking on macOS arm64/Metal, Linux x86_64/CPU, and Windows x86_64/MSVC (initially non-blocking), uploading per-platform binaries and caching
thirdparty/. Release workflow onv*tags rebuilds the same matrix and attaches binaries plus checksums to a GitHub Release. -
tokenizesubcommand -- token ids for a prompt, orid<TAB>piecerows with--pieces. Useful for debugging vocab and template behavior without running generation. -
embedsubcommand -- a single embedding vector via a GGUF embedding model, with pooling, normalization, and the usual context knobs. -
-f, --prompt-fileongen/tokenize/embedand--system-prompt-fileonchat, each reading-as stdin and mutually exclusive with the inline form. -
Structured exit codes (
ExitCodeenum +ChimeraError): 1 runtime, 2 bad input, 3 model-load failure, 4 generation failure. CLI11 parse errors keep CLI11's own codes. -
Whisper streaming: each finalized segment prints as whisper.cpp produces it, instead of buffering until the call returns. SD progress goes to stderr, so stdout still receives only the produced PNG paths and pipelines stay clean.
-
make test/make smoke. Smoke exercises--versionand--helpon every subcommand; the end-to-end tier runs gen, whisper, and sd when the model files are present, reporting missing models as SKIP rather than FAIL. -
REVIEW.md-- architecture, feature, usability, and best-practices review of the 0.1.0 baseline.
-
whispermis-detected language when-lwas omitted, defaulting to auto-detect and occasionally identifying English-only models as Azerbaijani, producing empty output. Now leaves whisper.cpp's own default in place unless the user asks for auto or an explicit code. -
whispercrashed when--threadswas left at its default of -1. The value reached a field whisper.cpp casts tosize_tto size a vector. The default is now left untouched unless the user passes a positive override.
-
stb_impl.cppno longer definesSTB_IMAGE_IMPLEMENTATION, because libmtmd ships its own non-staticstbi_loadthat would duplicate-symbol on link as soon as any mtmd helper is referenced. The image-write implementation is still chimera's. -
fail()andtrim()deduplicated intochimera.has inline helpers. They pull in no ggml headers, so the three-TU isolation is preserved. -
scripts/manage.pytrimmed of cyllama-specific code -- wheel building, dynamic-library machinery, and a dozen unused subcommands. ~3170 lines down to ~1210. Retained:build,info,clean,download. -
--helpoutput compacted -- short and long flags packed together, explicit usage string, single blank lines between sections. -
Top-level description tightened to
chimera - {llama,whisper,stable-diffusion}.cpp multitool.
-
Initial repository, extracted from cyllama.
-
Static multitool executable bundling llama.cpp, whisper.cpp, and stable-diffusion.cpp against a single shared ggml backend set.
-
Subcommands:
gen,chat,whisper,sd. -
Top-level
-v,--verbose; native backend logging silenced by default. -
Three-TU layout to isolate the colliding
ggml.hheaders shipped by llama.cpp and whisper.cpp. -
Late
llama_backend_init(), deferred until after CLI parsing so--helpand parse errors do not trigger backend loading. -
scripts/manage.pybuild driver and themake deps/build/clean/resetwrappers. -
Verified end-to-end on macOS arm64 with Metal.
-
Only macOS arm64 + Metal is verified. Other platforms are believed to work via the inherited cyllama build matrix but have not been re-validated post-split.
-
whisperandsdbuild cleanly but have not been exercised end-to-end in this repo yet.