Skip to content

Eval bug: draft-mtp + --parallel > 1 causes cross-slot content contamination (not HIP-graphs related) #28286

Description

@jsaigou

Summary

With --spec-type draft-mtp and --parallel N (N > 1), concurrent requests carrying clearly
distinct prompts can produce responses that drift into content belonging to a different
concurrent request's prompt/completion. Output stays valid UTF-8 with no garbage-token
signature — it reads as plausible, on-topic-adjacent text, not obviously broken output, which
makes it easy to miss in casual testing.

Reproduces identically with GGML_CUDA_DISABLE_GRAPHS=1 set, so it's unrelated to HIP graph
capture. Likely adjacent to #28019 (qwen4exp multi-seq rs-rollback corruption) — both are real
gaps in how MTP's still-draft code (originally from #27836) handles per-sequence state under
concurrent slots — but this reproduces the content-mixing symptom specifically, distinct from
that issue's logits-corruption report, and isn't architecture-specific in the way #28019 is
(not yet tested on a non-recurrent target model, see below).

Environment

Steps to reproduce

Launch with:

llama-server --model Qwen3.8-Flash-Next-UD-Q3_K_XL.gguf \
  --model-draft mtp-Qwen3.8-Flash-Next-Q8_0.gguf \
  --spec-type draft-mtp --spec-draft-n-max 4 \
  --parallel 4 --flash-attn on \
  --cache-type-k q8_0 --cache-type-v q8_0

Fire 4 concurrent /v1/completions requests (temperature 0) with clearly distinct prompts,
e.g.:

  • "def quicksort(arr):\n if len(arr) <= 1:\n return arr\n"
  • "class Matrix:\n def __init__(self, data):\n"
  • "The history of the Roman Empire began with"
  • "In distributed systems, the CAP theorem states that"

Observed

Responses drift into content that only makes sense in a different slot's request. Examples
from one run:

  • The quicksort-completion request's response trailed into: ... Caesar's assassination in 44 BC, and he is considered one of (content belonging to the Roman Empire request)
  • The CAP-theorem request's response trailed into: a distributed data)\n def __arr) + right\n return quick_sort(arr) (content belonging to the quicksort request)
  • A second run: the Roman-Empire request's response included arr = [38, 27, 43, 3, 8, 64, 21, 17, 52, 10]\n print("Orig — again, code from a different concurrent request

Expected

Each concurrent request's response should only ever reflect that request's own prompt/context.

Notes on isolation

Suspected area

common_speculative_draft() / the accept-and-verify loop in tools/server/server-context.cpp
(added by #27836) — likely a shared buffer or per-sequence draft state that isn't correctly
keyed/isolated across concurrent slots when --parallel > 1.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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