Skip to content

Commit 12706d0

Browse files
committed
feat: one-sided target probability acceptance for MTP drafts increases acceptance rate and throughput compared to argmax alone
MTP drafters use greedy argmax internally — they do not expose a full logit distribution, by design, for speed. This change adds a further tok/s improvement by allowing users to tune the acceptance threshold, achieving ~20% throughput gains by accepting more draft tokens while retaining the ability to manually verify the threshold at which semantic breakdown occurs for their specific model/task combination. When the drafter and target model disagree on a token, rather than immediately rejecting (standard argmax behaviour), --draft-p-accept triggers a one-sided softmax check over the target model's logits for the draft token. If the target assigns p >= draft-p-accept to that token, it is accepted in place of the target's own argmax prediction and decoding continues. No drafter logits are required, keeping the drafter inference path unchanged and preserving the speed advantage of argmax-only drafting. This is intentionally lighter than the full ratio test in the MTP paper. Changes: - common/sampling.cpp: add p_accept parameter to sample_and_accept_n; on drafter/target disagreement compute softmax over target logits and accept draft token if p_target(draft_token) >= p_accept - common/sampling.h: update both overloads of sample_and_accept_n signature - common/arg.cpp: register --draft-p-accept CLI argument - common/common.h: add p_accept field to common_params_speculative struct - tools/server/server-context.cpp: wire p_accept into speculative config Usage: --draft-p-accept 0.005 # accept draft token if p_target >= 0.005 --draft-p-accept 0.0 # standard argmax-only behaviour (default)
1 parent 2e81dc5 commit 12706d0

5 files changed

Lines changed: 30 additions & 10 deletions

File tree

