DeepSeek-V4.1-Flash (PR #30817): single-token output corruption when a cached prompt prefix is reused (Engram n-gram history)
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 38/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- cpp
- Domain
- backend, machine-learning
Research direction
Start with the Engram history path described in the issue, especially engram_hash and get_prev_tokens, then trace how server prompt-prefix reuse restores cached KV state. Compare the token history used for Engram row lookups after a reused prefix with a fresh prefill, using the linked reproduction harness and its cache_prompt: false control. Done means reused-prefix requests no longer produce the reported single-token substitutions.
Written by the indexing model from the issue text.
Description
This is against the deepseek41 branch of #30817 (draft), not master — filing as an Issue so it doesn't clutter the PR thread.
Trigger isolated: KV prompt-prefix reuse corrupts output with the Engram (both --lazy-mode settings)
Name and Version
Build of the deepseek41 branch @ b72050d (the branch behind PR #30817); this commit is also the first bad commit.
Operating systems
Linux
GGML backends
CUDA
Hardware
2× RTX PRO 6000 Blackwell (96 GB) + 251 GB RAM, WRX90E-SAGE-SE
Models
unsloth/DeepSeek-V4.1-Flash-1017-GGUF, UD-IQ3_S (8 shards)
Setup
llama-server \
-m DeepSeek-V4.1-Flash-1017-UD-IQ3_S-00001-of-00008.gguf \
--alias ds41f \
-ngl 99 \
-ncmoe 16 \
-ts 27,13 \
-c 131072 -fa on -ub 2048 -b 4096 --parallel 1 \
-t 32 \
--load-mode mmap --lazy-mode on \
--override-tensor "per_layer_token_embd.weight=CPU" \
--jinja \
--reasoning on --reasoning-format deepseek \
--chat-template-kwargs '{"reasoning_effort":75}' \
--temp 1.0 --top-p 1.0 --top-k 0 --min-p 0.0
CUDA 13.2, sm_120. The --lazy-mode off variant was run on the same build with the Engram tensor resident (no CPU override).
We ran a controlled discriminant with a literal-copy harness (20 hex + 20 prose items, temp 0, forced tool-call), each pass being the full harness against the same server process:
| server mode | pass 1 (fresh prefixes) | pass 2 (reused prefixes) | pass 3 ("cache_prompt": false) |
|---|---|---|---|
--lazy-mode on |
1/20 hex, 0/20 prose | 6/20 hex, 2/20 prose | 0/20 hex, 0/20 prose |
--lazy-mode off |
0/20 hex, 0/20 prose | 8/20 hex, 0/20 prose | — |
Pass 1 already reuses the shared system-prompt prefix across items, which likely explains the 1/20 in lazy-on pass 1.
Corruption in pass 2 accumulates progressively in our runs (first items clean, grows monotonically) — consistent with reused KV state.
Mechanism (appears to be): engram_hash builds the n-gram row-ids from the token history reconstructed via get_prev_tokens. When the server reuses a cached prompt prefix (slot selected by LCP similarity, f_keep), the n-gram history of the first tokens after the reused prefix diverges from what the original forward pass saw, so the Engram row lookups land on wrong rows and single-token substitutions appear in the output, growing with the reused-prefix length.
Reproduction: run the harness once against a fresh server (clean), run it again immediately (hex corruption jumps to 6-8/20), run a third time with "cache_prompt": false on every request (clean again).
Harness: https://gist.github.com/bertusmt/cd1bba69bc92836e338b2fb5e35291ad (iso_2x20.py; defaults to localhost:8000 — pass your port as the first argument).
Workaround: send "cache_prompt": false on requests that must be bit-exact (cost: full re-prefill).
In a separate 500-item tool-calling battery with CJK content on the same model we observed the same class of single-token substitutions when prefixes were reused; the same mitigation applied.
Reference: PR #30817.
Relevant log output
Slot selection on every harness call (f_sim_best/f_keep from the server log):
oct 20 23:47:53 llama-ds41dbg[3977692]: slot get_availabl: id 0 | task -1 | selected slot by LCP similarity, f_sim_best = 0.910 (> 0.100 thold), f_keep = 0.398
oct 20 23:48:16 llama-ds41dbg[3977692]: slot get_availabl: id 0 | task -1 | selected slot by LCP similarity, f_sim_best = 0.910 (> 0.100 thold), f_keep = 0.281
oct 20 23:48:24 llama-ds41dbg[3977692]: slot get_availabl: id 0 | task -1 | selected slot by LCP similarity, f_sim_best = 0.899 (> 0.100 thold), f_keep = 0.573
oct 20 23:48:47 llama-ds41dbg[3977692]: slot get_availabl: id 0 | task -1 | selected slot by LCP similarity, f_sim_best = 0.899 (> 0.100 thold), f_keep = 0.280
(376 slot-selection lines of this kind across the two-pass run.)
Example of observed output diffs on hex items (expected vs produced, pass 2, from iso2x20_hex_raw.json):
iso/hex02.bin char 0: expected '1', produced '9'
iso/hex05.bin char 47: expected '7', produced ' '
iso/hex07.bin char 0: expected '6', produced 'x'
iso/hex11.bin char 150: expected '3', produced ' '
iso/hex17.bin char 0: expected '9', produced 'x'
iso/hex12.bin char 171: expected '9', produced ' '
iso/hex15.bin char 0: expected 'a', produced 'c'
iso/hex19.bin char 194: expected '8', produced ' '
(8/20 corrupted hex items in this pass; prose items in the same pass: 0/20 corrupted.)
- Dominant language
- C++
- Stars
- 132k
- Forks
- 24.6k
- Avg merge
- 2d 1h
- Merged PRs (30d)
- 500
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from ggml-org/llama.cpp
-
bug-unconfirmed
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
ggml-org/llama.cpp#31163 · 1 comment ·
Maintainers usually reply within 1 day
-
Misc. bug: docs mention --spec-draft-conf-min, but that flag doesn't existPossibly taken @jajamabhijith claimed this 1 day ago. Openbug-unconfirmed
Difficulty 1/5 Under an hour Newbie friendliness 91/100
Maintainers usually reply within 1 day
-
bug-unconfirmed
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
Maintainers usually reply within 1 day
-
Misc. bug: Metal aborts on MUL_MAT with BF16 src1 at K >= 64, batch > 8 (no BF16 mul_mm kernel)Possibly taken @rashid5925 claimed this 3 days ago. Openbug-unconfirmed
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
ggml-org/llama.cpp#30973 · 4 comments ·
Maintainers usually reply within 1 day
All issues in ggml-org/llama.cpp
Similar issues
-
Status: Needs confirmation Status: Needs triage
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
A-combat P-low S-ready-to-start task
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
Maintainers usually reply within 1 day
-
bug intrinsic medium priority rt_lib
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
OpenPrinting/splix#33 · 3 comments ·