Repository navigation
Conversation
…efixes Multimodal (mtmd) image chunks are decoded into the target context by the mtmd helper and never pass through common_speculative_process(), so the dflash draft cache is left with a positional hole. The next injection then fails the KV cache's consecutive-position check: init: the tokens of sequence 0 in the input batch have inconsistent sequence positions: ... X = 62 ... Y = 86 ... Y = X + 1 llama_decode(ctx_dft) failed rc=-1 srv update_slots: decode() failed: failed to process speculative batch and the server returns HTTP 500 for any request whose prompt contains an image. The same hole appears when the target reuses a cached prompt prefix the draft cache never saw (e.g. after a server restart with --cache-reuse). Detect the per-sequence gap at the top of process() and seed it with zero-feature encoder injections. Drafted tokens remain verified by the target, so this degrades post-image draft acceptance only - output remains distribution-exact (verified: greedy OCR output byte-identical between draft-dflash and a non-speculative run of the same request). A fuller fix would extract real target features for mtmd chunks (see the existing TODO referencing ggml-org#24669); this is the minimal change that stops the crash without touching the mtmd path.
Large images break the previous path: a 4000x3000 photo produces a 2961-row image-embedding chunk whose rows carry M-RoPE positions. The first ubatch happens to pass the draft KV cache's checks, but the second fails the consecutive-position rule: process: llama_decode(ctx_dft) failed rc=-1 (n_tokens=913, offset=2048) slot operator(): failed to decode mtmd chunk, idx = 1755, res = 1 srv send_error: failed to process mtmd chunk and even single-ubatch chunks inject rows at bogus draft positions (the M-RoPE temporal component, constant across the image), polluting the draft sliding window. Skip embedding batches entirely; the positional hole they leave is covered by the zero-fill pass when the next token batch arrives. Verified: 4000x3000 photo request now returns 200 with greedy output byte-identical to a non-speculative run; small-image OCR and rasterized-PDF transcription remain exact.
|
Pushed a second commit ( A 4000×3000 photo produces a ~3000-row image-embedding chunk that does reach Smaller images "worked" only because their chunk fit in one ubatch — and even then the injected image rows sat at wrong positions inside the draft's sliding window. The second commit skips embedding batches in Re-validated on Qwen3.8-27B BF16 + BF16 mmproj (M5 Max): the 4000×3000 photo returns 200 with greedy output byte-identical to a non-speculative run; small-image OCR and rasterized-PDF transcription remain exact; text-only behavior unchanged. If a target ever provides linear positions for image chunks (or after ggml-org#24669 lands), the embedding path could be re-enabled behind a position-linearity check — but skip-plus-zero-fill is the correct conservative default for M-RoPE targets today. 🤖 Generated with Claude Code |
Follow-up to ggml-org#28587. That PR skips M-RoPE-pinned mtmd embedding batches in the DFlash process() ("the draft can jump over the gap"), but the gap it leaves in the draft KV cache is not harmless: the next token batch fails the KV cache's consecutive-position check and llama-server returns HTTP 500 for any request whose prompt contains an image (issue ggml-org#27408). The same hole appears when the target reuses a cached prompt prefix the draft cache never saw. Detect the per-sequence gap at the top of the DFlash process() and seed it with zero-feature injections. Drafted tokens are still verified by the target, so this only degrades draft acceptance right after an image -- output stays distribution-exact. Tested on 2x RTX 5060 Ti (CUDA, b11434): Qwen3.6-35B-A3B Q4_K_M + mmproj Q8_0 + DFlash sidecar. Image requests go from HTTP 500 to 200 with no speculative-batch errors; text-only n=6 quality is unchanged (paired P=0.505) and speed is within noise. The approach is due to @dagnarf (z-lab#1); this is the minimal port to upstream. Signed-off-by: Chazyl ARFI <arfi.chazyl@gmail.com>
Fixes the multimodal breakage in DFlash2 speculative decoding (reported on ggml-org#27342: vision requests failing while native MTP works).
Repro (M5 Max, Metal,
dflash2head5ecbe1ac1)Any
/v1/chat/completionsrequest containing an image returns HTTP 500:Root cause
mtmd image chunks are decoded into the target context by the mtmd helper, so they never pass through
common_speculative_process(). The dflash draft cache is left with a positional hole spanning the image chunk, and the first text ubatch after the image fails the draft KV cache's consecutive-position check. (Theprocess()comment already anticipates this — "skipping the embedding batches leaves a hole in the draft's cache" — but the batches aren't skipped, they simply never arrive.) The same hole can appear when the target reuses a cached prompt prefix the draft cache never saw.Fix
Detect the per-sequence gap at the top of the dflash
process()and seed it with zero-feature encoder injections. Drafted tokens are still verified by the target, so this only degrades draft acceptance right after an image — output stays distribution-exact.A fuller fix would extract real target features for mtmd chunks (per the existing TODO referencing ggml-org#24669); this is the minimal change that stops the 500 without touching the mtmd path. The
LOG_WRNon each hole could arguably beLOG_DBGfor cache-reuse-heavy serving — happy to adjust.Validation (Qwen3.8-27B BF16 + BF16 mmproj +
Qwen3.8-27B-DFlash2-BF16, M5 Max)🤖 Generated with Claude Code