Join the conversation

Join the community of Machine Learners and AI enthusiasts.

Sign Up
ManniX-ITA 
posted an update 23 days ago
Post
155
opencoti-llamafile — single-file inference engine for long-context agentic serving

This is the bundled runtime of opencoti, a soft fork of opencode adding a multi-tier local inference engine. The main opencoti project is not yet publicly released on GitHub — its engine ships here first as a standalone, zero-dependency single file usable with any OpenAI-compatible client.

One executable (Cosmopolitan APE) runs on Linux, Windows, macOS & BSD. Three artifacts: x86_64 with embedded CUDA (sm_75→sm_120, Turing→Blackwell), a bare Windows .exe, and (untested!) aarch64 with embedded sbsa CUDA for DGX Spark (GB10). Side-load DSOs under dso/. Base: llamafile 0.10.3 / llama.cpp + 80 additive patches (full series published in this repo).

Runs every llama.cpp-supported GGUF; tuning targets Gemma-4 (26B-A4B MoE, dense 12B/31B, E-series) and Qwen 2.5/3/3.5/3.6 (dense, MoE, recurrent-hybrid).

Highlights:
- DCA (dual-chunk attention): context extension validated to 768k–1M
- MTP speculative decoding: Qwen NextN self-spec (fused N-step) + Gemma-4 assistant drafter (dual-context) — up to ~1.9× decode
- KV-quant ladder: TurboQuant 2/3-bit, TCQ trellis-coded 2/3-bit, asymmetric hi-K/cheap-V mixes, q6_0
- Rolling-KV window: streaming host-RAM spill of KV overflow — contexts beyond VRAM, auto T*-aware policy
- Mixed position-axis KV: f16 recent window ⊕ quantized tail, auto-tiered at boot
- Agentic multi-session serving: PolyKV shared-prefix KV pool, session-keyed KV reuse, lazy slot grow/shrink
- Sparse-V + sparse attention (vertical-slash) long-ctx decode wins
- Runtime introspection (/props, /slots), layer-repeat (RYS) self-stacking, and more

Every feature is gated by KLD / logit-equivalence and RULER retrieval evals — quality numbers live in the docs alongside the binaries.

Grab a binary + MANIFEST + USAGE.md and point your agent at it. Feedback very welcome!

ManniX-ITA/opencoti-llamafile

The 1M context is the headline, but the agentic serving line is the one that matters. An agent loop re-sends a near-identical prefix every turn, so the win is not decode speed, it is never re-prefilling.

Which is where PolyKV and the auto-tiered KV seem to pull against each other. If the f16-recent / quantized-tail boundary gets picked per session at boot, then two sessions sitting at different depths on the same shared prefix tier those blocks differently, and the pool has nothing identical left to hand both of them.

Is the tiering pinned per-pool, or does a session re-quantize its own copy once it diverges?

·

Really depends on the specific use case; the headline is that with DCA you can extend any context and overcome the model limitation.
It's not only useful to get to 1M context, which can get damn slow even with a MoE on an RTX6000, but also to get up to an acceptable range with a model trained for a small context.

There's also the KV-window which it's truly novel, not to be under-estimated considering the crazy GPU and memory prices. A modern system with full PCIe 5.0 x16 and DDR5 can hide behind computation up to 3-4GB, depending on the model attention mechanism, and keep almost full gen tps speed.
Offloading model layers to RAM, the classic llama.cpp method, tanks the decoding speed immediately, no matter if is by a small amount or not.

If you are working with agentic framework coding SharedKVPool is the headline.
The auto-tier is a boot selection algorithm, doesn't have much to do with them; it's an helper (pretty sophisticated with probing and micro-bench at boot) to automatically enable the KV-window in conservative mode and/or select a K/V type and strategy that fits the available VRAM.

The prefix with PolyKV is what is shared between the sessions; it doesn't change. Same quantization for everyone, if it changes it's done only once.
The sessions in the same pool share the same prefix and only the suffix changes, that's what diverges and always.
The win is by definition in prefill and memory usage but also on gen tps (less overhead) and multi-turn re-query.

Made a benchmark and updated the docs:
https://huggingface.co/ManniX-ITA/opencoti-llamafile/blob/main/USAGE.md#34-polykv-sharedkvpool-multi-agent-shared-prefix

It's really a game-changer and a killer-feature for agentic frameworks that can drive it, both in capacity and performances.

