DeepSeek V4 speculative decode SIGSEGV in ggml_visit_parents_graph after client_disconnect prefix cache restore
Summary
When an in-flight generation is aborted mid-stream (e.g. client disconnect / manual cancellation during an extended reasoning turn), the active prefix cache slot is left in an unfinalized/dirty state. When a subsequent request matches the prefix and performs a cache restore (restore=true slot=N), speculative decoding occasionally crashes with SIGSEGV (Signal 11) in ggml_visit_parents_graph due to corrupt/stale tensor pointer traversal during draft graph verification.
Motivation & Symptoms
In production and interactive agent use (e.g. pi-coding-agent), long reasoning turns can occasionally loop or produce thousands of tokens. When a user manually interrupts generation, the next prompt sent to the server may stop abruptly mid-sentence because dflash_server crashes with SIGSEGV and dumps core.
Timeline from Server Logs (aimax:8087):
- Request A (
chatcmpl_...0010) aborted mid-generation:
Aug 28 10:55:02 aimax dflash_server[1862]: [server] chat START chatcmpl_...0010 format=chat stream=true prompt_tokens=11338 max_tokens=9218 tools=7
Aug 28 10:59:36 aimax dflash_server[1862]: [server] client disconnected — generation aborted (prompt=11338 out=3867)
Aug 28 10:59:36 aimax dflash_server[1862]: [server] chat DONE chatcmpl_...0010 ok=true in=11338 effective_in=11338 out=3868 273.7s 14.1 tok/s finish=client_disconnect restore=true slot=1 prefix_len=2162
- Request B (
chatcmpl_...0011) restores slot 1:
Aug 28 11:00:53 aimax dflash_server[1862]: [server] chat START chatcmpl_...0011 format=chat stream=true prompt_tokens=11382 max_tokens=4634 tools=7
Aug 28 11:00:53 aimax dflash_server[1862]: [pc] lookup hit slot=1 prefix_len=2162 (of 11382 total)
Aug 28 11:00:53 aimax dflash_server[1862]: [server] chat CACHE chatcmpl_...0011 restore=true slot=1 prefix_len=2162
- Crash at Speculative Decode Step 96:
Aug 28 11:02:20 aimax dflash_server[1862]: [ds4-spec-t] step=96 q=4 acc=4 | draft=15.9 head=3.2 save=2.8 verify=142.4 ...
Aug 28 11:06:20 aimax systemd[1]: lucebox.service: Main process exited, code=dumped, status=11/SEGV
Aug 28 11:06:20 aimax systemd[1]: lucebox.service: Failed with result 'core-dump'.
Debugging Methodology on Memory-Constrained / UMA Systems (128 GB)
When debugging large LLM inference servers (e.g. DeepSeek V4 on 128 GB UMA where the process holds ~115–120 GB RAM):
- Avoid Live GDB Attachment to Coredump:
- The coredump on disk is ~71.2 GB (
/var/lib/systemd/coredump/core.dflash_server...zst).
- Loading the coredump in GDB while
lucebox.service is running (holding ~120 GB RAM) will immediately trigger the kernel OOM killer.
- Safe Core Extraction Procedure:
# 1. Stop service to free physical RAM (~123 GB free)
sudo systemctl stop lucebox.service
# 2. Check metadata without loading full memory image
coredumpctl info
# 3. Extract backtrace in batch mode
coredumpctl debug /home/dpavlin/lucebox/server/build-hip/dflash_server \
--debugger-arguments='-batch -ex "info proc mappings" -ex "bt full" -ex "thread apply all bt"'
# 4. Restart service
sudo systemctl start lucebox.service
- Address Resolution & Disassembly:
- Stack trace identified crash at
0x7fc67eb7f793 in libggml-base.so.0.9.11 (base 0x7fc67eb24000, offset 0x5b793).
addr2line -e libggml-base.so.0.9.11 0x5b793 -f -C:
ggml_visit_parents_graph
ggml.c:7670
- Faulting instruction:
0x5b793 <ggml_visit_parents_graph+0x2a3>: mov 0x98(%rcx), %rsi ; reading node->src[k] from invalid pointer %rcx
Reproduction Observations & State
Preconditions Observed During Live Failure:
- Speculative decoding active:
DFLASH_DS4_SPEC=1 with DSpark drafter (DeepSeek-V4-Flash-DSpark-draft-Q4RMFP4-denseF16.gguf).
- Prefix caching enabled:
--prefix-cache-slots >= 1.
- High-context multi-turn session with tools: ~11,382 prompt tokens.
- Deep generation before abort: Request 0010 ran for ~4.5 minutes generating 3,868 speculative tokens before client disconnection.
- Subsequent restore & deep verification: Request 0011 restored
slot=1 (prefix_len=2162), prefilled 9,220 tokens suffix, and crashed at step 96 of speculative decode.
Note: Short synthetic scripts that abort after a few tokens do not reliably reproduce the crash because the memory corruption depends on deep speculative draft-tree mutations across thousands of tokens on the active slot.
Relationship to Prior Issues & PRs
- PR #371 (
fix(server): drop stale prefix-cache entries when a snapshot slot is reused): Addressed stale entries left behind when snapshots abort, but did not handle active in-flight slot rollback on client disconnect.
- PR #231 (
fix(dflash): abandoned stream drain): Fixed daemon pipe synchronization on disconnect, but did not invalidate dirty KV/speculative state in the restored slot.
- PR #584 (
Fix long-running SSE client disconnect during spec decode): Handled SSE disconnect timeouts during spec decode.
Proposed Remediation
In server/src/server/http_server.cpp (around line 4186):
When client_disconnected == true on a request using a prefix cache slot, explicitly invalidate the slot and purge its metadata so subsequent requests do not restore from unfinalized speculative state:
if (client_disconnected) {
std::fprintf(stderr, "[server] client disconnected — generation aborted "
"(prompt=%zu out=%d)\n",
req.prompt_tokens.size(), completion_tokens);
if (cache_slot >= 0) {
backend->invalidate_slot(cache_slot);
forget_inline_slot_metadata(cache_slot);
}
}
DeepSeek V4 speculative decode SIGSEGV in
ggml_visit_parents_graphafter client_disconnect prefix cache restoreSummary
When an in-flight generation is aborted mid-stream (e.g. client disconnect / manual cancellation during an extended reasoning turn), the active prefix cache slot is left in an unfinalized/dirty state. When a subsequent request matches the prefix and performs a cache restore (
restore=true slot=N), speculative decoding occasionally crashes withSIGSEGV(Signal 11) inggml_visit_parents_graphdue to corrupt/stale tensor pointer traversal during draft graph verification.Motivation & Symptoms
In production and interactive agent use (e.g.
pi-coding-agent), long reasoning turns can occasionally loop or produce thousands of tokens. When a user manually interrupts generation, the next prompt sent to the server may stop abruptly mid-sentence becausedflash_servercrashes withSIGSEGVand dumps core.Timeline from Server Logs (
aimax:8087):chatcmpl_...0010) aborted mid-generation:chatcmpl_...0011) restores slot 1:Debugging Methodology on Memory-Constrained / UMA Systems (128 GB)
When debugging large LLM inference servers (e.g. DeepSeek V4 on 128 GB UMA where the process holds ~115–120 GB RAM):
/var/lib/systemd/coredump/core.dflash_server...zst).lucebox.serviceis running (holding ~120 GB RAM) will immediately trigger the kernel OOM killer.0x7fc67eb7f793inlibggml-base.so.0.9.11(base0x7fc67eb24000, offset0x5b793).addr2line -e libggml-base.so.0.9.11 0x5b793 -f -C:Reproduction Observations & State
Preconditions Observed During Live Failure:
DFLASH_DS4_SPEC=1with DSpark drafter (DeepSeek-V4-Flash-DSpark-draft-Q4RMFP4-denseF16.gguf).--prefix-cache-slots >= 1.slot=1(prefix_len=2162), prefilled 9,220 tokens suffix, and crashed at step 96 of speculative decode.Note: Short synthetic scripts that abort after a few tokens do not reliably reproduce the crash because the memory corruption depends on deep speculative draft-tree mutations across thousands of tokens on the active slot.
Relationship to Prior Issues & PRs
fix(server): drop stale prefix-cache entries when a snapshot slot is reused): Addressed stale entries left behind when snapshots abort, but did not handle active in-flight slot rollback on client disconnect.fix(dflash): abandoned stream drain): Fixed daemon pipe synchronization on disconnect, but did not invalidate dirty KV/speculative state in the restored slot.Fix long-running SSE client disconnect during spec decode): Handled SSE disconnect timeouts during spec decode.Proposed Remediation
In
server/src/server/http_server.cpp(around line 4186):When
client_disconnected == trueon a request using a prefix cache slot, explicitly invalidate the slot and purge its metadata so subsequent requests do not restore from unfinalized speculative state: