--- 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<std::vector<float>> verify_h;
std::vector<int32_t> verify_h_rows;
+ std::vector<llama_pos> verify_pos_first; // position of verify_h row 0
std::vector<int> i_last;
std::vector<std::vector<float>> 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<int32_t>(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);
}
};