Skip to content

Feature request: speculative decoding (MTP) in the HTTP coli serve path for single-slot (--kv-slots 1) #492

Description

@SimonRafferty

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:

  1. 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
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNuova funzionalità

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions