Repository navigation
fix(server): keep chat prefill alive with protocol chunks - #865
Avicennasis wants to merge 2 commits into
Conversation
A keep-alive is a chat.completion.chunk after the role chunk, so it goes through the same emit() as every other one: with continuous_usage_stats it carries the usage so far, and with timings_per_token the snapshot of the ack behind it. Before the first ack nothing is measured, so no timings block.
5ccef3e to
8d6b772
Compare
|
Restacked onto The second commit is new and is the part worth a look. A keep-alive is a Re-ran on a CPU-only Ubuntu 24.04 x86_64 box (AMD Ryzen 7 5800X, 16 threads, 121 GiB, no GPU; Python 3.12.3, torch 2.14.1+cpu, triton 3.8.0, transformers 5.18.0, flashlib 0.4.0, numpy 2.5.4, safetensors 0.8.0, pytest 9.2.0, OpenAI SDK 2.54.0;
Not exercised here: CUDA, a real model, a real client's idle timer, and the 15-second interval at production length (the tests and probes run a 20 ms interval against a stub engine). #572's SSE comments were not combined with this branch. One thing changed upstream of the idea since this was filed: the donor project moved its Chat Completions keep-alive back to an SSE comment (Wallawalla47/Infernix |
|
Closing this draft. #572 covers the same silence with SSE comments on every stream at once, which is also where the donor project ended up. The branch stays on my fork in case the chat-side keep-alive chunks are wanted later. |
A queued or prefilling Chat Completions request currently sends its role chunk and then stays silent until generation produces output. This reuses
with_keepalivewith the same 15-second interval policy as Anthropic and Responses, sendingchat.completion.chunkdata frames withchoices: [{"index": 0, "delta": {}, "finish_reason": null}]. Event-based idle timers can count these frames. Heartbeats stop after the first nonempty content/reasoning delta or tool output. A heartbeat is a chunk like any other after the role chunk: withcontinuous_usage_statsit carries the usage so far, and withtimings_per_tokenthe timings of the ack behind it, so none while queued or prefilling. All chat chunks, including the optional usage chunk, retain the same id, model and creation time; finish reasons, timings and[DONE]retain their existing behavior. Non-streaming paths are untouched.Idea from Wallawalla47/Infernix commit 94953a26 (UE-05), reviewed against donor head
048d484da86144ad10f27fcabe218d528530c9de. Both projects use Apache-2.0. No donor code was copied, so no third-party notice was added.Verified on Linux x86_64, Python 3.12, torch 2.14.1+cpu, OpenAI SDK 2.54.0, from base
f69d50686d4f9bba578f7ac91135c609bb9f9e90. No GPU, driver or model checkpoint was used. The two new event-gated regressions sit immediately beside the existing shared keepalive test. Removing prefill heartbeats, sending heartbeats after content, changing their wire shape, drifting metadata, changing terminal/usage frames, or dropping the running usage or timings from a heartbeat that should carry them can turn them red.On the unchanged implementation, the focused command failed because no case received a heartbeat before its one-second guard: 4 failed, 145 deselected (exit 1). After implementation: 4 passed, 145 deselected (exit 0). From the repository root:
export PYTHONPATH=python python -m pytest tests/server/test_streaming_model_matrix.py -k test_chat_keepalive -q python -m pytest tests/server/ -qFinal server-subsystem gate: 692 passed, 0 failed, 0 skipped (exit 0);
mainalone gives 688. The full suite gives 20 failed, 1721 passed, 977 skipped against 20 failed, 1717 passed, 977 skipped onmainin the same environment: the same twenty test ids, which need a compiled extension or flashinfer, or are parametrised over the local model cache. A local probe (not part of this change) also passed one real localhost HTTP request through the registered route: two SDK-decoded prefill heartbeats, none during the later decode pause, unchangedlengthfinish reason, usage total 4, and termination on[DONE]. A second probe passed six adapter checks for content/reasoning/tool suppression, closing streams during prefill and after output, and errors after a heartbeat.NOT VERIFIED: CUDA, a real model or checkpoint, real client idle-timeout behavior, production-length 15-second/multi-minute prefill timing, or a combined checkout with other pending PRs. Some declared runtime dependencies were missing from the test environment and were installed locally; no package/dependency changes are in this diff.
Based directly on
origin/mainf69d50686d4f9bba578f7ac91135c609bb9f9e90. File-list checks show no overlap with the open #334, #459, #565, #592, #593 or draft #656; none is a stack prerequisite. Open upstream #572 adds SSE comments and disconnect polling inapi_server.py; this change supplies protocol data events inopenai_api.py, so the paths are separate. Their combined runtime behavior has not been tested. The companion connection-level keep-alive window change is separate and not included here.Question for the maintainer: is prefill-only heartbeat suppression also the preferred policy for reasoning and tool output? This implementation treats either as generation having started, consistent with stopping once tokens are flowing.