Thanks for pinning that down, and for putting it in the docs. "Chosen once at boot, before any session exists" is the answer, and it kills my worry cleanly. No per-session copy means there is nothing to re-quantize.

The row I would put on the headline is not the 6.9x memory. It is the 2.3x multi-turn re-query.

99 to 225 tok/s is the agent loop, exactly. And the reason is almost an accident: the pool slot never decodes, so its prefix never slides, so every re-attach is free. A private slot pays a checkpoint restore every turn because its prefix sits in something that moved. Capacity is the number people will quote. But the thing that makes an agent framework feel different is that turn 40 costs what turn 2 did.

Which points at the one thing I would want to know before wiring this in.

The pooled prefix is system prompt plus tool defs, boot-pinned, read-only for the pool's lifetime. Tool defs are not static in a real agent run. An MCP server registers mid-session. A planner adds a tool at step 12. A subagent gets a narrowed toolset. Any of those edits the prefix.

Does a tool-def change rebuild the pool for every attached sharer, or is there a way to append to the shared prefix while sessions are on it?

·

I would wait before wiring it in, the mechanics are there but they are raw.
I'm adding a proper API control and Langchain/Langgraph extension, actually a very sophisticated one, which will make it easier to use it and way more powerful.

About your question: the prefix is immutable.

If you want to change the prefix you will have to spawn a new pool and new sub-agents.
There's no problem adding a new mcp server or tool def in the suffix but it will live inside that sub-agent session only.
(Which anyway makes sense as if it was something needed by all sub-agents it would be part of the prefix)

PolyKV is exactly this, sharing an immutable prefix and run a pool of sub-agents that shares it.
This allows parallelizing tasks for a single agent/session very efficiently.

Immutable is the right call. A mutable shared prefix across a live pool is a coherence problem, and you would be inventing invalidation for a cache that exists to avoid work.

But the rationale under it is carrying more weight than the design admits.

"If it was needed by all sub-agents it would be part of the prefix" assumes you know at pool-construction time what all of them will need. That holds for the tool set you booted with. It does not hold for the one the planner discovers at step 12.

The failure mode isn't correctness. It's that the sharing ratio decays quietly. A tool def that turns out to be needed by k of N sub-agents now lives in k suffixes instead of 1 prefix. Nothing breaks, nothing errors, you just pay k copies. The memory win is what absorbs it.

Which makes me read your two numbers as measurements of different moments. 6.9x is close to a step-0 property, when the prefix is everything and the suffixes are still empty. 2.3x multi-turn re-query is the one that lives where suffixes have had time to accumulate.

So what is the prefix-to-suffix token ratio at the END of a long run, not the start? Does 6.9x still hold at step 50?

·

The ratio grows with the number of agents since the shared prefix is stored once instead of N times: 6.9× is the measured KV-cell reduction at N=8 agents (with a ~875-token shared prefix and short private suffixes).

Projected onto a fixed KV buffer, that geometry fits about 306 agents pooled vs about 9 naive before exhaustion. The more sub-agents attached to a pool, the closer you get to the prefix/suffix ceiling.
The suffixes always diverge, so there's no memory saving there — the batched-decode gains (~+14% aggregate at N=8) come from the smaller cache footprint, not from suffix sharing.

llama.cpp's built-in cache reuse (--cache-reuse) is a different axis: a slot reusing its own previous prompt across turns. It doesn't share cells across concurrent sequences — that's exactly what PolyKV adds, and the two compose.

The ceiling isn't a prefix/suffix ratio. It's N.

Pooled cells are P + NS. Naive are N(P+S). As S goes to zero the ratio goes to N, exactly. So at N=8, 8x is the hard maximum and you measured 6.9. That's 86% of the most that geometry can ever give.

Which means your number solves for your suffix. 8(875+S)/(875+8S) = 6.9 puts S at about 20 tokens.

That is the answer to what I asked, and it's the thing I was worried about. 20 tokens is not a sub-agent mid-task. It's a sub-agent that has been handed its instruction and has not called anything yet.

Same geometry at step 50, each sub-agent carrying 2k of tool calls and observations: 8(875+2000)/(875+16000) = 1.4x.

The buffer number moves with it. 306 vs 9 is 34x, and that's the S=20 geometry. Push S to 2000 and the pooled-vs-naive agent count lands around 1.3x instead, near enough the same whatever buffer size I assume.

