Commit 77757a346 for llama.cpp
commit 77757a34672a888845a09ab45728b8d34aa475ff
Author: Daniel Bevenius <daniel.bevenius@gmail.com>
Date: Mon Oct 12 12:02:24 2026 +0200
examples : fix draft sampling and rejection handling (#30095)
* examples : fix draft sampling and rejection handling
This commit fixes two issues in the speculative example which are
described in detail in the linked issue.
Resolves: https://github.com/ggml-org/llama.cpp/issues/29975
* examples : add kv-unified requirement for parallel > 1
diff --git a/common/arg.cpp b/common/arg.cpp
index 7c1b81e0f..6bc4a84e2 100644
--- a/common/arg.cpp
+++ b/common/arg.cpp
@@ -1730,7 +1730,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
[](common_params & params, bool value) {
params.kv_unified = value;
}
- ).set_env("LLAMA_ARG_KV_UNIFIED").set_examples({LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_PERPLEXITY, LLAMA_EXAMPLE_BATCHED, LLAMA_EXAMPLE_BENCH, LLAMA_EXAMPLE_PARALLEL}));
+ ).set_env("LLAMA_ARG_KV_UNIFIED").set_examples({LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_PERPLEXITY, LLAMA_EXAMPLE_BATCHED, LLAMA_EXAMPLE_BENCH, LLAMA_EXAMPLE_PARALLEL, LLAMA_EXAMPLE_SPECULATIVE}));
add_opt(common_arg(
{"--cache-idle-slots"},
{"--no-cache-idle-slots"},
diff --git a/examples/speculative/speculative.cpp b/examples/speculative/speculative.cpp
index 1bb47594b..8f347b0d4 100644
--- a/examples/speculative/speculative.cpp
+++ b/examples/speculative/speculative.cpp
@@ -50,6 +50,11 @@ int main(int argc, char ** argv) {
return 1;
}
+ if (params.n_parallel > 1 && !params.kv_unified) {
+ LOG_ERR("%s: --kv-unified/-kvu is required when --parallel/-np > 1\n", __func__);
+ return 1;
+ }
+
if (params.speculative.draft.mparams.path.empty()) {
LOG_ERR("%s: --model-draft is required\n", __func__);
return 1;
@@ -267,7 +272,7 @@ int main(int argc, char ** argv) {
// for stochastic sampling, attempt to match the token with the drafted tokens
{
bool accept = false;
- if (params.sampling.temp > 0) {
+ if (params.sampling.temp > 0 && n_seq_dft == 1) {
// stochastic verification
common_sampler_sample(smpl, ctx_tgt, drafts[s_keep].i_batch_tgt[i_dft], true);
@@ -341,13 +346,17 @@ int main(int argc, char ** argv) {
float sum_probs = 0.0f;
- for (size_t i = 0; i < dist_tgt.size; i++) {
- if (i < dist_dft.size) {
- dist_tgt.data[i].p = std::max(0.0f, dist_tgt.data[i].p - dist_dft.data[i].p);
- } else {
- dist_tgt.data[i].p = std::max(0.0f, dist_tgt.data[i].p);
+ for (size_t i = 0, j = 0; i < dist_tgt.size; i++) {
+ // try to find the current target token id in the draft distribution
+ while (j < dist_dft.size && dist_dft.data[j].id < dist_tgt.data[i].id) {
+ ++j;
}
+ // tokens absent from the draft distribution have zero probability
+ float p_dft = j < dist_dft.size && dist_dft.data[j].id == dist_tgt.data[i].id ?
+ dist_dft.data[j].p : 0.0f;
+
+ dist_tgt.data[i].p = std::max(0.0f, dist_tgt.data[i].p - p_dft);
sum_probs += dist_tgt.data[i].p;
}
@@ -370,7 +379,7 @@ int main(int argc, char ** argv) {
// synchronize active status for sequences with the same drafted token
drafts[i].active = drafts[i].active && accept;
if (!drafts[i].active) {
- active_seqs.erase(s);
+ active_seqs.erase(i);
}
}
}
@@ -394,7 +403,7 @@ int main(int argc, char ** argv) {
token_str = common_token_to_piece(ctx_tgt, token_id);
}
} else {
- // greedy verification
+ // verify drafts by matching tokens sampled from the target
// sample from the target model
LOG_DBG("sampling target: s_keep = %3d, i_dft = %3d, i_batch_tgt = %3d\n", s_keep, i_dft, drafts[s_keep].i_batch_tgt[i_dft]);
@@ -518,7 +527,7 @@ int main(int argc, char ** argv) {
continue;
}
- common_sampler_sample(drafts[s].smpl, ctx_dft, drafts[s].i_batch_dft, true);
+ const llama_token sampled_token = common_sampler_sample(drafts[s].smpl, ctx_dft, drafts[s].i_batch_dft, true);
const auto * cur_p = common_sampler_get_candidates(drafts[s].smpl, true);
@@ -567,7 +576,7 @@ int main(int argc, char ** argv) {
// add drafted token for each sequence
for (int is = 0; is < (int) sa.size(); ++is) {
- const llama_token id = cur_p->data[is].id;
+ const llama_token id = n_seq_dft == 1 ? sampled_token : cur_p->data[is].id;
const int s = sa[is];