F2 β Local llamafile Model Serving
Parent: ../MASTER_PLAN.md Status: planning Owner: TBD
Problem
The GPU realization of Tier 0 in the tiered inference engine (F1) wants a local backend that works out of the box β no separate Ollama install required, no separate model server. We want a single binary opencoti can launch on demand.
Beyond out-of-box, the GPU path is where opencoti gets to expose "advanced features" the no-GPU path (Ollama Cloud) can't: KV-cache reuse across agentic turns, big-context throughput, and direct mount of an existing Ollama blob without duplicating the download. The mainline llamafile / llama.cpp do parts of this β opencoti wants a patched build that does all of it.
Goals
- G1. Ship a patched llamafile binary inside opencoti release artifacts.
- G2. Provide a small TypeScript launcher that starts / stops the llamafile, exposes an OpenAI-compatible endpoint to the tier engine, and pins it to loopback by default.
- G3. Maintain patches against upstream llamafile / llama.cpp in
vendors/patches/llamafile/, applied at build time. - G4. Patches focus on three areas:
- Big-context throughput β keep tokens/sec usable at the context sizes agents actually use (64K+, up to the model's 256K cap).
- KV-cache reuse across agentic turns β when the new turn shares a prefix with the previous turn (system prompt + tool log), skip reprocessing.
- Ollama-blob mount β load a GGUF directly from
~/.ollama/models/blobs/sha256-β¦without copying. Single download, reusable by both Ollama and the patched llamafile.
Non-goals
- Replacing Ollama for users who prefer it as Tier 0. The no-GPU path of F1's Tier 0 explicitly targets Ollama Cloud as an OpenAI-compatible provider β that doesn't go through this package.
- Bundling models. Models are downloaded on first use into the opencoti data dir (or imported from an existing Ollama install); we don't ship weights inside the binary.
Default model
Primary GPU target:
ManniX-ITA/gemma-4-A4B-98e-v5-coder-it.
- Architecture: Gemma 4 (MoE), 98-expert prune of Gemma 4 26B-A4B
(30 experts dropped per layer,
protect_top=16, no shared FFN scaling). - Total ~20.8 B params, ~4 B active per token.
- Context: 256 K max (recipe defaults to a smaller window for speed but the model supports the full 256 K).
- Pitch: HumanEval 98.17 %, HumanEval+ 92.68 %, LCB-medium 85.45 %. Top of the 14β22 B coder field at this writing.
Quant table (the GGUFs we surface as choices during first-run setup, see F1 M5.7):
| Quant | Size | HE+ | When to pick |
|---|---|---|---|
| IQ2_S | 7.83 GB | 85.37% | tight VRAM, 8 GB cards |
| IQ3_M | 9.82 GB | 91.46% | default for β₯10 GB VRAM |
| CD-IQ4_K_M | 10.29 GB | 92.07% | per-layer "Canary W" variant |
| IQ4_XS | 11.01 GB | 93.29% | 12 GB cards |
| Q4_K_S | 12.21 GB | 93.29% | 16 GB cards, good headroom |
| CD-Q4_K_M_L | 13.00 GB | 93.29% | per-layer mid-quant variant |
| Q5_K_L | 15.25 GB | 93.29% | 24 GB cards, room for big context |
| Q8_0 | 21.16 GB | 93.90% | 24 GB cards, maximum quality |
| F16 | 39.8 GB | β | reference only |
The CD-* variants are "ContribDynamic" per-layer quants from the model card. Both plain and CD- families are presented; the setup flow recommends one based on detected VRAM, the user can override.
Secondary target (deferred): smaller Gemma 4 variants (E2B, E4B, or a non-pruned 26B-A4B). Out of scope until the primary target is working end-to-end. Stock Gemma 3 / Gemma 3 1B are not on the roadmap.
Design sketch
Layout
vendors/
βββ sources/
β βββ llamafile/ # upstream llamafile tree (vendored, unmodified)
βββ patches/
βββ llamafile/
βββ 0001-kv-reuse.patch
βββ 0002-big-context-throughput.patch
βββ README.md # what each patch does, against which upstream
packages/
βββ opencoti-llamafile/ # launcher + adapter for the tier engine
βββ src/
β βββ launch.ts
β βββ adapter.ts # OpenAI-compatible HTTP client wired to tier engine
β βββ kv-cache.ts # session-aware cache key derivation
βββ package.json
Pattern: same as Ollama's vendoring of llama.cpp.
Build-time patch application
A small build script under vendors/llamafile/build.{sh,ts} does:
git -C vendors/sources/llamafile clean -fdx && git checkout <pinned-sha>- Apply every patch under
vendors/patches/llamafile/*.patchin lexical order. Bail on conflict. - Build (mode depending on target β release CI builds binaries, local dev can use system llamafile if present).
- Emit the resulting binary to a known location the launcher checks.
The pinned upstream SHA is recorded in
vendors/sources/llamafile/UPSTREAM.txt. Updating the SHA is a
normal PR β patches that no longer apply must be rebased in the same
PR.
Launcher
packages/opencoti-llamafile/src/launch.ts is a thin process
manager: starts the binary, waits for the OpenAI-compatible endpoint
to respond healthy, exposes port + baseURL. Tears down on
opencoti shutdown.
Adapter
packages/opencoti-llamafile/src/adapter.ts implements whatever the
tier engine consumes (provider interface β to be defined in F1 M1).
It is purely an HTTP client to the local llamafile + cache-key
plumbing.
KV-cache reuse β sketch
The agentic loop has a strong invariant: turn N+1 reuses turn N's context prefix verbatim, then appends the model output + new tool result. A KV-reuse patch:
- Adds a session id to the request schema.
- On the server side, persists the KV cache state keyed by
(session_id, hash(prefix)). - On the next request with matching
(session_id, hash(prefix)), skips re-prefilling the prefix tokens.
This is conceptually like prompt caching in cloud providers β applied to a local llamafile.
Ollama-blob mount β sketch
If the user already has Ollama installed and has pulled the target
model, opencoti should not re-download the GGUF. Ollama stores blobs
content-addressed at ~/.ollama/models/blobs/sha256-<hash>, with a
manifest at
~/.ollama/models/manifests/registry.ollama.ai/library/<model>/<tag>
mapping the model+tag to a digest list (the GGUF blob is one of the
layers).
The detection helper (F1 M5.7) parses the manifest and returns the
blob path. The patched llamafile accepts that path directly via
-m. Single download, no duplication β the same physical file is
used by both Ollama and llamafile.
The patch in llamafile itself is small: it already accepts -m <path>, but blob filenames are hash-named (no .gguf extension), so
any code path that gates on file-extension needs adjusting. That's
the patch.
Milestones
M1 β Llamafile vendor present, launcher boots it (done β 2026-05-21)
- Vendor llamafile under
vendors/sources/llamafile/(git submodule pinned to0.10.1/6490e16). -
vendors/patches/llamafile/exists withREADME.mddocumenting the patch protocol (numeric prefixes, lexical apply order, upstreaming policy). No patches yet. -
packages/opencoti-llamafile/(@opencoti/llamafile) shipsconfig.ts(binary resolution: env β vendored build path β PATH),launch.ts(Bun.spawn+waitForReadypolling/v1/models),adapter.ts(M2 stub), andscript/smoke.ts. Typecheck is clean. - Smoke script exits 2 with a clear diagnostic when no binary is
resolvable, and 0 when one is. Live
--serverlaunch +/v1/modelsping is gated onOPENCOTI_LLAMAFILE_MODELbeing set.
M2 β Adapter wired to tier engine (done β 2026-05-21)
-
packages/opencoti-llamafile/src/tier-provider.tsexportstierProvider()returning aTierProvider(the interface defined by F1 M3 in@opencoti/tiers/provider). - Implementation uses
@ai-sdk/openai-compatibleto build aLanguageModelagainst the running llamafile's/v1endpoint; the previous M1 stubadapter.tsis preserved alongside (it's used by the smoke script). - Singleton llamafile process per host (
runningPromisemodule cache); cleanup is process-exit (good enough for M3, refined later). -
@opencoti/llamafile/package.jsondeclares deps on@ai-sdk/openai-compatibleand@opencoti/tiers(for the interface). - F1 M3 (Tier 0 = llamafile end-to-end) is achieved jointly with this milestone.
M3 β Build pipeline applies patches (done β 2026-05-22)
Pure-helpers + CLI orchestrator split (same testability pattern as M5.7's setup-flow):
packages/opencoti-llamafile/src/build-pipeline.ts(new) β pure helpers:discoverPatches,validatePatchName,summarizePatchSet,readPinnedSha. Re-exported from the package and from a new./build-pipelinesubpath.packages/opencoti-llamafile/script/build-pipeline.ts(new) β CLI orchestrator with subcommandscheck,reset,apply,build, plushelp. Shells to git + make viaBun.spawn. Detects uninitialized submodule and emits a clear "rungit submodule update --init" message.vendors/pin/llamafile.txt(new) β pinned SHA + tag, machine-readable. Lives OUTSIDE the submodule sogit clean -fdxdoesn't wipe it. The user-facing pin table invendors/README.mdis kept in sync manually.- Root
package.jsonscripts:build:llamafile,build:llamafile:check,build:llamafile:apply,build:llamafile:reset.
Patch protocol enforced:
NNNN-<kebab>.patchwith 4-digit prefix and lowercase kebab title.- Duplicate prefixes are flagged (both names appear in the
invalidlist, neither inpatches). - Empty patch set produces "no patches" β the build still runs to completion.
checkwarns when the submodule HEAD has drifted from the pin but doesn't fail (a developer may be intentionally bumping).
Test coverage: 21 new unit tests in
packages/opencoti-llamafile/test/build-pipeline.test.ts covering
all helpers + the fixture-based discover path. Fixtures live under
test/fixtures/patches/ (0001-foo.patch, 0002-bar-baz.patch,
README.md to exercise the non-.patch-ignore path). The CLI
orchestrator itself is NOT unit-tested (it shells out to git/make;
integration testing belongs to F2 M6).
Verification: bun run build:llamafile:check against the current
empty patch set:
[build-pipeline] Pinned upstream: 6490e16
[build-pipeline] Vendor HEAD: 6490e16f7a8fb7d8a0a0734b41490c3f8db78763
[build-pipeline] no patches
[build-pipeline] check OK
Zero new surgical hooks. Hook footprint stays at 4 markers.
M4 β First patch: KV-cache reuse v1 (absorbed into F5 M0 β shipped 2026-05-24)
This milestone was absorbed into the F5 advanced-KV series as F5 M0
(homologous to the rest of that patch series) and has shipped. It landed
as vendors/patches/llamafile/0006-kv-reuse-prefix.patch (not 0001-, since
F4 M3's lazy-slot work consumed 0001β0005): an optional session_id request
field + sessionβslot affinity, with the adapter threading the session id and
cache_prompt through a fetch wrapper. Bench
perf/llamafile/turn-2-latency.bench.ts shows turn-2 prefill collapsing from
2061β20 tokens. See
docs/features/advanced_kv.md "M0" for the design, the
upstream-already-reuses finding, and the deferred prefix-hash/disk extension.
M5 β Big-context throughput patches
- Targeted micro-patches that improve tokens/sec at 64K+ contexts (up to the 256K model cap).
- Each patch documented in
vendors/patches/llamafile/README.md. - Each patch with a benchmark in
perf/(we can reuse the existingperf/dir from opencode).
M6 β Release artifact bundling
- CI builds llamafile-with-patches for the platforms opencoti ships on. Binary is included in the opencoti release tarball under a known path the launcher will find.
M7 β Ollama-blob mount patch
- Patch lands in
vendors/patches/llamafile/0002-ollama-blob-mount.patch. - The patched llamafile accepts hash-named (extension-less) GGUF
paths from
~/.ollama/models/blobs/sha256-β¦directly. - Wired into F1 M5.7's first-run setup flow as the "Import from local Ollama" option.
- Smoke test: install Ollama,
ollama pull gemma3:1b(or any test model), point the patched llamafile at the blob, assert health.
Open questions
- Llamafile vs. raw llama.cpp. Llamafile gives us the single-binary-cross-platform property. If KV-reuse patching is easier against llama.cpp upstream, we may switch vendors. Still open after M1 β re-evaluate when the first patch (M4) is in flight.
Default model. Qwen2.5-Coder-7B vs Qwen2.5-Coder-14B.Resolved:ManniX-ITA/gemma-4-A4B-98e-v5-coder-it(see Default model section above). Smaller Gemma 4 variants are explicitly deferred secondary targets.- Sandboxing. Launching a long-running binary needs robust shutdown. Use a process group + signal forwarding so an opencoti crash doesn't leave a llamafile orphan.
head_dim=512constraint. Gemma 4 hashead_dim=512, which means FlashAttention 2 is NOT supported (per the model card). Patches need to leave theattn_implementation="eager"path intact and not enable FA2 indiscriminately for big-context work.
Risks
- Patches drift when upstream moves. Mitigation: pinned SHA + the rebase-on-update rule, and per-patch tests.
- Binary size. Llamafile binaries are large. Mitigation: ship the binary as a separate artifact downloaded on first run, not inside the main opencoti package, if it gets prohibitive.