Skip to content

kolibri1: n-gram spec-decode on the engine path — 14.1 tok/s bench, byte-identical chains - #3450

Merged
lu-zero merged 2 commits into
localai-org:mainfrom
lu-zero:row/kolibri-spec-engine
Oct 12, 2026
Merged

lu-zero merged 2 commits into
localai-org:mainfrom
lu-zero:row/kolibri-spec-engine

Conversation

@lu-zero

@lu-zero lu-zero commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

The spec-decode probe (#3449) proved the mechanism; this lands it on the engine's serving path. Review findings on this PR are addressed (the two-pin C API reach test, the corrected decode denominator) — see below.

The one seam — LoadedEngine::MakeKVCacheMaybeSpec (src/vllm/entrypoints/model_loader.cpp:1757-1781) built MakeQwen3_5KVCacheSpec for ANY spec config, ignoring the model's own cache spec, so kolibri under a spec config got a MambaSpec and the runner refused. Now the model's own spec is built first; the widened qwen3.5 branch is taken only when the config uses EAGLE or the model's groups carry a recurrent/mamba group. Verified how fa_draft is consumed before deciding kolibri needs no draft-proposal group: it is allocated only for the MTP draft head's own paged KV, a forward the n-gram proposer does not have. Default (no spec config) path byte-identical; qwen3.5's landed n-gram gate untouched.

The wiring — no new engine code: EngineParams::speculative_config (C ABI v6 field + the server's --speculative-config) already feeds the generic scheduler slots, draft install/rollback, sample_tokens_with_rejection greedy accept-iff-argmax, and the n-gram proposer. The e2e gate proves the whole loop through LoadedEngine.

Review finding 1 (C API reach pin was vacuous) — fixed in tests/capi/test_capi.cpp: a two-pin case now proves the field is parsed AND forwarded: a malformed spec (bad n-gram window) fails with the spec-validation error class (VLLM_ERR_INVALID_ARGUMENT, not VLLM_ERR_MODEL_LOAD), and a drafter-chain document the ABI parse accepts is refused BY NAME by the engine's up-front ResolveSpecConfig — the error message discriminates (vllm_cpp.drafter_chain refusal vs model path is not a directory); the status is VLLM_ERR_MODEL_LOAD either way through the load's generic catch, documented in the test. Mutation-proven: reverting the parse block reds both pins (the pre-existing malformed case reds too — the reviewer's exact vacuousness), reverting only the assignment reds the forwarding pin. The valid-ngram case's claim is narrowed to parse-acceptance.

Review finding 2 (decode denominator overcounted) — fixed in the e2e gate: the per-request output count at t_first is recorded; decode tok/s = (final_count − count_at_first) / decode_interval; wall keeps the full count. Corrected figures (t32+32GiB, sync scheduler, the A/B of record): engine bench prompt decode 4.53 -> 13.78 tok/s (3.04x; wall 3.04 -> 5.89); goldens aggregate decode 6.27 -> 8.13 tok/s (1.30x; wall 5.46 -> 6.91). Default-scheduler run for the record: bench decode 4.80 -> 15.72 (wall 3.18 -> 6.34), goldens 6.35 -> 8.99. The 12 tok/s target is cleared through the engine under the corrected denominator. Operator verification reproduced the corrected figures within run noise (13.52 bench / 7.87 goldens decode).

Acceptance is exact by construction (accept iff target greedy argmax agrees): spec-ON == spec-OFF byte-identical on 9/9 prompts (bench + 8 W3 goldens); acceptance matches the pinned simulation (53/53 bench, 146/270 goldens). W3: 900/900 with spec off; spec-on chains identical so the torch-golden relationship is unchanged (4/8 end-to-end identical, 4 adjudicated near-ties). Gates: e2e 83/83 in both scheduler modes; seam unit 21/21; acceptance sim 90/90; test_capi 75/75 (743/743, incl. the new two-pin case); test_kolibri1 234/234; test_kolibri1_numa 11/11; seam-adjacent suites green; preflight green; record-anchor rot 0.

Issue: ISSUE-LOCAL-01M4P22PX6BPRQAY1CK20FZQF6 (Resolution carries the record and the corrected figures).

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:zai/glm-5.3-flash [maki]

The probe (ISSUE-LOCAL-01M4P22PX6BPRQAY1CK20FZQF6) found the n-gram
spec-decode machinery generic end to end and pinned exactly one blocker:
LoadedEngine::MakeKVCacheMaybeSpec built MakeQwen3_5KVCacheSpec for ANY
speculative config, ignoring the model's own make_kv_cache, so a spec
config on kolibri injected a MambaSpec group with empty shapes plus an
unused fa_draft group and the runner refused at construction
(runner.cpp:1074).

The seam now builds the model's own KV spec and takes the widened qwen3.5
branch only when spec->use_eagle() or the model's own groups carry a
recurrent/mamba group. A draft-free n-gram config on a model with no
recurrent group keeps the model's own spec: the drafts are ordinary
scheduled tokens, so the target's groups are the whole cache need and no
draft-proposal group is added (fa_draft serves only the MTP draft head's
paged KV, a forward n-gram does not have). The default (no spec config)
path is byte-identical. qwen3.5's ngram path keeps the widened spec, so
the landed 27B ngram gate is untouched. No new engine wiring was needed:
EngineParams::speculative_config (the C ABI v6 field /
--speculative-config) already fed the generic scheduler slots, engine
draft pull, runner verify and n-gram proposer; the e2e gate proves the
loop ran.

test_kolibri1_ngram_spec flips from the pinned blocker into a
checkpoint-free seam unit (kolibri keeps its own 2 groups under an ngram
spec config — no MambaSpec, no fa_draft, byte-identical to spec-off —
while qwen3.5 still widens) plus the engine e2e gate: spec-ON == spec-OFF
byte-identical on 9/9 prompts (bench prompt + 8 W3 goldens), acceptance
exactly 53/53 on the bench prompt and 146/270 on the goldens (the probe
harness's numbers), engine decode tok/s 4.34 -> 13.39 on the bench prompt
(3.08x, clears the 12 tok/s target; harness reference 4.92 -> 14.51) and
6.80 -> 8.17 on the goldens (1.20x), measured with the synchronous
scheduler on both arms (ngram forces sync). W3 unchanged: 900/900 with
spec off; with spec ON the chains are byte-identical to spec-OFF, so the
torch-golden relationship (4/8 end-to-end identical, 4 adjudicated
near-tie flips) is unchanged. test_capi gains the ABI reach pin (a valid
ngram speculative_config reaches the engine load). The row spec's ## Now
and the canonical issue's Resolution carry the record; the engine-matrix
anchor for ENG-MOE-LOADSTREAM is re-anchored to the shifted FromModelDir
line.

Gates: seam unit 21/21; e2e 83/83 (104/104 both cases); acceptance sim
90/90; probe harness 17/17; W3 900/900; decode bench anchor 109726;
test_kolibri1 234/234; test_kolibri1_numa 11/11; seam-adjacent suites
(test_loaded_engine_dense 157, test_model_registry 1229,
test_deepseek_v4_scaffold 684, test_kv_pool_min_fit 16, test_runner 1918,
test_kv_cache_fp8_wiring 487) green; preflight green; record-anchor rot 0.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:zai/glm-5.3-flash [maki]

@localai-org-maint-bot localai-org-maint-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mudler @richiejp Focused source review at 7675d31: the Kolibri cache dispatch addresses the injected Mamba group, and the added engine test checks both token equality and nonzero speculation. Two validation claims need attention before treating this as merge-ready.

  1. The new C API test (tests/capi/test_capi.cpp, "a valid ngram speculative_config reaches the engine load") does not prove forwarding into the engine. It loads a nonexistent directory and expects VLLM_ERR_MODEL_LOAD. If ep.speculative_config is discarded immediately before FromModelDir in src/capi/vllm_c.cpp, the missing-directory error and null handle still satisfy both assertions. The C++ engine test bypasses the C API, so it does not cover that break either. Please add a C API behavior check that fails when forwarding is removed, or narrow the claim to acceptance of the JSON document and leave C API reachability pending.

  2. The new throughput calculation divides the full output count (64, or 8*32) by elapsed time starting after the first output arrives. Tokens already present at t_first are outside that interval. Please record that initial count per request and subtract it when calculating decode throughput; retain full counts for wall throughput. This matters for speculative output, where an engine step can emit several tokens. Update the reported measurements with the corrected denominator/numerator pairing.

These are source-level findings. I could not run the native or model-backed gates here: Python, CMake, and a C++ compiler are unavailable. I have not independently confirmed the timing/parity claims or completed the repository's mutation-review gate.

…ode denominator

Two review findings on 7675d31 (PR localai-org#3450), both addressed:

(1) The C API reach pin was vacuous. "a valid ngram speculative_config
reaches the engine load" loads a nonexistent directory and expects
VLLM_ERR_MODEL_LOAD, so it passes whether or not the parsed config is
forwarded into EngineParams. test_capi.cpp now pins the field in two
halves (new case "capi: speculative_config is parsed AND forwarded into
the engine"). The PARSE pin: an invalid ngram window is a caller error,
VLLM_ERR_INVALID_ARGUMENT, never MODEL_LOAD, handle null. The FORWARDING
pin: a drafter-chain document the ABI layer accepts is refused by name by
the engine's up-front ResolveSpecConfig (FromModelDir's first act, ahead
of every path/config/tokenizer/weight operation), so against a missing
checkpoint the load fails with the spec-validation refusal, not the
missing-directory error. The valid-ngram case's claim is narrowed to what
it actually proves (parse acceptance). The engine-side refusal surfaces
through the load's generic catch, so its STATUS is VLLM_ERR_MODEL_LOAD
either way — the MESSAGE is the discriminator, pinned deliberately.
Mutation-verified both ways: reverting the whole parse+assignment block in
src/capi/vllm_c.cpp fails the parse pin (and the pre-existing malformed
case) while the valid-ngram case stays green — the reviewer's exact
vacuousness; dropping only the assignment into EngineParams fails the
forwarding pin. vllm_c.cpp restored byte-for-byte after each mutation.

(2) The e2e gate's decode tok/s divided the FULL output count by the
interval from the first output to finish, billing tokens already present
at t_first (several at once under spec decode) with no generation time.
The gate now records the per-request output count at t_first and divides
(final_count - count_at_first) by the decode interval; wall tok/s keeps
the full count. Re-measured on this box (t32 + 32 GiB cache, under flock,
VT_ASYNC_SCHED=0, the A/B of record): bench prompt decode 4.53 -> 13.78
tok/s (3.04x; wall 3.04 -> 5.89, 1.93x); goldens aggregate decode 6.27 ->
8.13 tok/s (1.30x; wall 5.46 -> 6.91, 1.27x). The corrected decode figures
read modestly lower than the overcounted ones (the operator's verification
measured 14.06 bench / 8.60 goldens decode; its wall figures 3.08 -> 5.86
and 5.47 -> 7.08 are unchanged within noise). Default-scheduler run for
the record: bench decode 4.80 -> 15.72 (wall 3.18 -> 6.34), goldens decode
6.35 -> 8.98 (wall 5.39 -> 7.60). The fix is reporting-only: acceptance
(53/53 bench, 146/270 goldens) and byte-identity (9/9) are unchanged, and
the 12 tok/s target is still cleared through the engine. The canonical
issue's Resolution and the row spec carry the corrected figures and
denominator.

Gates: e2e 83/83 in BOTH VT_ASYNC_SCHED states; seam unit 21/21;
acceptance sim 90/90; W3 900/900; test_capi full suite 75/75 (743/743
assertions, including the new two-pin case 8/8); staged preflight green;
record-anchor rot 0.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:zai/glm-5.3-flash [maki]
@lu-zero
lu-zero merged commit dbe1d6a into localai-org:main Oct 12, 2026
23 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants