mirror of
https://github.com/mudler/LocalAI.git
synced 2026-09-17 00:40:52 -04:00
fix(llama-cpp): stop a generation whose stream is gone
grpc::ServerWriter::Write() returns false once the peer is gone, and PredictStream ignored that result at every call site. The handler kept pulling decoded tokens and writing them into a dead stream, so the llama.cpp slot stayed busy until the generation ended on its own terms. A model configured with max_tokens 0 and a large context ends on its own terms only at the context limit. On a 35B model at ~41 t/s a 120k context is about fifty minutes, and a slot held that long is a slot every other request for that model queues behind. Two abandoned requests were enough to make a node with free VRAM and a healthy control plane serve nothing: new requests timed out waiting for a slot, each timeout abandoned another generation, and the node fell further behind the longer it ran. Track the peer instead. The first failed write retires it for good, since a stream never recovers, and the RPC's own cancellation flag folds into the same predicate so the loop has one condition to test. Returning early is what frees the slot: ~server_response_reader() posts SERVER_TASK_TYPE_CANCEL for whatever is still decoding. TTSStream already checked Write(); this brings PredictStream in line. Cancellation stays cooperative and is checked between decoded results, so a batch already in flight may finish before the request stops. Assisted-by: Claude:claude-opus-5
This commit is contained in:
1 parent
7a234473e8
commit
f06eb61633
4 files changed
+156
-7
No files matched your search
@@ -195,3 +195,26 @@ cannot be retracted; DS4 does not flush incomplete buffered parser state or
|
||||
persist an abandoned request to the disk KV cache. Cancellation is cooperative:
|
||||
DS4 checks it at safe prompt-prefill and decode-loop boundaries, so a GPU kernel
|
||||
already in flight may finish before the request stops.
|
||||
|
||||
### llama.cpp request cancellation
|
||||
|
||||
The llama.cpp backend stops a streaming generation as soon as the response can
|
||||
no longer be written to the client, not only when the RPC is formally cancelled.
|
||||
A stream never recovers once a write fails, so the backend treats the first
|
||||
failed write as final and returns, which releases the slot the generation held.
|
||||
|
||||
This matters most for a model configured without a generation cap. With
|
||||
`max_tokens: 0` and a large `context_size`, an abandoned request that keeps
|
||||
decoding occupies its slot until it reaches the context limit — tens of minutes
|
||||
on a large model — and every other request for that model queues behind it. A
|
||||
couple of abandoned requests is enough to make a healthy node look wedged.
|
||||
|
||||
Cancellation is cooperative and checked between decoded results, so a batch
|
||||
already in flight may finish before the request stops.
|
||||
|
||||
{{% notice tip %}}
|
||||
A generation cap is still worth setting. Cancellation only helps once a client
|
||||
has actually gone away; a client that waits receives the full context worth of
|
||||
tokens. Set `max_tokens` on the model config, and keep `repeat_penalty` above
|
||||
`1` so a repetition loop terminates on its own.
|
||||
{{% /notice %}}
|
||||
Reference in new issue
Block a user