Skip to content

Turbo dflash#219

Open
giveen wants to merge 1016 commits into
TheTom:masterfrom
giveen:turbo-dflash
Open

Turbo dflash#219
giveen wants to merge 1016 commits into
TheTom:masterfrom
giveen:turbo-dflash

Conversation

@giveen

@giveen giveen commented Jul 14, 2026

Copy link
Copy Markdown

Hardware: RTX 5090 (32GB) · Intel Core Ultra 9 285K
Target model: Qwen3.6-35B-A3B-UD-Q4_K_S.gguf
Draft model: Qwen3.6-35B-A3B-DFlash-Q8_0.gguf
Prompt: "Write a quicksort algorithm in Python. Write code only."
Sampling: --temp 0 --top-k 1 --seed 42


Overview

Port DFlash speculative decoding (block-diffusion drafting with KV cache injection, from (#103)) onto feature/turboquant-kv-cache. DFlash uses a lightweight draft model to propose blocks of tokens by encoding target model hidden states and conditioning on noise latents, then validates against the target model.

Key changes

  • Adds llama_model_dflash and llama_model_eagle3 model architectures (encoder/decoder graph builders, tensor loader, hparams)
  • Registers --dflash flag in CLI/server, with speculative type auto-detection
  • Wires ctx_other for target model embedding/output sharing in DFlash decoder
  • Adds target layer extraction infrastructure (embeddings_layer_inp + t_layer_inp population) needed by the DFlash encoder
  • Fixes output_reserve sizing for target contexts when draft flags are active (server_n_outputs_max now accounts for --dflash/--eagle3)
  • Adds KV/tensor enum entries and template instantiations for DFlash/EAGLE3 metadata keys

Results

Baseline (no speculation)

Command Prompt t/s Generation t/s
q8_0 cache, 1024 ctx 252.5 237.4
LLAMA_SPEC_NO_THINK=1 timeout 120 ./build/bin/llama-cli \
  -m /mnt/storage/models/qwen3.6/35B/Qwen3.6-35B-A3B-UD-Q4_K_S.gguf \
  -p "Write a quicksort algorithm in Python. Write code only." \
  -n 256 -c 1024 --temp 0 --top-k 1 --seed 42 -ngl 99 \
  --cache-type-k q8_0 --cache-type-v q8_0 --reasoning off

DFlash On

Cache K Cache V Context Prompt t/s Generation t/s
q8_0 q8_0 1024 184.8 340.6
q8_0 q8_0 32k 155.9 317.3
q8_0 q8_0 64k 147.7 319.0
q8_0 turbo4 64k 146.5 316.0
turbo4 turbo4 64k 144.1 315.4
turbo4 q8_0 64k 144.5 396.2
LLAMA_SPEC_NO_THINK=1 timeout 120 ./build/bin/llama-cli \
  -m /mnt/storage/models/qwen3.6/35B/Qwen3.6-35B-A3B-UD-Q4_K_S.gguf \
  -md /mnt/storage/models/qwen3.6/35B/Qwen3.6-35B-A3B-DFlash-Q8_0.gguf \
  --dflash \
  -p "Write a quicksort algorithm in Python. Write code only." \
  -n 128 -c <CONTEXT> --spec-draft-n-max 16 \
  --temp 0 --top-k 1 --seed 42 -ngl 99 -ngld 99 \
  --cache-type-k <K> --cache-type-v <V> --reasoning off

Summary

  • Best result: 396.2 t/s generation (DFlash, turbo4 K / q8_0 V, 64k context) vs 237.4 t/s baseline — 1.67x speedup
  • DFlash maintains high throughput even at 64k context (319.0 t/s with q8_0 both)
  • turbo4 KV cache works with DFlash and can improve generation speed in mixed configurations
  • Acceptance rate on this model pair is ~7% (model training quality, not a code issue)

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: Yes, to resolve merge conflicts and to fix my coding mistakes.

ggerganov and others added 30 commits June 3, 2026 10:15
* tests : add support for qwen3 SSM archs

* arch : add LLM_KV_ATTENTION_RECURRENT_LAYERS

* cont : naming + TODOs
* cuda: reserve space for quantize kv-cache at startup

* address review comments

* remove forward decl

Co-authored-by: Johannes Gäßler <johannesg@5d6.de>

* remove assert in ggml-cuda.cu

Co-authored-by: Johannes Gäßler <johannesg@5d6.de>

---------

Co-authored-by: Johannes Gäßler <johannesg@5d6.de>
…gml-org#24030)

* Removes __restrict__ from PDL kernel headers due to incompatibility with
PDL. Adds preprocessor directives based on arch in kernel body to add
__restrict__ to retain performance on older architectures.

* Simplifies new __restrict__ usage via macro

* Add hopper to PDL __restrict__ fix.

Co-authored-by: Oliver Simons <osimons@nvidia.com>

---------

Co-authored-by: Oliver Simons <osimons@nvidia.com>
* qwen35: use post-norm hidden state for MTP

* rename pre_norm to nextn

* fix step35
* Tidy up SYCL doc a bit

- Add explicit links to referenced items
- Fix spelling errors

Signed-off-by: Todd Malsbary <todd.malsbary@intel.com>

* Correct documented default for GGML_SYCL_GRAPH

The default is ON, not OFF:

  $ cmake -LAH -B build | grep GGML_SYCL_GRAPH
  ...
  GGML_SYCL_GRAPH:BOOL=ON

Signed-off-by: Todd Malsbary <todd.malsbary@intel.com>

* Move docker instructions from SYCL.md to docker.md

This makes them directly accesible from the Quick Start section
of the top-level README.md.

Signed-off-by: Todd Malsbary <todd.malsbary@intel.com>

* Refer to intel.Dockerfile for ARGs and their defaults

The defaults are always changing; this avoids accuracy errors
from duplicating the information.

Signed-off-by: Todd Malsbary <todd.malsbary@intel.com>

* Remove mention of Nvidia in SYCL row of backend table

This support was removed in 2026.02 - refer to the SYCL.md News.

Signed-off-by: Todd Malsbary <todd.malsbary@intel.com>

---------

Signed-off-by: Todd Malsbary <todd.malsbary@intel.com>
…2754)

* ggml-cpu: add rvv 512b,1024b impls for iq4_xs

* ggml-cpu: refactor; add rvv 512b, 1024b impls for q6_K, i-quants

* ggml-cpu: refactor; add 512 and 1024 implementations of tq3_s, iq3_xxs, iq2_s, iq2_xs, iq2_xxs

improve iq2_xs impl for rvv 256

Co-authored-by: Rehan Qasim <rehan.qasim@10xengineers.ai>

---------

Co-authored-by: taimur-10x <taimur.ahmad@10xengineers.ai>
Co-authored-by: Rehan Qasim <rehan.qasim@10xengineers.ai>
…rt (ggml-org#23834)

* Start work on flash_attn refactor

* Refactor

* Split k/v quantization

* Refactor and abstract quantization logic for flash_attn and mul_mat

* Add quantization support to tile path

* formatting

* Move to functions, add a check
…#24073)

* tests : refactor test-save-load-state to accept token input

- Default prompt is now empty; when not provided, generate n_batch
  random tokens (useful for models without a tokenizer)
- Tokenization happens once upfront; pass token vector to test functions
- generate_tokens prints token IDs instead of decoded pieces
- Use llama_model_get_vocab / llama_vocab_n_tokens API
- Upgrade log level from LOG_TRC to LOG_INF for visibility

Assisted-by: llama.cpp:local pi

* cont : use llama_tokens alias
)

* mtmd: handle Gemma 4 audio projector embedding size

* rm projection_dim from clip_n_mmproj_embd

---------

Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
…ml-org#23974)

The XCFramework generated by build-xcframework.sh creates a module map
that manually lists public headers.

That list can fall out of sync with the framework's Headers directory.
The module map is currently missing ggml-opt.h, which is present in the
framework headers. This can cause downstream Apple builds to fail with:

    Include of non-modular header inside framework module 'llama'

Use the framework's Headers directory itself as the module map umbrella
instead of maintaining a manual header list. This makes all public headers
under the generated framework's Headers directory part of the llama module.
…ggml-org#24065)

* webui: fix tool selector toggle/counter, key tools by stable identity

Key the disabled set, counts and toggles by a stable per-tool key
instead of bare function name, deduped from one canonical list. Per-tool
checkboxes become presentational (single row handler, no nested button),
category checkboxes drop the tristate (n/total carries partial). One
getEnabledToolsForLLM keeps normalized MCP schemas and dedupes by name.

* ui: use SvelteSet and SvelteMap for local tool collections to satisfy svelte/prefer-svelte-reactivity
* agents: refactor, include more guidelines

* better example

* rephrase a bit

* add more examples

* nits
…ent (ggml-org#24110)

* server: avoid unnecessary checkpoint restore when new tokens are present

The pos_min_thold calculation unconditionally subtracts 1 to ensure at
least one token is evaluated for logits when no new tokens exist.
However, when the request contains new tokens beyond the cached prefix,
this -1 is overly conservative and may trigger an unnecessary checkpoint
restore.

Conditionally apply the -1 only when n_past >= task.n_tokens() (no new
tokens), avoiding redundant KV state restoration when there is actual
work to do.

* cont : add ref

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
)

* ggml: vectorize ggml_vec_dot_q4_1_q8_1 with WASM SIMD128

Optimize the inner loop of ggml_vec_dot_q4_1_q8_1_generic using
WASM SIMD128 intrinsics, gated behind #ifdef __wasm_simd128__ so
non-wasm builds are completely unaffected.

Approach:
- single wasm_v128_load covers all 32 packed 4-bit weights
- nibbles unpacked via AND/SHR into two u8x16 registers
- widened to i16 before multiply (WASM SIMD has no i8*i8 instruction)
- 4x wasm_i32x4_dot_i16x8 calls accumulate all 32 element pairs
- horizontal reduce via 4x wasm_i32x4_extract_lane

Benchmark (node v25, emcc -O3 -msimd128, 64 blocks x QK8_1=32,
200k iterations):

| impl   | ns/call | speedup |
|--------|---------|---------|
| scalar |   880.7 |   1.00x |
| simd   |   257.8 |   3.42x |

Correctness verified against scalar reference across 10 random seeds
with exact output match.

* ggml: move q4_1_q8_1 WASM SIMD implementation to wasm backend

Relocate the SIMD128 implementation of ggml_vec_dot_q4_1_q8_1 to ggml/src/ggml-cpu/arch/wasm/quants.c to follow architecture-specific layout. Restore the generic implementation in ggml/src/ggml-cpu/quants.c.
Move for loop in the else block.

* ggml: use generic q4_1_q8_1 fallback in wasm backend
* Fix Gemma 4 Unified conversion

* Set audio hidden size to audio_embed_dim
Co-authored-by: lvyichen <lvyichen@stepfun.com>
* webui: added single line reasoning preview.

* patch: reduce width slightly for the previewing section

* refactor: move formatter constants to the right file

* feat: reimplement reasoning preview with throttled dynamic per-line rendering

* chore: fix spacing

Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>

* chore: refactor to requested changes

* refactor: grouped by capture pattern instead of block-level + inline

* ui: fax interrupt state only trigger for 1st reasoning message

* chore: make reasoning preview respects showThoughtInProgress setting

* chore; newline at EOF

Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>

* fix: thread rawContent so collapsible content can handle compute preview

* patch: showThoughtInProgress accidentally blocks rawContent being passed

* chore: fix lint

* chore: change smoke test

---------

Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>
* chore(ui): pin package versions to currently installed

- Update all dependencies and devDependencies to match exactly what's in package-lock.json
- This ensures reproducible builds by locking to specific versions rather than semver ranges

* chore: Update packages

* chore: Move remaining dependencies to devDependencies

* fix: Add missing `mermaid` package

* chore: Update `cookie` package to `v1.1.1`

* chore: Formatting

* test: Update test configs
…gml-org#22445)

* Deduplicate imatrix loading code

* Add back LLAMA_TRACE, early exit on quantize missing metadata
…debar (ggml-org#23132)

* use child snippets for landing and chat message elements

* make ... icon visible in conversation history menu

* conversation history forward tab fix

* add snippet fix for fork icon in conversation history

* focus/keyboard fix for attachment x icon and scroll left/right

* formatting

* fix scroll down issue

* simply Statistics and pointer events in scrolldown

* create storybook tests and move to folder

* improve tests to actually assert on element
@giveen

giveen commented Jul 14, 2026

Copy link
Copy Markdown
Author

Speculative Decoding (Draft Model Enabled)

./build/bin/llama-cli \
  -m /mnt/storage/models/qwen3.6/27B/Qwen3.6-27B-UD-Q5_K_XL.gguf \
  -md /mnt/storage/models/qwen3.6/27B/Qwen3.6-27B-DFlash-Q8_0.gguf \
  --dflash \
  -p "Write a quicksort algorithm in Python. Write code only." \
  -c 64000 \
  --spec-draft-n-max 16 \
  -ngl 99 \
  --cache-type-k q8_0 \
  --cache-type-v q8_0 \
  --reasoning off \
  --temp 0.1
[ Prompt: 100.9 t/s | Generation: 164.8 t/s ]

Standard Decoding (No Draft Model)

./build/bin/llama-cli \
  -m /mnt/storage/models/qwen3.6/27B/Qwen3.6-27B-UD-Q5_K_XL.gguf \
  -p "Write a quicksort algorithm in Python. Write code only." \
  -c 64000 \
  -ngl 99 \
  --cache-type-k q8_0 \
  --cache-type-v q8_0 \
  --reasoning off \
  --temp 0.1
[ Prompt: 203.3 t/s | Generation: 63.5 t/s ]

@giveen

giveen commented Jul 14, 2026

Copy link
Copy Markdown
Author

Okay got server up and running

Prompt Eval (Prefill) : 104.34 t/s (24 tokens in 230.03 ms)
Generation (Decode)   : 156.27 t/s (96 tokens in 614.32 ms)
Draft Acceptance Rate : 86.67% (91 accepted / 105 generated)

@giveen

giveen commented Jul 14, 2026

Copy link
Copy Markdown
Author

Gemma 4 Speculative Decoding Benchmarks

A performance comparison of Gemma 4 (26B and 31B parameter models, including QAT variants) running baseline vs. speculative decoding via DFlash.

Summary of Results

Model Speculative (DFlash) Prompt Eval (t/s) Generation (t/s) Speedup (Gen)
Gemma4-26B (Q5_K_S) ❌ Baseline 236.7 182.3 1.00x
Gemma4-26B (Q5_K_S) DFlash (Q8_0) 142.8 312.4 1.71x
Gemma4-26B-QAT (Q4_K_XL) ❌ Baseline 229.7 215.1 1.00x
Gemma4-26B-QAT (Q4_K_XL) DFlash (Q8_0) 132.1 255.9 1.19x
Gemma4-31B (Q5_K_XL) ❌ Baseline 385.4 55.7 1.00x
Gemma4-31B (Q5_K_XL) DFlash (Q5_K) 139.0 310.5 5.57x
Gemma4-31B-QAT (Q4_K_XL) ❌ Baseline 487.8 66.8 1.00x
Gemma4-31B-QAT (Q4_K_XL) DFlash (Q5_K) 142.7 308.9 4.62x

Detailed Benchmark Logs

1. Gemma4-26B

Baseline (Standard Decoding)

Command:
./build/bin/llama-cli
-m /mnt/storage/models/gemma4/26B/gemma-4-26B-A4B-it-UD-Q5_K_S.gguf
-p "Write a quicksort algorithm in Python. Write code only."
-c 64000
-ngl 99
--cache-type-k q8_0
--cache-type-v q8_0
--reasoning off
--temp 0.1

Metrics:

  • Prompt: 236.7 t/s
  • Generation: 182.3 t/s

DFlash (Speculative Decoding)

Command:
./build/bin/llama-cli
-m /mnt/storage/models/gemma4/26B/gemma-4-26B-A4B-it-UD-Q5_K_S.gguf
-md /mnt/storage/models/gemma4/26B/gemma-4-26B-A4B-it-DFlash-Q8_0.gguf
--dflash
-p "Write a quicksort algorithm in Python. Write code only."
-c 64000
--spec-draft-n-max 16
-ngl 99
--cache-type-k q8_0
--cache-type-v q8_0
--reasoning off
--temp 0.1

Metrics:

  • Prompt: 142.8 t/s
  • Generation: 312.4 t/s

2. Gemma4-26B-QAT

Baseline (Standard Decoding)

Command:
./build/bin/llama-cli
-m /mnt/storage/models/gemma4/26B/gemma-4-26B-A4B-it-qat-UD-Q4_K_XL.gguf
-p "Write a quicksort algorithm in Python. Write code only."
-c 64000
-ngl 99
--cache-type-k q8_0
--cache-type-v q8_0
--reasoning off
--temp 0.1

Metrics:

  • Prompt: 229.7 t/s
  • Generation: 215.1 t/s

DFlash (Speculative Decoding)

Command:
./build/bin/llama-cli
-m /mnt/storage/models/gemma4/26B/gemma-4-26B-A4B-it-qat-UD-Q4_K_XL.gguf
-md /mnt/storage/models/gemma4/26B/gemma-4-26B-A4B-it-DFlash-Q8_0.gguf
--dflash
-p "Write a quicksort algorithm in Python. Write code only."
-c 64000
--spec-draft-n-max 16
-ngl 99
--cache-type-k q8_0
--cache-type-v q8_0
--reasoning off
--temp 0.1

Metrics:

  • Prompt: 132.1 t/s
  • Generation: 255.9 t/s

3. Gemma4-31B

Baseline (Standard Decoding)

Command:
./build/bin/llama-cli
-m /mnt/storage/models/gemma4/31B/gemma-4-31B-it-UD-Q5_K_XL.gguf
-p "Write a quicksort algorithm in Python. Write code only."
-c 64000
-ngl 99
--cache-type-k q8_0
--cache-type-v q8_0
--reasoning off
--temp 0.1

Metrics:

  • Prompt: 385.4 t/s
  • Generation: 55.7 t/s

DFlash (Speculative Decoding)

Command:
./build/bin/llama-cli
-m /mnt/storage/models/gemma4/31B/gemma-4-31B-it-UD-Q5_K_XL.gguf
-md /mnt/storage/models/gemma4/31B/gemma-4-31B-it-DFlash-Q5_K.gguf
--dflash
-p "Write a quicksort algorithm in Python. Write code only."
-c 64000
--spec-draft-n-max 16
-ngl 99
--cache-type-k q8_0
--cache-type-v q8_0
--reasoning off
--temp 0.1

Metrics:

  • Prompt: 139.0 t/s
  • Generation: 310.5 t/s

4. Gemma4-31B-QAT

Baseline (Standard Decoding)

Command:
./build/bin/llama-cli
-m /mnt/storage/models/gemma4/31B/gemma-4-31B-it-qat-UD-Q4_K_XL.gguf
-p "Write a quicksort algorithm in Python. Write code only."
-c 64000
-ngl 99
--cache-type-k q8_0
--cache-type-v q8_0
--reasoning off
--temp 0.1

Metrics:

  • Prompt: 487.8 t/s
  • Generation: 66.8 t/s

DFlash (Speculative Decoding)

Command:
./build/bin/llama-cli
-m /mnt/storage/models/gemma4/31B/gemma-4-31B-it-qat-UD-Q4_K_XL.gguf
-md /mnt/storage/models/gemma4/31B/gemma-4-31B-it-DFlash-Q5_K.gguf
--dflash
-p "Write a quicksort algorithm in Python. Write code only."
-c 64000
--spec-draft-n-max 16
-ngl 99
--cache-type-k q8_0
--cache-type-v q8_0
--reasoning off
--temp 0.1

Metrics:

  • Prompt: 142.7 t/s
  • Generation: 308.9 t/s

Batch encoder outputs and defer KV cache injection to draft() time
instead of injecting per-ubatch. Improves Qwen DFlash throughput by
14-29%, but Gemma4's lower acceptance rate (~43%) causes regression
by concentrating wasted work in draft(). Gate by target model type:
enable deferred injection for most models, disable for Gemma4.

Assisted-by: Claude
@giveen

giveen commented Jul 14, 2026

Copy link
Copy Markdown
Author

Deferred KV Injection Optimization for DFLASH Speculative Decoding

The Problem

DFLASH is a block-diffusion drafting method for speculative decoding. It uses a small "draft" model alongside the big "target" model. The draft model has two parts:

  1. An encoder that reads hidden states from the target model's layers
  2. A decoder with its own KV cache that generates draft tokens

The connection between these two: every time the target model processes a token, DFLASH needs to encode that token through the draft encoder, then inject the encoder output into the draft decoder's KV cache so the decoder can attend to it.

In the original implementation, this injection happened immediately inside the process() callback -- one llama_decode call per ubatch of tokens (typically 16 tokens). For a verification batch of 16 draft tokens, the original code would:

  • Call process() → encode 16 tokens → immediately decode (inject) 16 tokens
  • Generate 3 accepted tokens
  • Target processes the next token at the new position → process() fires → encode & inject
  • Repeat

Each injection decode is cheap -- it's just K/V projections (Wk, Wv matmuls) plus a cache write -- but it still takes GPU time, and it's serialized with the target model's own work.

The Optimization: Deferred Injection

The insight: we don't actually need to inject each encoder output into the KV cache right away. The draft decoder only reads the cache when it's time to generate drafts (the draft() call). Between process() calls, the cache isn't accessed.

So instead of injecting immediately, we stash the encoder output and wait. When the orchestrator calls draft(), we batch all the stashed entries together into a single llama_decode call.

This turns:

  tgt: [decode 16] → [encode&inject 16]  [decode 16] → [encode&inject 16]  [decode 16] → [encode&inject 16]  ... draft()

Into:

  tgt: [decode 16] → [encode 16]          [decode 16] → [encode 16]         [decode 16] → [encode 16]        ... [inject all 48][draft()]

The total work is the same, but batching the injection improves GPU utilization (larger matmuls) and removes serialization overhead between the target and draft GPU operations.

A safety cap (MAX_STASH = 64) prevents unbounded memory growth.

Benchmark Results

Two model families told opposite stories:

Qwen3.6 models (big improvement)

Model Before After Change
Qwen3.6-27B dense 164.8 t/s 212.6 t/s +29%
Qwen3.6-35B MoE 319.0 t/s 364-380 t/s +14-19%

These models have near-100% draft acceptance rate. Every stashed encoder output gets used by the decoder, and batching the injection is a pure win.

Gemma4 models (regression)

Model Before After Change
Gemma4-26B 312.4 t/s 289.6-293.2 t/s -6%
Gemma4-31B 310.5 t/s 267.8-278.5 t/s -10%
Gemma4-31B-QAT 308.9 t/s 237.2 t/s -23%

Acceptance rate is the root cause. Gemma4 accepts only ~43% of draft tokens, versus Qwen's near-100%. These low rates mean most tokens the target verifies are rejected, but they still trigger process() calls that stash encoder output. Every rejection adds to the stash at a position the sequence will skip past. By the time draft() fires, flush_injection() has to process a large batch of mostly-wasted entries — concentrating that dead work inside draft() makes it the bottleneck. High acceptance (Qwen) avoids this entirely: the stash stays small, the flush is quick, and batching is pure win.

The Gate

Rather than applying the optimization universally, we detect the target model type and disable deferred injection for Gemma4 models. When disabled, the code falls back to the original per-chunk immediate injection behavior.

The detection is a simple substring check on llama_model_desc(), which returns strings like "gemma4 Q5_K_XL":

if (strstr(model_desc, "gemma")) {
    m_use_deferred = false;
}

When m_use_deferred = false:

  • process() stashes each chunk and flushes immediately -- same as original code
  • draft() skips the flush loop (stash is always empty)

When m_use_deferred = true (the default):

  • process() accumulates a stash, auto-flushing only at 64 entries
  • draft() flushes all stashed entries before generating

Verified After Gating

Model Gen t/s Note
Gemma4-31B DFlash 314.3 Back to PR baseline (~310)
Qwen3.6-35B DFlash 364.4 Still showing 14-19% improvement

What's in the Code

  • ~70 lines added to common/speculative.cpp in the common_speculative_impl_draft_dflash struct
  • Covers: stash type (StashedG), stash array per-sequence, flush_injection() method, deferred/immediate branch in process(), deferred guard in draft(), and model-type detection in the constructor
  • One #include <cstring> for strstr

giveen added 4 commits July 14, 2026 14:55
The speculative checkpoint save/restore condition excluded RS mode
when the rollback fit within the RS window. In RS mode,
llama_memory_seq_rm does not actually remove individual positions
from the KV cache, so stale rejected-token positions persisted and
caused every subsequent llama_decode to fail with:

  the tokens of sequence 0 in the input batch have inconsistent
  sequence positions ... Y = X + 1

Fix: always save and restore speculative checkpoints for RS mode,
not only when the draft size exceeds the RS limit.

Assisted-by: Claude
When deferred KV injection is enabled, the stash accumulates encoder
outputs across process() calls. After checkpoint restoration (partial
draft acceptance), the stash retains entries with positions past
the restored prefix. The next flush_injection would inject stale
positions into the draft cache.

Fix: check if the incoming batch starts at or before the last stashed
position; if so, the sequence was rolled back, so clear the stash.

Assisted-by: Claude
@TheTom

TheTom commented Jul 15, 2026

Copy link
Copy Markdown
Owner

@giveen — thanks again for the DFlash work and the benches on #219.

We could not land #219 as-is: base was master, so the GitHub diff was mostly fork history, and a straight merge would also undo some tip fixes (e.g. speculative/server gate from #217).

Tom rebased your intended CL onto current feature/turboquant-kv-cache as #220 (branch tom/pr-219-full-rebase).

He tried to keep your intended changes (content from your turbo-dflash tip for the DFlash paths): deferred KV injection, --dflash / --eagle3 / -cd, model/arch/spec stack, qwen35 extract, CUDA fattn bits, etc.

Only small tip-preserving tweaks on top:

We do not have your DFlash draft GGUFs here. Could you please re-test that PR branch the same way you did on #219 (5090, -md draft, turbo/q8 caches, acceptance)? If anything from your CL is missing or wrong, say so and we will fix on tip.

Once that looks good we can merge the rebased PR and close this one as superseded. Appreciate you driving this.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.