TL;DR
coli serve unconditionally disables MTP/speculative decoding, so the OpenAI-compatible HTTP API decodes at ~1 tok/s, while interactive coli chat on the same box gets 2–3 tokens/forward from MTP. The disable is correct for the multi-slot mux path (speculation isn't ragged-safe across KV slots), but with --kv-slots 1 there is only ever one non-ragged decoding sequence, so speculation ought to be expressible. Enabling MTP for the single-slot serve path would roughly 2–3× decode throughput for every API/agentic client (aider, opencode, Cline, OpenAI SDK, …) — the clients that can't use coli chat.
Motivation
Agentic/coding use requires the HTTP server, not the interactive TUI. In multi-turn tool-calling against coli serve, I confirmed the server's KV-prefix reuse keeps prefill cheap on every turn — e.g. across one agent loop:
turn 1: prefix 0/2751 prefill 2751 (cold)
turn 2: prefix 2765/2804 prefill 39
turn 3: prefix 2834/2868 prefill 34
turn 4: prefix 2887/3068 prefill 181
So after the first turn, prefill is no longer the bottleneck — decode is. And decode over HTTP is stuck at ~1 tok/s precisely because MTP is off. Interactive coli chat (with MTP) reaches 2–3 tokens/forward on the same model/box, but it can't drive tools or an OpenAI-API client. Closing this gap is the single biggest decode win available to agentic users.
Current behaviour (code trace)
coli serve hardcodes SERVE_BATCH=1 into the engine's environment:
# c/openai_server.py:470
child_env = dict(env or os.environ, SNAP=str(model), SERVE="1", SERVE_BATCH="1",
NGEN=str(max_tokens), KV_SLOTS=str(kv_slots))
and the engine then force-disables the draft/verify path whenever SERVE + SERVE_BATCH are set — independent of --kv-slots:
// c/glm.c:6479-6480
int mux_will_disable_mtp = getenv("SERVE") && getenv("SERVE_BATCH") && atoi(getenv("SERVE_BATCH"));
int eff_draft = mux_will_disable_mtp ? 0 : g_draft;
// c/glm.c:5334
g_draft=0; /* one scheduler owns every forward; MTP/speculation is not ragged-safe */
// c/glm.c:6401
int kv_limit=(getenv("SERVE_BATCH")&&atoi(getenv("SERVE_BATCH")))?512:16;
So there is no serve configuration — including --kv-slots 1 — that keeps MTP.
What I tried (and why it confirms the guard is needed as written)
I patched openai_server.py:470 to make SERVE_BATCH overridable and ran the server with SERVE_BATCH=0 --kv-slots 1:
- MTP engaged: startup logged
[MTP] active: native speculative decoding (draft=3) and, on the first request, [SPEC_PIN] draft+verify pinned to the S=1 kernel family.
- Prefill completed normally, but the model then produced corrupted output — an unrelated, incoherent response — which the server rejected:
[api] request failed: invalid engine response: I don't understand what you mean by that input...
[api] "POST /v1/chat/completions HTTP/1.1" 500
So even at single-slot, the serve decode path (the ragged/mux KV route) is not compatible with the current speculation code — the not ragged-safe comment holds in practice, not just in theory. I reverted the patch. This is a feature request, not a bug report: the guard is correct; what's missing is a non-ragged single-slot decode path that speculation can run on.
Proposed direction
When --kv-slots 1 (no multiplexing), route decode through the single-sequence, non-ragged kernel family that coli chat already uses for MTP, rather than the mux/ragged path — so draft+verify is expressible and correct. Concretely, one of:
- Gate
eff_draft on slot count: allow g_draft when KV_SLOTS==1, and select the non-ragged decode kernel for that case in the serve loop; or
- Make the mux decode ragged-safe for speculation (harder — general fix that also helps multi-slot).
Option 1 seems the lower-risk, high-value path: single-client agentic use is the common case, and it wouldn't touch the multi-slot behaviour.
Impact
- ~2–3× decode throughput for all OpenAI-compatible clients (the ones that can't use
coli chat).
- Combined with the already-working KV-prefix reuse (above), this makes multi-turn agentic coding meaningfully faster, since decode is the remaining per-turn cost.
Environment
Happy to test patches on this hardware — I can A/B single-slot serve with/without speculation and report decode tok/s + output-correctness.
TL;DR
coli serveunconditionally disables MTP/speculative decoding, so the OpenAI-compatible HTTP API decodes at ~1 tok/s, while interactivecoli chaton the same box gets 2–3 tokens/forward from MTP. The disable is correct for the multi-slot mux path (speculation isn't ragged-safe across KV slots), but with--kv-slots 1there is only ever one non-ragged decoding sequence, so speculation ought to be expressible. Enabling MTP for the single-slot serve path would roughly 2–3× decode throughput for every API/agentic client (aider, opencode, Cline, OpenAI SDK, …) — the clients that can't usecoli chat.Motivation
Agentic/coding use requires the HTTP server, not the interactive TUI. In multi-turn tool-calling against
coli serve, I confirmed the server's KV-prefix reuse keeps prefill cheap on every turn — e.g. across one agent loop:So after the first turn, prefill is no longer the bottleneck — decode is. And decode over HTTP is stuck at ~1 tok/s precisely because MTP is off. Interactive
coli chat(with MTP) reaches 2–3 tokens/forward on the same model/box, but it can't drive tools or an OpenAI-API client. Closing this gap is the single biggest decode win available to agentic users.Current behaviour (code trace)
coli servehardcodesSERVE_BATCH=1into the engine's environment:and the engine then force-disables the draft/verify path whenever
SERVE + SERVE_BATCHare set — independent of--kv-slots:So there is no serve configuration — including
--kv-slots 1— that keeps MTP.What I tried (and why it confirms the guard is needed as written)
I patched
openai_server.py:470to makeSERVE_BATCHoverridable and ran the server withSERVE_BATCH=0 --kv-slots 1:[MTP] active: native speculative decoding (draft=3)and, on the first request,[SPEC_PIN] draft+verify pinned to the S=1 kernel family.So even at single-slot, the serve decode path (the ragged/mux KV route) is not compatible with the current speculation code — the
not ragged-safecomment holds in practice, not just in theory. I reverted the patch. This is a feature request, not a bug report: the guard is correct; what's missing is a non-ragged single-slot decode path that speculation can run on.Proposed direction
When
--kv-slots 1(no multiplexing), route decode through the single-sequence, non-ragged kernel family thatcoli chatalready uses for MTP, rather than the mux/ragged path — sodraft+verifyis expressible and correct. Concretely, one of:eff_drafton slot count: allowg_draftwhenKV_SLOTS==1, and select the non-ragged decode kernel for that case in the serve loop; orOption 1 seems the lower-risk, high-value path: single-client agentic use is the common case, and it wouldn't touch the multi-slot behaviour.
Impact
coli chat).Environment
GLM-5.2-colibri-int4-with-int8-mtp(int8 MTP heads verified).coliv1.0.0.Happy to test patches on this hardware — I can A/B single-slot serve with/without speculation and report decode tok/s + output-correctness.