common/arg.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3448,13 +3448,23 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
34483448
params.speculative.p_split = std::stof(value);
34493449
}
34503450
).set_examples({LLAMA_EXAMPLE_SPECULATIVE}).set_env("LLAMA_ARG_DRAFT_P_SPLIT"));
3451+
34513452
add_opt(common_arg(
34523453
{"--draft-p-min"}, "P",
34533454
string_format("minimum speculative decoding probability (greedy) (default: %.2f)", (double)params.speculative.p_min),
34543455
[](common_params & params, const std::string & value) {
34553456
params.speculative.p_min = std::stof(value);
34563457
}
34573458
).set_examples({LLAMA_EXAMPLE_SPECULATIVE, LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI}).set_env("LLAMA_ARG_DRAFT_P_MIN"));
3459+
3460+
add_opt(common_arg(
3461+
{"--draft-p-accept"}, "P",
3462+
string_format("MTP draft acceptance probability threshold - accept non-argmax draft token if main model assigns it at least this probability (default: %.2f, 0.0 = greedy match only)", (double)params.speculative.p_accept),
3463+
[](common_params & params, const std::string & value) {
3464+
params.speculative.p_accept = std::stof(value);
3465+
}
3466+
).set_examples({LLAMA_EXAMPLE_SPECULATIVE, LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI}).set_env("LLAMA_ARG_DRAFT_P_ACCEPT"));
3467+
34583468
add_opt(common_arg(
34593469
{"-cd", "--ctx-size-draft"}, "N",
34603470
string_format("size of the prompt context for the draft model (default: %d, 0 = loaded from model)", params.speculative.n_ctx),

common/common.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,8 @@ struct common_params_speculative {
325325
int32_t draft_block_size = 3;
326326
float p_split = 0.1f; // speculative decoding split probability
327327
float p_min = 0.75f; // minimum speculative decoding probability (greedy)
328+
float p_accept = 0.0f; // min probability for main model to accept a non-argmax MTP draft token
329+
328330

329331
// ngram-based speculative decoding
330332

common/sampling.cpp

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ llama_token common_sampler_sample(struct common_sampler * gsmpl, struct llama_co
605605
return id;
606606
}
607607

608-
std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sampler * gsmpl, struct llama_context * ctx, const std::vector<int> & idxs, const llama_tokens & draft, bool grammar_first) {
608+
std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sampler * gsmpl, struct llama_context * ctx, const std::vector<int> & idxs, const llama_tokens & draft, bool grammar_first, float p_accept) {
609609
GGML_ASSERT(idxs.size() == draft.size() + 1 && "idxs.size() must be draft.size() + 1");
610610

611611
std::vector<llama_token> result;
@@ -614,12 +614,21 @@ std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sample
614614
size_t i = 0;
615615
for (; i < draft.size(); i++) {
616616
const llama_token id = common_sampler_sample(gsmpl, ctx, idxs[i], grammar_first);
617-
618617
common_sampler_accept(gsmpl, id, true);
619-
620618
result.push_back(id);
621-
622619
if (draft[i] != id) {
620+
if (p_accept > 0.0f) {
621+
const float * logits = llama_get_logits_ith(ctx, idxs[i]);
622+
const int n_vocab = llama_vocab_n_tokens(llama_model_get_vocab(llama_get_model(ctx)));
623+
float max_l = *std::max_element(logits, logits + n_vocab);
624+
float sum = 0.0f;
625+
for (int j = 0; j < n_vocab; j++) sum += expf(logits[j] - max_l);
626+
const float p_main = expf(logits[draft[i]] - max_l) / sum;
627+
if (p_main >= p_accept) {
628+
result.back() = draft[i];
629+
continue;
630+
}
631+
}
623632
break;
624633
}
625634
}
@@ -635,13 +644,12 @@ std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sample
635644
return result;
636645
}
637646

638-
std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sampler * gsmpl, struct llama_context * ctx, const llama_tokens & draft, bool grammar_first) {
647+
std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sampler * gsmpl, struct llama_context * ctx, const llama_tokens & draft, bool grammar_first, float p_accept) {
639648
std::vector<int> idxs(draft.size() + 1);
640649
for (size_t i = 0; i < idxs.size(); ++i) {
641650
idxs[i] = i;
642651
}
643-
644-
return common_sampler_sample_and_accept_n(gsmpl, ctx, idxs, draft, grammar_first);
652+
return common_sampler_sample_and_accept_n(gsmpl, ctx, idxs, draft, grammar_first, p_accept);
645653
}
646654

647655
uint32_t common_sampler_get_seed(const struct common_sampler * gsmpl) {

common/sampling.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ llama_token common_sampler_sample(struct common_sampler * gsmpl, struct llama_co
8282
//
8383
// returns at least 1 token, up to idxs.size()
8484
//
85-
std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sampler * gsmpl, struct llama_context * ctx, const std::vector<int> & idxs, const llama_tokens & draft, bool grammar_first = false);
85+
std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sampler * gsmpl, struct llama_context * ctx, const std::vector<int> & idxs, const llama_tokens & draft, bool grammar_first = false, float p_accept = 0.0f);
8686

8787
// assume idxs == [ 0, 1, 2, ..., draft.size() ]
88-
std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sampler * gsmpl, struct llama_context * ctx, const llama_tokens & draft, bool grammar_first = false);
88+
std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sampler * gsmpl, struct llama_context * ctx, const llama_tokens & draft, bool grammar_first = false, float p_accept = 0.0f);
8989

9090
uint32_t common_sampler_get_seed(const struct common_sampler * gsmpl);
9191

tools/server/server-context.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2938,7 +2938,7 @@ struct server_context_impl {
29382938
const size_t n_draft = slot.drafted.size();
29392939

29402940
// the accepted tokens from the speculation
2941-
const auto ids = common_sampler_sample_and_accept_n(slot.smpl.get(), ctx, slot.i_batch_dft, slot.drafted);
2941+
const auto ids = common_sampler_sample_and_accept_n(slot.smpl.get(), ctx, slot.i_batch_dft, slot.drafted, false, params_base.speculative.p_accept);
29422942

29432943
// For MTP speculation, h_prev for the next draft must come from the LAST ACCEPTED
29442944
// batch output - not embeddings_ith(-1), which would point at a rejected draft's

0 commit comments

Comments
 (0)