--- a/common/speculative.cpp 2026-10-13 05:29:52.854135340 -0400 +++ b/common/speculative.cpp 2026-10-13 05:29:52.967135794 -0400 @@ -2132,6 +2132,7 @@ // Row 0 corresponds to the sampled token, row N to the Nth accepted draft token. std::vector> verify_h; std::vector verify_h_rows; + std::vector verify_pos_first; // position of verify_h row 0 std::vector i_last; std::vector> chain_h; @@ -2221,6 +2222,7 @@ verify_h.assign(n_seq, {}); verify_h_rows.assign(n_seq, 0); + verify_pos_first.assign(n_seq, -1); deferred_tok.assign(n_seq, {}); deferred_pos.assign(n_seq, {}); @@ -2443,7 +2445,8 @@ } const int32_t n_rows = i_batch_end[seq_id] - i_batch_beg[seq_id] + 1; - verify_h_rows[seq_id] = n_rows; + verify_h_rows[seq_id] = n_rows; + verify_pos_first[seq_id] = batch_in.pos[i_batch_beg[seq_id]]; verify_h[seq_id].resize((size_t) n_rows * n_embd); for (int32_t i = 0; i < n_rows; ++i) { @@ -2627,6 +2630,11 @@ const int32_t i_h = std::min(n_accepted, n_rows - 1); const size_t row_bytes = (size_t) n_embd * sizeof(float); std::memcpy(pending_h[seq_id].data(), verify_h[seq_id].data() + (size_t) i_h * n_embd, row_bytes); + + // the rejected rows of the verification batch must never reach ctx_dft: draft() skips them + // (add_deferred stops at n_past), but a flush decodes every deferred row, and a flush happens + // whenever a batch without this seq is too big to defer (a fair prompt step) or the slot resets + drop_deferred_from(seq_id, verify_pos_first[seq_id] + i_h + 1); } };