Skip to content

Distributed ring: worker death mid-decode hangs the server (no timeout in llama_recv_tensors); meta-phase failures misreported as "KV cache is full" #1

Description

@negronjl

Context

While evaluating prima.cpp's distributed server mode for integration with a Go cluster orchestrator, I did a source-level review of the ring transport's failure handling (at commit 6f9b7c4). The pipelined-ring design and the server-mode engineering (slot-scoped KV forwarding, server_mode seq-id alignment) look solid — this report is specifically about what happens when a worker rank dies mid-decode.

Observed behavior (3 related code paths)

1. llama_recv_tensors has no receive timeoutsrc/llama.cpp:18054:

static void llama_recv_tensors(zmq::socket_t & socket, struct llama_ubatch * ubatch, const bool is_out_embd=false) {
    std::vector<zmq::message_t> recv_msgs;
    if (!zmq::recv_multipart(socket, std::back_inserter(recv_msgs))) {

Unlike llama_recv_meta (src/llama.cpp:17920), which sets rcvtimeo=1000 for its first poll, the tensor-phase receive blocks on a default (infinite) socket. If a peer rank dies while the ring is inside the tensor-forwarding phase, its neighbor blocks in recv_multipart forever. In server mode this wedges the decode loop — all slots hang, /health still answers, and only a process restart recovers.

2. When the meta-phase 1s timeout does catch the failure, the error is misattributedexamples/server/server.cpp:2434-2449:

if (ret != 0) {
    if (n_batch == 1 || ret < 0) {
        // if you get here, it means the KV cache is full - try increasing it via the context size
        SRV_ERR("failed to decode the batch: KV cache is full - ...");
        ...
        send_error(slot, "Input prompt is too big compared to KV size. Please try increasing KV size.");

llama_recv_meta returning -1 (dead peer) propagates as a nonzero llama_decode return, which this handler interprets exclusively as KV pressure: the batch-halving retry loop runs pointlessly, and the client receives "Input prompt is too big compared to KV size" for what is actually a ring-communication failure.

3. llama_recv_tensors failure is void-logged rather than surfaced — on a failed recv_multipart it logs "Failed to receive tensor data." and returns void, so the caller proceeds with an unfilled ubatch rather than aborting the decode with a distinct error.

Reproduction sketch

Two-device ring in server mode (llama-server --world 2 --rank 0 ... + llama-cli --world 2 --rank 1 ...), start a long streaming completion, kill -9 the rank-1 process mid-generation. Expected observations per the code above: the request either hangs indefinitely (tensor phase) or errors with the KV-size message (meta phase); subsequent requests hang.

Suggested direction

  • Apply a bounded rcvtimeo to the tensor-phase receive, mirroring llama_recv_meta's pattern (perhaps a few seconds rather than 1s, given tensor sizes).
  • Give ring-communication failures a distinct error return through llama_decode so server.cpp can distinguish "peer unreachable" from "KV cache full" — both for the retry logic and the client-facing message.
  • Have llama_recv_tensors return an error status instead of logging + returning void.

Happy to test patches on a multi-node setup. Thanks for the project — the ICLR paper's piped-ring + prefetch design is genuinely compelling for heterogeneous home clusters.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions