[RFC] Model-agnostic Colibrì without a generic forward: shared runtime, specialized family engines #1057
Replies: 3 comments
|
@JustVugg Take a look at this, I think is a good roadmap. |
|
This is the right diagnosis, and I can give it stronger evidence than the issue list: the drift class you describe produced the same bug twice today, in two unrelated PRs, found by two independent reviews. Today's proofI spent today reviewing the two model-family PRs currently in flight — #712 (Qwen3.6) and #601 (MiniMax-M3). They share no code, no author, and no design: Qwen is a standalone Both fail the planner in exactly the same way:
So The rest of the Qwen review reads like a checklist generated from your RFC:
And MiniMax's headline blocker is §4 verbatim: the EOS fallback was added to What that changes about the proposalIt moves this from "reasonable refactor" to "we are currently paying this cost per PR, in review time." Every new family costs a full manual integration audit precisely because there is no contract to check against — and the audit is unreliable, since both of today's blockers were found by reading rather than by any test. Your §1 is also, by some distance, the cheapest item on the list. A family registry alone would have caught six of the eight rows above, and it requires touching no forward pass, no kernel and no engine The precedent is already in the tree, and it is yours: #969 consolidated expert history into Where I would push backA half-finished abstraction is worse than none. If the registry lands but stays optional, family six routes around it and we have added a layer while keeping the drift. The registry has to become the only way a family is declared, with a test that fails when a family exists outside it — the same trick §3 is the one I would guard most carefully. You are right that a universal rectangular KV tensor is the wrong abstraction, and the reason is visible in today's reviews: Qwen's DeltaNet recurrent state cannot be prefix-reused the way GLM's KV can, so "reuse a token prefix" is not a uniform operation — for some families it is correctness-bearing rather than an optimization. The operational contract needs to let a family answer "I cannot do that" without the host assuming it can. Sequencing. I would take it strictly in this order, each landing separately with its own test: registry (§1) → serving codec (§4) → ExpertStore seam (§6) → Engine/Session adapters (§2). §5 and §3 are the ones to design last, once three families have gone through the first four and the contract has been falsified by real use. ConcretelyIf you want to take §1, I will review it as a priority, and I would suggest scoping the first PR to: the descriptor table, the four Python consumers, and a test that fails when a known family is missing from it — with Qwen and MiniMax used as the fixtures, since we now have two documented cases of exactly what it must catch. Thank you for writing this up properly, with the bug list rather than the aesthetics. The argument that a generic forward would not have prevented these bugs, but a shared runtime contract would, is the correct framing and it is now supported by two same-day measurements. |
|
Roadmap update after #1063:
Validation on #1068: full PR: #1068 |
Uh oh!
There was an error while loading. Please reload this page.
Summary
Colibrì now supports five very different model families, but it still pays for many common capabilities once per engine. That is producing a recurring class of drift bugs: one family learns a fix or mechanism, while the siblings retain older behavior.
I would like to propose a specific meaning of model-agnostic Colibrì:
This is not a proposal for a generic graph interpreter, a universal
config.jsonloader, a Rust rewrite, or an immediate replacement of the one-C-file-per-family model. It is an incremental route to make a new family plug into one stable host contract instead of copying the launcher, serving, persistence, telemetry, storage, and cache plumbing again.The key distinction is:
This keeps the performance identity of Colibrì while reducing the maintenance cost of adding and operating model families.
Why discuss this now
The project has already evolved from one GLM engine into a family roster:
c/colibri.cc/inkling.cc/kimi_k3.cc/olmoe.cc/deepseek_v4.cMore families are proposed or under review, including Qwen3.6, MiniMax-M3, Hy3, Mistral Large 3, Dots3 and others.
The separate-engine policy has real benefits:
I do not think those benefits should be discarded.
The problem is that the isolation boundary currently includes much more than model math. Every engine has grown its own version of pieces that should have one contract. Across the five engines there are roughly 27K lines of C, and the most dangerous duplication is not the attention arithmetic. It is at the edges.
Evidence: drift has become a recurring defect class
Several issues were not isolated coding mistakes; they were consequences of a mechanism living in one engine or one dispatch table instead of a shared contract.
.coli_usagehad incompatible producers/readers and incomplete engine coverage. The successful fix wasroute_trace.h: one format, reader and writer, adopted incrementally.coli tunebanners DeepSeek V4 but calibrates with the GLM engine and fails #898:coli tuneannounced DeepSeek V4 but invoked the GLM engine.st.h, but read-splitting policy remained GLM-only and the planner did not understand K3 names.USAGE_SAVE=0is presented as a shared control but is not honored uniformly.The pattern is important: a generic forward would not have prevented most of these bugs. A shared family/runtime contract would have.
What model-agnostic should mean
1. One family registry and capability contract
Today family detection and metadata are repeated across the launcher, gateway, doctor, planner, autotune, build targets, installation, release packaging and tests.
A family descriptor should answer, from one source of truth:
Unknown architectures should be refused explicitly, not silently treated as GLM.
The first version does not need to be a C plugin registry. A shared control-plane registry consumed by
coli,openai_server.py, doctor and tests would already remove a large amount of drift.2. A common Engine/Session lifecycle
DeepSeek V4 already demonstrates a useful shape in
deepseek_v4.h:A neutral API could eventually look like:
The family implementation remains responsible for its private state and math. The common host only needs lifecycle, capabilities, errors, memory summary, request options and token events.
This should first be implemented as adapters around existing engines, not as a rewrite of their forwards.
3. Opaque sequence state, not a universal KV tensor
A universal rectangular
KVCachewould be the wrong abstraction:The common contract should be operational:
The bytes and layout stay private to the family.
4. Shared serving codec, family-owned generation
The
READY/SUBMIT/ACCEPT/DATA/ERROR/DONEprotocol is implemented separately by every engine. The framing, limits and validation can be shared without sharing the scheduler or forward pass.A common codec should own:
STOPandCANCELframing;READY,ACCEPT,DATA,ERROR,DONEemitters;Each engine would still own:
This avoids putting callbacks inside hot arithmetic while removing five copies of protocol security and correctness logic.
5. Tensor and storage contracts
The project already has useful foundations:
st.h: safetensors indexing and reads;tensor.h: non-owning semantic tensor views;quant.h: shared quantized CPU kernels;The next abstraction should be an inventory/catalog of named tensor metadata and validated views, not an interpreted graph of operations.
A family adapter should map checkpoint names and shapes into its own layer plan. The runtime should provide:
This preserves architecture-specific naming and layout, including fused expert tensors and native MXFP4/FP4 formats.
6. ExpertStore as a strangler seam
expert_store.halready defines the most promising runtime vtable:(layer, expert)key;An indirect call here is negligible relative to disk I/O or expert matmul. The migration should not replace every cache at once.
Suggested sequence:
lookup_manyor batch leases only after a real second consumer proves the requirement.7. Shared usage-history lifecycle
route_trace.hunified the file format, but path selection, save controls and save cadence remain engine-specific.A common policy should define:
<model>/.coli_usage;COLI_USAGEoverride;USAGE_SAVEbehavior;This is a small, low-risk first runtime extraction and would directly address #1039 and the remaining lifecycle inconsistencies.
8. Shared telemetry core
The dashboard protocol should have one implementation for:
STAT,HWINFO, common profile fields;Each family should expose an adapter that enumerates its sparse rows, tiers, heat and phase counters. This avoids claiming every model has the same layers or expert geometry while ensuring every shipped engine satisfies the same wire contract.
9. Backend capabilities before backend unification
A generic backend should not begin by lowering every family to
matmul()calls. Colibrì benefits from family-specific fused paths and device-resident pipelines.The safe first step is a capability contract:
Primitive operations can be shared. Fused family-specific extensions must remain possible.
What must remain specialized
The following are not good candidates for a generic interpreted IR:
Examples make the point:
Trying to describe all of these as a dynamic list of primitive tensor operations would likely create intermediate tensors, barriers, lost fusion, device transfers and numerical drift. A coarse family plugin avoids that.
Performance model
A vtable call is not the relevant risk when it occurs:
Those costs are negligible relative to storage reads and matrix multiplication.
Callbacks should not occur inside:
The rule should be: abstraction at operation/lifecycle boundaries; static specialized code inside hot loops.
Proposed architecture
Initially, “plugin” may simply mean an adapter around a separate sibling binary. A stable in-process ABI should be considered only after multiple families implement the same lifecycle contract without losing correctness or speed.
Incremental roadmap
Phase 0: freeze an executable family contract
Before refactoring, add a conformance matrix for every shipped family.
For each family, test:
run,chat,serve,web,tune,doctor,build;This should be a test/check first, not a runtime rewrite. It would have caught several historical dispatch and packaging bugs.
Phase 1: one control-plane family registry
Move duplicated family metadata into one source of truth consumed by the launcher and gateway. Extend doctor/planner/build/release checks from that registry.
No C forward changes.
Phase 2: common usage-history policy
Normalize
COLI_USAGE,USAGE_SAVE, default path and save cadence. Migrate one engine per PR. Keep explicit stats export independent.This is a good first shared runtime mechanism because it cannot alter logits or token order.
Phase 3: common serve codec
Build transcript/golden tests first. Migrate framing only, in this order:
Do not move scheduling or KV ownership in this phase.
Phase 4: telemetry and config transport
Share:
Keep each family config struct and semantic validation private.
Phase 5: neutral Engine/Session API
Generalize the proven V4 lifecycle shape. First wrap a simpler sibling, probably OLMoE, without changing its forward.
Demonstrate two real consumers before expanding the API.
Phase 6: storage policy and ExpertStore adapters
Introduce:
Do not change eviction, pinning, direct I/O and GPU placement simultaneously.
Phase 7: optional common host executable
Only after several families pass the same ABI and oracle gates:
A single executable is an optional result, not the first goal.
Acceptance criteria for every migration
I suggest requiring all of the following:
make checkand all-engine CI remain green;For model-specific additions, preserve the established bar:
Relationship to existing work
This proposal is intended to complement, not invalidate, existing directions:
Model/Cfgseam before proving a second consumer. A smaller capability/lifecycle contract would be easier to validate.ColiExpertStoreinterface is promising; a cross-family factory should eventually avoid V4-specific option types.The strongest positive precedent is #700: a duplicated cross-engine mechanism became one small header with primitive inputs, then each engine adopted it separately. That is the migration style proposed here.
Non-goals
To avoid ambiguity, this RFC does not propose:
Suggested first deliverable
The most useful first PR would be:
It should contain tests and only the minimal mechanical fixes they expose. It should not introduce the Engine ABI yet.
A second PR could then introduce the shared family registry. A third could normalize usage-history policy. That sequence gives us measured, reviewable steps and lets maintainers stop or redirect the architecture before any risky runtime migration.
Questions for maintainers and contributors
expert_store.hintended to become cross-family, or remain V4-local for now?If the direction is welcome, I am willing to start with the conformance test/registry work and keep every step narrowly scoped, oracle-backed and independently revertible.
All reactions