I don't think this sinks PolyKV. You said it yourself: the suffixes diverge, the win is footprint. But that means the win is a function of how much of the run is still prefix, and that fraction only falls.

The number I would lead with is still 99 to 225 tok/s. A re-query that never slides is a property. It doesn't decay with step count the way a cell ratio does.

So is there a run where the suffixes were allowed to grow? 8 sub-agents at step 50 of a real tool loop. What does the cell reduction come out to there?

·

You can start wiring it now and test yourself.
The new c4 release provide a very rich and sophisticated API for PolyKV, including a floor-tps scheduler that allows automatic workers allocation based on engine capacity.
Should be properly documented but it's just been done and I didn't have time to double check.

There is also an evaluation benchmark test harness, package-courier, and a few benchmark runs.
You can use the test harness as a reference implementation of the API in Langchain/Langgraph.

You can simulate multi-turn tool usage with it and track the gen tps over turns, there's even a specific graph in the report.
Didn't have time yet to test it with more than 10 turns, context per-agent is small at n=10 and the tps increase over turns due to that (you need really a massive context to see the tps decrease and opencoti-llamafile is slower with small context sizes).
If you test it let me know how it goes and if the documentation is clear enough!

image

I went and ran your numbers instead of asking you again. You'd answered both my questions in code already: cache_n is right there in the per-turn telemetry, and --steps is the knob.

So the run I asked for exists. It's in your own reports.

a4b-floor15, events log, N=8, P=1216 from step 1's in_toks. Cell ratio N(P+S)/(P+N*S) by step:

step 1: 6.42x
step 5: 2.54x
step 11: 1.74x

That's the decay, and look where it starts. 6.42x at step 1 is your 6.9x headline. The number isn't wrong. It's a step-1 measurement. Suffixes grow ~123 tok/turn here, so P + NS catches N(P+S) quickly, and the same run is at 1.74x nine turns later.

But I think you're right about the win. Just not about that number.

ab-pooled vs ab-naive is the honest A/B and nobody is quoting it. 15.8 vs 12.0 mean gen tps, 2189s vs 2732s for the same 100 packages. And the naive leg isn't a strawman: cache_n mean 1613, only 724 of 2200 turns cold, so llama.cpp's per-slot reuse is already working in the baseline.

One catch on that though. ab-pooled ran tps-floor 15. It scored 15.8. The scheduler grows the fleet until per-session tps sits on the floor, so 15.8 isn't what the engine did, it's where you told it to stop. That's a thermostat next to a thermometer.

Which leaves capacity as the real claim. The fleet climbed 4 to 64 while naive sat at 8. Same engine, 8x the live sessions, and wall clock only moved 1.25x. So the engine saturated and fleet size isn't the lever. The win is footprint, exactly what you said in your first reply.

Six charts in that report and cache_n is sitting in the events. Why isn't cell reduction one of them?

·

You need to think more "out of the box" and have a boarder vision.
The evaluation harness is limited and exercise a single use case.

The only number that matters is the reduction at P, Step 0.
If you consider also S per step, it's not going to tell you anything really valuable.

This is a fixed, mocked eval harness that runs a static number of steps.
It has its own purpose which is exercising and validating the API and the engine.
Doesn't really reflect a real use case; maybe there are similar ones but it's an edge case.

