spec: keep the DFlash drafter working after an image in the prompt - #344
Conversation
Two faults, both only visible once a prompt contains an image: 1. draft() placed the noise block at dp.n_past, the slot's token count. For an M-RoPE target that stops being the next position after an image: image tokens advance the position by the grid size, not by their count, so the block landed hundreds of positions past the draft's own cache and every draft was rejected (acceptance 0.03 for the rest of the conversation, decode below plain speed). Take the next position from the target's memory instead; without images the two are equal. 2. process() mirrored image chunks into the draft: vision-layer states the draft was never trained on, all at one temporal position. With the default sliding-window draft cache that could not even be placed (find_slot failed, then the server aborted). Skip embedding batches; the draft keeps the target's positions across the gap and the text after the image carries the image's influence in its injected features. Qwen3.8-27B TQ3_1S + DFlash2 + mmproj on MI210, 20 KB text + a 1024x768 image, then a text turn: acceptance 0.013 / 0.026 -> 0.34 / 0.43, decode 27 / 29 -> 66 / 78 t/s, with the default draft cache. Mirroring the chunks with the position fix still crashes; a full-size draft cache is not needed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxP6x5bmUDYFvmouceN2mR (cherry picked from commit 9fa62c4d52407857429db56261d2874813e51e05)
|
Both fixes read correctly to me. One thing I could not confirm. The new comment in Nit: CI: the Windows x64-vulkan failure is |
The comment claimed the gap left by skipped image chunks needs a full-size draft cache that the server enables; nothing does that, and the measurements were taken with the default sliding-window cache, which is fine: every batch the draft sees has consecutive positions. Say so, and write the condition as !has_tokens || has_embeddings. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxP6x5bmUDYFvmouceN2mR
|
You're right, that sentence described a server-side change I had tried and then reverted: forcing |
340b345
into
TheTom:feature/turboquant-kv-cache
Overview
With a DFlash drafter, any image in the prompt silently destroyed speculative decoding for the rest of the conversation: acceptance fell to ~0.03 and decode dropped below plain speed. With the default sliding-window draft cache it was worse, the server aborted on the image chunk (
llama_decode(ctx_dft) failed,failed to process image).Two faults, both only visible with M-RoPE targets and multimodal prompts:
draft()placed the noise block atdp.n_past, the slot's token count. After an image that is no longer the next position, because image tokens advance the position by the grid size rather than by their count, so the block landed hundreds of positions past the draft's own cache and every draft was rejected. The block now starts at the target'sllama_memory_seq_pos_max() + 1; without images the two values are equal, so nothing changes for text-only use.process()mirrored image chunks into the draft: vision-layer target states the draft was never trained on, all at one temporal position. The sliding-window cache could not even place them (find_slotfailed). Embedding batches are now skipped; the draft keeps the target's positions across the gap, and the text after the image carries the image's influence in its injected features.The TODO pointing at ggml-org#24669 stays: a proper batch API would let the draft know about the chunk, but nothing here depends on it.
Additional information
Qwen3.8-27B TQ3_1S + DFlash2 +
mmproj-F16on an MI210 (ROCm 7.2.3), a 20 KB text prefix plus a 1024×768 image, then a text-only turn in the same conversation:Measured with the default draft cache. Mirroring the image chunks with only the position fix applied still crashes the ring cache, and a full-size draft cache (
--swa-full) is not needed once they are skipped; both variants were run.The same code and the same TODO exist on ggml-org master, so this applies there as well.
Requirements
🤖 Generated with Claude Code
https://claude.ai/code/session_01NxP6x5bmUDYFvmouceN2mR