Every agent in the harness have a very small prefix (P=1216) which is most often not true.
Unless you are using agents to do some very basic and trivial things, hardly a real use case (most probably you don't need an agent for this kind of stuff).

In a real use case the P is what matters most of the time; in my claude-hooks consultants skill the P is gigantic: 60-200K to describe the project, provide the tools, the AST, list of the files to inspect, what was done, test results, etc etc.

Then the agent will start its work and you have no idea how many steps will take, how long will be the output, how much thinking will be needed. Ever spawned agent will have, in non synthetic tasks, most likely a very different behavior. So you can't compute and forecast a ratio of the savings due to the shared P and involve S and the steps.

The capacity lever is at P: let's frame a realistic scenario, 128k of "P" plus max 128k for "S".
If you have space for 1M tokens on your GPU and you need to run agents without PolyKV you can run 4 of them.
With PolyKV you can run 7 agents and they will be faster overall plus the context compaction gotcha.

The context compaction is another big win: without PolyKV every agent needs to be instructed to preserve the P and they all need to process its own P. They could easily mess it up and remove critical information or just make mistakes.
With PolyKV the P is static and it's not reprocessed by the agents, doesn't have a chance to get messed up.

You are really wrong when you say that the fleet size is not a lever: it's exactly the opposite.
The floor tps scheduler is a killer feature just like the shared P.
That's why I uploaded so many reports with different models and different floor tps.
Each combo model/GPU needs the right floor tps otherwise it starts crapping out and the wall time will increase, the quality will decrease. That's the value of the test harness as it is configured now.

You can't quite precisely predict the load of every agent, if you choose a static value that's it. You are bound to it.
If you are running multiples instances of any other inference engine without the floor tps scheduler you have either to under or over subscribe the number of agents.
Unless you put on top a complex custom orchestrator which needs to be managed.

Since opencoti-llamafile with the PolyKV API knows the actual load on the GPU it can auto-balance with just the right tps floor value for the model, independently of any other instance running or not running in parallel.
If there's no other load can scale up the workers up or above the available slots, your decision.
In case there's load the floor tps scheduler will restrict the amount of agents that can be spawned automatically.
I still need to improve concurrency and avoid over subscription at start but the logic is simple and should be clear.

Abou the chart: I will probably replace the graph of tps at step as it's redundant to the tps with context and less granular.
But as I said a "cell reduction" graph as you describe I don't think that really make sense.
Have to think a bit more about it on how to frame it.

Ran your 1M budget numbers. 128k P plus 128k S, no sharing is 256k per agent, so 4 fit. Shared P is 128k plus n times 128k under 1024k, so n up to 7. The arithmetic holds.

And that is the whole argument in one line: the saving is (n-1) times P. At your harness P=1216 that is 7k, noise. At P=128k it is 768k, three quarters of the budget. Same mechanism, and the harness parked it in the one regime where it cannot show. That is not the engine underperforming, it is the eval hiding the win.

So I take back "fleet size is not the lever." With the floor-tps scheduler setting n against live load, fleet size is the output the lever produces. Fair.

The context-compaction point is the one I underrated. A static shared P cannot be re-derived wrong per agent. That is not a memory win, it is a correctness win. Every agent that reprocesses its own P has a chance to silently drop a line and pass its own self-check anyway. Shared P deletes the whole failure class.

Which is why the chart I would want is not cell-reduction per step. It is agents-fit versus P, sharing on and off. One curve, flat at P=1216, blowing open at P=128k. That single frame makes the harness and the real use case tell the same story. Worth building?

·

I'm very busy with many other things right now but I'm considering it.
Working on the same type of harness to excercise Agent2Agent protocol that simulates a more "real" usage.
That does have 2 modes with an heavy template and a light one.

Do you mean as context of the agents increases a projection of what would be the context without PolyKV and track the ratio?

Yes, that is it. For each agent, log its real context, then compute what the same run would have cost without PolyKV: n copies of P instead of one shared. Plot the ratio.

Make P the x-axis, not step count. The ratio sits near 1 while P is small and only opens up as P grows, which is the whole point you have been making. A step-count axis averages over the regime where sharing does nothing, so it hides the win.

Your A2A harness is the better home for this than the current one. Heavy-template mode is exactly where P is large and the ratio blows open, the light mode is your control. Same chart, two modes, and the gap between the lines is the feature.

When you run heavy versus light, are you holding agent count fixed, or letting the floor-tps scheduler set it per mode?

·

I can track the context size and context / concurrent agents ratio and plot over time the with/without PolyKV ratio and context size with and without.

Right now I'm validating the fine-tuned model with the harness, once done will make some runs comparing all the uses cases with and without polykv, pooled and static workers.

Pooled versus static is the answer to what I asked. Static workers is the fixed-n control, pooled is the scheduler setting n against load. Keep both, the gap between the two lines is a second story sitting on top of the PolyKV one.

One ask for the plot: put the with/without ratio on a P axis, not time. Over time it averages across whatever P happened to be running, so the win smears. Against P it sits near 1 while P is small and only opens as P grows, which is the whole claim.

Static-worker heavy-template is your cleanest single frame: fixed n, large P, sharing on versus off. If that one line blows open, the feature is proven before the scheduler even enters.

When you compare pooled versus static, are you feeding both the same request trace, or letting each generate its own load?